SearchProviderView.jsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import { h } from 'preact'
  2. import SearchInput from './InputView.jsx'
  3. import Browser from '../Browser.jsx'
  4. import LoaderView from '../Loader.jsx'
  5. import Header from './Header.jsx'
  6. import CloseWrapper from '../CloseWrapper.js'
  7. import View from '../View.js'
  8. import packageJson from '../../package.json'
  9. /**
  10. * Class to easily generate generic views for Provider plugins
  11. */
  12. export default class SearchProviderView extends View {
  13. static VERSION = packageJson.version
  14. /**
  15. * @param {object} plugin instance of the plugin
  16. * @param {object} opts
  17. */
  18. constructor (plugin, opts) {
  19. super(plugin, opts)
  20. // set default options
  21. const defaultOptions = {
  22. viewType: 'grid',
  23. showTitles: false,
  24. showFilter: false,
  25. showBreadcrumbs: false,
  26. }
  27. // merge default options with the ones set by user
  28. this.opts = { ...defaultOptions, ...opts }
  29. // Logic
  30. this.search = this.search.bind(this)
  31. this.triggerSearchInput = this.triggerSearchInput.bind(this)
  32. this.addFile = this.addFile.bind(this)
  33. this.handleScroll = this.handleScroll.bind(this)
  34. this.donePicking = this.donePicking.bind(this)
  35. // Visual
  36. this.render = this.render.bind(this)
  37. // Set default state for the plugin
  38. this.plugin.setPluginState({
  39. isInputMode: true,
  40. files: [],
  41. folders: [],
  42. directories: [],
  43. filterInput: '',
  44. currentSelection: [],
  45. searchTerm: null,
  46. })
  47. }
  48. // eslint-disable-next-line class-methods-use-this
  49. tearDown () {
  50. // Nothing.
  51. }
  52. clearSelection () {
  53. this.plugin.setPluginState({
  54. currentSelection: [],
  55. isInputMode: true,
  56. files: [],
  57. searchTerm: null,
  58. })
  59. }
  60. #updateFilesAndInputMode (res, files) {
  61. this.nextPageQuery = res.nextPageQuery
  62. res.items.forEach((item) => { files.push(item) })
  63. this.plugin.setPluginState({
  64. isInputMode: false,
  65. files,
  66. searchTerm: res.searchedFor,
  67. })
  68. }
  69. search (query) {
  70. const { searchTerm } = this.plugin.getPluginState()
  71. if (query && query === searchTerm) {
  72. // no need to search again as this is the same as the previous search
  73. return undefined
  74. }
  75. return this.sharedHandler.loaderWrapper(
  76. this.provider.search(query),
  77. (res) => {
  78. this.#updateFilesAndInputMode(res, [])
  79. },
  80. this.handleError,
  81. )
  82. }
  83. triggerSearchInput () {
  84. this.plugin.setPluginState({ isInputMode: true })
  85. }
  86. async handleScroll (event) {
  87. const query = this.nextPageQuery || null
  88. if (this.shouldHandleScroll(event) && query) {
  89. this.isHandlingScroll = true
  90. try {
  91. const { files, searchTerm } = this.plugin.getPluginState()
  92. const response = await this.provider.search(searchTerm, query)
  93. this.#updateFilesAndInputMode(response, files)
  94. } catch (error) {
  95. this.handleError(error)
  96. } finally {
  97. this.isHandlingScroll = false
  98. }
  99. }
  100. }
  101. donePicking () {
  102. const { currentSelection } = this.plugin.getPluginState()
  103. const promises = currentSelection.map((file) => this.addFile(file))
  104. this.sharedHandler.loaderWrapper(Promise.all(promises), () => {
  105. this.clearSelection()
  106. }, () => {})
  107. }
  108. render (state, viewOptions = {}) {
  109. const { didFirstRender, isInputMode, searchTerm } = this.plugin.getPluginState()
  110. if (!didFirstRender) {
  111. this.preFirstRender()
  112. }
  113. const targetViewOptions = { ...this.opts, ...viewOptions }
  114. const { files, folders, filterInput, loading, currentSelection } = this.plugin.getPluginState()
  115. const { isChecked, toggleCheckbox, filterItems } = this.sharedHandler
  116. const hasInput = filterInput !== ''
  117. const browserProps = {
  118. isChecked,
  119. toggleCheckbox,
  120. currentSelection,
  121. files: hasInput ? filterItems(files) : files,
  122. folders: hasInput ? filterItems(folders) : folders,
  123. handleScroll: this.handleScroll,
  124. done: this.donePicking,
  125. cancel: this.cancelPicking,
  126. headerComponent: Header({
  127. search: this.search,
  128. i18n: this.plugin.uppy.i18n,
  129. searchTerm,
  130. }),
  131. title: this.plugin.title,
  132. viewType: targetViewOptions.viewType,
  133. showTitles: targetViewOptions.showTitles,
  134. showFilter: targetViewOptions.showFilter,
  135. showBreadcrumbs: targetViewOptions.showBreadcrumbs,
  136. pluginIcon: this.plugin.icon,
  137. i18n: this.plugin.uppy.i18n,
  138. uppyFiles: this.plugin.uppy.getFiles(),
  139. validateRestrictions: (...args) => this.plugin.uppy.validateRestrictions(...args),
  140. }
  141. if (loading) {
  142. return (
  143. <CloseWrapper onUnmount={this.clearSelection}>
  144. <LoaderView i18n={this.plugin.uppy.i18n} />
  145. </CloseWrapper>
  146. )
  147. }
  148. if (isInputMode) {
  149. return (
  150. <CloseWrapper onUnmount={this.clearSelection}>
  151. <SearchInput
  152. search={this.search}
  153. i18n={this.plugin.uppy.i18n}
  154. />
  155. </CloseWrapper>
  156. )
  157. }
  158. return (
  159. <CloseWrapper onUnmount={this.clearSelection}>
  160. {/* eslint-disable-next-line react/jsx-props-no-spreading */}
  161. <Browser {...browserProps} />
  162. </CloseWrapper>
  163. )
  164. }
  165. }