Socket
Socket
Sign inDemoInstall

@uppy/provider-views

Package Overview
Dependencies
Maintainers
6
Versions
108
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@uppy/provider-views - npm Package Compare versions

Comparing version 3.4.0 to 3.4.1

9

CHANGELOG.md
# @uppy/provider-views
## 3.4.1
Released: 2023-07-20
Included in: Uppy v3.13.0
- @uppy/provider-views: Add VirtualList to ProviderView (Merlijn Vos / #4566)
- @uppy/provider-views: fix race conditions with folder loading (Mikael Finstad / #4578)
- @uppy/provider-views: fix infinite folder loading (Mikael Finstad / #4590)
## 3.4.0

@@ -4,0 +13,0 @@

133

lib/Browser.js
import { h } from 'preact';
import classNames from 'classnames';
import remoteFileObjToLocal from '@uppy/utils/lib/remoteFileObjToLocal';
import { useMemo } from 'preact/hooks';
import VirtualList from '@uppy/utils/lib/VirtualList';
import SearchFilterInput from "./SearchFilterInput.js";

@@ -8,2 +10,54 @@ import FooterActions from "./FooterActions.js";

const VIRTUAL_SHARED_DIR = 'shared-with-me';
function ListItem(props) {
const {
currentSelection,
uppyFiles,
viewType,
isChecked,
toggleCheckbox,
recordShiftKeyPress,
showTitles,
i18n,
validateRestrictions,
getNextFolder,
columns,
f
} = props;
if (f.isFolder) {
var _isChecked;
return Item({
columns,
showTitles,
viewType,
i18n,
id: f.id,
title: f.name,
getItemIcon: () => f.icon,
isChecked: isChecked(f),
toggleCheckbox: event => toggleCheckbox(event, f),
recordShiftKeyPress,
type: 'folder',
isDisabled: (_isChecked = isChecked(f)) == null ? void 0 : _isChecked.loading,
isCheckboxDisabled: f.id === VIRTUAL_SHARED_DIR,
handleFolderClick: () => getNextFolder(f)
});
}
const restrictionError = validateRestrictions(remoteFileObjToLocal(f), [...uppyFiles, ...currentSelection]);
return Item({
id: f.id,
title: f.name,
author: f.author,
getItemIcon: () => f.icon,
isChecked: isChecked(f),
toggleCheckbox: event => toggleCheckbox(event, f),
recordShiftKeyPress,
columns,
showTitles,
viewType,
i18n,
type: 'file',
isDisabled: restrictionError && !isChecked(f),
restrictionError
});
}
function Browser(props) {

@@ -37,5 +91,7 @@ const {

columns,
noResultsLabel
noResultsLabel,
loadAllFiles
} = props;
const selected = currentSelection.length;
const rows = useMemo(() => [...folders, ...files], [folders, files]);
return h("div", {

@@ -68,2 +124,26 @@ className: classNames('uppy-ProviderBrowser', `uppy-ProviderBrowser-viewType--${viewType}`)

}
if (loadAllFiles) {
return h("div", {
className: "uppy-ProviderBrowser-body"
}, h("ul", {
className: "uppy-ProviderBrowser-list"
}, h(VirtualList, {
data: rows,
renderRow: f => h(ListItem, {
currentSelection: currentSelection,
uppyFiles: uppyFiles,
viewType: viewType,
isChecked: isChecked,
toggleCheckbox: toggleCheckbox,
recordShiftKeyPress: recordShiftKeyPress,
showTitles: showTitles,
i18n: i18n,
validateRestrictions: validateRestrictions,
getNextFolder: getNextFolder,
columns: columns,
f: f
}),
rowHeight: 31
})));
}
return h("div", {

@@ -78,39 +158,16 @@ className: "uppy-ProviderBrowser-body"

tabIndex: "-1"
}, folders.map(folder => {
var _isChecked;
return Item({
columns,
showTitles,
viewType,
i18n,
id: folder.id,
title: folder.name,
getItemIcon: () => folder.icon,
isChecked: isChecked(folder),
toggleCheckbox: event => toggleCheckbox(event, folder),
recordShiftKeyPress,
type: 'folder',
isDisabled: (_isChecked = isChecked(folder)) == null ? void 0 : _isChecked.loading,
isCheckboxDisabled: folder.id === VIRTUAL_SHARED_DIR,
handleFolderClick: () => getNextFolder(folder)
});
}), files.map(file => {
const restrictionError = validateRestrictions(remoteFileObjToLocal(file), [...uppyFiles, ...currentSelection]);
return Item({
id: file.id,
title: file.name,
author: file.author,
getItemIcon: () => file.icon,
isChecked: isChecked(file),
toggleCheckbox: event => toggleCheckbox(event, file),
recordShiftKeyPress,
columns,
showTitles,
viewType,
i18n,
type: 'file',
isDisabled: restrictionError && !isChecked(file),
restrictionError
});
})));
}, rows.map(f => h(ListItem, {
currentSelection: currentSelection,
uppyFiles: uppyFiles,
viewType: viewType,
isChecked: isChecked,
toggleCheckbox: toggleCheckbox,
recordShiftKeyPress: recordShiftKeyPress,
showTitles: showTitles,
i18n: i18n,
validateRestrictions: validateRestrictions,
getNextFolder: getNextFolder,
columns: columns,
f: f
}))));
})(), selected > 0 && h(FooterActions, {

@@ -117,0 +174,0 @@ selected: selected,

@@ -63,3 +63,5 @@ import { h } from 'preact';

alt: alt,
loading: "lazy"
loading: "lazy",
width: 16,
height: 16
});

@@ -66,0 +68,0 @@ }

@@ -15,3 +15,3 @@ function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; }

const packageJson = {
"version": "3.4.0"
"version": "3.4.1"
};

@@ -47,2 +47,4 @@ function getOrigin() {

*/
var _abortController = /*#__PURE__*/_classPrivateFieldLooseKey("abortController");
var _withAbort = /*#__PURE__*/_classPrivateFieldLooseKey("withAbort");
var _list = /*#__PURE__*/_classPrivateFieldLooseKey("list");

@@ -68,2 +70,9 @@ var _listFilesAndFolders = /*#__PURE__*/_classPrivateFieldLooseKey("listFilesAndFolders");

});
Object.defineProperty(this, _withAbort, {
value: _withAbort2
});
Object.defineProperty(this, _abortController, {
writable: true,
value: void 0
});
const defaultOptions = {

@@ -122,60 +131,48 @@ viewType: 'list',

async getFolder(requestPath, name) {
const controller = new AbortController();
const cancelRequest = () => {
controller.abort();
this.clearSelection();
};
this.plugin.uppy.on('dashboard:close-panel', cancelRequest);
this.plugin.uppy.on('cancel-all', cancelRequest);
this.setLoading(true);
try {
this.lastCheckbox = undefined;
let {
breadcrumbs
} = this.plugin.getPluginState();
const index = breadcrumbs.findIndex(dir => requestPath === dir.requestPath);
if (index !== -1) {
// means we navigated back to a known directory (already in the stack), so cut the stack off there
breadcrumbs = breadcrumbs.slice(0, index + 1);
} else {
// we have navigated into a new (unknown) folder, add it to the stack
breadcrumbs = [...breadcrumbs, {
requestPath,
name
}];
}
let files = [];
let folders = [];
do {
const {
files: newFiles,
folders: newFolders
} = await _classPrivateFieldLooseBase(this, _listFilesAndFolders)[_listFilesAndFolders]({
requestPath,
await _classPrivateFieldLooseBase(this, _withAbort)[_withAbort](async signal => {
this.lastCheckbox = undefined;
let {
breadcrumbs
} = this.plugin.getPluginState();
const index = breadcrumbs.findIndex(dir => requestPath === dir.requestPath);
if (index !== -1) {
// means we navigated back to a known directory (already in the stack), so cut the stack off there
breadcrumbs = breadcrumbs.slice(0, index + 1);
} else {
// we have navigated into a new (unknown) folder, add it to the stack
breadcrumbs = [...breadcrumbs, {
requestPath,
name
}];
}
this.nextPagePath = requestPath;
let files = [];
let folders = [];
do {
const {
files: newFiles,
folders: newFolders
} = await _classPrivateFieldLooseBase(this, _listFilesAndFolders)[_listFilesAndFolders]({
breadcrumbs,
signal
});
files = files.concat(newFiles);
folders = folders.concat(newFolders);
this.setLoading(this.plugin.uppy.i18n('loadedXFiles', {
numFiles: files.length + folders.length
}));
} while (this.opts.loadAllFiles && this.nextPagePath);
this.plugin.setPluginState({
folders,
files,
breadcrumbs,
signal: controller.signal
filterInput: ''
});
files = files.concat(newFiles);
folders = folders.concat(newFolders);
this.setLoading(this.plugin.uppy.i18n('loadedXFiles', {
numFiles: files.length + folders.length
}));
} while (this.opts.loadAllFiles && this.nextPagePath);
this.plugin.setPluginState({
folders,
files,
breadcrumbs,
filterInput: ''
});
} catch (err) {
var _err$cause;
if (((_err$cause = err.cause) == null ? void 0 : _err$cause.name) === 'AbortError') {
// Expected, user clicked “cancel”
return;
}
this.handleError(err);
} finally {
this.setLoading(false);
this.plugin.uppy.off('dashboard:close-panel', cancelRequest);
this.plugin.uppy.off('cancel-all', cancelRequest);
}

@@ -197,22 +194,29 @@ }

*/
logout() {
this.provider.logout().then(res => {
if (res.ok) {
if (!res.revoked) {
const message = this.plugin.uppy.i18n('companionUnauthorizeHint', {
provider: this.plugin.title,
url: res.manual_revoke_url
});
this.plugin.uppy.info(message, 'info', 7000);
async logout() {
try {
await _classPrivateFieldLooseBase(this, _withAbort)[_withAbort](async signal => {
const res = await this.provider.logout({
signal
});
if (res.ok) {
if (!res.revoked) {
const message = this.plugin.uppy.i18n('companionUnauthorizeHint', {
provider: this.plugin.title,
url: res.manual_revoke_url
});
this.plugin.uppy.info(message, 'info', 7000);
}
const newState = {
authenticated: false,
files: [],
folders: [],
breadcrumbs: [],
filterInput: ''
};
this.plugin.setPluginState(newState);
}
const newState = {
authenticated: false,
files: [],
folders: [],
breadcrumbs: [],
filterInput: ''
};
this.plugin.setPluginState(newState);
}
}).catch(this.handleError);
});
} catch (err) {
this.handleError(err);
}
}

@@ -275,24 +279,25 @@ filterQuery(input) {

async handleScroll(event) {
const requestPath = this.nextPagePath || null;
if (this.shouldHandleScroll(event) && requestPath) {
if (this.shouldHandleScroll(event) && this.nextPagePath) {
this.isHandlingScroll = true;
try {
const {
files,
folders,
breadcrumbs
} = this.plugin.getPluginState();
const {
files: newFiles,
folders: newFolders
} = await _classPrivateFieldLooseBase(this, _listFilesAndFolders)[_listFilesAndFolders]({
requestPath,
breadcrumbs
await _classPrivateFieldLooseBase(this, _withAbort)[_withAbort](async signal => {
const {
files,
folders,
breadcrumbs
} = this.plugin.getPluginState();
const {
files: newFiles,
folders: newFolders
} = await _classPrivateFieldLooseBase(this, _listFilesAndFolders)[_listFilesAndFolders]({
breadcrumbs,
signal
});
const combinedFiles = files.concat(newFiles);
const combinedFolders = folders.concat(newFolders);
this.plugin.setPluginState({
folders: combinedFolders,
files: combinedFiles
});
});
const combinedFiles = files.concat(newFiles);
const combinedFolders = folders.concat(newFolders);
this.plugin.setPluginState({
folders: combinedFolders,
files: combinedFiles
});
} catch (error) {

@@ -308,83 +313,86 @@ this.handleError(error);

try {
const {
currentSelection
} = this.plugin.getPluginState();
const messages = [];
const newFiles = [];
for (const selectedItem of currentSelection) {
await _classPrivateFieldLooseBase(this, _withAbort)[_withAbort](async signal => {
const {
requestPath
} = selectedItem;
const withRelDirPath = newItem => ({
...newItem,
// calculate the file's path relative to the user's selected item's path
// see https://github.com/transloadit/uppy/pull/4537#issuecomment-1614236655
relDirPath: newItem.absDirPath.replace(selectedItem.absDirPath, '').replace(/^\//, '')
});
if (selectedItem.isFolder) {
let isEmpty = true;
let numNewFiles = 0;
const queue = new PQueue({
concurrency: 6
currentSelection
} = this.plugin.getPluginState();
const messages = [];
const newFiles = [];
for (const selectedItem of currentSelection) {
const {
requestPath
} = selectedItem;
const withRelDirPath = newItem => ({
...newItem,
// calculate the file's path relative to the user's selected item's path
// see https://github.com/transloadit/uppy/pull/4537#issuecomment-1614236655
relDirPath: newItem.absDirPath.replace(selectedItem.absDirPath, '').replace(/^\//, '')
});
const onFiles = files => {
for (const newFile of files) {
const tagFile = this.getTagFile(newFile);
const id = getSafeFileId(tagFile);
// If the same folder is added again, we don't want to send
// X amount of duplicate file notifications, we want to say
// the folder was already added. This checks if all files are duplicate,
// if that's the case, we don't add the files.
if (!this.plugin.uppy.checkIfFileAlreadyExists(id)) {
newFiles.push(withRelDirPath(newFile));
numNewFiles++;
this.setLoading(this.plugin.uppy.i18n('addedNumFiles', {
numFiles: numNewFiles
}));
if (selectedItem.isFolder) {
let isEmpty = true;
let numNewFiles = 0;
const queue = new PQueue({
concurrency: 6
});
const onFiles = files => {
for (const newFile of files) {
const tagFile = this.getTagFile(newFile);
const id = getSafeFileId(tagFile);
// If the same folder is added again, we don't want to send
// X amount of duplicate file notifications, we want to say
// the folder was already added. This checks if all files are duplicate,
// if that's the case, we don't add the files.
if (!this.plugin.uppy.checkIfFileAlreadyExists(id)) {
newFiles.push(withRelDirPath(newFile));
numNewFiles++;
this.setLoading(this.plugin.uppy.i18n('addedNumFiles', {
numFiles: numNewFiles
}));
}
isEmpty = false;
}
isEmpty = false;
};
await _classPrivateFieldLooseBase(this, _recursivelyListAllFiles)[_recursivelyListAllFiles]({
requestPath,
absDirPath: prependPath(selectedItem.absDirPath, selectedItem.name),
relDirPath: selectedItem.name,
queue,
onFiles,
signal
});
await queue.onIdle();
let message;
if (isEmpty) {
message = this.plugin.uppy.i18n('emptyFolderAdded');
} else if (numNewFiles === 0) {
message = this.plugin.uppy.i18n('folderAlreadyAdded', {
folder: selectedItem.name
});
} else {
// TODO we don't really know at this point whether any files were actually added
// (only later after addFiles has been called) so we should probably rewrite this.
// Example: If all files fail to add due to restriction error, it will still say "Added 100 files from folder"
message = this.plugin.uppy.i18n('folderAdded', {
smart_count: numNewFiles,
folder: selectedItem.name
});
}
};
await _classPrivateFieldLooseBase(this, _recursivelyListAllFiles)[_recursivelyListAllFiles]({
requestPath,
absDirPath: prependPath(selectedItem.absDirPath, selectedItem.name),
relDirPath: selectedItem.name,
queue,
onFiles
});
await queue.onIdle();
let message;
if (isEmpty) {
message = this.plugin.uppy.i18n('emptyFolderAdded');
} else if (numNewFiles === 0) {
message = this.plugin.uppy.i18n('folderAlreadyAdded', {
folder: selectedItem.name
});
messages.push(message);
} else {
// TODO we don't really know at this point whether any files were actually added
// (only later after addFiles has been called) so we should probably rewrite this.
// Example: If all files fail to add due to restriction error, it will still say "Added 100 files from folder"
message = this.plugin.uppy.i18n('folderAdded', {
smart_count: numNewFiles,
folder: selectedItem.name
});
newFiles.push(withRelDirPath(selectedItem));
}
messages.push(message);
} else {
newFiles.push(withRelDirPath(selectedItem));
}
}
// Note: this.plugin.uppy.addFiles must be only run once we are done fetching all files,
// because it will cause the loading screen to disappear,
// and that will allow the user to start the upload, so we need to make sure we have
// finished all async operations before we add any file
// see https://github.com/transloadit/uppy/pull/4384
this.plugin.uppy.log('Adding remote provider files');
this.plugin.uppy.addFiles(newFiles.map(file => this.getTagFile(file)));
this.plugin.setPluginState({
filterInput: ''
// Note: this.plugin.uppy.addFiles must be only run once we are done fetching all files,
// because it will cause the loading screen to disappear,
// and that will allow the user to start the upload, so we need to make sure we have
// finished all async operations before we add any file
// see https://github.com/transloadit/uppy/pull/4384
this.plugin.uppy.log('Adding remote provider files');
this.plugin.uppy.addFiles(newFiles.map(file => this.getTagFile(file)));
this.plugin.setPluginState({
filterInput: ''
});
messages.forEach(message => this.plugin.uppy.info(message));
this.clearSelection();
});
messages.forEach(message => this.plugin.uppy.info(message));
this.clearSelection();
} catch (err) {

@@ -449,2 +457,3 @@ this.handleError(err);

getFolder: this.getFolder,
loadAllFiles: this.opts.loadAllFiles,
// For SearchFilterInput component

@@ -499,2 +508,22 @@ showSearchFilter: targetViewOptions.showFilter,

}
async function _withAbort2(op) {
var _classPrivateFieldLoo;
// prevent multiple requests in parallel from causing race conditions
(_classPrivateFieldLoo = _classPrivateFieldLooseBase(this, _abortController)[_abortController]) == null ? void 0 : _classPrivateFieldLoo.abort();
const abortController = new AbortController();
_classPrivateFieldLooseBase(this, _abortController)[_abortController] = abortController;
const cancelRequest = () => {
abortController.abort();
this.clearSelection();
};
try {
this.plugin.uppy.on('dashboard:close-panel', cancelRequest);
this.plugin.uppy.on('cancel-all', cancelRequest);
await op(abortController.signal);
} finally {
this.plugin.uppy.off('dashboard:close-panel', cancelRequest);
this.plugin.uppy.off('cancel-all', cancelRequest);
_classPrivateFieldLooseBase(this, _abortController)[_abortController] = undefined;
}
}
async function _list2(_ref) {

@@ -524,3 +553,2 @@ let {

let {
requestPath,
breadcrumbs,

@@ -534,3 +562,3 @@ signal

} = await _classPrivateFieldLooseBase(this, _list)[_list]({
requestPath,
requestPath: this.nextPagePath,
absDirPath,

@@ -560,3 +588,4 @@ signal

queue,
onFiles
onFiles,
signal
} = _ref3;

@@ -567,3 +596,4 @@ let curPath = requestPath;

requestPath: curPath,
absDirPath
absDirPath,
signal
});

@@ -581,3 +611,4 @@ curPath = res.nextPagePath;

queue,
onFiles
onFiles,
signal
})));

@@ -584,0 +615,0 @@ await Promise.all(promises); // in case we get an error

@@ -10,3 +10,3 @@ function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; }

const packageJson = {
"version": "3.4.0"
"version": "3.4.1"
};

@@ -13,0 +13,0 @@ /**

@@ -126,2 +126,3 @@ import getFileType from '@uppy/utils/lib/getFileType';

handleError(error) {
var _error$cause;
const {

@@ -132,3 +133,5 @@ uppy

uppy.log(error.toString());
if (error.isAuthError) {
if (error.isAuthError || ((_error$cause = error.cause) == null ? void 0 : _error$cause.name) === 'AbortError') {
// authError just means we're not authenticated, don't show to user
// AbortError means the user has clicked "cancel" on an operation
return;

@@ -135,0 +138,0 @@ }

{
"name": "@uppy/provider-views",
"description": "View library for Uppy remote provider plugins.",
"version": "3.4.0",
"version": "3.4.1",
"license": "MIT",

@@ -23,3 +23,3 @@ "main": "lib/index.js",

"dependencies": {
"@uppy/utils": "^5.4.1",
"@uppy/utils": "^5.4.2",
"classnames": "^2.2.6",

@@ -26,0 +26,0 @@ "nanoid": "^4.0.0",

@@ -5,2 +5,4 @@ import { h } from 'preact'

import remoteFileObjToLocal from '@uppy/utils/lib/remoteFileObjToLocal'
import { useMemo } from 'preact/hooks'
import VirtualList from '@uppy/utils/lib/VirtualList'
import SearchFilterInput from './SearchFilterInput.jsx'

@@ -12,2 +14,59 @@ import FooterActions from './FooterActions.jsx'

function ListItem (props) {
const {
currentSelection,
uppyFiles,
viewType,
isChecked,
toggleCheckbox,
recordShiftKeyPress,
showTitles,
i18n,
validateRestrictions,
getNextFolder,
columns,
f,
} = props
if (f.isFolder) {
return Item({
columns,
showTitles,
viewType,
i18n,
id: f.id,
title: f.name,
getItemIcon: () => f.icon,
isChecked: isChecked(f),
toggleCheckbox: (event) => toggleCheckbox(event, f),
recordShiftKeyPress,
type: 'folder',
isDisabled: isChecked(f)?.loading,
isCheckboxDisabled: f.id === VIRTUAL_SHARED_DIR,
handleFolderClick: () => getNextFolder(f),
})
}
const restrictionError = validateRestrictions(remoteFileObjToLocal(f), [
...uppyFiles,
...currentSelection,
])
return Item({
id: f.id,
title: f.name,
author: f.author,
getItemIcon: () => f.icon,
isChecked: isChecked(f),
toggleCheckbox: (event) => toggleCheckbox(event, f),
recordShiftKeyPress,
columns,
showTitles,
viewType,
i18n,
type: 'file',
isDisabled: restrictionError && !isChecked(f),
restrictionError,
})
}
function Browser (props) {

@@ -42,2 +101,3 @@ const {

noResultsLabel,
loadAllFiles,
} = props

@@ -47,2 +107,4 @@

const rows = useMemo(() => [...folders, ...files], [folders, files])
return (

@@ -92,5 +154,30 @@ <div

if (!folders.length && !files.length) {
return <div className="uppy-Provider-empty">{noResultsLabel}</div>
}
if (loadAllFiles) {
return (
<div className="uppy-Provider-empty">
{noResultsLabel}
<div className="uppy-ProviderBrowser-body">
<ul className="uppy-ProviderBrowser-list">
<VirtualList
data={rows}
renderRow={(f) => (
<ListItem
currentSelection={currentSelection}
uppyFiles={uppyFiles}
viewType={viewType}
isChecked={isChecked}
toggleCheckbox={toggleCheckbox}
recordShiftKeyPress={recordShiftKeyPress}
showTitles={showTitles}
i18n={i18n}
validateRestrictions={validateRestrictions}
getNextFolder={getNextFolder}
columns={columns}
f={f}
/>
)}
rowHeight={31}
/>
</ul>
</div>

@@ -109,44 +196,18 @@ )

>
{folders.map((folder) => {
return Item({
columns,
showTitles,
viewType,
i18n,
id: folder.id,
title: folder.name,
getItemIcon: () => folder.icon,
isChecked: isChecked(folder),
toggleCheckbox: (event) => toggleCheckbox(event, folder),
recordShiftKeyPress,
type: 'folder',
isDisabled: isChecked(folder)?.loading,
isCheckboxDisabled: folder.id === VIRTUAL_SHARED_DIR,
handleFolderClick: () => getNextFolder(folder),
})
})}
{files.map((file) => {
const restrictionError = validateRestrictions(
remoteFileObjToLocal(file),
[...uppyFiles, ...currentSelection],
)
return Item({
id: file.id,
title: file.name,
author: file.author,
getItemIcon: () => file.icon,
isChecked: isChecked(file),
toggleCheckbox: (event) => toggleCheckbox(event, file),
recordShiftKeyPress,
columns,
showTitles,
viewType,
i18n,
type: 'file',
isDisabled: restrictionError && !isChecked(file),
restrictionError,
})
})}
{rows.map((f) => (
<ListItem
currentSelection={currentSelection}
uppyFiles={uppyFiles}
viewType={viewType}
isChecked={isChecked}
toggleCheckbox={toggleCheckbox}
recordShiftKeyPress={recordShiftKeyPress}
showTitles={showTitles}
i18n={i18n}
validateRestrictions={validateRestrictions}
getNextFolder={getNextFolder}
columns={columns}
f={f}
/>
))}
</ul>

@@ -153,0 +214,0 @@ </div>

@@ -41,5 +41,5 @@ import { h } from 'preact'

const { alt } = props
return <img src={itemIconString} alt={alt} loading="lazy" />
return <img src={itemIconString} alt={alt} loading="lazy" width={16} height={16} />
}
}
}

@@ -98,2 +98,25 @@ import { h } from 'preact'

#abortController
async #withAbort (op) {
// prevent multiple requests in parallel from causing race conditions
this.#abortController?.abort()
const abortController = new AbortController()
this.#abortController = abortController
const cancelRequest = () => {
abortController.abort()
this.clearSelection()
}
try {
this.plugin.uppy.on('dashboard:close-panel', cancelRequest)
this.plugin.uppy.on('cancel-all', cancelRequest)
await op(abortController.signal)
} finally {
this.plugin.uppy.off('dashboard:close-panel', cancelRequest)
this.plugin.uppy.off('cancel-all', cancelRequest)
this.#abortController = undefined
}
}
async #list ({ requestPath, absDirPath, signal }) {

@@ -112,6 +135,6 @@ const { username, nextPagePath, items } = await this.provider.list(requestPath, { signal })

async #listFilesAndFolders ({ requestPath, breadcrumbs, signal }) {
async #listFilesAndFolders ({ breadcrumbs, signal }) {
const absDirPath = formatBreadcrumbs(breadcrumbs)
const { items, nextPagePath } = await this.#list({ requestPath, absDirPath, signal })
const { items, nextPagePath } = await this.#list({ requestPath: this.nextPagePath, absDirPath, signal })

@@ -144,53 +167,41 @@ this.nextPagePath = nextPagePath

async getFolder (requestPath, name) {
const controller = new AbortController()
const cancelRequest = () => {
controller.abort()
this.clearSelection()
}
this.plugin.uppy.on('dashboard:close-panel', cancelRequest)
this.plugin.uppy.on('cancel-all', cancelRequest)
this.setLoading(true)
try {
this.lastCheckbox = undefined
await this.#withAbort(async (signal) => {
this.lastCheckbox = undefined
let { breadcrumbs } = this.plugin.getPluginState()
let { breadcrumbs } = this.plugin.getPluginState()
const index = breadcrumbs.findIndex((dir) => requestPath === dir.requestPath)
const index = breadcrumbs.findIndex((dir) => requestPath === dir.requestPath)
if (index !== -1) {
// means we navigated back to a known directory (already in the stack), so cut the stack off there
breadcrumbs = breadcrumbs.slice(0, index + 1)
} else {
// we have navigated into a new (unknown) folder, add it to the stack
breadcrumbs = [...breadcrumbs, { requestPath, name }]
}
if (index !== -1) {
// means we navigated back to a known directory (already in the stack), so cut the stack off there
breadcrumbs = breadcrumbs.slice(0, index + 1)
} else {
// we have navigated into a new (unknown) folder, add it to the stack
breadcrumbs = [...breadcrumbs, { requestPath, name }]
}
let files = []
let folders = []
do {
const { files: newFiles, folders: newFolders } = await this.#listFilesAndFolders({
requestPath, breadcrumbs, signal: controller.signal,
})
this.nextPagePath = requestPath
let files = []
let folders = []
do {
const { files: newFiles, folders: newFolders } = await this.#listFilesAndFolders({
breadcrumbs, signal,
})
files = files.concat(newFiles)
folders = folders.concat(newFolders)
files = files.concat(newFiles)
folders = folders.concat(newFolders)
this.setLoading(this.plugin.uppy.i18n('loadedXFiles', { numFiles: files.length + folders.length }))
} while (
this.opts.loadAllFiles && this.nextPagePath
)
this.setLoading(this.plugin.uppy.i18n('loadedXFiles', { numFiles: files.length + folders.length }))
} while (
this.opts.loadAllFiles && this.nextPagePath
)
this.plugin.setPluginState({ folders, files, breadcrumbs, filterInput: '' })
this.plugin.setPluginState({ folders, files, breadcrumbs, filterInput: '' })
})
} catch (err) {
if (err.cause?.name === 'AbortError') {
// Expected, user clicked “cancel”
return
}
this.handleError(err)
} finally {
this.setLoading(false)
this.plugin.uppy.off('dashboard:close-panel', cancelRequest)
this.plugin.uppy.off('cancel-all', cancelRequest)
}

@@ -212,5 +223,6 @@ }

*/
logout () {
this.provider.logout()
.then((res) => {
async logout () {
try {
await this.#withAbort(async (signal) => {
const res = await this.provider.logout({ signal })
if (res.ok) {

@@ -234,3 +246,6 @@ if (!res.revoked) {

}
}).catch(this.handleError)
})
} catch (err) {
this.handleError(err)
}
}

@@ -289,18 +304,18 @@

async handleScroll (event) {
const requestPath = this.nextPagePath || null
if (this.shouldHandleScroll(event) && requestPath) {
if (this.shouldHandleScroll(event) && this.nextPagePath) {
this.isHandlingScroll = true
try {
const { files, folders, breadcrumbs } = this.plugin.getPluginState()
await this.#withAbort(async (signal) => {
const { files, folders, breadcrumbs } = this.plugin.getPluginState()
const { files: newFiles, folders: newFolders } = await this.#listFilesAndFolders({
requestPath, breadcrumbs,
})
const { files: newFiles, folders: newFolders } = await this.#listFilesAndFolders({
breadcrumbs, signal,
})
const combinedFiles = files.concat(newFiles)
const combinedFolders = folders.concat(newFolders)
const combinedFiles = files.concat(newFiles)
const combinedFolders = folders.concat(newFolders)
this.plugin.setPluginState({ folders: combinedFolders, files: combinedFiles })
this.plugin.setPluginState({ folders: combinedFolders, files: combinedFiles })
})
} catch (error) {

@@ -314,7 +329,7 @@ this.handleError(error)

async #recursivelyListAllFiles ({ requestPath, absDirPath, relDirPath, queue, onFiles }) {
async #recursivelyListAllFiles ({ requestPath, absDirPath, relDirPath, queue, onFiles, signal }) {
let curPath = requestPath
while (curPath) {
const res = await this.#list({ requestPath: curPath, absDirPath })
const res = await this.#list({ requestPath: curPath, absDirPath, signal })
curPath = res.nextPagePath

@@ -335,2 +350,3 @@

onFiles,
signal,
})

@@ -345,83 +361,86 @@ )))

try {
const { currentSelection } = this.plugin.getPluginState()
await this.#withAbort(async (signal) => {
const { currentSelection } = this.plugin.getPluginState()
const messages = []
const newFiles = []
const messages = []
const newFiles = []
for (const selectedItem of currentSelection) {
const { requestPath } = selectedItem
for (const selectedItem of currentSelection) {
const { requestPath } = selectedItem
const withRelDirPath = (newItem) => ({
...newItem,
// calculate the file's path relative to the user's selected item's path
// see https://github.com/transloadit/uppy/pull/4537#issuecomment-1614236655
relDirPath: newItem.absDirPath.replace(selectedItem.absDirPath, '').replace(/^\//, ''),
})
const withRelDirPath = (newItem) => ({
...newItem,
// calculate the file's path relative to the user's selected item's path
// see https://github.com/transloadit/uppy/pull/4537#issuecomment-1614236655
relDirPath: newItem.absDirPath.replace(selectedItem.absDirPath, '').replace(/^\//, ''),
})
if (selectedItem.isFolder) {
let isEmpty = true
let numNewFiles = 0
if (selectedItem.isFolder) {
let isEmpty = true
let numNewFiles = 0
const queue = new PQueue({ concurrency: 6 })
const queue = new PQueue({ concurrency: 6 })
const onFiles = (files) => {
for (const newFile of files) {
const tagFile = this.getTagFile(newFile)
const id = getSafeFileId(tagFile)
// If the same folder is added again, we don't want to send
// X amount of duplicate file notifications, we want to say
// the folder was already added. This checks if all files are duplicate,
// if that's the case, we don't add the files.
if (!this.plugin.uppy.checkIfFileAlreadyExists(id)) {
newFiles.push(withRelDirPath(newFile))
numNewFiles++
this.setLoading(this.plugin.uppy.i18n('addedNumFiles', { numFiles: numNewFiles }))
const onFiles = (files) => {
for (const newFile of files) {
const tagFile = this.getTagFile(newFile)
const id = getSafeFileId(tagFile)
// If the same folder is added again, we don't want to send
// X amount of duplicate file notifications, we want to say
// the folder was already added. This checks if all files are duplicate,
// if that's the case, we don't add the files.
if (!this.plugin.uppy.checkIfFileAlreadyExists(id)) {
newFiles.push(withRelDirPath(newFile))
numNewFiles++
this.setLoading(this.plugin.uppy.i18n('addedNumFiles', { numFiles: numNewFiles }))
}
isEmpty = false
}
isEmpty = false
}
}
await this.#recursivelyListAllFiles({
requestPath,
absDirPath: prependPath(selectedItem.absDirPath, selectedItem.name),
relDirPath: selectedItem.name,
queue,
onFiles,
})
await queue.onIdle()
await this.#recursivelyListAllFiles({
requestPath,
absDirPath: prependPath(selectedItem.absDirPath, selectedItem.name),
relDirPath: selectedItem.name,
queue,
onFiles,
signal,
})
await queue.onIdle()
let message
if (isEmpty) {
message = this.plugin.uppy.i18n('emptyFolderAdded')
} else if (numNewFiles === 0) {
message = this.plugin.uppy.i18n('folderAlreadyAdded', {
folder: selectedItem.name,
})
let message
if (isEmpty) {
message = this.plugin.uppy.i18n('emptyFolderAdded')
} else if (numNewFiles === 0) {
message = this.plugin.uppy.i18n('folderAlreadyAdded', {
folder: selectedItem.name,
})
} else {
// TODO we don't really know at this point whether any files were actually added
// (only later after addFiles has been called) so we should probably rewrite this.
// Example: If all files fail to add due to restriction error, it will still say "Added 100 files from folder"
message = this.plugin.uppy.i18n('folderAdded', {
smart_count: numNewFiles, folder: selectedItem.name,
})
}
messages.push(message)
} else {
// TODO we don't really know at this point whether any files were actually added
// (only later after addFiles has been called) so we should probably rewrite this.
// Example: If all files fail to add due to restriction error, it will still say "Added 100 files from folder"
message = this.plugin.uppy.i18n('folderAdded', {
smart_count: numNewFiles, folder: selectedItem.name,
})
newFiles.push(withRelDirPath(selectedItem))
}
messages.push(message)
} else {
newFiles.push(withRelDirPath(selectedItem))
}
}
// Note: this.plugin.uppy.addFiles must be only run once we are done fetching all files,
// because it will cause the loading screen to disappear,
// and that will allow the user to start the upload, so we need to make sure we have
// finished all async operations before we add any file
// see https://github.com/transloadit/uppy/pull/4384
this.plugin.uppy.log('Adding remote provider files')
this.plugin.uppy.addFiles(newFiles.map((file) => this.getTagFile(file)))
// Note: this.plugin.uppy.addFiles must be only run once we are done fetching all files,
// because it will cause the loading screen to disappear,
// and that will allow the user to start the upload, so we need to make sure we have
// finished all async operations before we add any file
// see https://github.com/transloadit/uppy/pull/4384
this.plugin.uppy.log('Adding remote provider files')
this.plugin.uppy.addFiles(newFiles.map((file) => this.getTagFile(file)))
this.plugin.setPluginState({ filterInput: '' })
messages.forEach(message => this.plugin.uppy.info(message))
this.plugin.setPluginState({ filterInput: '' })
messages.forEach(message => this.plugin.uppy.info(message))
this.clearSelection()
this.clearSelection()
})
} catch (err) {

@@ -467,2 +486,3 @@ this.handleError(err)

getFolder: this.getFolder,
loadAllFiles: this.opts.loadAllFiles,

@@ -469,0 +489,0 @@ // For SearchFilterInput component

@@ -51,3 +51,5 @@ import getFileType from '@uppy/utils/lib/getFileType'

if (error.isAuthError) {
if (error.isAuthError || error.cause?.name === 'AbortError') {
// authError just means we're not authenticated, don't show to user
// AbortError means the user has clicked "cancel" on an operation
return

@@ -54,0 +56,0 @@ }

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc