SearchProviderView.jsx 5.6 KB

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