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.handleScroll = this.handleScroll.bind(this)
  33. this.donePicking = this.donePicking.bind(this)
  34. // Visual
  35. this.render = this.render.bind(this)
  36. this.defaultState = {
  37. isInputMode: true,
  38. files: [],
  39. folders: [],
  40. directories: [],
  41. filterInput: '',
  42. currentSelection: [],
  43. searchTerm: null,
  44. }
  45. // Set default state for the plugin
  46. this.plugin.setPluginState(this.defaultState)
  47. }
  48. // eslint-disable-next-line class-methods-use-this
  49. tearDown () {
  50. // Nothing.
  51. }
  52. resetPluginState () {
  53. this.plugin.setPluginState(this.defaultState)
  54. }
  55. #updateFilesAndInputMode (res, files) {
  56. this.nextPageQuery = res.nextPageQuery
  57. res.items.forEach((item) => { files.push(item) })
  58. this.plugin.setPluginState({
  59. currentSelection: [],
  60. isInputMode: false,
  61. files,
  62. searchTerm: res.searchedFor,
  63. })
  64. }
  65. async search (query) {
  66. const { searchTerm } = this.plugin.getPluginState()
  67. if (query && query === searchTerm) {
  68. // no need to search again as this is the same as the previous search
  69. return
  70. }
  71. this.setLoading(true)
  72. try {
  73. const res = await this.provider.search(query)
  74. this.#updateFilesAndInputMode(res, [])
  75. } catch (err) {
  76. this.handleError(err)
  77. } finally {
  78. this.setLoading(false)
  79. }
  80. }
  81. clearSearch () {
  82. this.plugin.setPluginState({
  83. currentSelection: [],
  84. files: [],
  85. searchTerm: null,
  86. })
  87. }
  88. async handleScroll (event) {
  89. const query = this.nextPageQuery || null
  90. if (this.shouldHandleScroll(event) && query) {
  91. this.isHandlingScroll = true
  92. try {
  93. const { files, searchTerm } = this.plugin.getPluginState()
  94. const response = await this.provider.search(searchTerm, query)
  95. this.#updateFilesAndInputMode(response, files)
  96. } catch (error) {
  97. this.handleError(error)
  98. } finally {
  99. this.isHandlingScroll = false
  100. }
  101. }
  102. }
  103. donePicking () {
  104. const { currentSelection } = this.plugin.getPluginState()
  105. this.plugin.uppy.log('Adding remote search provider files')
  106. this.plugin.uppy.addFiles(currentSelection.map((file) => this.getTagFile(file)))
  107. this.resetPluginState()
  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, recordShiftKeyPress } = this
  118. const hasInput = filterInput !== ''
  119. const browserProps = {
  120. isChecked,
  121. toggleCheckbox,
  122. recordShiftKeyPress,
  123. currentSelection,
  124. files: hasInput ? filterItems(files) : files,
  125. folders: hasInput ? filterItems(folders) : folders,
  126. handleScroll: this.handleScroll,
  127. done: this.donePicking,
  128. cancel: this.cancelPicking,
  129. // For SearchFilterInput component
  130. showSearchFilter: targetViewOptions.showFilter,
  131. search: this.search,
  132. clearSearch: this.clearSearch,
  133. searchTerm,
  134. searchOnInput: false,
  135. searchInputLabel: i18n('search'),
  136. clearSearchLabel: i18n('resetSearch'),
  137. noResultsLabel: i18n('noSearchResults'),
  138. title: this.plugin.title,
  139. viewType: targetViewOptions.viewType,
  140. showTitles: targetViewOptions.showTitles,
  141. showFilter: targetViewOptions.showFilter,
  142. isLoading: loading,
  143. showBreadcrumbs: targetViewOptions.showBreadcrumbs,
  144. pluginIcon: this.plugin.icon,
  145. i18n,
  146. uppyFiles: this.plugin.uppy.getFiles(),
  147. validateRestrictions: (...args) => this.plugin.uppy.validateRestrictions(...args),
  148. }
  149. if (isInputMode) {
  150. return (
  151. <CloseWrapper onUnmount={this.resetPluginState}>
  152. <div className="uppy-SearchProvider">
  153. <SearchFilterInput
  154. search={this.search}
  155. clearSelection={this.clearSelection}
  156. inputLabel={i18n('enterTextToSearch')}
  157. buttonLabel={i18n('searchImages')}
  158. inputClassName="uppy-c-textInput uppy-SearchProvider-input"
  159. buttonCSSClassName="uppy-SearchProvider-searchButton"
  160. showButton
  161. />
  162. </div>
  163. </CloseWrapper>
  164. )
  165. }
  166. return (
  167. <CloseWrapper onUnmount={this.resetPluginState}>
  168. {/* eslint-disable-next-line react/jsx-props-no-spreading */}
  169. <Browser {...browserProps} />
  170. </CloseWrapper>
  171. )
  172. }
  173. }