<commit_before>// Copyright (c) 2011 Firebase.co - http://www.firebase.co
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

var im = require('imagemagick');
var fs = require('fs');
var path = require('path');
var async = require('async');
var existsFn = fs.exists || path.exists;

// keeps a global registry of storage providers
var providersRegistry = {};

var supportedDecodingFormats = [
  'PNG',
  'GIF',
  'TIFF',
  'JPEG'
];

function findProvider(name) {
  var provider = providersRegistry[name];
  if(!provider) throw new Error('Storage Provider "' + name + '" can not be found');
  return provider;
}

function findImageMagickFormats(options, callback) {
  var opts = { read: true };
  if (typeof options === 'function') {
    callback = options;
  } else if (options.read || options.write || options.multi || options.blob ) {
    opts = options;
  } else {
    callback(new Error("Options have to contain one or more of 'read', 'write', 'multi', 'blob'"));
  }
  im.convert(['-list','format'], function(err, stdout, stderr) {
    if (err) return callback(err);
    if (stderr && stderr.search(/\S/) >= 0) return callback(new Error(stderr));
    if (stdout && stdout.search(/\S/) >= 0) {
      // capture groups:
      // 0: all
      // 1: format
      // 2: if '*' = native blob support; if ' ' (whitespace) none
      // 3: module
      // 4: if 'r' = read support; if '-' none
      // 5: if 'w' = write support; if '-' none
      // 6: if '+' = support for multiple images; if '-' none
      // 7: description
      var regex = /^\s*([^\*\s]+)(\*|\s)\s(\S+)\s+([-r])([-w])([-+])\s+(.*)$/;
      var lines = stdout.split("\n");
      var comps = [];
      var formats = [];
      for (i in lines) {
        currentLine = lines[i];
        comps = regex.exec(currentLine);
        if (comps) {
          if ((!opts.read  || comps[4] === 'r') &&
              (!opts.write || comps[5] === 'w') &&
              (!opts.multi || comps[6] === '+') &&
              (!opts.blob  || comps[2] === '*')) {
            formats.push(comps[1]);
          }
        }
      }
      return callback(null,formats);
    } else {
      return callback(new Error("No format supports the requested operation(s): "
                       + Object.keys(opts).toString()
                       + " . Check 'convert -list format'"));
    }
  });
}

var plugin = function(schema, options) {
  options = options || {};
  if(typeof(options.directory) !== 'string') throw new Error('option "directory" is required');
  if(typeof(options.properties) !== 'object') throw new Error('option "properties" is required');
  if(typeof(options.storage) !== 'object') throw new Error('option "storage" is required');
  var storageOptions = options.storage;
  storageOptions.schema = schema;

  if(typeof(storageOptions.providerName) !== 'string') throw new Error('option "storage.providerName" is required');
  var providerPrototype = findProvider(storageOptions.providerName);

  var providerOptions = storageOptions.options || {};
  var providerInstance = new providerPrototype(providerOptions);
  var propertyNames = Object.keys(options.properties);
  propertyNames.forEach(function(propertyName) {
    var propertyOptions = options.properties[propertyName];
    if(!propertyOptions) throw new Error('property "' + propertyName + '" requires an specification');

    var styles = propertyOptions.styles || {};
    var styleNames = Object.keys(styles);
    if(styleNames.length == 0) throw new Error('property "' + propertyName + '" needs to define at least one style');

    var addOp = {};
    var propSchema = addOp[propertyName] = {};
    styleNames.forEach(function(styleName) {
      propSchema[styleName] = {
        size: Number // Size of the File
        , oname: String // Original name of the file
        , mtime: Date
        , ctime: Date
        , path: String // Storage Path
        , defaultUrl: String // Default (non-secure, most of the time public) Url
        , format: String // Format of the File(provided by identify).
        , depth: Number
        , dims: { // Dimensions of the Image
          h: Number, // Height
          w: Number // Width
        }
      };
    });

    // Add the Property
    schema.add(addOp);
  }); // for each property name

  // Finally we set the method 'attach'
  // => propertyName: String. Name of the property to attach the file to.
  // => attachmentInfo: {
  //  path: String(required). Path to the file in the file system.
  //  name: String(optional). Original Name of the file.
  //  mime: String(optional). Mime type of the file.
  schema.methods.attach = function(propertyName, attachmentInfo, cb) {
    var selfModel = this;
    if(propertyNames.indexOf(propertyName) == -1) return cb(new Error('property "' + propertyName + '" was not registered as an attachment property'));
    var propertyOptions = options.properties[propertyName];
    var styles = propertyOptions.styles || {};
    if(!attachmentInfo || typeof(attachmentInfo) !== 'object') return cb(new Error('attachmentInfo is not valid'));
    if(typeof(attachmentInfo.path) !== 'string') return cb(new Error('attachmentInfo has no valid path'));
    if(!attachmentInfo.name) {
      // No original name provided? We infer it from the path
      attachmentInfo.name = path.basename(attachmentInfo.path);
    }
    existsFn(attachmentInfo.path, function(exists) {
      if(!exists) return cb(new Error('file to attach at path "' + attachmentInfo.path + '" does not exists'));
      fs.stat(attachmentInfo.path, function(err, stats) {
        if(!stats.isFile()) return cb(new Error('path to attach from "' + attachmentInfo.path + '" is not a file'));
        im.identify(attachmentInfo.path, function(err, atts) {
          // if 'identify' fails, that probably means the file is not an image.
          var canTransform = !!atts && supportedDecodingFormats.indexOf(atts.format) != -1;
          var fileExt = path.extname(attachmentInfo.path);
          var styles = propertyOptions.styles || {};
          var styleNames = Object.keys(styles);

          var tasks = [];
          var stylesToReset = []; // names of the style that needs to be reset at the end of the process.
          styleNames.forEach(function(styleName) {
            var styleOptions = styles[styleName] || {};
            var finishConversion = function(styleFilePath, atts, cb) {
              var ext = path.extname(styleFilePath);
              var storageStylePath = '/' + options.directory + '/' + selfModel.id + '-' + styleName + ext;
              fs.stat(styleFilePath, function(err, stats) {
                if(err) return cb(err);
                cb(null, {
                  style: {
                    name: styleName,
                    options: styleOptions
                  },
                  filename: styleFilePath,
                  stats: stats,
                  propertyName: propertyName,
                  model: selfModel,
                  path: storageStylePath,
                  defaultUrl: null, // let the storage assign this
                  features: atts
                });
              });
            };
            var optionKeys = Object.keys(styleOptions);
            var transformationNames = [];
            optionKeys.forEach(function(transformationName) {
              if(transformationName.indexOf('$') != 0) {  // if is not special command, add it as an special transformation argument
                transformationNames.push(transformationName);
              }
            });
            if(optionKeys.length != 0) {
              if(canTransform) {
                var styleFileExt = styleOptions['$format'] ? ('.' + styleOptions['$format']) : fileExt;
                var styleFileName = path.basename(attachmentInfo.path, fileExt);
                styleFileName += '-' + styleName + styleFileExt;
                var styleFilePath = path.join(path.dirname(attachmentInfo.path), styleFileName);
                var convertArgs = [attachmentInfo.path]; // source file name

                // add all the transformations args

                transformationNames.forEach(function(transformationName) {
                  convertArgs.push('-' + transformationName);
                  convertArgs.push(styleOptions[transformationName]);
                });

                convertArgs.push(styleFilePath);
                tasks.push(function(cb) {

                  // invoke 'convert'
                  im.convert(convertArgs, function(err, stdout, stderr) {
                    if(err) return cb(err);

                    // run identify in the styled image
                    im.identify(styleFilePath, function(err, atts) {
                      if(err) return cb(err);
                      finishConversion(styleFilePath, atts, cb);
                    });
                  });

                }); // tasks.push
              } else {
                stylesToReset.push(styleName);
              }// if can decode
            } else {
              // keep the file as original
              tasks.push(function(cb) {
                finishConversion(attachmentInfo.path, atts, cb);
              });
            }

          }); // for each style

          async.parallel(tasks, function(err, convertResults) {
            if(err) return cb(err);

            //console.log(convertResults);
            tasks = [];
            convertResults.forEach(function(convertResult) {
              tasks.push(function(cb) {

                // tell the provider to create or replace the attachment
                providerInstance.createOrReplace(convertResult, function(err, attachment) {
                  if(err) return cb(err);
                  cb(null, attachment);
                });

              });
            });

            async.parallel(tasks, function(err, storageResults) {
              if(err) return cb(err);

              // Finally Update the Model
              var propModel = selfModel[propertyName];
              if(storageResults.length > 0) { // only update the model if a transformation was performed.
                storageResults.forEach(function(styleStorage) {
                  var modelStyle = propModel[styleStorage.style.name];
                  modelStyle.defaultUrl = styleStorage.defaultUrl;
                  modelStyle.path = styleStorage.path;
                  modelStyle.size = styleStorage.stats.size;
                  modelStyle.mime = styleStorage.mime;
                  modelStyle.ctime = styleStorage.stats.ctime;
                  modelStyle.mtime = styleStorage.stats.mtime;
                  modelStyle.oname = attachmentInfo.name; // original name of the file
                  if(atts) {
                    modelStyle.format = styleStorage.features.format;
                    modelStyle.depth = styleStorage.features.depth;
                    modelStyle.dims.h = styleStorage.features.height;
                    modelStyle.dims.w = styleStorage.features.width;
                  }
                });
              }

              stylesToReset.forEach(function(resetStyleName) {
                var path = [propertyName, resetStyleName].join('.');
                selfModel.set(path, null);
              });

              cb(null);
            });

          });
        });
      });
    });
  }; // method attach
};

// Prototype for Storage Providers
function StorageProvider(options) {
  this.options = options;
}
StorageProvider.prototype.update = function(attachment, cb) {
  throw new Error('method update implemented');
};
plugin.StorageProvider = StorageProvider;

// Method to Register Storage Providers
plugin.registerStorageProvider = function(name, provider) {
  if(typeof(name) !== 'string') throw new Error('storage engine name is required');
  if(provider && provider._super == StorageProvider) throw new Error('provider is not valid. it does not inherits from StorageEngine');
  providersRegistry[name] = provider;
}

// Register a Known Decoding Format(e.g 'PNG')
plugin.registerDecodingFormat = function(name) {
  supportedDecodingFormats.push(name);
}

/*
 * Use this to register all formats for which your local ImageMagick installation supports
 * read operations.
 */
plugin.registerImageMagickDecodingFormats = function() {
  plugin.registerImageMagickFormats({ read: true });
}

/*
 * You can register formats based on certain modes or a combination of those:
 * 'read' : true|false
 * 'write': true|false
 * 'multi': true|false
 * 'blob' : true|false
 * options is optional and defaults to { read: true }. If several modes with value true are given,
 * only formats supporting all of them are included.
 */
plugin.registerImageMagickFormats = function(options, callback) {
  if (!callback) {
    callback = function(error, formats) {
      if (error) throw new Error(error);
      else if (formats && formats.length > 0) {
        supportedDecodingFormats = formats;
      } else {
        throw new Error("No formats supported for decoding!");
      }
    };
  }
  findImageMagickFormats(options, callback);
}

// Export the Plugin for mongoose.js
module.exports = plugin;
<commit_msg>added var i declaration<commit_after>// Copyright (c) 2011 Firebase.co - http://www.firebase.co
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

var im = require('imagemagick');
var fs = require('fs');
var path = require('path');
var async = require('async');
var existsFn = fs.exists || path.exists;

// keeps a global registry of storage providers
var providersRegistry = {};

var supportedDecodingFormats = [
  'PNG',
  'GIF',
  'TIFF',
  'JPEG'
];

function findProvider(name) {
  var provider = providersRegistry[name];
  if(!provider) throw new Error('Storage Provider "' + name + '" can not be found');
  return provider;
}

function findImageMagickFormats(options, callback) {
  var opts = { read: true };
  if (typeof options === 'function') {
    callback = options;
  } else if (options.read || options.write || options.multi || options.blob ) {
    opts = options;
  } else {
    callback(new Error("Options have to contain one or more of 'read', 'write', 'multi', 'blob'"));
  }
  im.convert(['-list','format'], function(err, stdout, stderr) {
    if (err) return callback(err);
    if (stderr && stderr.search(/\S/) >= 0) return callback(new Error(stderr));
    if (stdout && stdout.search(/\S/) >= 0) {
      // capture groups:
      // 0: all
      // 1: format
      // 2: if '*' = native blob support; if ' ' (whitespace) none
      // 3: module
      // 4: if 'r' = read support; if '-' none
      // 5: if 'w' = write support; if '-' none
      // 6: if '+' = support for multiple images; if '-' none
      // 7: description
      var regex = /^\s*([^\*\s]+)(\*|\s)\s(\S+)\s+([-r])([-w])([-+])\s+(.*)$/;
      var lines = stdout.split("\n");
      var comps = [];
      var formats = [];
      var i;
      for (i in lines) {
        currentLine = lines[i];
        comps = regex.exec(currentLine);
        if (comps) {
          if ((!opts.read  || comps[4] === 'r') &&
              (!opts.write || comps[5] === 'w') &&
              (!opts.multi || comps[6] === '+') &&
              (!opts.blob  || comps[2] === '*')) {
            formats.push(comps[1]);
          }
        }
      }
      return callback(null,formats);
    } else {
      return callback(new Error("No format supports the requested operation(s): "
                       + Object.keys(opts).toString()
                       + " . Check 'convert -list format'"));
    }
  });
}

var plugin = function(schema, options) {
  options = options || {};
  if(typeof(options.directory) !== 'string') throw new Error('option "directory" is required');
  if(typeof(options.properties) !== 'object') throw new Error('option "properties" is required');
  if(typeof(options.storage) !== 'object') throw new Error('option "storage" is required');
  var storageOptions = options.storage;
  storageOptions.schema = schema;

  if(typeof(storageOptions.providerName) !== 'string') throw new Error('option "storage.providerName" is required');
  var providerPrototype = findProvider(storageOptions.providerName);

  var providerOptions = storageOptions.options || {};
  var providerInstance = new providerPrototype(providerOptions);
  var propertyNames = Object.keys(options.properties);
  propertyNames.forEach(function(propertyName) {
    var propertyOptions = options.properties[propertyName];
    if(!propertyOptions) throw new Error('property "' + propertyName + '" requires an specification');

    var styles = propertyOptions.styles || {};
    var styleNames = Object.keys(styles);
    if(styleNames.length == 0) throw new Error('property "' + propertyName + '" needs to define at least one style');

    var addOp = {};
    var propSchema = addOp[propertyName] = {};
    styleNames.forEach(function(styleName) {
      propSchema[styleName] = {
        size: Number // Size of the File
        , oname: String // Original name of the file
        , mtime: Date
        , ctime: Date
        , path: String // Storage Path
        , defaultUrl: String // Default (non-secure, most of the time public) Url
        , format: String // Format of the File(provided by identify).
        , depth: Number
        , dims: { // Dimensions of the Image
          h: Number, // Height
          w: Number // Width
        }
      };
    });

    // Add the Property
    schema.add(addOp);
  }); // for each property name

  // Finally we set the method 'attach'
  // => propertyName: String. Name of the property to attach the file to.
  // => attachmentInfo: {
  //  path: String(required). Path to the file in the file system.
  //  name: String(optional). Original Name of the file.
  //  mime: String(optional). Mime type of the file.
  schema.methods.attach = function(propertyName, attachmentInfo, cb) {
    var selfModel = this;
    if(propertyNames.indexOf(propertyName) == -1) return cb(new Error('property "' + propertyName + '" was not registered as an attachment property'));
    var propertyOptions = options.properties[propertyName];
    var styles = propertyOptions.styles || {};
    if(!attachmentInfo || typeof(attachmentInfo) !== 'object') return cb(new Error('attachmentInfo is not valid'));
    if(typeof(attachmentInfo.path) !== 'string') return cb(new Error('attachmentInfo has no valid path'));
    if(!attachmentInfo.name) {
      // No original name provided? We infer it from the path
      attachmentInfo.name = path.basename(attachmentInfo.path);
    }
    existsFn(attachmentInfo.path, function(exists) {
      if(!exists) return cb(new Error('file to attach at path "' + attachmentInfo.path + '" does not exists'));
      fs.stat(attachmentInfo.path, function(err, stats) {
        if(!stats.isFile()) return cb(new Error('path to attach from "' + attachmentInfo.path + '" is not a file'));
        im.identify(attachmentInfo.path, function(err, atts) {
          // if 'identify' fails, that probably means the file is not an image.
          var canTransform = !!atts && supportedDecodingFormats.indexOf(atts.format) != -1;
          var fileExt = path.extname(attachmentInfo.path);
          var styles = propertyOptions.styles || {};
          var styleNames = Object.keys(styles);

          var tasks = [];
          var stylesToReset = []; // names of the style that needs to be reset at the end of the process.
          styleNames.forEach(function(styleName) {
            var styleOptions = styles[styleName] || {};
            var finishConversion = function(styleFilePath, atts, cb) {
              var ext = path.extname(styleFilePath);
              var storageStylePath = '/' + options.directory + '/' + selfModel.id + '-' + styleName + ext;
              fs.stat(styleFilePath, function(err, stats) {
                if(err) return cb(err);
                cb(null, {
                  style: {
                    name: styleName,
                    options: styleOptions
                  },
                  filename: styleFilePath,
                  stats: stats,
                  propertyName: propertyName,
                  model: selfModel,
                  path: storageStylePath,
                  defaultUrl: null, // let the storage assign this
                  features: atts
                });
              });
            };
            var optionKeys = Object.keys(styleOptions);
            var transformationNames = [];
            optionKeys.forEach(function(transformationName) {
              if(transformationName.indexOf('$') != 0) {  // if is not special command, add it as an special transformation argument
                transformationNames.push(transformationName);
              }
            });
            if(optionKeys.length != 0) {
              if(canTransform) {
                var styleFileExt = styleOptions['$format'] ? ('.' + styleOptions['$format']) : fileExt;
                var styleFileName = path.basename(attachmentInfo.path, fileExt);
                styleFileName += '-' + styleName + styleFileExt;
                var styleFilePath = path.join(path.dirname(attachmentInfo.path), styleFileName);
                var convertArgs = [attachmentInfo.path]; // source file name

                // add all the transformations args

                transformationNames.forEach(function(transformationName) {
                  convertArgs.push('-' + transformationName);
                  convertArgs.push(styleOptions[transformationName]);
                });

                convertArgs.push(styleFilePath);
                tasks.push(function(cb) {

                  // invoke 'convert'
                  im.convert(convertArgs, function(err, stdout, stderr) {
                    if(err) return cb(err);

                    // run identify in the styled image
                    im.identify(styleFilePath, function(err, atts) {
                      if(err) return cb(err);
                      finishConversion(styleFilePath, atts, cb);
                    });
                  });

                }); // tasks.push
              } else {
                stylesToReset.push(styleName);
              }// if can decode
            } else {
              // keep the file as original
              tasks.push(function(cb) {
                finishConversion(attachmentInfo.path, atts, cb);
              });
            }

          }); // for each style

          async.parallel(tasks, function(err, convertResults) {
            if(err) return cb(err);

            //console.log(convertResults);
            tasks = [];
            convertResults.forEach(function(convertResult) {
              tasks.push(function(cb) {

                // tell the provider to create or replace the attachment
                providerInstance.createOrReplace(convertResult, function(err, attachment) {
                  if(err) return cb(err);
                  cb(null, attachment);
                });

              });
            });

            async.parallel(tasks, function(err, storageResults) {
              if(err) return cb(err);

              // Finally Update the Model
              var propModel = selfModel[propertyName];
              if(storageResults.length > 0) { // only update the model if a transformation was performed.
                storageResults.forEach(function(styleStorage) {
                  var modelStyle = propModel[styleStorage.style.name];
                  modelStyle.defaultUrl = styleStorage.defaultUrl;
                  modelStyle.path = styleStorage.path;
                  modelStyle.size = styleStorage.stats.size;
                  modelStyle.mime = styleStorage.mime;
                  modelStyle.ctime = styleStorage.stats.ctime;
                  modelStyle.mtime = styleStorage.stats.mtime;
                  modelStyle.oname = attachmentInfo.name; // original name of the file
                  if(atts) {
                    modelStyle.format = styleStorage.features.format;
                    modelStyle.depth = styleStorage.features.depth;
                    modelStyle.dims.h = styleStorage.features.height;
                    modelStyle.dims.w = styleStorage.features.width;
                  }
                });
              }

              stylesToReset.forEach(function(resetStyleName) {
                var path = [propertyName, resetStyleName].join('.');
                selfModel.set(path, null);
              });

              cb(null);
            });

          });
        });
      });
    });
  }; // method attach
};

// Prototype for Storage Providers
function StorageProvider(options) {
  this.options = options;
}
StorageProvider.prototype.update = function(attachment, cb) {
  throw new Error('method update implemented');
};
plugin.StorageProvider = StorageProvider;

// Method to Register Storage Providers
plugin.registerStorageProvider = function(name, provider) {
  if(typeof(name) !== 'string') throw new Error('storage engine name is required');
  if(provider && provider._super == StorageProvider) throw new Error('provider is not valid. it does not inherits from StorageEngine');
  providersRegistry[name] = provider;
}

// Register a Known Decoding Format(e.g 'PNG')
plugin.registerDecodingFormat = function(name) {
  supportedDecodingFormats.push(name);
}

/*
 * Use this to register all formats for which your local ImageMagick installation supports
 * read operations.
 */
plugin.registerImageMagickDecodingFormats = function() {
  plugin.registerImageMagickFormats({ read: true });
}

/*
 * You can register formats based on certain modes or a combination of those:
 * 'read' : true|false
 * 'write': true|false
 * 'multi': true|false
 * 'blob' : true|false
 * options is optional and defaults to { read: true }. If several modes with value true are given,
 * only formats supporting all of them are included.
 */
plugin.registerImageMagickFormats = function(options, callback) {
  if (!callback) {
    callback = function(error, formats) {
      if (error) throw new Error(error);
      else if (formats && formats.length > 0) {
        supportedDecodingFormats = formats;
      } else {
        throw new Error("No formats supported for decoding!");
      }
    };
  }
  findImageMagickFormats(options, callback);
}

// Export the Plugin for mongoose.js
module.exports = plugin;
<|endoftext|>


<commit_before>import React from 'react';
import NotificationsContainer from './containers/notifications_container';
import PropTypes from 'prop-types';
import LoadingBarContainer from './containers/loading_bar_container';
import TabsBar from './components/tabs_bar';
import ModalContainer from './containers/modal_container';
import { connect } from 'react-redux';
import { Redirect, withRouter } from 'react-router-dom';
import { isMobile } from '../../is_mobile';
import { debounce } from 'lodash';
import { uploadCompose, resetCompose } from '../../actions/compose';
import { refreshHomeTimeline } from '../../actions/timelines';
import { refreshNotifications } from '../../actions/notifications';
import { clearHeight } from '../../actions/height_cache';
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
import UploadArea from './components/upload_area';
import ColumnsAreaContainer from './containers/columns_area_container';
import {
  Compose,
  Status,
  GettingStarted,
  PublicTimeline,
  CommunityTimeline,
  AccountTimeline,
  AccountGallery,
  HomeTimeline,
  Followers,
  Following,
  Reblogs,
  Favourites,
  HashtagTimeline,
  Notifications,
  FollowRequests,
  GenericNotFound,
  FavouritedStatuses,
  Blocks,
  Mutes,
  PinnedStatuses,
} from './util/async-components';
import { HotKeys } from 'react-hotkeys';

// Dummy import, to make sure that <Status /> ends up in the application bundle.
// Without this it ends up in ~8 very commonly used bundles.
import '../../components/status';

const mapStateToProps = state => ({
  me: state.getIn(['meta', 'me']),
  isComposing: state.getIn(['compose', 'is_composing']),
});

const keyMap = {
  new: 'n',
  search: 's',
  forceNew: 'option+n',
  focusColumn: ['1', '2', '3', '4', '5', '6', '7', '8', '9'],
  reply: 'r',
  favourite: 'f',
  boost: 'b',
  mention: 'm',
  open: ['enter', 'o'],
  openProfile: 'p',
  moveDown: ['down', 'j'],
  moveUp: ['up', 'k'],
  back: 'backspace',
  goToHome: 'g h',
  goToNotifications: 'g n',
  goToLocal: 'g l',
  goToFederated: 'g t',
  goToStart: 'g s',
  goToFavourites: 'g f',
  goToPinned: 'g p',
  goToProfile: 'g u',
  goToBlocked: 'g b',
  goToMuted: 'g m',
};

@connect(mapStateToProps)
@withRouter
export default class UI extends React.Component {

  static contextTypes = {
    router: PropTypes.object.isRequired,
  };

  static propTypes = {
    dispatch: PropTypes.func.isRequired,
    children: PropTypes.node,
    isComposing: PropTypes.bool,
    me: PropTypes.string,
    location: PropTypes.object,
  };

  state = {
    width: window.innerWidth,
    draggingOver: false,
  };

  handleResize = debounce(() => {
    // The cached heights are no longer accurate, invalidate
    this.props.dispatch(clearHeight());

    this.setState({ width: window.innerWidth });
  }, 500, {
    trailing: true,
  });

  handleDragEnter = (e) => {
    e.preventDefault();

    if (!this.dragTargets) {
      this.dragTargets = [];
    }

    if (this.dragTargets.indexOf(e.target) === -1) {
      this.dragTargets.push(e.target);
    }

    if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
      this.setState({ draggingOver: true });
    }
  }

  handleDragOver = (e) => {
    e.preventDefault();
    e.stopPropagation();

    try {
      e.dataTransfer.dropEffect = 'copy';
    } catch (err) {

    }

    return false;
  }

  handleDrop = (e) => {
    e.preventDefault();

    this.setState({ draggingOver: false });

    if (e.dataTransfer && e.dataTransfer.files.length === 1) {
      this.props.dispatch(uploadCompose(e.dataTransfer.files));
    }
  }

  handleDragLeave = (e) => {
    e.preventDefault();
    e.stopPropagation();

    this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el));

    if (this.dragTargets.length > 0) {
      return;
    }

    this.setState({ draggingOver: false });
  }

  closeUploadModal = () => {
    this.setState({ draggingOver: false });
  }

  handleServiceWorkerPostMessage = ({ data }) => {
    if (data.type === 'navigate') {
      this.context.router.history.push(data.path);
    } else {
      console.warn('Unknown message type:', data.type);
    }
  }

  componentWillMount () {
    window.addEventListener('resize', this.handleResize, { passive: true });
    document.addEventListener('dragenter', this.handleDragEnter, false);
    document.addEventListener('dragover', this.handleDragOver, false);
    document.addEventListener('drop', this.handleDrop, false);
    document.addEventListener('dragleave', this.handleDragLeave, false);
    document.addEventListener('dragend', this.handleDragEnd, false);

    if ('serviceWorker' in  navigator) {
      navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
    }

    this.props.dispatch(refreshHomeTimeline());
    this.props.dispatch(refreshNotifications());
  }

  componentDidMount () {
    this.hotkeys.__mousetrap__.stopCallback = (e, element) => {
      return !(e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) && ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName);
    };
  }

  shouldComponentUpdate (nextProps) {
    if (nextProps.isComposing !== this.props.isComposing) {
      // Avoid expensive update just to toggle a class
      this.node.classList.toggle('is-composing', nextProps.isComposing);

      return false;
    }

    // Why isn't this working?!?
    // return super.shouldComponentUpdate(nextProps, nextState);
    return true;
  }

  componentDidUpdate (prevProps) {
    if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
      this.columnsAreaNode.handleChildrenContentChange();
    }
  }

  componentWillUnmount () {
    window.removeEventListener('resize', this.handleResize);
    document.removeEventListener('dragenter', this.handleDragEnter);
    document.removeEventListener('dragover', this.handleDragOver);
    document.removeEventListener('drop', this.handleDrop);
    document.removeEventListener('dragleave', this.handleDragLeave);
    document.removeEventListener('dragend', this.handleDragEnd);
  }

  setRef = c => {
    this.node = c;
  }

  setColumnsAreaRef = c => {
    this.columnsAreaNode = c.getWrappedInstance().getWrappedInstance();
  }

  handleHotkeyNew = e => {
    e.preventDefault();

    const element = this.node.querySelector('.compose-form__autosuggest-wrapper textarea');

    if (element) {
      element.focus();
    }
  }

  handleHotkeySearch = e => {
    e.preventDefault();

    const element = this.node.querySelector('.search__input');

    if (element) {
      element.focus();
    }
  }

  handleHotkeyForceNew = e => {
    this.handleHotkeyNew(e);
    this.props.dispatch(resetCompose());
  }

  handleHotkeyFocusColumn = e => {
    const index  = (e.key * 1) + 1; // First child is drawer, skip that
    const column = this.node.querySelector(`.column:nth-child(${index})`);

    if (column) {
      const status = column.querySelector('.focusable');

      if (status) {
        status.focus();
      }
    }
  }

  handleHotkeyBack = () => {
    if (window.history && window.history.length === 1) {
      this.context.router.history.push('/');
    } else {
      this.context.router.history.goBack();
    }
  }

  setHotkeysRef = c => {
    this.hotkeys = c;
  }

  handleHotkeyGoToHome = () => {
    this.context.router.history.push('/timelines/home');
  }

  handleHotkeyGoToNotifications = () => {
    this.context.router.history.push('/notifications');
  }

  handleHotkeyGoToLocal = () => {
    this.context.router.history.push('/timelines/public/local');
  }

  handleHotkeyGoToFederated = () => {
    this.context.router.history.push('/timelines/public');
  }

  handleHotkeyGoToStart = () => {
    this.context.router.history.push('/getting-started');
  }

  handleHotkeyGoToFavourites = () => {
    this.context.router.history.push('/favourites');
  }

  handleHotkeyGoToPinned = () => {
    this.context.router.history.push('/pinned');
  }

  handleHotkeyGoToProfile = () => {
    this.context.router.history.push(`/accounts/${this.props.me}`);
  }

  handleHotkeyGoToBlocked = () => {
    this.context.router.history.push('/blocks');
  }

  handleHotkeyGoToMuted = () => {
    this.context.router.history.push('/mutes');
  }

  render () {
    const { width, draggingOver } = this.state;
    const { children } = this.props;

    const handlers = {
      new: this.handleHotkeyNew,
      search: this.handleHotkeySearch,
      forceNew: this.handleHotkeyForceNew,
      focusColumn: this.handleHotkeyFocusColumn,
      back: this.handleHotkeyBack,
      goToHome: this.handleHotkeyGoToHome,
      goToNotifications: this.handleHotkeyGoToNotifications,
      goToLocal: this.handleHotkeyGoToLocal,
      goToFederated: this.handleHotkeyGoToFederated,
      goToStart: this.handleHotkeyGoToStart,
      goToFavourites: this.handleHotkeyGoToFavourites,
      goToPinned: this.handleHotkeyGoToPinned,
      goToProfile: this.handleHotkeyGoToProfile,
      goToBlocked: this.handleHotkeyGoToBlocked,
      goToMuted: this.handleHotkeyGoToMuted,
    };

    return (
      <HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}>
        <div className='ui' ref={this.setRef}>
          <TabsBar />

          <ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width)}>
            <WrappedSwitch>
              <Redirect from='/' to='/getting-started' exact />
              <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
              <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
              <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
              <WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
              <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />

              <WrappedRoute path='/notifications' component={Notifications} content={children} />
              <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
              <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />

              <WrappedRoute path='/statuses/new' component={Compose} content={children} />
              <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
              <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
              <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />

              <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
              <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
              <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
              <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />

              <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
              <WrappedRoute path='/blocks' component={Blocks} content={children} />
              <WrappedRoute path='/mutes' component={Mutes} content={children} />

              <WrappedRoute component={GenericNotFound} content={children} />
            </WrappedSwitch>
          </ColumnsAreaContainer>

          <NotificationsContainer />
          <LoadingBarContainer className='loading-bar' />
          <ModalContainer />
          <UploadArea active={draggingOver} onClose={this.closeUploadModal} />
        </div>
      </HotKeys>
    );
  }

}
<commit_msg>Fix #5306: Stop hotkeys in input fields even when shift is pressed (#5309)<commit_after>import React from 'react';
import NotificationsContainer from './containers/notifications_container';
import PropTypes from 'prop-types';
import LoadingBarContainer from './containers/loading_bar_container';
import TabsBar from './components/tabs_bar';
import ModalContainer from './containers/modal_container';
import { connect } from 'react-redux';
import { Redirect, withRouter } from 'react-router-dom';
import { isMobile } from '../../is_mobile';
import { debounce } from 'lodash';
import { uploadCompose, resetCompose } from '../../actions/compose';
import { refreshHomeTimeline } from '../../actions/timelines';
import { refreshNotifications } from '../../actions/notifications';
import { clearHeight } from '../../actions/height_cache';
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
import UploadArea from './components/upload_area';
import ColumnsAreaContainer from './containers/columns_area_container';
import {
  Compose,
  Status,
  GettingStarted,
  PublicTimeline,
  CommunityTimeline,
  AccountTimeline,
  AccountGallery,
  HomeTimeline,
  Followers,
  Following,
  Reblogs,
  Favourites,
  HashtagTimeline,
  Notifications,
  FollowRequests,
  GenericNotFound,
  FavouritedStatuses,
  Blocks,
  Mutes,
  PinnedStatuses,
} from './util/async-components';
import { HotKeys } from 'react-hotkeys';

// Dummy import, to make sure that <Status /> ends up in the application bundle.
// Without this it ends up in ~8 very commonly used bundles.
import '../../components/status';

const mapStateToProps = state => ({
  me: state.getIn(['meta', 'me']),
  isComposing: state.getIn(['compose', 'is_composing']),
});

const keyMap = {
  new: 'n',
  search: 's',
  forceNew: 'option+n',
  focusColumn: ['1', '2', '3', '4', '5', '6', '7', '8', '9'],
  reply: 'r',
  favourite: 'f',
  boost: 'b',
  mention: 'm',
  open: ['enter', 'o'],
  openProfile: 'p',
  moveDown: ['down', 'j'],
  moveUp: ['up', 'k'],
  back: 'backspace',
  goToHome: 'g h',
  goToNotifications: 'g n',
  goToLocal: 'g l',
  goToFederated: 'g t',
  goToStart: 'g s',
  goToFavourites: 'g f',
  goToPinned: 'g p',
  goToProfile: 'g u',
  goToBlocked: 'g b',
  goToMuted: 'g m',
};

@connect(mapStateToProps)
@withRouter
export default class UI extends React.Component {

  static contextTypes = {
    router: PropTypes.object.isRequired,
  };

  static propTypes = {
    dispatch: PropTypes.func.isRequired,
    children: PropTypes.node,
    isComposing: PropTypes.bool,
    me: PropTypes.string,
    location: PropTypes.object,
  };

  state = {
    width: window.innerWidth,
    draggingOver: false,
  };

  handleResize = debounce(() => {
    // The cached heights are no longer accurate, invalidate
    this.props.dispatch(clearHeight());

    this.setState({ width: window.innerWidth });
  }, 500, {
    trailing: true,
  });

  handleDragEnter = (e) => {
    e.preventDefault();

    if (!this.dragTargets) {
      this.dragTargets = [];
    }

    if (this.dragTargets.indexOf(e.target) === -1) {
      this.dragTargets.push(e.target);
    }

    if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
      this.setState({ draggingOver: true });
    }
  }

  handleDragOver = (e) => {
    e.preventDefault();
    e.stopPropagation();

    try {
      e.dataTransfer.dropEffect = 'copy';
    } catch (err) {

    }

    return false;
  }

  handleDrop = (e) => {
    e.preventDefault();

    this.setState({ draggingOver: false });

    if (e.dataTransfer && e.dataTransfer.files.length === 1) {
      this.props.dispatch(uploadCompose(e.dataTransfer.files));
    }
  }

  handleDragLeave = (e) => {
    e.preventDefault();
    e.stopPropagation();

    this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el));

    if (this.dragTargets.length > 0) {
      return;
    }

    this.setState({ draggingOver: false });
  }

  closeUploadModal = () => {
    this.setState({ draggingOver: false });
  }

  handleServiceWorkerPostMessage = ({ data }) => {
    if (data.type === 'navigate') {
      this.context.router.history.push(data.path);
    } else {
      console.warn('Unknown message type:', data.type);
    }
  }

  componentWillMount () {
    window.addEventListener('resize', this.handleResize, { passive: true });
    document.addEventListener('dragenter', this.handleDragEnter, false);
    document.addEventListener('dragover', this.handleDragOver, false);
    document.addEventListener('drop', this.handleDrop, false);
    document.addEventListener('dragleave', this.handleDragLeave, false);
    document.addEventListener('dragend', this.handleDragEnd, false);

    if ('serviceWorker' in  navigator) {
      navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
    }

    this.props.dispatch(refreshHomeTimeline());
    this.props.dispatch(refreshNotifications());
  }

  componentDidMount () {
    this.hotkeys.__mousetrap__.stopCallback = (e, element) => {
      return !(e.altKey || e.ctrlKey || e.metaKey) && ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName);
    };
  }

  shouldComponentUpdate (nextProps) {
    if (nextProps.isComposing !== this.props.isComposing) {
      // Avoid expensive update just to toggle a class
      this.node.classList.toggle('is-composing', nextProps.isComposing);

      return false;
    }

    // Why isn't this working?!?
    // return super.shouldComponentUpdate(nextProps, nextState);
    return true;
  }

  componentDidUpdate (prevProps) {
    if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
      this.columnsAreaNode.handleChildrenContentChange();
    }
  }

  componentWillUnmount () {
    window.removeEventListener('resize', this.handleResize);
    document.removeEventListener('dragenter', this.handleDragEnter);
    document.removeEventListener('dragover', this.handleDragOver);
    document.removeEventListener('drop', this.handleDrop);
    document.removeEventListener('dragleave', this.handleDragLeave);
    document.removeEventListener('dragend', this.handleDragEnd);
  }

  setRef = c => {
    this.node = c;
  }

  setColumnsAreaRef = c => {
    this.columnsAreaNode = c.getWrappedInstance().getWrappedInstance();
  }

  handleHotkeyNew = e => {
    e.preventDefault();

    const element = this.node.querySelector('.compose-form__autosuggest-wrapper textarea');

    if (element) {
      element.focus();
    }
  }

  handleHotkeySearch = e => {
    e.preventDefault();

    const element = this.node.querySelector('.search__input');

    if (element) {
      element.focus();
    }
  }

  handleHotkeyForceNew = e => {
    this.handleHotkeyNew(e);
    this.props.dispatch(resetCompose());
  }

  handleHotkeyFocusColumn = e => {
    const index  = (e.key * 1) + 1; // First child is drawer, skip that
    const column = this.node.querySelector(`.column:nth-child(${index})`);

    if (column) {
      const status = column.querySelector('.focusable');

      if (status) {
        status.focus();
      }
    }
  }

  handleHotkeyBack = () => {
    if (window.history && window.history.length === 1) {
      this.context.router.history.push('/');
    } else {
      this.context.router.history.goBack();
    }
  }

  setHotkeysRef = c => {
    this.hotkeys = c;
  }

  handleHotkeyGoToHome = () => {
    this.context.router.history.push('/timelines/home');
  }

  handleHotkeyGoToNotifications = () => {
    this.context.router.history.push('/notifications');
  }

  handleHotkeyGoToLocal = () => {
    this.context.router.history.push('/timelines/public/local');
  }

  handleHotkeyGoToFederated = () => {
    this.context.router.history.push('/timelines/public');
  }

  handleHotkeyGoToStart = () => {
    this.context.router.history.push('/getting-started');
  }

  handleHotkeyGoToFavourites = () => {
    this.context.router.history.push('/favourites');
  }

  handleHotkeyGoToPinned = () => {
    this.context.router.history.push('/pinned');
  }

  handleHotkeyGoToProfile = () => {
    this.context.router.history.push(`/accounts/${this.props.me}`);
  }

  handleHotkeyGoToBlocked = () => {
    this.context.router.history.push('/blocks');
  }

  handleHotkeyGoToMuted = () => {
    this.context.router.history.push('/mutes');
  }

  render () {
    const { width, draggingOver } = this.state;
    const { children } = this.props;

    const handlers = {
      new: this.handleHotkeyNew,
      search: this.handleHotkeySearch,
      forceNew: this.handleHotkeyForceNew,
      focusColumn: this.handleHotkeyFocusColumn,
      back: this.handleHotkeyBack,
      goToHome: this.handleHotkeyGoToHome,
      goToNotifications: this.handleHotkeyGoToNotifications,
      goToLocal: this.handleHotkeyGoToLocal,
      goToFederated: this.handleHotkeyGoToFederated,
      goToStart: this.handleHotkeyGoToStart,
      goToFavourites: this.handleHotkeyGoToFavourites,
      goToPinned: this.handleHotkeyGoToPinned,
      goToProfile: this.handleHotkeyGoToProfile,
      goToBlocked: this.handleHotkeyGoToBlocked,
      goToMuted: this.handleHotkeyGoToMuted,
    };

    return (
      <HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}>
        <div className='ui' ref={this.setRef}>
          <TabsBar />

          <ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width)}>
            <WrappedSwitch>
              <Redirect from='/' to='/getting-started' exact />
              <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
              <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
              <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
              <WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
              <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />

              <WrappedRoute path='/notifications' component={Notifications} content={children} />
              <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
              <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />

              <WrappedRoute path='/statuses/new' component={Compose} content={children} />
              <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
              <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
              <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />

              <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
              <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
              <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
              <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />

              <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
              <WrappedRoute path='/blocks' component={Blocks} content={children} />
              <WrappedRoute path='/mutes' component={Mutes} content={children} />

              <WrappedRoute component={GenericNotFound} content={children} />
            </WrappedSwitch>
          </ColumnsAreaContainer>

          <NotificationsContainer />
          <LoadingBarContainer className='loading-bar' />
          <ModalContainer />
          <UploadArea active={draggingOver} onClose={this.closeUploadModal} />
        </div>
      </HotKeys>
    );
  }

}
<|endoftext|>


<commit_before>// Copyright (c) 2011 Firebase.co - http://www.firebase.co
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

var im = require('imagemagick');
var fs = require('fs');
var path = require('path');
var async = require('async');

// keeps a global registry of storage providers
var providersRegistry = {};

var supportedDecodingFormats = [
  'PNG',
  'GIF',
  'TIFF',
  'JPEG'
];

function findProvider(name) {
  var provider = providersRegistry[name];
  if(!provider) throw new Error('Storage Provider "' + name + '" can not be found');
  return provider;
}

var plugin = function(schema, options) {
  options = options || {};
  if(typeof(options.directory) !== 'string') throw new Error('option "directory" is required');
  if(typeof(options.properties) !== 'object') throw new Error('option "properties" is required');
  if(typeof(options.storage) !== 'object') throw new Error('option "storage" is required');
  var storageOptions = options.storage;
  storageOptions.schema = schema;

  if(typeof(storageOptions.providerName) !== 'string') throw new Error('option "storage.providerName" is required');
  var providerPrototype = findProvider(storageOptions.providerName);

  var providerOptions = storageOptions.options || {};
  var providerInstance = new providerPrototype(providerOptions);
  var propertyNames = Object.keys(options.properties);
  propertyNames.forEach(function(propertyName) {
    var propertyOptions = options.properties[propertyName];
    if(!propertyOptions) throw new Error('property "' + propertyName + '" requires an specification');

    var styles = propertyOptions.styles || {};
    var styleNames = Object.keys(styles);
    if(styleNames.length == 0) throw new Error('property "' + propertyName + '" needs to define at least one style');

    var addOp = {};
    var propSchema = addOp[propertyName] = {};
    styleNames.forEach(function(styleName) {
      propSchema[styleName] = {
        size: Number // Size of the File
        , oname: String // Original name of the file
        , mtime: Date
        , ctime: Date
        , path: String // Storage Path
        , defaultUrl: String // Default (non-secure, most of the time public) Url
        , format: String // Format of the File(provided by identify).
        , depth: Number
        , dims: { // Dimensions of the Image
          h: Number, // Height
          w: Number // Width
        }
      };
    });

    // Add the Property
    schema.add(addOp);
  }); // for each property name

  // Finally we set the method 'attach'
  // => propertyName: String. Name of the property to attach the file to.
  // => attachmentInfo: {
  //  path: String(required). Path to the file in the file system.
  //  name: String(optional). Original Name of the file.
  //  mime: String(optional). Mime type of the file.
  schema.methods.attach = function(propertyName, attachmentInfo, cb) {
    var selfModel = this;
    if(propertyNames.indexOf(propertyName) == -1) return cb(new Error('property "' + propertyName + '" was not registered as an attachment property'));
    var propertyOptions = options.properties[propertyName];
    var styles = propertyOptions.styles || {};
    if(!attachmentInfo || typeof(attachmentInfo) !== 'object') return cb(new Error('attachmentInfo is not valid'));
    if(typeof(attachmentInfo.path) !== 'string') return cb(new Error('attachmentInfo has no valid path'));
    if(!attachmentInfo.name) {
      // No original name provided? We infer it from the path
      attachmentInfo.name = path.basename(attachmentInfo.path);
    }
    path.exists(attachmentInfo.path, function(exists) {
      if(!exists) return cb(new Error('file to attach at path "' + attachmentInfo.path + '" does not exists'));
      fs.stat(attachmentInfo.path, function(err, stats) {
        if(!stats.isFile()) return cb(new Error('path to attach from "' + attachmentInfo.path + '" is not a file'));
        im.identify(attachmentInfo.path, function(err, atts) {
          // if 'identify' fails, that probably means the file is not an image.
          var canTransform = !!atts && supportedDecodingFormats.indexOf(atts.format) != -1;
          var fileExt = path.extname(attachmentInfo.path);
          var styles = propertyOptions.styles || {};
          var styleNames = Object.keys(styles);

          var tasks = [];
          var stylesToReset = []; // names of the style that needs to be reset at the end of the process.
          styleNames.forEach(function(styleName) {
            var styleOptions = styles[styleName] || {};
            var finishConversion = function(styleFilePath, atts, cb) {
              var ext = path.extname(styleFilePath);
              var storageStylePath = '/' + options.directory + '/' + selfModel.id + '-' + styleName + ext;
              fs.stat(styleFilePath, function(err, stats) {
                if(err) return cb(err);
                cb(null, {
                  style: {
                    name: styleName,
                    options: styleOptions
                  },
                  filename: styleFilePath,
                  stats: stats,
                  propertyName: propertyName,
                  model: selfModel,
                  path: storageStylePath,
                  defaultUrl: null, // let the storage assign this
                  features: atts
                });
              });
            };
            var optionKeys = Object.keys(styleOptions);
            var transformationNames = [];
            optionKeys.forEach(function(transformationName) {
              if(transformationName.indexOf('$') != 0) {  // if is not special command, add it as an special transformation argument
                transformationNames.push(transformationName);
              }
            });
            if(optionKeys.length != 0) {
              if(canTransform) {
                var styleFileExt = styleOptions['$format'] ? ('.' + styleOptions['$format']) : fileExt;
                var styleFileName = path.basename(attachmentInfo.path, fileExt);
                styleFileName += '-' + styleName + styleFileExt;
                var styleFilePath = path.join(path.dirname(attachmentInfo.path), styleFileName);
                var convertArgs = [attachmentInfo.path]; // source file name

                // add all the transformations args

                transformationNames.forEach(function(transformationName) {
                  convertArgs.push('-' + transformationName);
                  convertArgs.push(styleOptions[transformationName]);
                });

                convertArgs.push(styleFilePath);
                tasks.push(function(cb) {

                  // invoke 'convert'
                  im.convert(convertArgs, function(err, stdout, stderr) {
                    if(err) return cb(err);

                    // run identify in the styled image
                    im.identify(styleFilePath, function(err, atts) {
                      if(err) return cb(err);
                      finishConversion(styleFilePath, atts, cb);
                    });
                  });

                }); // tasks.push
              } else {
                stylesToReset.push(styleName);
              }// if can decode
            } else {
              // keep the file as original
              tasks.push(function(cb) {
                finishConversion(attachmentInfo.path, atts, cb);
              });
            }

          }); // for each style

          async.parallel(tasks, function(err, convertResults) {
            if(err) return cb(err);

            //console.log(convertResults);
            tasks = [];
            convertResults.forEach(function(convertResult) {
              tasks.push(function(cb) {

                // tell the provider to create or replace the attachment
                providerInstance.createOrReplace(convertResult, function(err, attachment) {
                  if(err) return cb(err);
                  cb(null, attachment);
                });

              });
            });

            async.parallel(tasks, function(err, storageResults) {
              if(err) return cb(err);

              // Finally Update the Model
              var propModel = selfModel[propertyName];
              if(storageResults.length > 0) { // only update the model if a transformation was performed.
                storageResults.forEach(function(styleStorage) {
                  var modelStyle = propModel[styleStorage.style.name];
                  modelStyle.defaultUrl = styleStorage.defaultUrl;
                  modelStyle.path = styleStorage.path;
                  modelStyle.size = styleStorage.stats.size;
                  modelStyle.mime = styleStorage.mime;
                  modelStyle.ctime = styleStorage.stats.ctime;
                  modelStyle.mtime = styleStorage.stats.mtime;
                  modelStyle.oname = attachmentInfo.name; // original name of the file
                  if(atts) {
                    modelStyle.format = styleStorage.features.format;
                    modelStyle.depth = styleStorage.features.depth;
                    modelStyle.dims.h = styleStorage.features.height;
                    modelStyle.dims.w = styleStorage.features.width;
                  }
                });
              }

              stylesToReset.forEach(function(resetStyleName) {
                var path = [propertyName, resetStyleName].join('.');
                selfModel.set(path, null);
              });

              cb(null);
            });

          });
        });
      });
    });
  }; // method attach
};

// Prototype for Storage Providers
function StorageProvider(options) {
  this.options = options;
}
StorageProvider.prototype.update = function(attachment, cb) {
  throw new Error('method update implemented');
};
plugin.StorageProvider = StorageProvider;

// Method to Register Storage Providers
plugin.registerStorageProvider = function(name, provider) {
  if(typeof(name) !== 'string') throw new Error('storage engine name is required');
  if(provider && provider._super == StorageProvider) throw new Error('provider is not valid. it does not inherits from StorageEngine');
  providersRegistry[name] = provider;
}

// Register a Known Decoding Format(e.g 'PNG')
plugin.registerDecodingFormat = function(name) {
  supportedDecodingFormats.push(name);
}

// Export the Plugin for mongoose.js
module.exports = plugin;
<commit_msg>backwards compatible handling of fn.exists and path.exists<commit_after>// Copyright (c) 2011 Firebase.co - http://www.firebase.co
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

var im = require('imagemagick');
var fs = require('fs');
var path = require('path');
var async = require('async');
var existsFn = fs.exists || path.exists;

// keeps a global registry of storage providers
var providersRegistry = {};

var supportedDecodingFormats = [
  'PNG',
  'GIF',
  'TIFF',
  'JPEG'
];

function findProvider(name) {
  var provider = providersRegistry[name];
  if(!provider) throw new Error('Storage Provider "' + name + '" can not be found');
  return provider;
}

var plugin = function(schema, options) {
  options = options || {};
  if(typeof(options.directory) !== 'string') throw new Error('option "directory" is required');
  if(typeof(options.properties) !== 'object') throw new Error('option "properties" is required');
  if(typeof(options.storage) !== 'object') throw new Error('option "storage" is required');
  var storageOptions = options.storage;
  storageOptions.schema = schema;

  if(typeof(storageOptions.providerName) !== 'string') throw new Error('option "storage.providerName" is required');
  var providerPrototype = findProvider(storageOptions.providerName);

  var providerOptions = storageOptions.options || {};
  var providerInstance = new providerPrototype(providerOptions);
  var propertyNames = Object.keys(options.properties);
  propertyNames.forEach(function(propertyName) {
    var propertyOptions = options.properties[propertyName];
    if(!propertyOptions) throw new Error('property "' + propertyName + '" requires an specification');

    var styles = propertyOptions.styles || {};
    var styleNames = Object.keys(styles);
    if(styleNames.length == 0) throw new Error('property "' + propertyName + '" needs to define at least one style');

    var addOp = {};
    var propSchema = addOp[propertyName] = {};
    styleNames.forEach(function(styleName) {
      propSchema[styleName] = {
        size: Number // Size of the File
        , oname: String // Original name of the file
        , mtime: Date
        , ctime: Date
        , path: String // Storage Path
        , defaultUrl: String // Default (non-secure, most of the time public) Url
        , format: String // Format of the File(provided by identify).
        , depth: Number
        , dims: { // Dimensions of the Image
          h: Number, // Height
          w: Number // Width
        }
      };
    });

    // Add the Property
    schema.add(addOp);
  }); // for each property name

  // Finally we set the method 'attach'
  // => propertyName: String. Name of the property to attach the file to.
  // => attachmentInfo: {
  //  path: String(required). Path to the file in the file system.
  //  name: String(optional). Original Name of the file.
  //  mime: String(optional). Mime type of the file.
  schema.methods.attach = function(propertyName, attachmentInfo, cb) {
    var selfModel = this;
    if(propertyNames.indexOf(propertyName) == -1) return cb(new Error('property "' + propertyName + '" was not registered as an attachment property'));
    var propertyOptions = options.properties[propertyName];
    var styles = propertyOptions.styles || {};
    if(!attachmentInfo || typeof(attachmentInfo) !== 'object') return cb(new Error('attachmentInfo is not valid'));
    if(typeof(attachmentInfo.path) !== 'string') return cb(new Error('attachmentInfo has no valid path'));
    if(!attachmentInfo.name) {
      // No original name provided? We infer it from the path
      attachmentInfo.name = path.basename(attachmentInfo.path);
    }
    existsFn(attachmentInfo.path, function(exists) {
      if(!exists) return cb(new Error('file to attach at path "' + attachmentInfo.path + '" does not exists'));
      fs.stat(attachmentInfo.path, function(err, stats) {
        if(!stats.isFile()) return cb(new Error('path to attach from "' + attachmentInfo.path + '" is not a file'));
        im.identify(attachmentInfo.path, function(err, atts) {
          // if 'identify' fails, that probably means the file is not an image.
          var canTransform = !!atts && supportedDecodingFormats.indexOf(atts.format) != -1;
          var fileExt = path.extname(attachmentInfo.path);
          var styles = propertyOptions.styles || {};
          var styleNames = Object.keys(styles);

          var tasks = [];
          var stylesToReset = []; // names of the style that needs to be reset at the end of the process.
          styleNames.forEach(function(styleName) {
            var styleOptions = styles[styleName] || {};
            var finishConversion = function(styleFilePath, atts, cb) {
              var ext = path.extname(styleFilePath);
              var storageStylePath = '/' + options.directory + '/' + selfModel.id + '-' + styleName + ext;
              fs.stat(styleFilePath, function(err, stats) {
                if(err) return cb(err);
                cb(null, {
                  style: {
                    name: styleName,
                    options: styleOptions
                  },
                  filename: styleFilePath,
                  stats: stats,
                  propertyName: propertyName,
                  model: selfModel,
                  path: storageStylePath,
                  defaultUrl: null, // let the storage assign this
                  features: atts
                });
              });
            };
            var optionKeys = Object.keys(styleOptions);
            var transformationNames = [];
            optionKeys.forEach(function(transformationName) {
              if(transformationName.indexOf('$') != 0) {  // if is not special command, add it as an special transformation argument
                transformationNames.push(transformationName);
              }
            });
            if(optionKeys.length != 0) {
              if(canTransform) {
                var styleFileExt = styleOptions['$format'] ? ('.' + styleOptions['$format']) : fileExt;
                var styleFileName = path.basename(attachmentInfo.path, fileExt);
                styleFileName += '-' + styleName + styleFileExt;
                var styleFilePath = path.join(path.dirname(attachmentInfo.path), styleFileName);
                var convertArgs = [attachmentInfo.path]; // source file name

                // add all the transformations args

                transformationNames.forEach(function(transformationName) {
                  convertArgs.push('-' + transformationName);
                  convertArgs.push(styleOptions[transformationName]);
                });

                convertArgs.push(styleFilePath);
                tasks.push(function(cb) {

                  // invoke 'convert'
                  im.convert(convertArgs, function(err, stdout, stderr) {
                    if(err) return cb(err);

                    // run identify in the styled image
                    im.identify(styleFilePath, function(err, atts) {
                      if(err) return cb(err);
                      finishConversion(styleFilePath, atts, cb);
                    });
                  });

                }); // tasks.push
              } else {
                stylesToReset.push(styleName);
              }// if can decode
            } else {
              // keep the file as original
              tasks.push(function(cb) {
                finishConversion(attachmentInfo.path, atts, cb);
              });
            }

          }); // for each style

          async.parallel(tasks, function(err, convertResults) {
            if(err) return cb(err);

            //console.log(convertResults);
            tasks = [];
            convertResults.forEach(function(convertResult) {
              tasks.push(function(cb) {

                // tell the provider to create or replace the attachment
                providerInstance.createOrReplace(convertResult, function(err, attachment) {
                  if(err) return cb(err);
                  cb(null, attachment);
                });

              });
            });

            async.parallel(tasks, function(err, storageResults) {
              if(err) return cb(err);

              // Finally Update the Model
              var propModel = selfModel[propertyName];
              if(storageResults.length > 0) { // only update the model if a transformation was performed.
                storageResults.forEach(function(styleStorage) {
                  var modelStyle = propModel[styleStorage.style.name];
                  modelStyle.defaultUrl = styleStorage.defaultUrl;
                  modelStyle.path = styleStorage.path;
                  modelStyle.size = styleStorage.stats.size;
                  modelStyle.mime = styleStorage.mime;
                  modelStyle.ctime = styleStorage.stats.ctime;
                  modelStyle.mtime = styleStorage.stats.mtime;
                  modelStyle.oname = attachmentInfo.name; // original name of the file
                  if(atts) {
                    modelStyle.format = styleStorage.features.format;
                    modelStyle.depth = styleStorage.features.depth;
                    modelStyle.dims.h = styleStorage.features.height;
                    modelStyle.dims.w = styleStorage.features.width;
                  }
                });
              }

              stylesToReset.forEach(function(resetStyleName) {
                var path = [propertyName, resetStyleName].join('.');
                selfModel.set(path, null);
              });

              cb(null);
            });

          });
        });
      });
    });
  }; // method attach
};

// Prototype for Storage Providers
function StorageProvider(options) {
  this.options = options;
}
StorageProvider.prototype.update = function(attachment, cb) {
  throw new Error('method update implemented');
};
plugin.StorageProvider = StorageProvider;

// Method to Register Storage Providers
plugin.registerStorageProvider = function(name, provider) {
  if(typeof(name) !== 'string') throw new Error('storage engine name is required');
  if(provider && provider._super == StorageProvider) throw new Error('provider is not valid. it does not inherits from StorageEngine');
  providersRegistry[name] = provider;
}

// Register a Known Decoding Format(e.g 'PNG')
plugin.registerDecodingFormat = function(name) {
  supportedDecodingFormats.push(name);
}

// Export the Plugin for mongoose.js
module.exports = plugin;
<|endoftext|>


<commit_before>import React from 'react';
import Switch from 'react-router-dom/Switch';
import Route from 'react-router-dom/Route';
import Redirect from 'react-router-dom/Redirect';
import NotificationsContainer from './containers/notifications_container';
import PropTypes from 'prop-types';
import LoadingBarContainer from './containers/loading_bar_container';
import TabsBar from './components/tabs_bar';
import ModalContainer from './containers/modal_container';
import { connect } from 'react-redux';
import { isMobile } from '../../is_mobile';
import { debounce } from 'lodash';
import { uploadCompose } from '../../actions/compose';
import { refreshHomeTimeline } from '../../actions/timelines';
import { refreshNotifications } from '../../actions/notifications';
import UploadArea from './components/upload_area';
import ColumnsAreaContainer from './containers/columns_area_container';

import Status from '../../features/status';
import GettingStarted from '../../features/getting_started';
import PublicTimeline from '../../features/public_timeline';
import CommunityTimeline from '../../features/community_timeline';
import AccountTimeline from '../../features/account_timeline';
import AccountGallery from '../../features/account_gallery';
import HomeTimeline from '../../features/home_timeline';
import Compose from '../../features/compose';
import Followers from '../../features/followers';
import Following from '../../features/following';
import Reblogs from '../../features/reblogs';
import Favourites from '../../features/favourites';
import HashtagTimeline from '../../features/hashtag_timeline';
import Notifications from '../../features/notifications';
import FollowRequests from '../../features/follow_requests';
import GenericNotFound from '../../features/generic_not_found';
import FavouritedStatuses from '../../features/favourited_statuses';
import Blocks from '../../features/blocks';
import Mutes from '../../features/mutes';
import Report from '../../features/report';

// Small wrapper to pass multiColumn to the route components
const WrappedSwitch = ({ multiColumn, children }) => (
  <Switch>
    {React.Children.map(children, child => React.cloneElement(child, { multiColumn }))}
  </Switch>
);

WrappedSwitch.propTypes = {
  multiColumn: PropTypes.bool,
  children: PropTypes.node,
};

// Small Wraper to extract the params from the route and pass
// them to the rendered component, together with the content to
// be rendered inside (the children)
class WrappedRoute extends React.Component {

  static propTypes = {
    component: PropTypes.func.isRequired,
    content: PropTypes.node,
    multiColumn: PropTypes.bool,
  }

  renderComponent = ({ match: { params } }) => {
    const { component: Component, content, multiColumn } = this.props;

    return <Component params={params} multiColumn={multiColumn}>{content}</Component>;
  }

  render () {
    const { component: Component, content, ...rest } = this.props;

    return <Route {...rest} render={this.renderComponent} />;
  }

}

const noOp = () => false;

class UI extends React.PureComponent {

  static defaultProps = {
    intent: false,
  };

  static propTypes = {
    dispatch: PropTypes.func.isRequired,
    children: PropTypes.node,
    intent: PropTypes.bool,
  };

  state = {
    width: window.innerWidth,
    draggingOver: false,
  };

  handleResize = debounce(() => {
    this.setState({ width: window.innerWidth });
  }, 500, {
    trailing: true,
  });

  handleDragEnter = (e) => {
    e.preventDefault();

    if (!this.dragTargets) {
      this.dragTargets = [];
    }

    if (this.dragTargets.indexOf(e.target) === -1) {
      this.dragTargets.push(e.target);
    }

    if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
      this.setState({ draggingOver: true });
    }
  }

  handleDragOver = (e) => {
    e.preventDefault();
    e.stopPropagation();

    try {
      e.dataTransfer.dropEffect = 'copy';
    } catch (err) {

    }

    return false;
  }

  handleDrop = (e) => {
    e.preventDefault();

    this.setState({ draggingOver: false });

    if (e.dataTransfer && e.dataTransfer.files.length === 1) {
      this.props.dispatch(uploadCompose(e.dataTransfer.files));
    }
  }

  handleDragLeave = (e) => {
    e.preventDefault();
    e.stopPropagation();

    this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el));

    if (this.dragTargets.length > 0) {
      return;
    }

    this.setState({ draggingOver: false });
  }

  closeUploadModal = () => {
    this.setState({ draggingOver: false });
  }

  componentWillMount () {
    window.addEventListener('resize', this.handleResize, { passive: true });
    document.addEventListener('dragenter', this.handleDragEnter, false);
    document.addEventListener('dragover', this.handleDragOver, false);
    document.addEventListener('drop', this.handleDrop, false);
    document.addEventListener('dragleave', this.handleDragLeave, false);
    document.addEventListener('dragend', this.handleDragEnd, false);

    if (!this.props.intent) {
      this.props.dispatch(refreshHomeTimeline());
      this.props.dispatch(refreshNotifications());
    }
  }

  componentWillUnmount () {
    window.removeEventListener('resize', this.handleResize);
    document.removeEventListener('dragenter', this.handleDragEnter);
    document.removeEventListener('dragover', this.handleDragOver);
    document.removeEventListener('drop', this.handleDrop);
    document.removeEventListener('dragleave', this.handleDragLeave);
    document.removeEventListener('dragend', this.handleDragEnd);
  }

  setRef = (c) => {
    this.node = c;
  }

  render () {
    const { width, draggingOver } = this.state;
    const { intent, children } = this.props;

    if (intent) {
      return children;
    }

    return (

        <TabsBar />
        <ColumnsAreaContainer singleColumn={isMobile(width)}>
          <WrappedSwitch>
            <Redirect from='/' to='/getting-started' exact />
            <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
            <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
            <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
            <WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
            <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />

            <WrappedRoute path='/notifications' component={Notifications} content={children} />
            <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />

            <WrappedRoute path='/statuses/new' component={Compose} content={children} />
            <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
            <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
            <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />

            <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
            <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
            <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
            <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />

            <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
            <WrappedRoute path='/blocks' component={Blocks} content={children} />
            <WrappedRoute path='/mutes' component={Mutes} content={children} />
            <WrappedRoute path='/report' component={Report} content={children} />

            <WrappedRoute component={GenericNotFound} content={children} />
          </WrappedSwitch>
        </ColumnsAreaContainer>
        <NotificationsContainer />
        <LoadingBarContainer className='loading-bar' />
        <ModalContainer />
        <UploadArea active={draggingOver} onClose={this.closeUploadModal} />

    );
  }

}

export default connect()(UI);
<commit_msg>Fixes routes<commit_after>import React from 'react';
import Switch from 'react-router-dom/Switch';
import Route from 'react-router-dom/Route';
import Redirect from 'react-router-dom/Redirect';
import NotificationsContainer from './containers/notifications_container';
import PropTypes from 'prop-types';
import LoadingBarContainer from './containers/loading_bar_container';
import TabsBar from './components/tabs_bar';
import ModalContainer from './containers/modal_container';
import { connect } from 'react-redux';
import { isMobile } from '../../is_mobile';
import { debounce } from 'lodash';
import { uploadCompose } from '../../actions/compose';
import { refreshHomeTimeline } from '../../actions/timelines';
import { refreshNotifications } from '../../actions/notifications';
import UploadArea from './components/upload_area';
import ColumnsAreaContainer from './containers/columns_area_container';

import Status from '../../features/status';
import GettingStarted from '../../features/getting_started';
import PublicTimeline from '../../features/public_timeline';
import CommunityTimeline from '../../features/community_timeline';
import AccountTimeline from '../../features/account_timeline';
import AccountGallery from '../../features/account_gallery';
import HomeTimeline from '../../features/home_timeline';
import Compose from '../../features/compose';
import Followers from '../../features/followers';
import Following from '../../features/following';
import Reblogs from '../../features/reblogs';
import Favourites from '../../features/favourites';
import HashtagTimeline from '../../features/hashtag_timeline';
import Notifications from '../../features/notifications';
import FollowRequests from '../../features/follow_requests';
import GenericNotFound from '../../features/generic_not_found';
import FavouritedStatuses from '../../features/favourited_statuses';
import Blocks from '../../features/blocks';
import Mutes from '../../features/mutes';
import Report from '../../features/report';
import MediaTimeline from '../../features/media_timeline';
import SuggestedAccounts from '../../features/suggested_accounts';
import StatusSearchResults from '../../features/status_search_results';
import AccountMediaTimeline from '../../features/account_media_timeline';

// Small wrapper to pass multiColumn to the route components
const WrappedSwitch = ({ multiColumn, children }) => (
  <Switch>
    {React.Children.map(children, child => React.cloneElement(child, { multiColumn }))}
  </Switch>
);

WrappedSwitch.propTypes = {
  multiColumn: PropTypes.bool,
  children: PropTypes.node,
};

// Small Wraper to extract the params from the route and pass
// them to the rendered component, together with the content to
// be rendered inside (the children)
class WrappedRoute extends React.Component {

  static propTypes = {
    component: PropTypes.func.isRequired,
    content: PropTypes.node,
    multiColumn: PropTypes.bool,
  }

  renderComponent = ({ match: { params } }) => {
    const { component: Component, content, multiColumn } = this.props;

    return <Component params={params} multiColumn={multiColumn}>{content}</Component>;
  }

  render () {
    const { component: Component, content, ...rest } = this.props;

    return <Route {...rest} render={this.renderComponent} />;
  }

}

const noOp = () => false;

class UI extends React.PureComponent {

  static defaultProps = {
    intent: false,
  };

  static propTypes = {
    dispatch: PropTypes.func.isRequired,
    children: PropTypes.node,
    intent: PropTypes.bool,
  };

  state = {
    width: window.innerWidth,
    draggingOver: false,
  };

  handleResize = debounce(() => {
    this.setState({ width: window.innerWidth });
  }, 500, {
    trailing: true,
  });

  handleDragEnter = (e) => {
    e.preventDefault();

    if (!this.dragTargets) {
      this.dragTargets = [];
    }

    if (this.dragTargets.indexOf(e.target) === -1) {
      this.dragTargets.push(e.target);
    }

    if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
      this.setState({ draggingOver: true });
    }
  }

  handleDragOver = (e) => {
    e.preventDefault();
    e.stopPropagation();

    try {
      e.dataTransfer.dropEffect = 'copy';
    } catch (err) {

    }

    return false;
  }

  handleDrop = (e) => {
    e.preventDefault();

    this.setState({ draggingOver: false });

    if (e.dataTransfer && e.dataTransfer.files.length === 1) {
      this.props.dispatch(uploadCompose(e.dataTransfer.files));
    }
  }

  handleDragLeave = (e) => {
    e.preventDefault();
    e.stopPropagation();

    this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el));

    if (this.dragTargets.length > 0) {
      return;
    }

    this.setState({ draggingOver: false });
  }

  closeUploadModal = () => {
    this.setState({ draggingOver: false });
  }

  componentWillMount () {
    window.addEventListener('resize', this.handleResize, { passive: true });
    document.addEventListener('dragenter', this.handleDragEnter, false);
    document.addEventListener('dragover', this.handleDragOver, false);
    document.addEventListener('drop', this.handleDrop, false);
    document.addEventListener('dragleave', this.handleDragLeave, false);
    document.addEventListener('dragend', this.handleDragEnd, false);

    if (!this.props.intent) {
      this.props.dispatch(refreshHomeTimeline());
      this.props.dispatch(refreshNotifications());
    }
  }

  componentWillUnmount () {
    window.removeEventListener('resize', this.handleResize);
    document.removeEventListener('dragenter', this.handleDragEnter);
    document.removeEventListener('dragover', this.handleDragOver);
    document.removeEventListener('drop', this.handleDrop);
    document.removeEventListener('dragleave', this.handleDragLeave);
    document.removeEventListener('dragend', this.handleDragEnd);
  }

  setRef = (c) => {
    this.node = c;
  }

  render () {
    const { width, draggingOver } = this.state;
    const { intent, children } = this.props;

    if (intent) {
      return children;
    }

    return (

        <TabsBar />
        <ColumnsAreaContainer singleColumn={isMobile(width)}>
          <WrappedSwitch>
            <Redirect from='/' to='/getting-started' exact />
            <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
            <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
            <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
            <WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
            <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />

            <WrappedRoute path='/notifications' component={Notifications} content={children} />
            <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />

            <WrappedRoute path='/statuses/new' component={Compose} content={children} />
            <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
            <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
            <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />

            <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
            <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
            <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
            {/* use AccountMediaTimeline instead of <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />*/}

            <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
            <WrappedRoute path='/blocks' component={Blocks} content={children} />
            <WrappedRoute path='/mutes' component={Mutes} content={children} />
            <WrappedRoute path='/report' component={Report} content={children} />

            <WrappedRoute path='/timelines/public/media' component={MediaTimeline} content={children} />
            <WrappedRoute path='/suggested_accounts' component={SuggestedAccounts} content={children} />
            <WrappedRoute path='/statuses/search/:keyword' component={StatusSearchResults} content={children} />
            <WrappedRoute path='/accounts/:accountId/media' component={AccountMediaTimeline} content={children} />

            <WrappedRoute component={GenericNotFound} content={children} />
          </WrappedSwitch>
        </ColumnsAreaContainer>
        <NotificationsContainer />
        <LoadingBarContainer className='loading-bar' />
        <ModalContainer />
        <UploadArea active={draggingOver} onClose={this.closeUploadModal} />

    );
  }

}

export default connect()(UI);
<|endoftext|>


<commit_before>// ==UserScript==
// @name         K8 Dashboard Login
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Auto login for Kuberenetes Dashboard
// @author       You
// @match        https://k8.omnimed.com/dashboard/*
// @grant        none
// @require      https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.5/angular.min.js
// @require      http://malsup.github.io/jquery.blockUI.js
// @run-at document-end
// ==/UserScript==

(function() {
    'use strict';

    jQuery(function() {
        if (window.location.href.indexOf('login') !== -1) {
            var token = 'xxx';
            $.blockUI({ message: '
 Kubernetities
' });

            setTimeout(function() {
                /* Select the token authentication method*/
                angular.element(document).find('md-radio-button')[1].click();
                angular.element(document).find('input').focus();

                /* Fill the token values */
                angular.element(document).find('input').scope().$ctrl.token = token;
                angular.element(document).find('form')[0][0].value = token;
                angular.element(document).find('form').scope().$ctrl.loginSpec.token = token;

                /* Trigger the login after Angular update */
                setTimeout(function() {
                    angular.element(document).find('form').scope().$ctrl.login();
                    console.log('logged in');
                    $.unblockUI();
                }, 500);
            }, 1000);
        }
    });
})();
<commit_msg>Delete K8DashboardLogin.js<commit_after><|endoftext|>


<commit_before>"use strict";

angular.module('arethusa.core').directive('sidepanel', [
  'keyCapture',
  function(keyCapture) {
    return {
      restrict: 'A',
      link: function(scope, element, attrs) {
        var minIndex = 0;

        function currentIndex() {
          return scope.subPlugins.indexOf(scope.activePlugin);
        }

        function maxIndex() {
          return scope.subPlugins.length;
        }

        function moveToNext() {
          var nextPlugin = scope.subPlugins[currentIndex() + 1];
          scope.declareActive(nextPlugin);
        }

        function moveToPrev() {
          var nextPlugin = scope.subPlugins[currentIndex() - 1];
          scope.declareActive(nextPlugin);
        }

        keyCapture.onKeyPressed(keyCapture.keyCodes.j, function() {
          scope.$apply(moveToNext);
        });

        keyCapture.onKeyPressed(keyCapture.keyCodes.k, function() {
          scope.$apply(moveToPrev);
        });
      }
    };
  }
]);
<commit_msg>Enable roundtrips in sidepanel directive<commit_after>"use strict";

angular.module('arethusa.core').directive('sidepanel', [
  'keyCapture',
  function(keyCapture) {
    return {
      restrict: 'A',
      link: function(scope, element, attrs) {
        var minIndex = 0;

        function currentIndex() {
          return scope.subPlugins.indexOf(scope.activePlugin);
        }

        function maxIndex() {
          return scope.subPlugins.length - 1;
        }

        function selectPluginByIndex(index) {
          var plugin = scope.subPlugins[index];
          scope.declareActive(plugin);
        }

        function moveToNext() {
          var current = currentIndex();
          var index = current === maxIndex() ? minIndex : current + 1;
          selectPluginByIndex(index);
        }

        function moveToPrev() {
          var current = currentIndex();
          var index = current === minIndex ? maxIndex() : current - 1;
          selectPluginByIndex(index);
        }

        keyCapture.onKeyPressed(keyCapture.keyCodes.j, function() {
          scope.$apply(moveToNext);
        });

        keyCapture.onKeyPressed(keyCapture.keyCodes.k, function() {
          scope.$apply(moveToPrev);
        });
      }
    };
  }
]);
<|endoftext|>