UNPKG

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
'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