|
@@ -2,7 +2,27 @@ const AuthView = require('./AuthView')
|
|
|
const Browser = require('./Browser')
|
|
|
const LoaderView = require('./Loader')
|
|
|
const Utils = require('../../core/Utils')
|
|
|
-const { h } = require('preact')
|
|
|
+const { h, Component } = require('preact')
|
|
|
+
|
|
|
+/**
|
|
|
+ * Array.prototype.findIndex ponyfill for old browsers.
|
|
|
+ */
|
|
|
+function findIndex (array, predicate) {
|
|
|
+ for (let i = 0; i < array.length; i++) {
|
|
|
+ if (predicate(array[i])) return i
|
|
|
+ }
|
|
|
+ return -1
|
|
|
+}
|
|
|
+
|
|
|
+class CloseWrapper extends Component {
|
|
|
+ componentWillUnmount () {
|
|
|
+ this.props.onUnmount()
|
|
|
+ }
|
|
|
+
|
|
|
+ render () {
|
|
|
+ return this.props.children[0]
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
/**
|
|
|
* Class to easily generate generic views for plugins
|
|
@@ -54,7 +74,6 @@ module.exports = class ProviderView {
|
|
|
this.opts = Object.assign({}, 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)
|
|
@@ -73,15 +92,17 @@ module.exports = class ProviderView {
|
|
|
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)
|
|
|
+ this.cancelPicking = this.cancelPicking.bind(this)
|
|
|
+ this.clearSelection = this.clearSelection.bind(this)
|
|
|
|
|
|
// Visual
|
|
|
this.render = this.render.bind(this)
|
|
|
+
|
|
|
+ this.clearSelection()
|
|
|
}
|
|
|
|
|
|
tearDown () {
|
|
|
- this.plugin.uppy.off('file-removed', this.updateFolderState)
|
|
|
+ // Nothing.
|
|
|
}
|
|
|
|
|
|
_updateFilesAndFolders (res, files, folders) {
|
|
@@ -123,7 +144,7 @@ module.exports = class ProviderView {
|
|
|
let updatedDirectories
|
|
|
|
|
|
const state = this.plugin.getPluginState()
|
|
|
- const index = state.directories.findIndex((dir) => id === dir.id)
|
|
|
+ const index = findIndex(state.directories, (dir) => id === dir.id)
|
|
|
|
|
|
if (index !== -1) {
|
|
|
updatedDirectories = state.directories.slice(0, index + 1)
|
|
@@ -149,8 +170,9 @@ module.exports = class ProviderView {
|
|
|
this.lastCheckbox = undefined
|
|
|
}
|
|
|
|
|
|
- addFile (file, isCheckbox = false) {
|
|
|
+ addFile (file) {
|
|
|
const tagFile = {
|
|
|
+ id: this.providerFileToId(file),
|
|
|
source: this.plugin.id,
|
|
|
data: this.plugin.getItemData(file),
|
|
|
name: this.plugin.getItemName(file) || this.plugin.getItemId(file),
|
|
@@ -179,9 +201,13 @@ module.exports = class ProviderView {
|
|
|
} catch (err) {
|
|
|
// Nothing, restriction errors handled in Core
|
|
|
}
|
|
|
- if (!isCheckbox) {
|
|
|
- this.donePicking()
|
|
|
- }
|
|
|
+ }
|
|
|
+
|
|
|
+ removeFile (id) {
|
|
|
+ const { currentSelection } = this.plugin.getPluginState()
|
|
|
+ this.plugin.setPluginState({
|
|
|
+ currentSelection: currentSelection.filter((file) => file.id !== id)
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -220,6 +246,9 @@ module.exports = class ProviderView {
|
|
|
|
|
|
filterItems (items) {
|
|
|
const state = this.plugin.getPluginState()
|
|
|
+ if (state.filterInput === '') {
|
|
|
+ return items
|
|
|
+ }
|
|
|
return items.filter((folder) => {
|
|
|
return this.plugin.getItemName(folder).toLowerCase().indexOf(state.filterInput.toLowerCase()) !== -1
|
|
|
})
|
|
@@ -311,17 +340,9 @@ module.exports = class ProviderView {
|
|
|
return this.plugin.getPluginState().activeRow === this.plugin.getItemId(file)
|
|
|
}
|
|
|
|
|
|
- isChecked (item) {
|
|
|
- const itemId = this.providerFileToId(item)
|
|
|
- if (this.plugin.isFolder(item)) {
|
|
|
- const state = this.plugin.getPluginState()
|
|
|
- const folders = state.selectedFolders || {}
|
|
|
- if (itemId in folders) {
|
|
|
- return folders[itemId]
|
|
|
- }
|
|
|
- return false
|
|
|
- }
|
|
|
- return (itemId in this.plugin.uppy.getState().files)
|
|
|
+ isChecked (file) {
|
|
|
+ const { currentSelection } = this.plugin.getPluginState()
|
|
|
+ return currentSelection.some((item) => item === file)
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -339,11 +360,11 @@ module.exports = class ProviderView {
|
|
|
}
|
|
|
folders[folderId] = {loading: true, files: []}
|
|
|
this.plugin.setPluginState({selectedFolders: folders})
|
|
|
- this.Provider.list(this.plugin.getItemRequestPath(folder)).then((res) => {
|
|
|
+ return this.Provider.list(this.plugin.getItemRequestPath(folder)).then((res) => {
|
|
|
let files = []
|
|
|
this.plugin.getItemSubList(res).forEach((item) => {
|
|
|
if (!this.plugin.isFolder(item)) {
|
|
|
- this.addFile(item, true)
|
|
|
+ this.addFile(item)
|
|
|
files.push(this.providerFileToId(item))
|
|
|
}
|
|
|
})
|
|
@@ -368,102 +389,44 @@ module.exports = class ProviderView {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- removeFolder (folderId) {
|
|
|
- let state = this.plugin.getPluginState()
|
|
|
- let folders = state.selectedFolders || {}
|
|
|
- if (!(folderId in folders)) {
|
|
|
- return
|
|
|
- }
|
|
|
- let 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.
|
|
|
- const files = folder.files.concat([])
|
|
|
- for (const fileId of files) {
|
|
|
- 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.
|
|
|
- */
|
|
|
- updateFolderState (file) {
|
|
|
- let state = this.plugin.getPluginState()
|
|
|
- let folders = state.selectedFolders || {}
|
|
|
- for (let folderId in folders) {
|
|
|
- let folder = folders[folderId]
|
|
|
- if (folder.loading) {
|
|
|
- continue
|
|
|
- }
|
|
|
- let 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.
|
|
|
+ * in between last checked file and current one.
|
|
|
*/
|
|
|
toggleCheckbox (e, file) {
|
|
|
e.stopPropagation()
|
|
|
e.preventDefault()
|
|
|
- let { folders, files, filterInput } = this.plugin.getPluginState()
|
|
|
- let items = folders.concat(files)
|
|
|
- if (filterInput !== '') {
|
|
|
- items = this.filterItems(items)
|
|
|
- }
|
|
|
- let itemsToToggle = [file]
|
|
|
+ let { folders, files } = this.plugin.getPluginState()
|
|
|
+ let items = this.filterItems(folders.concat(files))
|
|
|
+
|
|
|
+ // Shift-clicking selects a single consecutive list of items
|
|
|
+ // starting at the previous click and deselects everything else.
|
|
|
if (this.lastCheckbox && e.shiftKey) {
|
|
|
- let prevIndex = items.indexOf(this.lastCheckbox)
|
|
|
- let currentIndex = items.indexOf(file)
|
|
|
+ let currentSelection
|
|
|
+ const prevIndex = items.indexOf(this.lastCheckbox)
|
|
|
+ const currentIndex = items.indexOf(file)
|
|
|
if (prevIndex < currentIndex) {
|
|
|
- itemsToToggle = items.slice(prevIndex, currentIndex + 1)
|
|
|
+ currentSelection = items.slice(prevIndex, currentIndex + 1)
|
|
|
} else {
|
|
|
- itemsToToggle = items.slice(currentIndex, prevIndex + 1)
|
|
|
+ currentSelection = items.slice(currentIndex, prevIndex + 1)
|
|
|
}
|
|
|
+ this.plugin.setPluginState({ currentSelection })
|
|
|
+ return
|
|
|
}
|
|
|
+
|
|
|
this.lastCheckbox = file
|
|
|
+ const { currentSelection } = this.plugin.getPluginState()
|
|
|
if (this.isChecked(file)) {
|
|
|
- for (let item of itemsToToggle) {
|
|
|
- const 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)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ this.plugin.setPluginState({
|
|
|
+ currentSelection: currentSelection.filter((item) => item !== file)
|
|
|
+ })
|
|
|
} else {
|
|
|
- for (let item of itemsToToggle) {
|
|
|
- if (this.plugin.isFolder(item)) {
|
|
|
- this.addFolder(item)
|
|
|
- } else {
|
|
|
- this.addFile(item, true)
|
|
|
- }
|
|
|
- }
|
|
|
+ this.plugin.setPluginState({
|
|
|
+ currentSelection: currentSelection.concat([file])
|
|
|
+ })
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -538,10 +501,34 @@ module.exports = class ProviderView {
|
|
|
}
|
|
|
|
|
|
donePicking () {
|
|
|
+ const { currentSelection } = this.plugin.getPluginState()
|
|
|
+ const promises = currentSelection.map((file) => {
|
|
|
+ if (this.plugin.isFolder(file)) {
|
|
|
+ return this.addFolder(file)
|
|
|
+ } else {
|
|
|
+ return this.addFile(file)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ this._loaderWrapper(Promise.all(promises), () => {
|
|
|
+ this.clearSelection()
|
|
|
+
|
|
|
+ const dashboard = this.plugin.uppy.getPlugin('Dashboard')
|
|
|
+ if (dashboard) dashboard.hideAllPanels()
|
|
|
+ }, () => {})
|
|
|
+ }
|
|
|
+
|
|
|
+ cancelPicking () {
|
|
|
+ this.clearSelection()
|
|
|
+
|
|
|
const dashboard = this.plugin.uppy.getPlugin('Dashboard')
|
|
|
if (dashboard) dashboard.hideAllPanels()
|
|
|
}
|
|
|
|
|
|
+ clearSelection () {
|
|
|
+ this.plugin.setPluginState({ currentSelection: [] })
|
|
|
+ }
|
|
|
+
|
|
|
// displays loader view while asynchronous request is being made.
|
|
|
_loaderWrapper (promise, then, catch_) {
|
|
|
promise
|
|
@@ -554,26 +541,32 @@ module.exports = class ProviderView {
|
|
|
const { authenticated, checkAuthInProgress, loading } = this.plugin.getPluginState()
|
|
|
|
|
|
if (loading) {
|
|
|
- return LoaderView()
|
|
|
+ return (
|
|
|
+ <CloseWrapper onUnmount={this.clearSelection}>
|
|
|
+ <LoaderView />
|
|
|
+ </CloseWrapper>
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
if (!authenticated) {
|
|
|
- return h(AuthView, {
|
|
|
- pluginName: this.plugin.title,
|
|
|
- pluginIcon: this.plugin.icon,
|
|
|
- demo: this.plugin.opts.demo,
|
|
|
- checkAuth: this.checkAuth,
|
|
|
- handleAuth: this.handleAuth,
|
|
|
- handleDemoAuth: this.handleDemoAuth,
|
|
|
- checkAuthInProgress: checkAuthInProgress
|
|
|
- })
|
|
|
+ return (
|
|
|
+ <CloseWrapper onUnmount={this.clearSelection}>
|
|
|
+ <AuthView
|
|
|
+ pluginName={this.plugin.title}
|
|
|
+ pluginIcon={this.plugin.icon}
|
|
|
+ demo={this.plugin.opts.demo}
|
|
|
+ checkAuth={this.checkAuth}
|
|
|
+ handleAuth={this.handleAuth}
|
|
|
+ handleDemoAuth={this.handleDemoAuth}
|
|
|
+ checkAuthInProgress={checkAuthInProgress} />
|
|
|
+ </CloseWrapper>
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
const browserProps = Object.assign({}, this.plugin.getPluginState(), {
|
|
|
username: this.username,
|
|
|
getNextFolder: this.getNextFolder,
|
|
|
getFolder: this.getFolder,
|
|
|
- addFile: this.addFile,
|
|
|
filterItems: this.filterItems,
|
|
|
filterQuery: this.filterQuery,
|
|
|
toggleSearch: this.toggleSearch,
|
|
@@ -589,6 +582,7 @@ module.exports = class ProviderView {
|
|
|
getItemIcon: this.plugin.getItemIcon,
|
|
|
handleScroll: this.handleScroll,
|
|
|
done: this.donePicking,
|
|
|
+ cancel: this.cancelPicking,
|
|
|
title: this.plugin.title,
|
|
|
viewType: this.opts.viewType,
|
|
|
showTitles: this.opts.showTitles,
|
|
@@ -598,6 +592,10 @@ module.exports = class ProviderView {
|
|
|
i18n: this.plugin.uppy.i18n
|
|
|
})
|
|
|
|
|
|
- return Browser(browserProps)
|
|
|
+ return (
|
|
|
+ <CloseWrapper onUnmount={this.clearSelection}>
|
|
|
+ <Browser {...browserProps} />
|
|
|
+ </CloseWrapper>
|
|
|
+ )
|
|
|
}
|
|
|
}
|