@uppy/provider-views
Advanced tools
Comparing version 4.0.0-beta.9 to 4.0.0-beta.10
# @uppy/provider-views | ||
## 4.0.0-beta.10 | ||
Released: 2024-06-27 | ||
Included in: Uppy v4.0.0-beta.13 | ||
- @uppy/provider-views: `Loader.tsx` - delete the file (Evgenia Karunus / #5284) | ||
- @uppy/provider-views: Provider views rewrite (.files, .folders => .partialTree) (Evgenia Karunus / #5050) | ||
## 4.0.0-beta.9 | ||
@@ -4,0 +12,0 @@ |
@@ -1,2 +0,2 @@ | ||
import type { UnknownProviderPluginState } from '@uppy/core/lib/Uppy'; | ||
import type { PartialTreeFolder } from '@uppy/core/lib/Uppy'; | ||
import { h } from 'preact'; | ||
@@ -6,6 +6,7 @@ import type { Body, Meta } from '@uppy/utils/lib/UppyFile'; | ||
type BreadcrumbsProps<M extends Meta, B extends Body> = { | ||
getFolder: ProviderView<M, B>['getFolder']; | ||
openFolder: ProviderView<M, B>['openFolder']; | ||
title: string; | ||
breadcrumbsIcon: h.JSX.Element; | ||
breadcrumbs: UnknownProviderPluginState['breadcrumbs']; | ||
breadcrumbs: PartialTreeFolder[]; | ||
i18n: any; | ||
}; | ||
@@ -12,0 +13,0 @@ export default function Breadcrumbs<M extends Meta, B extends Body>(props: BreadcrumbsProps<M, B>): h.JSX.Element; |
import { h, Fragment } from 'preact'; | ||
const Breadcrumb = props => { | ||
const { | ||
getFolder, | ||
title, | ||
isLast | ||
} = props; | ||
return h(Fragment, null, h("button", { | ||
type: "button", | ||
className: "uppy-u-reset uppy-c-btn", | ||
onClick: getFolder | ||
}, title), !isLast ? ' / ' : ''); | ||
}; | ||
export default function Breadcrumbs(props) { | ||
const { | ||
getFolder, | ||
openFolder, | ||
title, | ||
breadcrumbsIcon, | ||
breadcrumbs | ||
breadcrumbs, | ||
i18n | ||
} = props; | ||
@@ -25,8 +14,11 @@ return h("div", { | ||
className: "uppy-Provider-breadcrumbsIcon" | ||
}, breadcrumbsIcon), breadcrumbs.map((directory, i) => h(Breadcrumb, { | ||
key: directory.id, | ||
getFolder: () => getFolder(directory.requestPath, directory.name), | ||
title: i === 0 ? title : directory.name, | ||
isLast: i + 1 === breadcrumbs.length | ||
}))); | ||
}, breadcrumbsIcon), breadcrumbs.map((folder, index) => { | ||
var _folder$data$name; | ||
return h(Fragment, null, h("button", { | ||
key: folder.id, | ||
type: "button", | ||
className: "uppy-u-reset uppy-c-btn", | ||
onClick: () => openFolder(folder.id) | ||
}, folder.type === 'root' ? title : (_folder$data$name = folder.data.name) != null ? _folder$data$name : i18n('unnamed')), breadcrumbs.length === index + 1 ? '' : ' / '); | ||
})); | ||
} |
import { h } from 'preact'; | ||
import type { CompanionFile } from '@uppy/utils/lib/CompanionFile'; | ||
import type { Body, Meta, UppyFile } from '@uppy/utils/lib/UppyFile'; | ||
import type { Body, Meta } from '@uppy/utils/lib/UppyFile'; | ||
import type { I18n } from '@uppy/utils/lib/Translator'; | ||
import type Uppy from '@uppy/core'; | ||
import type { PartialTreeFile, PartialTreeFolderNode } from '@uppy/core/lib/Uppy.ts'; | ||
import ProviderView from './ProviderView/ProviderView.tsx'; | ||
type BrowserProps<M extends Meta, B extends Body> = { | ||
currentSelection: any[]; | ||
folders: CompanionFile[]; | ||
files: CompanionFile[]; | ||
uppyFiles: UppyFile<M, B>[]; | ||
displayedPartialTree: (PartialTreeFile | PartialTreeFolderNode)[]; | ||
viewType: string; | ||
headerComponent?: h.JSX.Element; | ||
showBreadcrumbs: boolean; | ||
isChecked: (file: any) => boolean; | ||
toggleCheckbox: (event: Event, file: CompanionFile) => void; | ||
recordShiftKeyPress: (event: KeyboardEvent | MouseEvent) => void; | ||
handleScroll: (event: Event) => Promise<void>; | ||
toggleCheckbox: ProviderView<M, B>['toggleCheckbox']; | ||
handleScroll: ProviderView<M, B>['handleScroll']; | ||
showTitles: boolean; | ||
i18n: I18n; | ||
validateRestrictions: Uppy<M, B>['validateRestrictions']; | ||
isLoading: boolean | string; | ||
showSearchFilter: boolean; | ||
search: (query: string) => void; | ||
searchTerm?: string | null; | ||
clearSearch: () => void; | ||
searchOnInput: boolean; | ||
searchInputLabel: string; | ||
clearSearchLabel: string; | ||
getNextFolder?: (folder: any) => void; | ||
cancel: () => void; | ||
done: () => void; | ||
openFolder: ProviderView<M, B>['openFolder']; | ||
noResultsLabel: string; | ||
virtualList?: boolean; | ||
virtualList: boolean; | ||
}; | ||
@@ -35,0 +18,0 @@ declare function Browser<M extends Meta, B extends Body>(props: BrowserProps<M, B>): h.JSX.Element; |
@@ -1,175 +0,89 @@ | ||
/* eslint-disable react/require-default-props */ | ||
import { h } from 'preact'; | ||
import classNames from 'classnames'; | ||
import remoteFileObjToLocal from '@uppy/utils/lib/remoteFileObjToLocal'; | ||
import { useMemo } from 'preact/hooks'; | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore untyped | ||
import VirtualList from '@uppy/utils/lib/VirtualList'; | ||
import SearchFilterInput from "./SearchFilterInput.js"; | ||
import FooterActions from "./FooterActions.js"; | ||
import { useEffect, useState } from 'preact/hooks'; | ||
import Item from "./Item/index.js"; | ||
const VIRTUAL_SHARED_DIR = 'shared-with-me'; | ||
function ListItem(props) { | ||
const { | ||
currentSelection, | ||
uppyFiles, | ||
viewType, | ||
isChecked, | ||
toggleCheckbox, | ||
recordShiftKeyPress, | ||
showTitles, | ||
i18n, | ||
validateRestrictions, | ||
getNextFolder, | ||
f | ||
} = props; | ||
if (f.isFolder) { | ||
return Item({ | ||
showTitles, | ||
viewType, | ||
i18n, | ||
id: f.id, | ||
title: f.name, | ||
getItemIcon: () => f.icon, | ||
isChecked: isChecked(f), | ||
toggleCheckbox: event => toggleCheckbox(event, f), | ||
recordShiftKeyPress, | ||
type: 'folder', | ||
// TODO: when was this supposed to be true? | ||
isDisabled: false, | ||
isCheckboxDisabled: f.id === VIRTUAL_SHARED_DIR, | ||
// getNextFolder always exists when f.isFolder is true | ||
handleFolderClick: () => getNextFolder(f) | ||
}); | ||
} | ||
const restrictionError = validateRestrictions(remoteFileObjToLocal(f), [...uppyFiles, ...currentSelection]); | ||
return Item({ | ||
id: f.id, | ||
title: f.name, | ||
author: f.author, | ||
getItemIcon: () => viewType === 'grid' && f.thumbnail ? f.thumbnail : f.icon, | ||
isChecked: isChecked(f), | ||
toggleCheckbox: event => toggleCheckbox(event, f), | ||
isCheckboxDisabled: false, | ||
recordShiftKeyPress, | ||
showTitles, | ||
viewType, | ||
i18n, | ||
type: 'file', | ||
isDisabled: Boolean(restrictionError) && !isChecked(f), | ||
restrictionError | ||
}); | ||
} | ||
function Browser(props) { | ||
const { | ||
currentSelection, | ||
folders, | ||
files, | ||
uppyFiles, | ||
displayedPartialTree, | ||
viewType, | ||
headerComponent, | ||
showBreadcrumbs, | ||
isChecked, | ||
toggleCheckbox, | ||
recordShiftKeyPress, | ||
handleScroll, | ||
showTitles, | ||
i18n, | ||
validateRestrictions, | ||
isLoading, | ||
showSearchFilter, | ||
search, | ||
searchTerm, | ||
clearSearch, | ||
searchOnInput, | ||
searchInputLabel, | ||
clearSearchLabel, | ||
getNextFolder, | ||
cancel, | ||
done, | ||
openFolder, | ||
noResultsLabel, | ||
virtualList | ||
} = props; | ||
const selected = currentSelection.length; | ||
const rows = useMemo(() => [...folders, ...files], [folders, files]); | ||
return h("div", { | ||
className: classNames('uppy-ProviderBrowser', `uppy-ProviderBrowser-viewType--${viewType}`) | ||
}, headerComponent && h("div", { | ||
className: "uppy-ProviderBrowser-header" | ||
}, h("div", { | ||
className: classNames('uppy-ProviderBrowser-headerBar', !showBreadcrumbs && 'uppy-ProviderBrowser-headerBar--simple') | ||
}, headerComponent)), showSearchFilter && h("div", { | ||
class: "uppy-ProviderBrowser-searchFilter" | ||
}, h(SearchFilterInput, { | ||
search: search, | ||
searchTerm: searchTerm, | ||
clearSearch: clearSearch, | ||
inputLabel: searchInputLabel, | ||
clearSearchLabel: clearSearchLabel, | ||
inputClassName: "uppy-ProviderBrowser-searchFilterInput", | ||
searchOnInput: searchOnInput | ||
})), (() => { | ||
if (isLoading) { | ||
return h("div", { | ||
className: "uppy-Provider-loading" | ||
}, h("span", null, typeof isLoading === 'string' ? isLoading : i18n('loading'))); | ||
} | ||
if (!folders.length && !files.length) { | ||
return h("div", { | ||
className: "uppy-Provider-empty" | ||
}, noResultsLabel); | ||
} | ||
if (virtualList) { | ||
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, | ||
f: f | ||
}), | ||
rowHeight: 31 | ||
}))); | ||
} | ||
const [isShiftKeyPressed, setIsShiftKeyPressed] = useState(false); | ||
// This records whether the user is holding the SHIFT key this very moment. | ||
// Typically, this is implemented using `onClick((e) => e.shiftKey)` - | ||
// however we can't use that, because for accessibility reasons | ||
// we're using html tags that don't support `e.shiftKey` property (see #3768). | ||
useEffect(() => { | ||
const handleKeyUp = e => { | ||
if (e.key === 'Shift') setIsShiftKeyPressed(false); | ||
}; | ||
const handleKeyDown = e => { | ||
if (e.key === 'Shift') setIsShiftKeyPressed(true); | ||
}; | ||
document.addEventListener('keyup', handleKeyUp); | ||
document.addEventListener('keydown', handleKeyDown); | ||
return () => { | ||
document.removeEventListener('keyup', handleKeyUp); | ||
document.removeEventListener('keydown', handleKeyDown); | ||
}; | ||
}, []); | ||
if (isLoading) { | ||
return h("div", { | ||
className: "uppy-Provider-loading" | ||
}, h("span", null, i18n('loading'))); | ||
} | ||
if (displayedPartialTree.length === 0) { | ||
return h("div", { | ||
className: "uppy-Provider-empty" | ||
}, noResultsLabel); | ||
} | ||
const renderItem = item => h(Item, { | ||
viewType: viewType, | ||
toggleCheckbox: event => { | ||
var _document$getSelectio; | ||
event.stopPropagation(); | ||
event.preventDefault(); | ||
// Prevent shift-clicking from highlighting file names | ||
// (https://stackoverflow.com/a/1527797/3192470) | ||
(_document$getSelectio = document.getSelection()) == null || _document$getSelectio.removeAllRanges(); | ||
toggleCheckbox(item, isShiftKeyPressed); | ||
}, | ||
showTitles: showTitles, | ||
i18n: i18n, | ||
openFolder: openFolder, | ||
file: item | ||
}); | ||
if (virtualList) { | ||
return h("div", { | ||
className: "uppy-ProviderBrowser-body" | ||
}, h("ul", { | ||
className: "uppy-ProviderBrowser-list", | ||
onScroll: handleScroll, | ||
role: "listbox" | ||
// making <ul> not focusable for firefox | ||
, | ||
tabIndex: -1 | ||
}, 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, | ||
f: f | ||
})))); | ||
})(), selected > 0 && h(FooterActions, { | ||
selected: selected, | ||
done: done, | ||
cancel: cancel, | ||
i18n: i18n | ||
})); | ||
className: "uppy-ProviderBrowser-list" | ||
}, h(VirtualList, { | ||
data: displayedPartialTree, | ||
renderRow: renderItem, | ||
rowHeight: 31 | ||
}))); | ||
} | ||
return h("div", { | ||
className: "uppy-ProviderBrowser-body" | ||
}, h("ul", { | ||
className: "uppy-ProviderBrowser-list", | ||
onScroll: handleScroll, | ||
role: "listbox" | ||
// making <ul> not focusable for firefox | ||
, | ||
tabIndex: -1 | ||
}, displayedPartialTree.map(renderItem))); | ||
} | ||
export default Browser; |
import { h } from 'preact'; | ||
import type { I18n } from '@uppy/utils/lib/Translator'; | ||
export default function FooterActions({ cancel, done, i18n, selected, }: { | ||
cancel: () => void; | ||
done: () => void; | ||
import type { Meta, Body } from '@uppy/utils/lib/UppyFile'; | ||
import type { PartialTree } from '@uppy/core/lib/Uppy'; | ||
import ProviderView from './ProviderView/ProviderView.tsx'; | ||
export default function FooterActions<M extends Meta, B extends Body>({ cancelSelection, donePicking, i18n, partialTree, validateAggregateRestrictions, }: { | ||
cancelSelection: ProviderView<M, B>['cancelSelection']; | ||
donePicking: ProviderView<M, B>['donePicking']; | ||
i18n: I18n; | ||
selected: number; | ||
}): h.JSX.Element; | ||
partialTree: PartialTree; | ||
validateAggregateRestrictions: ProviderView<M, B>['validateAggregateRestrictions']; | ||
}): h.JSX.Element | null; | ||
//# sourceMappingURL=FooterActions.d.ts.map |
import { h } from 'preact'; | ||
import classNames from 'classnames'; | ||
import { useMemo } from 'preact/hooks'; | ||
import getNumberOfSelectedFiles from "./utils/PartialTreeUtils/getNumberOfSelectedFiles.js"; | ||
export default function FooterActions(_ref) { | ||
let { | ||
cancel, | ||
done, | ||
cancelSelection, | ||
donePicking, | ||
i18n, | ||
selected | ||
partialTree, | ||
validateAggregateRestrictions | ||
} = _ref; | ||
const aggregateRestrictionError = useMemo(() => { | ||
return validateAggregateRestrictions(partialTree); | ||
}, [partialTree, validateAggregateRestrictions]); | ||
const nOfSelectedFiles = useMemo(() => { | ||
return getNumberOfSelectedFiles(partialTree); | ||
}, [partialTree]); | ||
if (nOfSelectedFiles === 0) { | ||
return null; | ||
} | ||
return h("div", { | ||
className: "uppy-ProviderBrowser-footer" | ||
}, h("div", { | ||
className: "uppy-ProviderBrowser-footer-buttons" | ||
}, h("button", { | ||
className: "uppy-u-reset uppy-c-btn uppy-c-btn-primary", | ||
onClick: done, | ||
className: classNames('uppy-u-reset uppy-c-btn uppy-c-btn-primary', { | ||
'uppy-c-btn--disabled': aggregateRestrictionError | ||
}), | ||
disabled: !!aggregateRestrictionError, | ||
onClick: donePicking, | ||
type: "button" | ||
}, i18n('selectX', { | ||
smart_count: selected | ||
smart_count: nOfSelectedFiles | ||
})), h("button", { | ||
className: "uppy-u-reset uppy-c-btn uppy-c-btn-link", | ||
onClick: cancel, | ||
onClick: cancelSelection, | ||
type: "button" | ||
}, i18n('cancel'))); | ||
}, i18n('cancel'))), aggregateRestrictionError && h("div", { | ||
className: "uppy-ProviderBrowser-footer-error" | ||
}, aggregateRestrictionError)); | ||
} |
@@ -1,19 +0,15 @@ | ||
import { h, type ComponentChildren } from 'preact'; | ||
import type { RestrictionError } from '@uppy/core/lib/Restricter'; | ||
import type { Body, Meta } from '@uppy/utils/lib/UppyFile'; | ||
type GridItemProps<M extends Meta, B extends Body> = { | ||
import { h } from 'preact'; | ||
import type { PartialTreeFile, PartialTreeFolderNode } from '@uppy/core/lib/Uppy'; | ||
type GridItemProps = { | ||
file: PartialTreeFile | PartialTreeFolderNode; | ||
toggleCheckbox: (event: Event) => void; | ||
className: string; | ||
isDisabled: boolean; | ||
restrictionError?: RestrictionError<M, B> | null; | ||
isChecked: boolean; | ||
title?: string; | ||
itemIconEl: any; | ||
showTitles?: boolean; | ||
toggleCheckbox: (event: Event) => void; | ||
recordShiftKeyPress: (event: KeyboardEvent) => void; | ||
id: string; | ||
children?: ComponentChildren; | ||
restrictionError: string | null; | ||
showTitles: boolean; | ||
children?: h.JSX.Element | null; | ||
i18n: any; | ||
}; | ||
declare function GridItem<M extends Meta, B extends Body>(props: GridItemProps<M, B>): h.JSX.Element; | ||
declare function GridItem({ file, toggleCheckbox, className, isDisabled, restrictionError, showTitles, children, i18n, }: GridItemProps): h.JSX.Element; | ||
export default GridItem; | ||
//# sourceMappingURL=GridItem.d.ts.map |
@@ -1,43 +0,35 @@ | ||
/* eslint-disable react/require-default-props */ | ||
import { h } from 'preact'; | ||
import classNames from 'classnames'; | ||
function GridItem(props) { | ||
const { | ||
import ItemIcon from "./ItemIcon.js"; | ||
function GridItem(_ref) { | ||
var _file$data$name, _file$data$name2; | ||
let { | ||
file, | ||
toggleCheckbox, | ||
className, | ||
isDisabled, | ||
restrictionError, | ||
isChecked, | ||
title, | ||
itemIconEl, | ||
showTitles, | ||
toggleCheckbox, | ||
recordShiftKeyPress, | ||
id, | ||
children | ||
} = props; | ||
const checkBoxClassName = classNames('uppy-u-reset', 'uppy-ProviderBrowserItem-checkbox', 'uppy-ProviderBrowserItem-checkbox--grid', { | ||
'uppy-ProviderBrowserItem-checkbox--is-checked': isChecked | ||
}); | ||
children = null, | ||
i18n | ||
} = _ref; | ||
return h("li", { | ||
className: className, | ||
title: isDisabled ? restrictionError == null ? void 0 : restrictionError.message : undefined | ||
title: isDisabled && restrictionError ? restrictionError : undefined | ||
}, h("input", { | ||
type: "checkbox", | ||
className: checkBoxClassName, | ||
className: "uppy-u-reset uppy-ProviderBrowserItem-checkbox uppy-ProviderBrowserItem-checkbox--grid", | ||
onChange: toggleCheckbox, | ||
onKeyDown: recordShiftKeyPress | ||
// @ts-expect-error this is fine onMouseDown too | ||
, | ||
onMouseDown: recordShiftKeyPress, | ||
name: "listitem", | ||
id: id, | ||
checked: isChecked, | ||
id: file.id, | ||
checked: file.status === 'checked', | ||
disabled: isDisabled, | ||
"data-uppy-super-focusable": true | ||
}), h("label", { | ||
htmlFor: id, | ||
"aria-label": title, | ||
htmlFor: file.id, | ||
"aria-label": (_file$data$name = file.data.name) != null ? _file$data$name : i18n('unnamed'), | ||
className: "uppy-u-reset uppy-ProviderBrowserItem-inner" | ||
}, itemIconEl, showTitles && title, children)); | ||
}, h(ItemIcon, { | ||
itemIconString: file.data.thumbnail || file.data.icon | ||
}), showTitles && ((_file$data$name2 = file.data.name) != null ? _file$data$name2 : i18n('unnamed')), children)); | ||
} | ||
export default GridItem; |
@@ -1,22 +0,15 @@ | ||
import type { RestrictionError } from '@uppy/core/lib/Restricter'; | ||
import type { Body, Meta } from '@uppy/utils/lib/UppyFile'; | ||
import type { PartialTreeFile, PartialTreeFolderNode, PartialTreeId } from '@uppy/core/lib/Uppy'; | ||
import { h } from 'preact'; | ||
type ListItemProps<M extends Meta, B extends Body> = { | ||
type ListItemProps = { | ||
file: PartialTreeFile | PartialTreeFolderNode; | ||
openFolder: (folderId: PartialTreeId) => void; | ||
toggleCheckbox: (event: Event) => void; | ||
className: string; | ||
isDisabled: boolean; | ||
restrictionError?: RestrictionError<M, B> | null; | ||
isCheckboxDisabled: boolean; | ||
isChecked: boolean; | ||
toggleCheckbox: (event: Event) => void; | ||
recordShiftKeyPress: (event: KeyboardEvent | MouseEvent) => void; | ||
type: string; | ||
id: string; | ||
itemIconEl: any; | ||
title: string; | ||
handleFolderClick?: () => void; | ||
restrictionError: string | null; | ||
showTitles: boolean; | ||
i18n: any; | ||
}; | ||
export default function ListItem<M extends Meta, B extends Body>(props: ListItemProps<M, B>): h.JSX.Element; | ||
export default function ListItem({ file, openFolder, className, isDisabled, restrictionError, toggleCheckbox, showTitles, i18n, }: ListItemProps): h.JSX.Element; | ||
export {}; | ||
//# sourceMappingURL=ListItem.d.ts.map |
@@ -1,4 +0,3 @@ | ||
/* eslint-disable react/require-default-props */ | ||
import { h } from 'preact'; | ||
import ItemIcon from "./ItemIcon.js"; | ||
@@ -12,57 +11,54 @@ // if folder: | ||
export default function ListItem(props) { | ||
const { | ||
export default function ListItem(_ref) { | ||
var _file$data$name, _file$data$name2, _file$data$name3; | ||
let { | ||
file, | ||
openFolder, | ||
className, | ||
isDisabled, | ||
restrictionError, | ||
isCheckboxDisabled, | ||
isChecked, | ||
toggleCheckbox, | ||
recordShiftKeyPress, | ||
type, | ||
id, | ||
itemIconEl, | ||
title, | ||
handleFolderClick, | ||
showTitles, | ||
i18n | ||
} = props; | ||
} = _ref; | ||
return h("li", { | ||
className: className, | ||
title: isDisabled ? restrictionError == null ? void 0 : restrictionError.message : undefined | ||
}, !isCheckboxDisabled ? h("input", { | ||
title: file.status !== 'checked' && restrictionError ? restrictionError : undefined | ||
}, h("input", { | ||
type: "checkbox", | ||
className: `uppy-u-reset uppy-ProviderBrowserItem-checkbox ${isChecked ? 'uppy-ProviderBrowserItem-checkbox--is-checked' : ''}`, | ||
onChange: toggleCheckbox, | ||
onKeyDown: recordShiftKeyPress, | ||
onMouseDown: recordShiftKeyPress | ||
className: "uppy-u-reset uppy-ProviderBrowserItem-checkbox", | ||
onChange: toggleCheckbox | ||
// for the <label/> | ||
, | ||
name: "listitem", | ||
id: id, | ||
checked: isChecked, | ||
"aria-label": type === 'file' ? null : i18n('allFilesFromFolderNamed', { | ||
name: title | ||
}), | ||
id: file.id, | ||
checked: file.status === 'checked', | ||
"aria-label": file.data.isFolder ? i18n('allFilesFromFolderNamed', { | ||
name: (_file$data$name = file.data.name) != null ? _file$data$name : i18n('unnamed') | ||
}) : null, | ||
disabled: isDisabled, | ||
"data-uppy-super-focusable": true | ||
}) : null, type === 'file' ? | ||
// label for a checkbox | ||
h("label", { | ||
htmlFor: id, | ||
className: "uppy-u-reset uppy-ProviderBrowserItem-inner" | ||
}, h("div", { | ||
className: "uppy-ProviderBrowserItem-iconWrap" | ||
}, itemIconEl), showTitles && title) | ||
}), file.data.isFolder ? | ||
// button to open a folder | ||
: h("button", { | ||
h("button", { | ||
type: "button", | ||
className: "uppy-u-reset uppy-c-btn uppy-ProviderBrowserItem-inner", | ||
onClick: handleFolderClick, | ||
onClick: () => openFolder(file.id), | ||
"aria-label": i18n('openFolderNamed', { | ||
name: title | ||
name: (_file$data$name2 = file.data.name) != null ? _file$data$name2 : i18n('unnamed') | ||
}) | ||
}, h("div", { | ||
className: "uppy-ProviderBrowserItem-iconWrap" | ||
}, itemIconEl), showTitles && title ? h("span", null, title) : i18n('unnamed'))); | ||
}, h(ItemIcon, { | ||
itemIconString: file.data.icon | ||
})), showTitles && file.data.name ? h("span", null, file.data.name) : i18n('unnamed')) | ||
// label for a checkbox | ||
: h("label", { | ||
htmlFor: file.id, | ||
className: "uppy-u-reset uppy-ProviderBrowserItem-inner" | ||
}, h("div", { | ||
className: "uppy-ProviderBrowserItem-iconWrap" | ||
}, h(ItemIcon, { | ||
itemIconString: file.data.icon | ||
})), showTitles && ((_file$data$name3 = file.data.name) != null ? _file$data$name3 : i18n('unnamed')))); | ||
} |
import { h } from 'preact'; | ||
import type { I18n } from '@uppy/utils/lib/Translator'; | ||
import type { CompanionFile } from '@uppy/utils/lib/CompanionFile'; | ||
import type { RestrictionError } from '@uppy/core/lib/Restricter'; | ||
import type { Meta, Body } from '@uppy/utils/lib/UppyFile'; | ||
type ItemProps<M extends Meta, B extends Body> = { | ||
import type { PartialTreeFile, PartialTreeFolderNode, PartialTreeId } from '@uppy/core/lib/Uppy.ts'; | ||
type ItemProps = { | ||
file: PartialTreeFile | PartialTreeFolderNode; | ||
openFolder: (folderId: PartialTreeId) => void; | ||
toggleCheckbox: (event: Event) => void; | ||
viewType: string; | ||
showTitles: boolean; | ||
i18n: I18n; | ||
id: string; | ||
title: string; | ||
toggleCheckbox: (event: Event) => void; | ||
recordShiftKeyPress: (event: KeyboardEvent | MouseEvent) => void; | ||
handleFolderClick?: () => void; | ||
restrictionError?: RestrictionError<M, B> | null; | ||
isCheckboxDisabled: boolean; | ||
type: 'folder' | 'file'; | ||
author?: CompanionFile['author']; | ||
getItemIcon: () => string; | ||
isChecked: boolean; | ||
isDisabled: boolean; | ||
viewType: string; | ||
}; | ||
export default function Item<M extends Meta, B extends Body>(props: ItemProps<M, B>): h.JSX.Element; | ||
export default function Item(props: ItemProps): h.JSX.Element; | ||
export {}; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -1,6 +0,4 @@ | ||
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } | ||
/* eslint-disable react/require-default-props */ | ||
/* eslint-disable react/jsx-props-no-spreading */ | ||
import { h } from 'preact'; | ||
import classNames from 'classnames'; | ||
import ItemIcon from "./components/ItemIcon.js"; | ||
import GridItem from "./components/GridItem.js"; | ||
@@ -10,36 +8,38 @@ import ListItem from "./components/ListItem.js"; | ||
const { | ||
author, | ||
getItemIcon, | ||
isChecked, | ||
viewType, | ||
toggleCheckbox, | ||
showTitles, | ||
i18n, | ||
openFolder, | ||
file | ||
} = props; | ||
const restrictionError = file.type === 'folder' ? null : file.restrictionError; | ||
const isDisabled = !!restrictionError && file.status !== 'checked'; | ||
const ourProps = { | ||
file, | ||
openFolder, | ||
toggleCheckbox, | ||
i18n, | ||
viewType, | ||
showTitles, | ||
className: classNames('uppy-ProviderBrowserItem', { | ||
'uppy-ProviderBrowserItem--disabled': isDisabled | ||
}, { | ||
'uppy-ProviderBrowserItem--noPreview': file.data.icon === 'video' | ||
}, { | ||
'uppy-ProviderBrowserItem--is-checked': file.status === 'checked' | ||
}, { | ||
'uppy-ProviderBrowserItem--is-partial': file.status === 'partial' | ||
}), | ||
isDisabled, | ||
viewType | ||
} = props; | ||
const itemIconString = getItemIcon(); | ||
const className = classNames('uppy-ProviderBrowserItem', { | ||
'uppy-ProviderBrowserItem--selected': isChecked | ||
}, { | ||
'uppy-ProviderBrowserItem--disabled': isDisabled | ||
}, { | ||
'uppy-ProviderBrowserItem--noPreview': itemIconString === 'video' | ||
}); | ||
const itemIconEl = h(ItemIcon, { | ||
itemIconString: itemIconString | ||
}); | ||
restrictionError | ||
}; | ||
switch (viewType) { | ||
case 'grid': | ||
return h(GridItem, _extends({}, props, { | ||
className: className, | ||
itemIconEl: itemIconEl | ||
})); | ||
return h(GridItem, ourProps); | ||
case 'list': | ||
return h(ListItem, _extends({}, props, { | ||
className: className, | ||
itemIconEl: itemIconEl | ||
})); | ||
return h(ListItem, ourProps); | ||
case 'unsplash': | ||
return h(GridItem, _extends({}, props, { | ||
className: className, | ||
itemIconEl: itemIconEl | ||
}), h("a", { | ||
href: `${author.url}?utm_source=Companion&utm_medium=referral`, | ||
return h(GridItem, ourProps, h("a", { | ||
href: `${file.data.author.url}?utm_source=Companion&utm_medium=referral`, | ||
target: "_blank", | ||
@@ -49,3 +49,3 @@ rel: "noopener noreferrer", | ||
tabIndex: -1 | ||
}, author.name)); | ||
}, file.data.author.name)); | ||
default: | ||
@@ -52,0 +52,0 @@ throw new Error(`There is no such type ${viewType}`); |
import { h } from 'preact'; | ||
import type { Body, Meta } from '@uppy/utils/lib/UppyFile'; | ||
import type Translator from '@uppy/utils/lib/Translator'; | ||
import type { ProviderViewOptions } from './ProviderView.js'; | ||
import type { Opts } from './ProviderView.js'; | ||
import type ProviderViews from './ProviderView.js'; | ||
@@ -12,3 +12,3 @@ type AuthViewProps<M extends Meta, B extends Body> = { | ||
handleAuth: ProviderViews<M, B>['handleAuth']; | ||
renderForm?: ProviderViewOptions<M, B>['renderAuthForm']; | ||
renderForm?: Opts<M, B>['renderAuthForm']; | ||
}; | ||
@@ -15,0 +15,0 @@ export default function AuthView<M extends Meta, B extends Body>({ loading, pluginName, pluginIcon, i18n, handleAuth, renderForm, }: AuthViewProps<M, B>): h.JSX.Element; |
import { h } from 'preact'; | ||
import type { I18n } from '@uppy/utils/lib/Translator'; | ||
import type { Body, Meta } from '@uppy/utils/lib/UppyFile'; | ||
import type { UnknownProviderPluginState } from '@uppy/core/lib/Uppy'; | ||
import type { PartialTreeFolder } from '@uppy/core/lib/Uppy.ts'; | ||
import type ProviderView from './ProviderView.js'; | ||
type HeaderProps<M extends Meta, B extends Body> = { | ||
showBreadcrumbs: boolean; | ||
getFolder: ProviderView<M, B>['getFolder']; | ||
breadcrumbs: UnknownProviderPluginState['breadcrumbs']; | ||
openFolder: ProviderView<M, B>['openFolder']; | ||
breadcrumbs: PartialTreeFolder[]; | ||
pluginIcon: () => h.JSX.Element; | ||
title: string; | ||
logout: () => void; | ||
username: string | undefined; | ||
username: string | null; | ||
i18n: I18n; | ||
@@ -15,0 +15,0 @@ }; |
/* eslint-disable react/destructuring-assignment */ | ||
import { h, Fragment } from 'preact'; | ||
import { h } from 'preact'; | ||
import classNames from 'classnames'; | ||
import User from "./User.js"; | ||
import Breadcrumbs from "../Breadcrumbs.js"; | ||
export default function Header(props) { | ||
return h(Fragment, null, props.showBreadcrumbs && h(Breadcrumbs, { | ||
getFolder: props.getFolder, | ||
return h("div", { | ||
className: "uppy-ProviderBrowser-header" | ||
}, h("div", { | ||
className: classNames('uppy-ProviderBrowser-headerBar', !props.showBreadcrumbs && 'uppy-ProviderBrowser-headerBar--simple') | ||
}, props.showBreadcrumbs && h(Breadcrumbs, { | ||
openFolder: props.openFolder, | ||
breadcrumbs: props.breadcrumbs, | ||
breadcrumbsIcon: props.pluginIcon && props.pluginIcon(), | ||
title: props.title | ||
title: props.title, | ||
i18n: props.i18n | ||
}), h(User, { | ||
@@ -15,3 +21,3 @@ logout: props.logout, | ||
i18n: props.i18n | ||
})); | ||
}))); | ||
} |
import { h } from 'preact'; | ||
import type { UnknownProviderPlugin } from '@uppy/core/lib/Uppy.ts'; | ||
import type { UnknownProviderPlugin, PartialTreeFolderNode, PartialTreeFile, PartialTree } from '@uppy/core/lib/Uppy.ts'; | ||
import type { Body, Meta } from '@uppy/utils/lib/UppyFile'; | ||
import type { CompanionFile } from '@uppy/utils/lib/CompanionFile'; | ||
import type { CompanionFile } from '@uppy/utils/lib/CompanionFile.ts'; | ||
import type Translator from '@uppy/utils/lib/Translator'; | ||
import type { DefinePluginOpts } from '@uppy/core/lib/BasePlugin'; | ||
import View, { type ViewOptions } from '../View.ts'; | ||
export declare function defaultPickerIcon(): h.JSX.Element; | ||
type PluginType = 'Provider'; | ||
declare const defaultOptions: { | ||
viewType: string; | ||
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>; | ||
export interface Opts<M extends Meta, B extends Body> { | ||
provider: UnknownProviderPlugin<M, B>['provider']; | ||
viewType: 'list' | 'grid'; | ||
showTitles: boolean; | ||
@@ -16,5 +15,2 @@ showFilter: boolean; | ||
loadAllFiles: boolean; | ||
virtualList: boolean; | ||
}; | ||
export interface ProviderViewOptions<M extends Meta, B extends Body> extends ViewOptions<M, B, PluginType> { | ||
renderAuthForm?: (args: { | ||
@@ -26,37 +22,37 @@ pluginName: string; | ||
}) => h.JSX.Element; | ||
virtualList?: boolean; | ||
virtualList: boolean; | ||
} | ||
type Opts<M extends Meta, B extends Body> = DefinePluginOpts<ProviderViewOptions<M, B>, keyof typeof defaultOptions>; | ||
type PassedOpts<M extends Meta, B extends Body> = Optional<Opts<M, B>, 'viewType' | 'showTitles' | 'showFilter' | 'showBreadcrumbs' | 'loadAllFiles' | 'virtualList'>; | ||
type RenderOpts<M extends Meta, B extends Body> = Omit<PassedOpts<M, B>, 'provider'>; | ||
/** | ||
* Class to easily generate generic views for Provider plugins | ||
*/ | ||
export default class ProviderView<M extends Meta, B extends Body> extends View<M, B, PluginType, Opts<M, B>> { | ||
export default class ProviderView<M extends Meta, B extends Body> { | ||
#private; | ||
static VERSION: any; | ||
username: string | undefined; | ||
nextPagePath: string | undefined; | ||
constructor(plugin: UnknownProviderPlugin<M, B>, opts: ProviderViewOptions<M, B>); | ||
plugin: UnknownProviderPlugin<M, B>; | ||
provider: UnknownProviderPlugin<M, B>['provider']; | ||
opts: Opts<M, B>; | ||
isHandlingScroll: boolean; | ||
lastCheckbox: string | null; | ||
constructor(plugin: UnknownProviderPlugin<M, B>, opts: PassedOpts<M, B>); | ||
resetPluginState(): void; | ||
tearDown(): void; | ||
setLoading(loading: boolean | string): void; | ||
cancelSelection(): void; | ||
openFolder(folderId: string | null): Promise<void>; | ||
/** | ||
* Select a folder based on its id: fetches the folder and then updates state with its contents | ||
* TODO rename to something better like selectFolder or navigateToFolder (breaking change?) | ||
* | ||
*/ | ||
getFolder(requestPath?: string, name?: string): Promise<void>; | ||
/** | ||
* Fetches new folder | ||
*/ | ||
getNextFolder(folder: CompanionFile): void; | ||
/** | ||
* Removes session token on client side. | ||
*/ | ||
logout(): Promise<void>; | ||
filterQuery(input: string): void; | ||
clearFilter(): void; | ||
handleAuth(authFormData?: unknown): Promise<void>; | ||
handleScroll(event: Event): Promise<void>; | ||
validateSingleFile: (file: CompanionFile) => string | null; | ||
donePicking(): Promise<void>; | ||
render(state: unknown, viewOptions?: Omit<ViewOptions<M, B, PluginType>, 'provider'>): h.JSX.Element; | ||
toggleCheckbox(ourItem: PartialTreeFolderNode | PartialTreeFile, isShiftKeyPressed: boolean): void; | ||
getDisplayedPartialTree: () => (PartialTreeFile | PartialTreeFolderNode)[]; | ||
validateAggregateRestrictions: (partialTree: PartialTree) => string | null; | ||
render(state: unknown, viewOptions?: RenderOpts<M, B>): h.JSX.Element; | ||
} | ||
export {}; | ||
//# sourceMappingURL=ProviderView.d.ts.map |
@@ -5,9 +5,7 @@ function _classPrivateFieldLooseBase(e, t) { if (!{}.hasOwnProperty.call(e, t)) throw new TypeError("attempted to use private field on non-instance"); return e; } | ||
import { h } from 'preact'; | ||
import PQueue from 'p-queue'; | ||
import { getSafeFileId } from '@uppy/utils/lib/generateFileID'; | ||
import classNames from 'classnames'; | ||
import remoteFileObjToLocal from '@uppy/utils/lib/remoteFileObjToLocal'; | ||
import AuthView from "./AuthView.js"; | ||
import Header from "./Header.js"; | ||
import Browser from "../Browser.js"; | ||
import CloseWrapper from "../CloseWrapper.js"; | ||
import View from "../View.js"; | ||
@@ -17,11 +15,13 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
const packageJson = { | ||
"version": "4.0.0-beta.9" | ||
"version": "4.0.0-beta.10" | ||
}; | ||
function formatBreadcrumbs(breadcrumbs) { | ||
return breadcrumbs.slice(1).map(directory => directory.name).join('/'); | ||
} | ||
function prependPath(path, component) { | ||
if (!path) return component; | ||
return `${path}/${component}`; | ||
} | ||
import PartialTreeUtils from "../utils/PartialTreeUtils/index.js"; | ||
import shouldHandleScroll from "../utils/shouldHandleScroll.js"; | ||
import handleError from "../utils/handleError.js"; | ||
import getClickedRange from "../utils/getClickedRange.js"; | ||
import SearchInput from "../SearchInput.js"; | ||
import FooterActions from "../FooterActions.js"; | ||
import addFiles from "../utils/addFiles.js"; | ||
import getCheckedFilesWithPaths from "../utils/PartialTreeUtils/getCheckedFilesWithPaths.js"; | ||
import getBreadcrumbs from "../utils/PartialTreeUtils/getBreadcrumbs.js"; | ||
export function defaultPickerIcon() { | ||
@@ -38,38 +38,29 @@ return h("svg", { | ||
} | ||
const defaultOptions = { | ||
viewType: 'list', | ||
showTitles: true, | ||
showFilter: true, | ||
showBreadcrumbs: true, | ||
loadAllFiles: false, | ||
virtualList: false | ||
}; | ||
const getDefaultState = rootFolderId => ({ | ||
authenticated: undefined, | ||
// we don't know yet | ||
partialTree: [{ | ||
type: 'root', | ||
id: rootFolderId, | ||
cached: false, | ||
nextPagePath: null | ||
}], | ||
currentFolderId: rootFolderId, | ||
searchString: '', | ||
didFirstRender: false, | ||
username: null, | ||
loading: false | ||
}); | ||
var _abortController = /*#__PURE__*/_classPrivateFieldLooseKey("abortController"); | ||
var _withAbort = /*#__PURE__*/_classPrivateFieldLooseKey("withAbort"); | ||
var _list = /*#__PURE__*/_classPrivateFieldLooseKey("list"); | ||
var _listFilesAndFolders = /*#__PURE__*/_classPrivateFieldLooseKey("listFilesAndFolders"); | ||
var _recursivelyListAllFiles = /*#__PURE__*/_classPrivateFieldLooseKey("recursivelyListAllFiles"); | ||
/** | ||
* Class to easily generate generic views for Provider plugins | ||
*/ | ||
export default class ProviderView extends View { | ||
export default class ProviderView { | ||
constructor(plugin, opts) { | ||
super(plugin, { | ||
...defaultOptions, | ||
...opts | ||
}); | ||
// Logic | ||
Object.defineProperty(this, _recursivelyListAllFiles, { | ||
value: _recursivelyListAllFiles2 | ||
}); | ||
Object.defineProperty(this, _listFilesAndFolders, { | ||
value: _listFilesAndFolders2 | ||
}); | ||
Object.defineProperty(this, _list, { | ||
value: _list2 | ||
}); | ||
Object.defineProperty(this, _withAbort, { | ||
value: _withAbort2 | ||
}); | ||
this.isHandlingScroll = false; | ||
this.lastCheckbox = null; | ||
Object.defineProperty(this, _abortController, { | ||
@@ -79,27 +70,60 @@ writable: true, | ||
}); | ||
this.filterQuery = this.filterQuery.bind(this); | ||
this.clearFilter = this.clearFilter.bind(this); | ||
this.getFolder = this.getFolder.bind(this); | ||
this.getNextFolder = this.getNextFolder.bind(this); | ||
this.validateSingleFile = file => { | ||
const companionFile = remoteFileObjToLocal(file); | ||
const result = this.plugin.uppy.validateSingleFile(companionFile); | ||
return result; | ||
}; | ||
this.getDisplayedPartialTree = () => { | ||
const { | ||
partialTree, | ||
currentFolderId, | ||
searchString | ||
} = this.plugin.getPluginState(); | ||
const inThisFolder = partialTree.filter(item => item.type !== 'root' && item.parentId === currentFolderId); | ||
const filtered = searchString === '' ? inThisFolder : inThisFolder.filter(item => { | ||
var _item$data$name; | ||
return ((_item$data$name = item.data.name) != null ? _item$data$name : this.plugin.uppy.i18n('unnamed')).toLowerCase().indexOf(searchString.toLowerCase()) !== -1; | ||
}); | ||
return filtered; | ||
}; | ||
this.validateAggregateRestrictions = partialTree => { | ||
const checkedFiles = partialTree.filter(item => item.type === 'file' && item.status === 'checked'); | ||
const uppyFiles = checkedFiles.map(file => file.data); | ||
return this.plugin.uppy.validateAggregateRestrictions(uppyFiles); | ||
}; | ||
this.plugin = plugin; | ||
this.provider = opts.provider; | ||
const defaultOptions = { | ||
viewType: 'list', | ||
showTitles: true, | ||
showFilter: true, | ||
showBreadcrumbs: true, | ||
loadAllFiles: false, | ||
virtualList: false | ||
}; | ||
this.opts = { | ||
...defaultOptions, | ||
...opts | ||
}; | ||
this.openFolder = this.openFolder.bind(this); | ||
this.logout = this.logout.bind(this); | ||
this.handleAuth = this.handleAuth.bind(this); | ||
this.handleScroll = this.handleScroll.bind(this); | ||
this.resetPluginState = this.resetPluginState.bind(this); | ||
this.donePicking = this.donePicking.bind(this); | ||
// Visual | ||
this.render = this.render.bind(this); | ||
this.cancelSelection = this.cancelSelection.bind(this); | ||
this.toggleCheckbox = this.toggleCheckbox.bind(this); | ||
// Set default state for the plugin | ||
this.plugin.setPluginState({ | ||
authenticated: undefined, | ||
// we don't know yet | ||
files: [], | ||
folders: [], | ||
breadcrumbs: [], | ||
filterInput: '', | ||
isSearchVisible: false, | ||
currentSelection: [] | ||
}); | ||
this.registerRequestClient(); | ||
this.resetPluginState(); | ||
// todo | ||
// @ts-expect-error this should be typed in @uppy/dashboard. | ||
this.plugin.uppy.on('dashboard:close-panel', this.resetPluginState); | ||
this.plugin.uppy.registerRequestClient(this.provider.provider, this.provider); | ||
} | ||
resetPluginState() { | ||
this.plugin.setPluginState(getDefaultState(this.plugin.rootFolderId)); | ||
} | ||
@@ -110,270 +134,167 @@ // eslint-disable-next-line class-methods-use-this | ||
} | ||
/** | ||
* Select a folder based on its id: fetches the folder and then updates state with its contents | ||
* TODO rename to something better like selectFolder or navigateToFolder (breaking change?) | ||
* | ||
*/ | ||
async getFolder(requestPath, name) { | ||
setLoading(loading) { | ||
this.plugin.setPluginState({ | ||
loading | ||
}); | ||
} | ||
cancelSelection() { | ||
const { | ||
partialTree | ||
} = this.plugin.getPluginState(); | ||
const newPartialTree = partialTree.map(item => item.type === 'root' ? item : { | ||
...item, | ||
status: 'unchecked' | ||
}); | ||
this.plugin.setPluginState({ | ||
partialTree: newPartialTree | ||
}); | ||
} | ||
async openFolder(folderId) { | ||
this.lastCheckbox = null; | ||
// Returning cached folder | ||
const { | ||
partialTree | ||
} = this.plugin.getPluginState(); | ||
const clickedFolder = partialTree.find(folder => folder.id === folderId); | ||
if (clickedFolder.cached) { | ||
this.plugin.setPluginState({ | ||
currentFolderId: folderId, | ||
searchString: '' | ||
}); | ||
return; | ||
} | ||
this.setLoading(true); | ||
try { | ||
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); | ||
await _classPrivateFieldLooseBase(this, _withAbort)[_withAbort](async signal => { | ||
let currentPagePath = folderId; | ||
let currentItems = []; | ||
do { | ||
const { | ||
username, | ||
nextPagePath, | ||
items | ||
} = await this.provider.list(currentPagePath, { | ||
signal | ||
}); | ||
// It's important to set the username during one of our first fetches | ||
this.plugin.setPluginState({ | ||
folders, | ||
files, | ||
breadcrumbs, | ||
filterInput: '' | ||
username | ||
}); | ||
currentPagePath = nextPagePath; | ||
currentItems = currentItems.concat(items); | ||
this.setLoading(this.plugin.uppy.i18n('loadedXFiles', { | ||
numFiles: items.length | ||
})); | ||
} while (this.opts.loadAllFiles && currentPagePath); | ||
const newPartialTree = PartialTreeUtils.afterOpenFolder(partialTree, currentItems, clickedFolder, currentPagePath, this.validateSingleFile); | ||
this.plugin.setPluginState({ | ||
partialTree: newPartialTree, | ||
currentFolderId: folderId, | ||
searchString: '' | ||
}); | ||
} catch (err) { | ||
// This is the first call that happens when the provider view loads, after auth, so it's probably nice to show any | ||
// error occurring here to the user. | ||
if ((err == null ? void 0 : err.name) === 'UserFacingApiError') { | ||
this.plugin.uppy.info({ | ||
message: this.plugin.uppy.i18n(err.message) | ||
}, 'warning', 5000); | ||
return; | ||
} | ||
this.handleError(err); | ||
} finally { | ||
this.setLoading(false); | ||
} | ||
}).catch(handleError(this.plugin.uppy)); | ||
this.setLoading(false); | ||
} | ||
/** | ||
* Fetches new folder | ||
*/ | ||
getNextFolder(folder) { | ||
this.getFolder(folder.requestPath, folder.name); | ||
this.lastCheckbox = undefined; | ||
} | ||
/** | ||
* Removes session token on client side. | ||
*/ | ||
async logout() { | ||
try { | ||
await _classPrivateFieldLooseBase(this, _withAbort)[_withAbort](async signal => { | ||
const res = await this.provider.logout({ | ||
signal | ||
await _classPrivateFieldLooseBase(this, _withAbort)[_withAbort](async signal => { | ||
const res = await this.provider.logout({ | ||
signal | ||
}); | ||
// res.ok is from the JSON body, not to be confused with Response.ok | ||
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); | ||
} | ||
this.plugin.setPluginState({ | ||
...getDefaultState(this.plugin.rootFolderId), | ||
authenticated: false | ||
}); | ||
// res.ok is from the JSON body, not to be confused with Response.ok | ||
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); | ||
} | ||
} | ||
}).catch(handleError(this.plugin.uppy)); | ||
} | ||
async handleAuth(authFormData) { | ||
await _classPrivateFieldLooseBase(this, _withAbort)[_withAbort](async signal => { | ||
this.setLoading(true); | ||
await this.provider.login({ | ||
authFormData, | ||
signal | ||
}); | ||
} catch (err) { | ||
this.handleError(err); | ||
} | ||
this.plugin.setPluginState({ | ||
authenticated: true | ||
}); | ||
await Promise.all([this.provider.fetchPreAuthToken(), this.openFolder(this.plugin.rootFolderId)]); | ||
}).catch(handleError(this.plugin.uppy)); | ||
this.setLoading(false); | ||
} | ||
filterQuery(input) { | ||
this.plugin.setPluginState({ | ||
filterInput: input | ||
}); | ||
} | ||
clearFilter() { | ||
this.plugin.setPluginState({ | ||
filterInput: '' | ||
}); | ||
} | ||
async handleAuth(authFormData) { | ||
try { | ||
async handleScroll(event) { | ||
const { | ||
partialTree, | ||
currentFolderId | ||
} = this.plugin.getPluginState(); | ||
const currentFolder = partialTree.find(i => i.id === currentFolderId); | ||
if (shouldHandleScroll(event) && !this.isHandlingScroll && currentFolder.nextPagePath) { | ||
this.isHandlingScroll = true; | ||
await _classPrivateFieldLooseBase(this, _withAbort)[_withAbort](async signal => { | ||
this.setLoading(true); | ||
await this.provider.login({ | ||
authFormData, | ||
const { | ||
nextPagePath, | ||
items | ||
} = await this.provider.list(currentFolder.nextPagePath, { | ||
signal | ||
}); | ||
const newPartialTree = PartialTreeUtils.afterScrollFolder(partialTree, currentFolderId, items, nextPagePath, this.validateSingleFile); | ||
this.plugin.setPluginState({ | ||
authenticated: true | ||
partialTree: newPartialTree | ||
}); | ||
await this.getFolder(this.plugin.rootFolderId || undefined); | ||
}); | ||
} catch (err) { | ||
if (err.name === 'UserFacingApiError') { | ||
this.plugin.uppy.info({ | ||
message: this.plugin.uppy.i18n(err.message) | ||
}, 'warning', 5000); | ||
return; | ||
} | ||
this.plugin.uppy.log(`login failed: ${err.message}`); | ||
} finally { | ||
this.setLoading(false); | ||
}).catch(handleError(this.plugin.uppy)); | ||
this.isHandlingScroll = false; | ||
} | ||
} | ||
async handleScroll(event) { | ||
if (this.shouldHandleScroll(event) && this.nextPagePath) { | ||
this.isHandlingScroll = true; | ||
try { | ||
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 | ||
}); | ||
}); | ||
} catch (error) { | ||
this.handleError(error); | ||
} finally { | ||
this.isHandlingScroll = false; | ||
} | ||
} | ||
} | ||
async donePicking() { | ||
const { | ||
partialTree | ||
} = this.plugin.getPluginState(); | ||
this.setLoading(true); | ||
try { | ||
await _classPrivateFieldLooseBase(this, _withAbort)[_withAbort](async signal => { | ||
const { | ||
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(/^\//, '') | ||
}); | ||
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, this.plugin.uppy.getID()); | ||
// 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; | ||
} | ||
}; | ||
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 | ||
}); | ||
} | ||
messages.push(message); | ||
} else { | ||
newFiles.push(withRelDirPath(selectedItem)); | ||
} | ||
} | ||
await _classPrivateFieldLooseBase(this, _withAbort)[_withAbort](async signal => { | ||
// 1. Enrich our partialTree by fetching all 'checked' but not-yet-fetched folders | ||
const enrichedTree = await PartialTreeUtils.afterFill(partialTree, path => this.provider.list(path, { | ||
signal | ||
}), this.validateSingleFile); | ||
// 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 files from a remote provider'); | ||
this.plugin.uppy.addFiles( | ||
// @ts-expect-error `addFiles` expects `body` to be `File` or `Blob`, | ||
// but as the todo comment in `View.ts` indicates, we strangly pass `CompanionFile` as `body`. | ||
// For now it's better to ignore than to have a potential breaking change. | ||
newFiles.map(file => this.getTagFile(file, this.requestClientId))); | ||
// 2. Now that we know how many files there are - recheck aggregateRestrictions! | ||
const aggregateRestrictionError = this.validateAggregateRestrictions(enrichedTree); | ||
if (aggregateRestrictionError) { | ||
this.plugin.setPluginState({ | ||
filterInput: '' | ||
partialTree: enrichedTree | ||
}); | ||
messages.forEach(message => this.plugin.uppy.info(message)); | ||
this.clearSelection(); | ||
}); | ||
} catch (err) { | ||
this.handleError(err); | ||
} finally { | ||
this.setLoading(false); | ||
} | ||
return; | ||
} | ||
// 3. Add files | ||
const companionFiles = getCheckedFilesWithPaths(enrichedTree); | ||
addFiles(companionFiles, this.plugin, this.provider); | ||
// 4. Reset state | ||
this.resetPluginState(); | ||
}).catch(handleError(this.plugin.uppy)); | ||
this.setLoading(false); | ||
} | ||
toggleCheckbox(ourItem, isShiftKeyPressed) { | ||
const { | ||
partialTree | ||
} = this.plugin.getPluginState(); | ||
const clickedRange = getClickedRange(ourItem.id, this.getDisplayedPartialTree(), isShiftKeyPressed, this.lastCheckbox); | ||
const newPartialTree = PartialTreeUtils.afterToggleCheckbox(partialTree, clickedRange); | ||
this.plugin.setPluginState({ | ||
partialTree: newPartialTree | ||
}); | ||
this.lastCheckbox = ourItem.id; | ||
} | ||
render(state, viewOptions) { | ||
var _this = this; | ||
if (viewOptions === void 0) { | ||
@@ -383,3 +304,2 @@ viewOptions = {}; | ||
const { | ||
authenticated, | ||
didFirstRender | ||
@@ -395,5 +315,5 @@ } = this.plugin.getPluginState(); | ||
this.provider.fetchPreAuthToken(); | ||
this.getFolder(this.plugin.rootFolderId || undefined); | ||
this.openFolder(this.plugin.rootFolderId); | ||
} | ||
const targetViewOptions = { | ||
const opts = { | ||
...this.opts, | ||
@@ -403,78 +323,64 @@ ...viewOptions | ||
const { | ||
files, | ||
folders, | ||
filterInput, | ||
loading, | ||
currentSelection | ||
authenticated, | ||
loading | ||
} = this.plugin.getPluginState(); | ||
const pluginIcon = this.plugin.icon || defaultPickerIcon; | ||
if (authenticated === false) { | ||
return h(AuthView, { | ||
pluginName: this.plugin.title, | ||
pluginIcon: pluginIcon, | ||
handleAuth: this.handleAuth, | ||
i18n: this.plugin.uppy.i18nArray, | ||
renderForm: opts.renderAuthForm, | ||
loading: loading | ||
}); | ||
} | ||
const { | ||
isChecked, | ||
recordShiftKeyPress, | ||
filterItems | ||
} = this; | ||
const hasInput = filterInput !== ''; | ||
const pluginIcon = this.plugin.icon || defaultPickerIcon; | ||
const headerProps = { | ||
showBreadcrumbs: targetViewOptions.showBreadcrumbs, | ||
getFolder: this.getFolder, | ||
breadcrumbs: this.plugin.getPluginState().breadcrumbs, | ||
pluginIcon, | ||
partialTree, | ||
currentFolderId, | ||
username, | ||
searchString | ||
} = this.plugin.getPluginState(); | ||
const breadcrumbs = getBreadcrumbs(partialTree, currentFolderId); | ||
return h("div", { | ||
className: classNames('uppy-ProviderBrowser', `uppy-ProviderBrowser-viewType--${opts.viewType}`) | ||
}, h(Header, { | ||
showBreadcrumbs: opts.showBreadcrumbs, | ||
openFolder: this.openFolder, | ||
breadcrumbs: breadcrumbs, | ||
pluginIcon: pluginIcon, | ||
title: this.plugin.title, | ||
logout: this.logout, | ||
username: this.username, | ||
i18n | ||
}; | ||
const browserProps = { | ||
isChecked, | ||
toggleCheckbox: this.toggleCheckbox.bind(this), | ||
recordShiftKeyPress, | ||
currentSelection, | ||
files: hasInput ? filterItems(files) : files, | ||
folders: hasInput ? filterItems(folders) : folders, | ||
getNextFolder: this.getNextFolder, | ||
getFolder: this.getFolder, | ||
loadAllFiles: this.opts.loadAllFiles, | ||
virtualList: this.opts.virtualList, | ||
// For SearchFilterInput component | ||
showSearchFilter: targetViewOptions.showFilter, | ||
search: this.filterQuery, | ||
clearSearch: this.clearFilter, | ||
searchTerm: filterInput, | ||
searchOnInput: true, | ||
searchInputLabel: i18n('filter'), | ||
username: username, | ||
i18n: i18n | ||
}), opts.showFilter && h(SearchInput, { | ||
searchString: searchString, | ||
setSearchString: s => { | ||
this.plugin.setPluginState({ | ||
searchString: s | ||
}); | ||
}, | ||
submitSearchString: () => {}, | ||
inputLabel: i18n('filter'), | ||
clearSearchLabel: i18n('resetFilter'), | ||
wrapperClassName: "uppy-ProviderBrowser-searchFilter", | ||
inputClassName: "uppy-ProviderBrowser-searchFilterInput" | ||
}), h(Browser, { | ||
toggleCheckbox: this.toggleCheckbox, | ||
displayedPartialTree: this.getDisplayedPartialTree(), | ||
openFolder: this.openFolder, | ||
virtualList: opts.virtualList, | ||
noResultsLabel: i18n('noFilesFound'), | ||
logout: this.logout, | ||
handleScroll: this.handleScroll, | ||
done: this.donePicking, | ||
cancel: this.cancelPicking, | ||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
headerComponent: h(Header, headerProps), | ||
title: this.plugin.title, | ||
viewType: targetViewOptions.viewType, | ||
showTitles: targetViewOptions.showTitles, | ||
showBreadcrumbs: targetViewOptions.showBreadcrumbs, | ||
pluginIcon, | ||
viewType: opts.viewType, | ||
showTitles: opts.showTitles, | ||
i18n: this.plugin.uppy.i18n, | ||
uppyFiles: this.plugin.uppy.getFiles(), | ||
validateRestrictions: function () { | ||
return _this.plugin.uppy.validateRestrictions(...arguments); | ||
}, | ||
isLoading: loading | ||
}; | ||
if (authenticated === false) { | ||
return h(CloseWrapper, { | ||
onUnmount: this.clearSelection | ||
}, h(AuthView, { | ||
pluginName: this.plugin.title, | ||
pluginIcon: pluginIcon, | ||
handleAuth: this.handleAuth, | ||
i18n: this.plugin.uppy.i18nArray, | ||
renderForm: this.opts.renderAuthForm, | ||
loading: loading | ||
})); | ||
} | ||
return h(CloseWrapper, { | ||
onUnmount: this.clearSelection | ||
}, h(Browser, browserProps)); | ||
}), h(FooterActions, { | ||
partialTree: partialTree, | ||
donePicking: this.donePicking, | ||
cancelSelection: this.cancelSelection, | ||
i18n: i18n, | ||
validateAggregateRestrictions: this.validateAggregateRestrictions | ||
})); | ||
} | ||
@@ -490,3 +396,2 @@ } | ||
abortController.abort(); | ||
this.clearSelection(); | ||
}; | ||
@@ -509,86 +414,2 @@ try { | ||
} | ||
async function _list2(_ref) { | ||
let { | ||
requestPath, | ||
absDirPath, | ||
signal | ||
} = _ref; | ||
const { | ||
username, | ||
nextPagePath, | ||
items | ||
} = await this.provider.list(requestPath, { | ||
signal | ||
}); | ||
this.username = username || this.username; | ||
return { | ||
items: items.map(item => ({ | ||
...item, | ||
absDirPath | ||
})), | ||
nextPagePath | ||
}; | ||
} | ||
async function _listFilesAndFolders2(_ref2) { | ||
let { | ||
breadcrumbs, | ||
signal | ||
} = _ref2; | ||
const absDirPath = formatBreadcrumbs(breadcrumbs); | ||
const { | ||
items, | ||
nextPagePath | ||
} = await _classPrivateFieldLooseBase(this, _list)[_list]({ | ||
requestPath: this.nextPagePath, | ||
absDirPath, | ||
signal | ||
}); | ||
this.nextPagePath = nextPagePath; | ||
const files = []; | ||
const folders = []; | ||
items.forEach(item => { | ||
if (item.isFolder) { | ||
folders.push(item); | ||
} else { | ||
files.push(item); | ||
} | ||
}); | ||
return { | ||
files, | ||
folders | ||
}; | ||
} | ||
async function _recursivelyListAllFiles2(_ref3) { | ||
let { | ||
requestPath, | ||
absDirPath, | ||
relDirPath, | ||
queue, | ||
onFiles, | ||
signal | ||
} = _ref3; | ||
let curPath = requestPath; | ||
while (curPath) { | ||
const res = await _classPrivateFieldLooseBase(this, _list)[_list]({ | ||
requestPath: curPath, | ||
absDirPath, | ||
signal | ||
}); | ||
curPath = res.nextPagePath; | ||
const files = res.items.filter(item => !item.isFolder); | ||
const folders = res.items.filter(item => item.isFolder); | ||
onFiles(files); | ||
// recursively queue call to self for each folder | ||
const promises = folders.map(async folder => queue.add(async () => _classPrivateFieldLooseBase(this, _recursivelyListAllFiles)[_recursivelyListAllFiles]({ | ||
requestPath: folder.requestPath, | ||
absDirPath: prependPath(absDirPath, folder.name), | ||
relDirPath: prependPath(relDirPath, folder.name), | ||
queue, | ||
onFiles, | ||
signal | ||
}))); | ||
await Promise.all(promises); // in case we get an error | ||
} | ||
} | ||
ProviderView.VERSION = packageJson.version; |
@@ -5,3 +5,3 @@ import { h } from 'preact'; | ||
logout: () => void; | ||
username: string | undefined; | ||
username: string | null; | ||
}; | ||
@@ -8,0 +8,0 @@ export default function User({ i18n, logout, username }: UserProps): h.JSX.Element; |
@@ -8,3 +8,3 @@ import { h, Fragment } from 'preact'; | ||
} = _ref; | ||
return h(Fragment, null, h("span", { | ||
return h(Fragment, null, username && h("span", { | ||
className: "uppy-ProviderBrowser-user", | ||
@@ -11,0 +11,0 @@ key: "username" |
import { h } from 'preact'; | ||
import type { Body, Meta } from '@uppy/utils/lib/UppyFile'; | ||
import type { UnknownSearchProviderPlugin } from '@uppy/core/lib/Uppy'; | ||
import type { DefinePluginOpts } from '@uppy/core/lib/BasePlugin'; | ||
import View, { type ViewOptions } from '../View.ts'; | ||
type PluginType = 'SearchProvider'; | ||
declare const defaultOptions: { | ||
viewType: string; | ||
import type { PartialTree, PartialTreeFile, PartialTreeFolderNode, UnknownSearchProviderPlugin } from '@uppy/core/lib/Uppy.ts'; | ||
import type { CompanionFile } from '@uppy/utils/lib/CompanionFile'; | ||
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>; | ||
interface Opts<M extends Meta, B extends Body> { | ||
provider: UnknownSearchProviderPlugin<M, B>['provider']; | ||
viewType: 'list' | 'grid' | 'unsplash'; | ||
showTitles: boolean; | ||
showFilter: boolean; | ||
showBreadcrumbs: boolean; | ||
}; | ||
type Opts<M extends Meta, B extends Body, T extends PluginType> = DefinePluginOpts<ViewOptions<M, B, T>, keyof typeof defaultOptions>; | ||
} | ||
type PassedOpts<M extends Meta, B extends Body> = Optional<Opts<M, B>, 'viewType' | 'showTitles' | 'showFilter'>; | ||
type RenderOpts<M extends Meta, B extends Body> = Omit<PassedOpts<M, B>, 'provider'>; | ||
/** | ||
@@ -18,16 +18,25 @@ * SearchProviderView, used for Unsplash and future image search providers. | ||
*/ | ||
export default class SearchProviderView<M extends Meta, B extends Body> extends View<M, B, PluginType, Opts<M, B, PluginType>> { | ||
#private; | ||
export default class SearchProviderView<M extends Meta, B extends Body> { | ||
static VERSION: any; | ||
nextPageQuery: string | null; | ||
constructor(plugin: UnknownSearchProviderPlugin<M, B>, opts: ViewOptions<M, B, PluginType>); | ||
plugin: UnknownSearchProviderPlugin<M, B>; | ||
provider: UnknownSearchProviderPlugin<M, B>['provider']; | ||
opts: Opts<M, B>; | ||
isHandlingScroll: boolean; | ||
lastCheckbox: string | null; | ||
constructor(plugin: UnknownSearchProviderPlugin<M, B>, opts: PassedOpts<M, B>); | ||
tearDown(): void; | ||
setLoading(loading: boolean | string): void; | ||
resetPluginState(): void; | ||
search(query: string): Promise<void>; | ||
clearSearch(): void; | ||
cancelSelection(): void; | ||
search(): Promise<void>; | ||
handleScroll(event: Event): Promise<void>; | ||
donePicking(): void; | ||
render(state: unknown, viewOptions?: Omit<ViewOptions<M, B, PluginType>, 'provider'>): h.JSX.Element; | ||
donePicking(): Promise<void>; | ||
toggleCheckbox(ourItem: PartialTreeFolderNode | PartialTreeFile, isShiftKeyPressed: boolean): void; | ||
validateSingleFile: (file: CompanionFile) => string | null; | ||
getDisplayedPartialTree: () => (PartialTreeFile | PartialTreeFolderNode)[]; | ||
setSearchString: (searchString: string) => void; | ||
validateAggregateRestrictions: (partialTree: PartialTree) => string | null; | ||
render(state: unknown, viewOptions?: RenderOpts<M, B>): h.JSX.Element; | ||
} | ||
export {}; | ||
//# sourceMappingURL=SearchProviderView.d.ts.map |
@@ -1,9 +0,6 @@ | ||
function _classPrivateFieldLooseBase(e, t) { if (!{}.hasOwnProperty.call(e, t)) throw new TypeError("attempted to use private field on non-instance"); return e; } | ||
var id = 0; | ||
function _classPrivateFieldLooseKey(e) { return "__private_" + id++ + "_" + e; } | ||
import { h } from 'preact'; | ||
import SearchFilterInput from "../SearchFilterInput.js"; | ||
import classNames from 'classnames'; | ||
import remoteFileObjToLocal from '@uppy/utils/lib/remoteFileObjToLocal'; | ||
import SearchInput from "../SearchInput.js"; | ||
import Browser from "../Browser.js"; | ||
import CloseWrapper from "../CloseWrapper.js"; | ||
import View from "../View.js"; | ||
@@ -13,20 +10,23 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
const packageJson = { | ||
"version": "4.0.0-beta.9" | ||
"version": "4.0.0-beta.10" | ||
}; | ||
import PartialTreeUtils from "../utils/PartialTreeUtils/index.js"; | ||
import shouldHandleScroll from "../utils/shouldHandleScroll.js"; | ||
import handleError from "../utils/handleError.js"; | ||
import getClickedRange from "../utils/getClickedRange.js"; | ||
import FooterActions from "../FooterActions.js"; | ||
import addFiles from "../utils/addFiles.js"; | ||
import getCheckedFilesWithPaths from "../utils/PartialTreeUtils/getCheckedFilesWithPaths.js"; | ||
const defaultState = { | ||
isInputMode: true, | ||
files: [], | ||
folders: [], | ||
breadcrumbs: [], | ||
filterInput: '', | ||
currentSelection: [], | ||
searchTerm: null | ||
loading: false, | ||
searchString: '', | ||
partialTree: [{ | ||
type: 'root', | ||
id: null, | ||
cached: false, | ||
nextPagePath: null | ||
}], | ||
currentFolderId: null, | ||
isInputMode: true | ||
}; | ||
const defaultOptions = { | ||
viewType: 'grid', | ||
showTitles: true, | ||
showFilter: true, | ||
showBreadcrumbs: true | ||
}; | ||
var _updateFilesAndInputMode = /*#__PURE__*/_classPrivateFieldLooseKey("updateFilesAndInputMode"); | ||
/** | ||
@@ -36,20 +36,58 @@ * SearchProviderView, used for Unsplash and future image search providers. | ||
*/ | ||
export default class SearchProviderView extends View { | ||
export default class SearchProviderView { | ||
constructor(plugin, opts) { | ||
super(plugin, { | ||
this.isHandlingScroll = false; | ||
this.lastCheckbox = null; | ||
this.validateSingleFile = file => { | ||
const companionFile = remoteFileObjToLocal(file); | ||
const result = this.plugin.uppy.validateSingleFile(companionFile); | ||
return result; | ||
}; | ||
this.getDisplayedPartialTree = () => { | ||
const { | ||
partialTree | ||
} = this.plugin.getPluginState(); | ||
return partialTree.filter(item => item.type !== 'root'); | ||
}; | ||
this.setSearchString = searchString => { | ||
this.plugin.setPluginState({ | ||
searchString | ||
}); | ||
if (searchString === '') { | ||
this.plugin.setPluginState({ | ||
partialTree: [] | ||
}); | ||
} | ||
}; | ||
this.validateAggregateRestrictions = partialTree => { | ||
const checkedFiles = partialTree.filter(item => item.type === 'file' && item.status === 'checked'); | ||
const uppyFiles = checkedFiles.map(file => file.data); | ||
return this.plugin.uppy.validateAggregateRestrictions(uppyFiles); | ||
}; | ||
this.plugin = plugin; | ||
this.provider = opts.provider; | ||
const defaultOptions = { | ||
viewType: 'grid', | ||
showTitles: true, | ||
showFilter: true | ||
}; | ||
this.opts = { | ||
...defaultOptions, | ||
...opts | ||
}); | ||
Object.defineProperty(this, _updateFilesAndInputMode, { | ||
value: _updateFilesAndInputMode2 | ||
}); | ||
this.nextPageQuery = null; | ||
}; | ||
this.setSearchString = this.setSearchString.bind(this); | ||
this.search = this.search.bind(this); | ||
this.clearSearch = this.clearSearch.bind(this); | ||
this.resetPluginState = this.resetPluginState.bind(this); | ||
this.handleScroll = this.handleScroll.bind(this); | ||
this.donePicking = this.donePicking.bind(this); | ||
this.cancelSelection = this.cancelSelection.bind(this); | ||
this.toggleCheckbox = this.toggleCheckbox.bind(this); | ||
this.render = this.render.bind(this); | ||
this.plugin.setPluginState(defaultState); | ||
this.registerRequestClient(); | ||
// Set default state for the plugin | ||
this.resetPluginState(); | ||
// @ts-expect-error this should be typed in @uppy/dashboard. | ||
this.plugin.uppy.on('dashboard:close-panel', this.resetPluginState); | ||
this.plugin.uppy.registerRequestClient(this.provider.provider, this.provider); | ||
} | ||
@@ -61,58 +99,106 @@ | ||
} | ||
setLoading(loading) { | ||
this.plugin.setPluginState({ | ||
loading | ||
}); | ||
} | ||
resetPluginState() { | ||
this.plugin.setPluginState(defaultState); | ||
} | ||
async search(query) { | ||
cancelSelection() { | ||
const { | ||
searchTerm | ||
partialTree | ||
} = this.plugin.getPluginState(); | ||
if (query && query === searchTerm) { | ||
// no need to search again as this is the same as the previous search | ||
return; | ||
} | ||
const newPartialTree = partialTree.map(item => item.type === 'root' ? item : { | ||
...item, | ||
status: 'unchecked' | ||
}); | ||
this.plugin.setPluginState({ | ||
partialTree: newPartialTree | ||
}); | ||
} | ||
async search() { | ||
const { | ||
searchString | ||
} = this.plugin.getPluginState(); | ||
if (searchString === '') return; | ||
this.setLoading(true); | ||
try { | ||
const res = await this.provider.search(query); | ||
_classPrivateFieldLooseBase(this, _updateFilesAndInputMode)[_updateFilesAndInputMode](res, []); | ||
} catch (err) { | ||
this.handleError(err); | ||
} finally { | ||
this.setLoading(false); | ||
const response = await this.provider.search(searchString); | ||
const newPartialTree = [{ | ||
type: 'root', | ||
id: null, | ||
cached: false, | ||
nextPagePath: response.nextPageQuery | ||
}, ...response.items.map(item => ({ | ||
type: 'file', | ||
id: item.requestPath, | ||
status: 'unchecked', | ||
parentId: null, | ||
data: item | ||
}))]; | ||
this.plugin.setPluginState({ | ||
partialTree: newPartialTree, | ||
isInputMode: false | ||
}); | ||
} catch (error) { | ||
handleError(this.plugin.uppy)(error); | ||
} | ||
this.setLoading(false); | ||
} | ||
clearSearch() { | ||
this.plugin.setPluginState({ | ||
currentSelection: [], | ||
files: [], | ||
searchTerm: null | ||
}); | ||
} | ||
async handleScroll(event) { | ||
const query = this.nextPageQuery || null; | ||
if (this.shouldHandleScroll(event) && query) { | ||
const { | ||
partialTree, | ||
searchString | ||
} = this.plugin.getPluginState(); | ||
const root = partialTree.find(i => i.type === 'root'); | ||
if (shouldHandleScroll(event) && !this.isHandlingScroll && root.nextPagePath) { | ||
this.isHandlingScroll = true; | ||
try { | ||
const { | ||
files, | ||
searchTerm | ||
} = this.plugin.getPluginState(); | ||
const response = await this.provider.search(searchTerm, query); | ||
_classPrivateFieldLooseBase(this, _updateFilesAndInputMode)[_updateFilesAndInputMode](response, files); | ||
const response = await this.provider.search(searchString, root.nextPagePath); | ||
const newRoot = { | ||
...root, | ||
nextPagePath: response.nextPageQuery | ||
}; | ||
const oldItems = partialTree.filter(i => i.type !== 'root'); | ||
const newPartialTree = [newRoot, ...oldItems, ...response.items.map(item => ({ | ||
type: 'file', | ||
id: item.requestPath, | ||
status: 'unchecked', | ||
parentId: null, | ||
data: item | ||
}))]; | ||
this.plugin.setPluginState({ | ||
partialTree: newPartialTree | ||
}); | ||
} catch (error) { | ||
this.handleError(error); | ||
} finally { | ||
this.isHandlingScroll = false; | ||
handleError(this.plugin.uppy)(error); | ||
} | ||
this.isHandlingScroll = false; | ||
} | ||
} | ||
donePicking() { | ||
async donePicking() { | ||
const { | ||
currentSelection | ||
partialTree | ||
} = this.plugin.getPluginState(); | ||
this.plugin.uppy.log('Adding remote search provider files'); | ||
this.plugin.uppy.addFiles(currentSelection.map(file => this.getTagFile(file))); | ||
// 1. Add files | ||
const companionFiles = getCheckedFilesWithPaths(partialTree); | ||
addFiles(companionFiles, this.plugin, this.provider); | ||
// 2. Reset state | ||
this.resetPluginState(); | ||
} | ||
toggleCheckbox(ourItem, isShiftKeyPressed) { | ||
const { | ||
partialTree | ||
} = this.plugin.getPluginState(); | ||
const clickedRange = getClickedRange(ourItem.id, this.getDisplayedPartialTree(), isShiftKeyPressed, this.lastCheckbox); | ||
const newPartialTree = PartialTreeUtils.afterToggleCheckbox(partialTree, clickedRange); | ||
this.plugin.setPluginState({ | ||
partialTree: newPartialTree | ||
}); | ||
this.lastCheckbox = ourItem.id; | ||
} | ||
render(state, viewOptions) { | ||
var _this = this; | ||
if (viewOptions === void 0) { | ||
@@ -123,3 +209,5 @@ viewOptions = {}; | ||
isInputMode, | ||
searchTerm | ||
searchString, | ||
loading, | ||
partialTree | ||
} = this.plugin.getPluginState(); | ||
@@ -129,82 +217,49 @@ const { | ||
} = this.plugin.uppy; | ||
const targetViewOptions = { | ||
const opts = { | ||
...this.opts, | ||
...viewOptions | ||
}; | ||
const { | ||
files, | ||
folders, | ||
filterInput, | ||
loading, | ||
currentSelection | ||
} = this.plugin.getPluginState(); | ||
const { | ||
isChecked, | ||
filterItems, | ||
recordShiftKeyPress | ||
} = this; | ||
const hasInput = filterInput !== ''; | ||
const browserProps = { | ||
isChecked, | ||
toggleCheckbox: this.toggleCheckbox.bind(this), | ||
recordShiftKeyPress, | ||
currentSelection, | ||
files: hasInput ? filterItems(files) : files, | ||
folders: hasInput ? filterItems(folders) : folders, | ||
handleScroll: this.handleScroll, | ||
done: this.donePicking, | ||
cancel: this.cancelPicking, | ||
// For SearchFilterInput component | ||
showSearchFilter: targetViewOptions.showFilter, | ||
search: this.search, | ||
clearSearch: this.clearSearch, | ||
searchTerm, | ||
searchOnInput: false, | ||
searchInputLabel: i18n('search'), | ||
clearSearchLabel: i18n('resetSearch'), | ||
noResultsLabel: i18n('noSearchResults'), | ||
title: this.plugin.title, | ||
viewType: targetViewOptions.viewType, | ||
showTitles: targetViewOptions.showTitles, | ||
showFilter: targetViewOptions.showFilter, | ||
isLoading: loading, | ||
showBreadcrumbs: targetViewOptions.showBreadcrumbs, | ||
pluginIcon: this.plugin.icon, | ||
i18n, | ||
uppyFiles: this.plugin.uppy.getFiles(), | ||
validateRestrictions: function () { | ||
return _this.plugin.uppy.validateRestrictions(...arguments); | ||
} | ||
}; | ||
if (isInputMode) { | ||
return h(CloseWrapper, { | ||
onUnmount: this.resetPluginState | ||
}, h("div", { | ||
className: "uppy-SearchProvider" | ||
}, h(SearchFilterInput, { | ||
search: this.search, | ||
return h(SearchInput, { | ||
searchString: searchString, | ||
setSearchString: this.setSearchString, | ||
submitSearchString: this.search, | ||
inputLabel: i18n('enterTextToSearch'), | ||
buttonLabel: i18n('searchImages'), | ||
wrapperClassName: "uppy-SearchProvider", | ||
inputClassName: "uppy-c-textInput uppy-SearchProvider-input", | ||
buttonCSSClassName: "uppy-SearchProvider-searchButton", | ||
showButton: true | ||
}))); | ||
showButton: true, | ||
buttonCSSClassName: "uppy-SearchProvider-searchButton" | ||
}); | ||
} | ||
return h(CloseWrapper, { | ||
onUnmount: this.resetPluginState | ||
}, h(Browser, browserProps)); | ||
return h("div", { | ||
className: classNames('uppy-ProviderBrowser', `uppy-ProviderBrowser-viewType--${opts.viewType}`) | ||
}, opts.showFilter && h(SearchInput, { | ||
searchString: searchString, | ||
setSearchString: this.setSearchString, | ||
submitSearchString: this.search, | ||
inputLabel: i18n('search'), | ||
clearSearchLabel: i18n('resetSearch'), | ||
wrapperClassName: "uppy-ProviderBrowser-searchFilter", | ||
inputClassName: "uppy-ProviderBrowser-searchFilterInput" | ||
}), h(Browser, { | ||
toggleCheckbox: this.toggleCheckbox, | ||
displayedPartialTree: this.getDisplayedPartialTree(), | ||
handleScroll: this.handleScroll, | ||
openFolder: async () => {}, | ||
noResultsLabel: i18n('noSearchResults'), | ||
viewType: opts.viewType, | ||
showTitles: opts.showTitles, | ||
isLoading: loading, | ||
i18n: i18n, | ||
virtualList: false | ||
}), h(FooterActions, { | ||
partialTree: partialTree, | ||
donePicking: this.donePicking, | ||
cancelSelection: this.cancelSelection, | ||
i18n: i18n, | ||
validateAggregateRestrictions: this.validateAggregateRestrictions | ||
})); | ||
} | ||
} | ||
function _updateFilesAndInputMode2(res, files) { | ||
this.nextPageQuery = res.nextPageQuery; | ||
res.items.forEach(item => { | ||
files.push(item); | ||
}); | ||
this.plugin.setPluginState({ | ||
currentSelection: [], | ||
isInputMode: false, | ||
files, | ||
searchTerm: res.searchedFor | ||
}); | ||
} | ||
SearchProviderView.VERSION = packageJson.version; |
{ | ||
"name": "@uppy/provider-views", | ||
"description": "View library for Uppy remote provider plugins.", | ||
"version": "4.0.0-beta.9", | ||
"version": "4.0.0-beta.10", | ||
"license": "MIT", | ||
@@ -22,3 +22,3 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"@uppy/utils": "^6.0.0-beta.8", | ||
"@uppy/utils": "^6.0.0-beta.9", | ||
"classnames": "^2.2.6", | ||
@@ -29,6 +29,9 @@ "nanoid": "^5.0.0", | ||
}, | ||
"devDependencies": { | ||
"vitest": "^1.6.0" | ||
}, | ||
"peerDependencies": { | ||
"@uppy/core": "^4.0.0-beta.10" | ||
"@uppy/core": "^4.0.0-beta.11" | ||
}, | ||
"stableVersion": "3.11.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
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
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
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
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
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
383824
162
5349
1
Updated@uppy/utils@^6.0.0-beta.9