uppy
Version:
Extensible JavaScript file upload widget with support for drag&drop, resumable uploads, previews, restrictions, file processing/encoding, remote providers like Instagram, Dropbox, Google Drive, S3 and more :dog:
700 lines (586 loc) • 22.4 kB
JavaScript
'use strict';
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var AuthView = require('./AuthView');
var Browser = require('./Browser');
var LoaderView = require('./Loader');
var Utils = require('../../../core/Utils');
var _require = require('preact'),
h = _require.h;
/**
* Class to easily generate generic views for plugins
*
*
* This class expects the plugin instance using it to have the following
* accessor methods.
* Each method takes the item whose property is to be accessed
* as a param
*
* isFolder
* @return {Boolean} for if the item is a folder or not
* getItemData
* @return {Object} that is format ready for uppy upload/download
* getItemIcon
* @return {Object} html instance of the item's icon
* getItemSubList
* @return {Array} sub-items in the item. e.g a folder may contain sub-items
* getItemName
* @return {String} display friendly name of the item
* getMimeType
* @return {String} mime type of the item
* getItemId
* @return {String} unique id of the item
* getItemRequestPath
* @return {String} unique request path of the item when making calls to uppy server
* getItemModifiedDate
* @return {object} or {String} date of when last the item was modified
* getItemThumbnailUrl
* @return {String}
*/
module.exports = function () {
/**
* @param {object} instance of the plugin
*/
function View(plugin, opts) {
_classCallCheck(this, View);
this.plugin = plugin;
this.Provider = plugin[plugin.id];
// set default options
var defaultOptions = {
viewType: 'list'
// merge default options with the ones set by user
};this.opts = _extends({}, defaultOptions, opts);
// Logic
this.updateFolderState = this.updateFolderState.bind(this);
this.addFile = this.addFile.bind(this);
this.filterItems = this.filterItems.bind(this);
this.filterQuery = this.filterQuery.bind(this);
this.toggleSearch = this.toggleSearch.bind(this);
this.getFolder = this.getFolder.bind(this);
this.getNextFolder = this.getNextFolder.bind(this);
this.logout = this.logout.bind(this);
this.checkAuth = this.checkAuth.bind(this);
this.handleAuth = this.handleAuth.bind(this);
this.handleDemoAuth = this.handleDemoAuth.bind(this);
this.sortByTitle = this.sortByTitle.bind(this);
this.sortByDate = this.sortByDate.bind(this);
this.isActiveRow = this.isActiveRow.bind(this);
this.isChecked = this.isChecked.bind(this);
this.toggleCheckbox = this.toggleCheckbox.bind(this);
this.handleError = this.handleError.bind(this);
this.handleScroll = this.handleScroll.bind(this);
this.donePicking = this.donePicking.bind(this);
this.plugin.uppy.on('file-removed', this.updateFolderState);
// Visual
this.render = this.render.bind(this);
}
View.prototype.tearDown = function tearDown() {
this.plugin.uppy.off('file-removed', this.updateFolderState);
};
View.prototype._updateFilesAndFolders = function _updateFilesAndFolders(res, files, folders) {
var _this = this;
this.plugin.getItemSubList(res).forEach(function (item) {
if (_this.plugin.isFolder(item)) {
folders.push(item);
} else {
files.push(item);
}
});
this.plugin.setPluginState({ folders: folders, files: files });
};
View.prototype.checkAuth = function checkAuth() {
var _this2 = this;
this.plugin.setPluginState({ checkAuthInProgress: true });
this.Provider.checkAuth().then(function (authenticated) {
_this2.plugin.setPluginState({ checkAuthInProgress: false });
_this2.plugin.onAuth(authenticated);
}).catch(function (err) {
_this2.plugin.setPluginState({ checkAuthInProgress: false });
_this2.handleError(err);
});
};
/**
* Based on folder ID, fetch a new folder and update it to state
* @param {String} id Folder id
* @return {Promise} Folders/files in folder
*/
View.prototype.getFolder = function getFolder(id, name) {
var _this3 = this;
return this._loaderWrapper(this.Provider.list(id), function (res) {
var folders = [];
var files = [];
var updatedDirectories = void 0;
var state = _this3.plugin.getPluginState();
var index = state.directories.findIndex(function (dir) {
return id === dir.id;
});
if (index !== -1) {
updatedDirectories = state.directories.slice(0, index + 1);
} else {
updatedDirectories = state.directories.concat([{ id: id, title: name || _this3.plugin.getItemName(res) }]);
}
_this3._updateFilesAndFolders(res, files, folders);
_this3.plugin.setPluginState({ directories: updatedDirectories });
}, this.handleError);
};
/**
* Fetches new folder
* @param {Object} Folder
* @param {String} title Folder title
*/
View.prototype.getNextFolder = function getNextFolder(folder) {
var id = this.plugin.getItemRequestPath(folder);
this.getFolder(id, this.plugin.getItemName(folder));
this.lastCheckbox = undefined;
};
View.prototype.addFile = function addFile(file) {
var _this4 = this;
var isCheckbox = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var tagFile = {
source: this.plugin.id,
data: this.plugin.getItemData(file),
name: this.plugin.getItemName(file) || this.plugin.getItemId(file),
type: this.plugin.getMimeType(file),
isRemote: true,
body: {
fileId: this.plugin.getItemId(file)
},
remote: {
host: this.plugin.opts.host,
url: '' + this.Provider.fileUrl(this.plugin.getItemRequestPath(file)),
body: {
fileId: this.plugin.getItemId(file)
}
}
};
Utils.getFileType(tagFile).then(function (fileType) {
if (fileType && Utils.isPreviewSupported(fileType)) {
tagFile.preview = _this4.plugin.getItemThumbnailUrl(file);
}
_this4.plugin.uppy.log('Adding remote file');
_this4.plugin.uppy.addFile(tagFile).catch(function () {
// Ignore
});
if (!isCheckbox) {
_this4.donePicking();
}
});
};
/**
* Removes session token on client side.
*/
View.prototype.logout = function logout() {
var _this5 = this;
this.Provider.logout(location.href).then(function (res) {
return res.json();
}).then(function (res) {
if (res.ok) {
var newState = {
authenticated: false,
files: [],
folders: [],
directories: []
};
_this5.plugin.setPluginState(newState);
}
}).catch(this.handleError);
};
View.prototype.filterQuery = function filterQuery(e) {
var state = this.plugin.getPluginState();
this.plugin.setPluginState(_extends({}, state, {
filterInput: e.target.value
}));
};
View.prototype.toggleSearch = function toggleSearch(inputEl) {
var state = this.plugin.getPluginState();
this.plugin.setPluginState({
isSearchVisible: !state.isSearchVisible,
filterInput: ''
});
};
View.prototype.filterItems = function filterItems(items) {
var _this6 = this;
var state = this.plugin.getPluginState();
return items.filter(function (folder) {
return _this6.plugin.getItemName(folder).toLowerCase().indexOf(state.filterInput.toLowerCase()) !== -1;
});
};
View.prototype.sortByTitle = function sortByTitle() {
var _this7 = this;
var state = _extends({}, this.plugin.getPluginState());
var files = state.files,
folders = state.folders,
sorting = state.sorting;
var sortedFiles = files.sort(function (fileA, fileB) {
if (sorting === 'titleDescending') {
return _this7.plugin.getItemName(fileB).localeCompare(_this7.plugin.getItemName(fileA));
}
return _this7.plugin.getItemName(fileA).localeCompare(_this7.plugin.getItemName(fileB));
});
var sortedFolders = folders.sort(function (folderA, folderB) {
if (sorting === 'titleDescending') {
return _this7.plugin.getItemName(folderB).localeCompare(_this7.plugin.getItemName(folderA));
}
return _this7.plugin.getItemName(folderA).localeCompare(_this7.plugin.getItemName(folderB));
});
this.plugin.setPluginState(_extends({}, state, {
files: sortedFiles,
folders: sortedFolders,
sorting: sorting === 'titleDescending' ? 'titleAscending' : 'titleDescending'
}));
};
View.prototype.sortByDate = function sortByDate() {
var _this8 = this;
var state = _extends({}, this.plugin.getPluginState());
var files = state.files,
folders = state.folders,
sorting = state.sorting;
var sortedFiles = files.sort(function (fileA, fileB) {
var a = new Date(_this8.plugin.getItemModifiedDate(fileA));
var b = new Date(_this8.plugin.getItemModifiedDate(fileB));
if (sorting === 'dateDescending') {
return a > b ? -1 : a < b ? 1 : 0;
}
return a > b ? 1 : a < b ? -1 : 0;
});
var sortedFolders = folders.sort(function (folderA, folderB) {
var a = new Date(_this8.plugin.getItemModifiedDate(folderA));
var b = new Date(_this8.plugin.getItemModifiedDate(folderB));
if (sorting === 'dateDescending') {
return a > b ? -1 : a < b ? 1 : 0;
}
return a > b ? 1 : a < b ? -1 : 0;
});
this.plugin.setPluginState(_extends({}, state, {
files: sortedFiles,
folders: sortedFolders,
sorting: sorting === 'dateDescending' ? 'dateAscending' : 'dateDescending'
}));
};
View.prototype.sortBySize = function sortBySize() {
var _this9 = this;
var state = _extends({}, this.plugin.getPluginState());
var files = state.files,
sorting = state.sorting;
// check that plugin supports file sizes
if (!files.length || !this.plugin.getItemData(files[0]).size) {
return;
}
var sortedFiles = files.sort(function (fileA, fileB) {
var a = _this9.plugin.getItemData(fileA).size;
var b = _this9.plugin.getItemData(fileB).size;
if (sorting === 'sizeDescending') {
return a > b ? -1 : a < b ? 1 : 0;
}
return a > b ? 1 : a < b ? -1 : 0;
});
this.plugin.setPluginState(_extends({}, state, {
files: sortedFiles,
sorting: sorting === 'sizeDescending' ? 'sizeAscending' : 'sizeDescending'
}));
};
View.prototype.isActiveRow = function isActiveRow(file) {
return this.plugin.getPluginState().activeRow === this.plugin.getItemId(file);
};
View.prototype.isChecked = function isChecked(item) {
var itemId = this.providerFileToId(item);
if (this.plugin.isFolder(item)) {
var state = this.plugin.getPluginState();
var folders = state.selectedFolders || {};
if (itemId in folders) {
return folders[itemId];
}
return false;
}
return itemId in this.plugin.uppy.getState().files;
};
/**
* Adds all files found inside of specified folder.
*
* Uses separated state while folder contents are being fetched and
* mantains list of selected folders, which are separated from files.
*/
View.prototype.addFolder = function addFolder(folder) {
var _this10 = this;
var folderId = this.providerFileToId(folder);
var state = this.plugin.getPluginState();
var folders = state.selectedFolders || {};
if (folderId in folders && folders[folderId].loading) {
return;
}
folders[folderId] = { loading: true, files: [] };
this.plugin.setPluginState({ selectedFolders: folders });
this.Provider.list(this.plugin.getItemRequestPath(folder)).then(function (res) {
var files = [];
_this10.plugin.getItemSubList(res).forEach(function (item) {
if (!_this10.plugin.isFolder(item)) {
_this10.addFile(item, true);
files.push(_this10.providerFileToId(item));
}
});
state = _this10.plugin.getPluginState();
state.selectedFolders[folderId] = { loading: false, files: files };
_this10.plugin.setPluginState({ selectedFolders: folders });
var dashboard = _this10.plugin.uppy.getPlugin('Dashboard');
var message = void 0;
if (files.length) {
message = dashboard.i18n('folderAdded', {
smart_count: files.length, folder: _this10.plugin.getItemName(folder)
});
} else {
message = dashboard.i18n('emptyFolderAdded');
}
_this10.plugin.uppy.info(message);
}).catch(function (e) {
state = _this10.plugin.getPluginState();
delete state.selectedFolders[folderId];
_this10.plugin.setPluginState({ selectedFolders: state.selectedFolders });
_this10.handleError(e);
});
};
View.prototype.removeFolder = function removeFolder(folderId) {
var state = this.plugin.getPluginState();
var folders = state.selectedFolders || {};
if (!(folderId in folders)) {
return;
}
var folder = folders[folderId];
if (folder.loading) {
return;
}
// deepcopy the files before iteration because the
// original array constantly gets mutated during
// the iteration by updateFolderState as each file
// is removed and 'core:file-removed' is emitted.
var files = folder.files.concat([]);
for (var _iterator = files, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
var _ref;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref = _i.value;
}
var fileId = _ref;
if (fileId in this.plugin.uppy.getState().files) {
this.plugin.uppy.removeFile(fileId);
}
}
delete folders[folderId];
this.plugin.setPluginState({ selectedFolders: folders });
};
/**
* Updates selected folders state everytime file is being removed.
*
* Note that this is only important when files are getting removed from the
* main screen, and will do nothing when you uncheck folder directly, since
* it's already been done in removeFolder method.
*/
View.prototype.updateFolderState = function updateFolderState(file) {
var state = this.plugin.getPluginState();
var folders = state.selectedFolders || {};
for (var folderId in folders) {
var folder = folders[folderId];
if (folder.loading) {
continue;
}
var i = folder.files.indexOf(file.id);
if (i > -1) {
folder.files.splice(i, 1);
}
if (!folder.files.length) {
delete folders[folderId];
}
}
this.plugin.setPluginState({ selectedFolders: folders });
};
/**
* Toggles file/folder checkbox to on/off state while updating files list.
*
* Note that some extra complexity comes from supporting shift+click to
* toggle multiple checkboxes at once, which is done by getting all files
* in between last checked file and current one, and applying an on/off state
* for all of them, depending on current file state.
*/
View.prototype.toggleCheckbox = function toggleCheckbox(e, file) {
e.stopPropagation();
e.preventDefault();
var _plugin$getPluginStat = this.plugin.getPluginState(),
folders = _plugin$getPluginStat.folders,
files = _plugin$getPluginStat.files,
filterInput = _plugin$getPluginStat.filterInput;
var items = folders.concat(files);
if (filterInput !== '') {
items = this.filterItems(items);
}
var itemsToToggle = [file];
if (this.lastCheckbox && e.shiftKey) {
var prevIndex = items.indexOf(this.lastCheckbox);
var currentIndex = items.indexOf(file);
if (prevIndex < currentIndex) {
itemsToToggle = items.slice(prevIndex, currentIndex + 1);
} else {
itemsToToggle = items.slice(currentIndex, prevIndex + 1);
}
}
this.lastCheckbox = file;
if (this.isChecked(file)) {
for (var _iterator2 = itemsToToggle, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) {
var _ref2;
if (_isArray2) {
if (_i2 >= _iterator2.length) break;
_ref2 = _iterator2[_i2++];
} else {
_i2 = _iterator2.next();
if (_i2.done) break;
_ref2 = _i2.value;
}
var item = _ref2;
var itemId = this.providerFileToId(item);
if (this.plugin.isFolder(item)) {
this.removeFolder(itemId);
} else {
if (itemId in this.plugin.uppy.getState().files) {
this.plugin.uppy.removeFile(itemId);
}
}
}
} else {
for (var _iterator3 = itemsToToggle, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) {
var _ref3;
if (_isArray3) {
if (_i3 >= _iterator3.length) break;
_ref3 = _iterator3[_i3++];
} else {
_i3 = _iterator3.next();
if (_i3.done) break;
_ref3 = _i3.value;
}
var _item = _ref3;
if (this.plugin.isFolder(_item)) {
this.addFolder(_item);
} else {
this.addFile(_item, true);
}
}
}
};
View.prototype.providerFileToId = function providerFileToId(file) {
return Utils.generateFileID({
data: this.plugin.getItemData(file),
name: this.plugin.getItemName(file) || this.plugin.getItemId(file),
type: this.plugin.getMimeType(file)
});
};
View.prototype.handleDemoAuth = function handleDemoAuth() {
var state = this.plugin.getPluginState();
this.plugin.setPluginState({}, state, {
authenticated: true
});
};
View.prototype.handleAuth = function handleAuth() {
var _this11 = this;
var urlId = Math.floor(Math.random() * 999999) + 1;
var redirect = '' + location.href + (location.search ? '&' : '?') + 'id=' + urlId;
var authState = btoa(JSON.stringify({ redirect: redirect }));
var link = this.Provider.authUrl() + '?state=' + authState;
var authWindow = window.open(link, '_blank');
var checkAuth = function checkAuth() {
var authWindowUrl = void 0;
try {
authWindowUrl = authWindow.location.href;
} catch (e) {
if (e instanceof DOMException || e instanceof TypeError) {
return setTimeout(checkAuth, 100);
} else throw e;
}
// split url because chrome adds '#' to redirects
if (authWindowUrl && authWindowUrl.split('#')[0] === redirect) {
authWindow.close();
_this11._loaderWrapper(_this11.Provider.checkAuth(), _this11.plugin.onAuth, _this11.handleError);
} else {
setTimeout(checkAuth, 100);
}
};
checkAuth();
};
View.prototype.handleError = function handleError(error) {
var uppy = this.plugin.uppy;
var message = uppy.i18n('uppyServerError');
uppy.log(error.toString());
uppy.info({ message: message, details: error.toString() }, 'error', 5000);
};
View.prototype.handleScroll = function handleScroll(e) {
var _this12 = this;
var scrollPos = e.target.scrollHeight - (e.target.scrollTop + e.target.offsetHeight);
var path = this.plugin.getNextPagePath ? this.plugin.getNextPagePath() : null;
if (scrollPos < 50 && path && !this._isHandlingScroll) {
this.Provider.list(path).then(function (res) {
var _plugin$getPluginStat2 = _this12.plugin.getPluginState(),
files = _plugin$getPluginStat2.files,
folders = _plugin$getPluginStat2.folders;
_this12._updateFilesAndFolders(res, files, folders);
}).catch(this.handleError).then(function () {
_this12._isHandlingScroll = false;
}); // always called
this._isHandlingScroll = true;
}
};
View.prototype.donePicking = function donePicking() {
var dashboard = this.plugin.uppy.getPlugin('Dashboard');
if (dashboard) dashboard.hideAllPanels();
};
// displays loader view while asynchronous request is being made.
View.prototype._loaderWrapper = function _loaderWrapper(promise, then, catch_) {
var _this13 = this;
promise.then(then).catch(catch_).then(function () {
return _this13.plugin.setPluginState({ loading: false });
}); // always called.
this.plugin.setPluginState({ loading: true });
};
View.prototype.render = function render(state) {
var _plugin$getPluginStat3 = this.plugin.getPluginState(),
authenticated = _plugin$getPluginStat3.authenticated,
checkAuthInProgress = _plugin$getPluginStat3.checkAuthInProgress,
loading = _plugin$getPluginStat3.loading;
if (loading) {
return LoaderView();
}
if (!authenticated) {
return h(AuthView, {
pluginName: this.plugin.title,
demo: this.plugin.opts.demo,
checkAuth: this.checkAuth,
handleAuth: this.handleAuth,
handleDemoAuth: this.handleDemoAuth,
checkAuthInProgress: checkAuthInProgress
});
}
var browserProps = _extends({}, this.plugin.getPluginState(), {
getNextFolder: this.getNextFolder,
getFolder: this.getFolder,
addFile: this.addFile,
filterItems: this.filterItems,
filterQuery: this.filterQuery,
toggleSearch: this.toggleSearch,
sortByTitle: this.sortByTitle,
sortByDate: this.sortByDate,
logout: this.logout,
demo: this.plugin.opts.demo,
isActiveRow: this.isActiveRow,
isChecked: this.isChecked,
toggleCheckbox: this.toggleCheckbox,
getItemName: this.plugin.getItemName,
getItemIcon: this.plugin.getItemIcon,
handleScroll: this.handleScroll,
done: this.donePicking,
title: this.plugin.title,
viewType: this.opts.viewType
});
return Browser(browserProps);
};
return View;
}();
//# sourceMappingURL=index.js.map