systemtagsfilelist.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. /**
  2. * Copyright (c) 2016 Vincent Petry <pvince81@owncloud.com>
  3. *
  4. * @author Joas Schilling <coding@schilljs.com>
  5. * @author John Molakvoæ <skjnldsv@protonmail.com>
  6. * @author Vincent Petry <vincent@nextcloud.com>
  7. *
  8. * @license AGPL-3.0-or-later
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU Affero General Public License as
  12. * published by the Free Software Foundation, either version 3 of the
  13. * License, or (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU Affero General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Affero General Public License
  21. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. *
  23. */
  24. (function() {
  25. /**
  26. * @class OCA.SystemTags.FileList
  27. * @augments OCA.Files.FileList
  28. *
  29. * @classdesc SystemTags file list.
  30. * Contains a list of files filtered by system tags.
  31. *
  32. * @param {object} $el container element with existing markup for the .files-controls and a table
  33. * @param {Array} [options] map of options, see other parameters
  34. * @param {Array.<string>} [options.systemTagIds] array of system tag ids to
  35. * filter by
  36. */
  37. const FileList = function($el, options) {
  38. this.initialize($el, options)
  39. }
  40. FileList.prototype = _.extend(
  41. {},
  42. OCA.Files.FileList.prototype,
  43. /** @lends OCA.SystemTags.FileList.prototype */ {
  44. id: 'systemtagsfilter',
  45. appName: t('systemtags', 'Tagged files'),
  46. /**
  47. * Array of system tag ids to filter by
  48. *
  49. * @type {Array.<string>}
  50. */
  51. _systemTagIds: [],
  52. _lastUsedTags: [],
  53. _clientSideSort: true,
  54. _allowSelection: false,
  55. _filterField: null,
  56. /**
  57. * @private
  58. * @param {object} $el container element
  59. * @param {object} [options] map of options, see other parameters
  60. */
  61. initialize($el, options) {
  62. OCA.Files.FileList.prototype.initialize.apply(this, arguments)
  63. if (this.initialized) {
  64. return
  65. }
  66. if (options && options.systemTagIds) {
  67. this._systemTagIds = options.systemTagIds
  68. }
  69. OC.Plugins.attach('OCA.SystemTags.FileList', this)
  70. const $controls = this.$el.find('.files-controls').empty()
  71. _.defer(_.bind(this._getLastUsedTags, this))
  72. this._initFilterField($controls)
  73. },
  74. destroy() {
  75. this.$filterField.remove()
  76. OCA.Files.FileList.prototype.destroy.apply(this, arguments)
  77. },
  78. _getLastUsedTags() {
  79. const self = this
  80. $.ajax({
  81. type: 'GET',
  82. url: OC.generateUrl('/apps/systemtags/lastused'),
  83. success(response) {
  84. self._lastUsedTags = response
  85. },
  86. })
  87. },
  88. _initFilterField($container) {
  89. const self = this
  90. this.$filterField = $('<input type="hidden" name="tags"/>')
  91. this.$filterField.val(this._systemTagIds.join(','))
  92. $container.append(this.$filterField)
  93. this.$filterField.select2({
  94. placeholder: t('systemtags', 'Select tags to filter by'),
  95. allowClear: false,
  96. multiple: true,
  97. toggleSelect: true,
  98. separator: ',',
  99. query: _.bind(this._queryTagsAutocomplete, this),
  100. id(tag) {
  101. return tag.id
  102. },
  103. initSelection(element, callback) {
  104. const val = $(element)
  105. .val()
  106. .trim()
  107. if (val) {
  108. const tagIds = val.split(',')
  109. const tags = []
  110. OC.SystemTags.collection.fetch({
  111. success() {
  112. _.each(tagIds, function(tagId) {
  113. const tag = OC.SystemTags.collection.get(
  114. tagId
  115. )
  116. if (!_.isUndefined(tag)) {
  117. tags.push(tag.toJSON())
  118. }
  119. })
  120. callback(tags)
  121. self._onTagsChanged({ target: element })
  122. },
  123. })
  124. } else {
  125. // eslint-disable-next-line n/no-callback-literal
  126. callback([])
  127. }
  128. },
  129. formatResult(tag) {
  130. return OC.SystemTags.getDescriptiveTag(tag)
  131. },
  132. formatSelection(tag) {
  133. return OC.SystemTags.getDescriptiveTag(tag).outerHTML
  134. },
  135. sortResults(results) {
  136. results.sort(function(a, b) {
  137. const aLastUsed = self._lastUsedTags.indexOf(a.id)
  138. const bLastUsed = self._lastUsedTags.indexOf(b.id)
  139. if (aLastUsed !== bLastUsed) {
  140. if (bLastUsed === -1) {
  141. return -1
  142. }
  143. if (aLastUsed === -1) {
  144. return 1
  145. }
  146. return aLastUsed < bLastUsed ? -1 : 1
  147. }
  148. // Both not found
  149. return OC.Util.naturalSortCompare(a.name, b.name)
  150. })
  151. return results
  152. },
  153. escapeMarkup(m) {
  154. // prevent double markup escape
  155. return m
  156. },
  157. formatNoMatches() {
  158. return t('systemtags', 'No tags found')
  159. },
  160. })
  161. this.$filterField.parent().children('.select2-container').attr('aria-expanded', 'false')
  162. this.$filterField.on('select2-open', () => {
  163. this.$filterField.parent().children('.select2-container').attr('aria-expanded', 'true')
  164. })
  165. this.$filterField.on('select2-close', () => {
  166. this.$filterField.parent().children('.select2-container').attr('aria-expanded', 'false')
  167. })
  168. this.$filterField.on(
  169. 'change',
  170. _.bind(this._onTagsChanged, this)
  171. )
  172. return this.$filterField
  173. },
  174. /**
  175. * Autocomplete function for dropdown results
  176. *
  177. * @param {object} query select2 query object
  178. */
  179. _queryTagsAutocomplete(query) {
  180. OC.SystemTags.collection.fetch({
  181. success() {
  182. const results = OC.SystemTags.collection.filterByName(
  183. query.term
  184. )
  185. query.callback({
  186. results: _.invoke(results, 'toJSON'),
  187. })
  188. },
  189. })
  190. },
  191. /**
  192. * Event handler for when the URL changed
  193. *
  194. * @param {Event} e the urlchanged event
  195. */
  196. _onUrlChanged(e) {
  197. if (e.dir) {
  198. const tags = _.filter(e.dir.split('/'), function(val) {
  199. return val.trim() !== ''
  200. })
  201. this.$filterField.select2('val', tags || [])
  202. this._systemTagIds = tags
  203. this.reload()
  204. }
  205. },
  206. _onTagsChanged(ev) {
  207. const val = $(ev.target)
  208. .val()
  209. .trim()
  210. if (val !== '') {
  211. this._systemTagIds = val.split(',')
  212. } else {
  213. this._systemTagIds = []
  214. }
  215. this.$el.trigger(
  216. $.Event('changeDirectory', {
  217. dir: this._systemTagIds.join('/'),
  218. })
  219. )
  220. this.reload()
  221. },
  222. updateEmptyContent() {
  223. const dir = this.getCurrentDirectory()
  224. if (dir === '/') {
  225. // root has special permissions
  226. if (!this._systemTagIds.length) {
  227. // no tags selected
  228. this.$el
  229. .find('.emptyfilelist.emptycontent')
  230. .html(
  231. '<div class="icon-systemtags"></div>'
  232. + '<h2>'
  233. + t(
  234. 'systemtags',
  235. 'Please select tags to filter by'
  236. )
  237. + '</h2>'
  238. )
  239. } else {
  240. // tags selected but no results
  241. this.$el
  242. .find('.emptyfilelist.emptycontent')
  243. .html(
  244. '<div class="icon-systemtags"></div>'
  245. + '<h2>'
  246. + t(
  247. 'systemtags',
  248. 'No files found for the selected tags'
  249. )
  250. + '</h2>'
  251. )
  252. }
  253. this.$el
  254. .find('.emptyfilelist.emptycontent')
  255. .toggleClass('hidden', !this.isEmpty)
  256. this.$el
  257. .find('.files-filestable thead th')
  258. .toggleClass('hidden', this.isEmpty)
  259. } else {
  260. OCA.Files.FileList.prototype.updateEmptyContent.apply(
  261. this,
  262. arguments
  263. )
  264. }
  265. },
  266. getDirectoryPermissions() {
  267. return OC.PERMISSION_READ | OC.PERMISSION_DELETE
  268. },
  269. updateStorageStatistics() {
  270. // no op because it doesn't have
  271. // storage info like free space / used space
  272. },
  273. reload() {
  274. // there is only root
  275. this._setCurrentDir('/', false)
  276. if (!this._systemTagIds.length) {
  277. // don't reload
  278. this.updateEmptyContent()
  279. this.setFiles([])
  280. return $.Deferred().resolve()
  281. }
  282. this._selectedFiles = {}
  283. this._selectionSummary.clear()
  284. if (this._currentFileModel) {
  285. this._currentFileModel.off()
  286. }
  287. this._currentFileModel = null
  288. this.$el.find('.select-all').prop('checked', false)
  289. this.showMask()
  290. this._reloadCall = this.filesClient.getFilteredFiles(
  291. {
  292. systemTagIds: this._systemTagIds,
  293. },
  294. {
  295. properties: this._getWebdavProperties(),
  296. }
  297. )
  298. if (this._detailsView) {
  299. // close sidebar
  300. this._updateDetailsView(null)
  301. }
  302. const callBack = this.reloadCallback.bind(this)
  303. return this._reloadCall.then(callBack, callBack)
  304. },
  305. reloadCallback(status, result) {
  306. if (result) {
  307. // prepend empty dir info because original handler
  308. result.unshift({})
  309. }
  310. return OCA.Files.FileList.prototype.reloadCallback.call(
  311. this,
  312. status,
  313. result
  314. )
  315. },
  316. }
  317. )
  318. OCA.SystemTags.FileList = FileList
  319. })()