app.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. /**
  2. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-FileCopyrightText: 2014-2016 ownCloud, Inc.
  4. * SPDX-License-Identifier: AGPL-3.0-or-later
  5. */
  6. /* global dragOptions, folderDropOptions, OC */
  7. (function() {
  8. if (!OCA.Files) {
  9. /**
  10. * Namespace for the files app
  11. * @namespace OCA.Files
  12. */
  13. OCA.Files = {};
  14. }
  15. /**
  16. * @namespace OCA.Files.App
  17. */
  18. OCA.Files.App = {
  19. /**
  20. * Navigation instance
  21. *
  22. * @member {OCP.Files.Navigation}
  23. */
  24. navigation: null,
  25. /**
  26. * File list for the "All files" section.
  27. *
  28. * @member {OCA.Files.FileList}
  29. */
  30. fileList: null,
  31. currentFileList: null,
  32. /**
  33. * Backbone model for storing files preferences
  34. */
  35. _filesConfig: null,
  36. /**
  37. * Initializes the files app
  38. */
  39. initialize: function() {
  40. this.$showHiddenFiles = $('input#showhiddenfilesToggle');
  41. var showHidden = $('#showHiddenFiles').val() === "1";
  42. this.$showHiddenFiles.prop('checked', showHidden);
  43. // Toggle for grid view
  44. this.$showGridView = $('input#showgridview');
  45. this.$showGridView.on('change', _.bind(this._onGridviewChange, this));
  46. if ($('#fileNotFound').val() === "1") {
  47. OC.Notification.show(t('files', 'File could not be found'), {type: 'error'});
  48. }
  49. this._filesConfig = OCP.InitialState.loadState('files', 'config', {})
  50. var { fileid, scrollto, openfile } = OC.Util.History.parseUrlQuery();
  51. var fileActions = new OCA.Files.FileActions();
  52. // default actions
  53. fileActions.registerDefaultActions();
  54. // regular actions
  55. fileActions.merge(OCA.Files.fileActions);
  56. this._onActionsUpdated = _.bind(this._onActionsUpdated, this);
  57. OCA.Files.fileActions.on('setDefault.app-files', this._onActionsUpdated);
  58. OCA.Files.fileActions.on('registerAction.app-files', this._onActionsUpdated);
  59. this.files = OCA.Files.Files;
  60. // TODO: ideally these should be in a separate class / app (the embedded "all files" app)
  61. this.fileList = new OCA.Files.FileList(
  62. $('#app-content-files'), {
  63. dragOptions: dragOptions,
  64. folderDropOptions: folderDropOptions,
  65. fileActions: fileActions,
  66. allowLegacyActions: true,
  67. scrollTo: scrollto,
  68. openFile: openfile,
  69. filesClient: OC.Files.getClient(),
  70. multiSelectMenu: [
  71. {
  72. name: 'copyMove',
  73. displayName: t('files', 'Move or copy'),
  74. iconClass: 'icon-external',
  75. order: 10,
  76. },
  77. {
  78. name: 'download',
  79. displayName: t('files', 'Download'),
  80. iconClass: 'icon-download',
  81. order: 10,
  82. },
  83. OCA.Files.FileList.MultiSelectMenuActions.ToggleSelectionModeAction,
  84. {
  85. name: 'delete',
  86. displayName: t('files', 'Delete'),
  87. iconClass: 'icon-delete',
  88. order: 99,
  89. },
  90. ...(
  91. OCA?.SystemTags === undefined ? [] : ([{
  92. name: 'tags',
  93. displayName: t('files', 'Tags'),
  94. iconClass: 'icon-tag',
  95. order: 100,
  96. }])
  97. ),
  98. ],
  99. sorting: {
  100. mode: $('#defaultFileSorting').val() === 'basename'
  101. ? 'name'
  102. : $('#defaultFileSorting').val(),
  103. direction: $('#defaultFileSortingDirection').val()
  104. },
  105. config: this._filesConfig,
  106. enableUpload: true,
  107. maxChunkSize: OC.appConfig.files && OC.appConfig.files.max_chunk_size
  108. }
  109. );
  110. this.updateCurrentFileList(this.fileList)
  111. this.files.initialize();
  112. // for backward compatibility, the global FileList will
  113. // refer to the one of the "files" view
  114. window.FileList = this.fileList;
  115. OC.Plugins.attach('OCA.Files.App', this);
  116. this._setupEvents();
  117. if (sessionStorage.getItem('WhatsNewServerCheck') < (Date.now() - 3600*1000)) {
  118. OCP.WhatsNew.query(); // for Nextcloud server
  119. sessionStorage.setItem('WhatsNewServerCheck', Date.now());
  120. }
  121. window._nc_event_bus.emit('files:legacy-view:initialized', this);
  122. this.navigation = OCP.Files.Navigation
  123. },
  124. /**
  125. * Destroy the app
  126. */
  127. destroy: function() {
  128. this.fileList.destroy();
  129. this.fileList = null;
  130. this.files = null;
  131. OCA.Files.fileActions.off('setDefault.app-files', this._onActionsUpdated);
  132. OCA.Files.fileActions.off('registerAction.app-files', this._onActionsUpdated);
  133. },
  134. _onActionsUpdated: function(ev) {
  135. // forward new action to the file list
  136. if (ev.action) {
  137. this.fileList.fileActions.registerAction(ev.action);
  138. } else if (ev.defaultAction) {
  139. this.fileList.fileActions.setDefault(
  140. ev.defaultAction.mime,
  141. ev.defaultAction.name
  142. );
  143. }
  144. },
  145. /**
  146. * Set the currently active file list
  147. *
  148. * Due to the file list implementations being registered after clicking the
  149. * navigation item for the first time, OCA.Files.App is not aware of those until
  150. * they have initialized themselves. Therefore the files list needs to call this
  151. * method manually
  152. *
  153. * @param {OCA.Files.FileList} newFileList -
  154. */
  155. updateCurrentFileList: function(newFileList) {
  156. if (this.currentFileList === newFileList) {
  157. return
  158. }
  159. this.currentFileList = newFileList;
  160. if (this.currentFileList !== null) {
  161. // update grid view to the current value
  162. const isGridView = this.$showGridView.is(':checked');
  163. this.currentFileList.setGridView(isGridView);
  164. }
  165. },
  166. /**
  167. * Return the currently active file list
  168. * @return {?OCA.Files.FileList}
  169. */
  170. getCurrentFileList: function () {
  171. return this.currentFileList;
  172. },
  173. /**
  174. * Returns the container of the currently visible app.
  175. *
  176. * @return app container
  177. */
  178. getCurrentAppContainer: function() {
  179. var viewId = this.getActiveView();
  180. return $('#app-content-' + viewId);
  181. },
  182. /**
  183. * Sets the currently active view
  184. * @param viewId view id
  185. */
  186. setActiveView: function(viewId) {
  187. // The Navigation API will handle the final event
  188. window._nc_event_bus.emit('files:legacy-navigation:changed', { id: viewId })
  189. },
  190. /**
  191. * Returns the view id of the currently active view
  192. * @return view id
  193. */
  194. getActiveView: function() {
  195. return this.navigation
  196. && this.navigation.active
  197. && this.navigation.active.id;
  198. },
  199. /**
  200. *
  201. * @returns {Backbone.Model}
  202. */
  203. getFilesConfig: function() {
  204. return this._filesConfig;
  205. },
  206. /**
  207. * Setup events based on URL changes
  208. */
  209. _setupEvents: function() {
  210. OC.Util.History.addOnPopStateHandler(_.bind(this._onPopState, this));
  211. // detect when app changed their current directory
  212. $('#app-content').delegate('>div', 'changeDirectory', _.bind(this._onDirectoryChanged, this));
  213. $('#app-content').delegate('>div', 'afterChangeDirectory', _.bind(this._onAfterDirectoryChanged, this));
  214. $('#app-content').delegate('>div', 'changeViewerMode', _.bind(this._onChangeViewerMode, this));
  215. },
  216. /**
  217. * Event handler for when the current navigation item has changed
  218. */
  219. _onNavigationChanged: function(view) {
  220. var params;
  221. if (view && (view.itemId || view.id)) {
  222. if (view.id) {
  223. params = {
  224. view: view.id,
  225. dir: '/',
  226. }
  227. } else {
  228. // Legacy handling
  229. params = {
  230. view: typeof view.view === 'string' && view.view !== '' ? view.view : view.itemId,
  231. dir: view.dir ? view.dir : '/'
  232. }
  233. }
  234. this._changeUrl(params.view, params.dir);
  235. OCA.Files.Sidebar.close();
  236. this.getCurrentAppContainer().trigger(new $.Event('urlChanged', params));
  237. window._nc_event_bus.emit('files:navigation:changed')
  238. }
  239. },
  240. /**
  241. * Event handler for when an app notified that its directory changed
  242. */
  243. _onDirectoryChanged: function(e) {
  244. if (e.dir && !e.changedThroughUrl) {
  245. this._changeUrl(this.getActiveView(), e.dir, e.fileId);
  246. }
  247. },
  248. /**
  249. * Event handler for when an app notified that its directory changed
  250. */
  251. _onAfterDirectoryChanged: function(e) {
  252. if (e.dir && e.fileId) {
  253. this._changeUrl(this.getActiveView(), e.dir, e.fileId);
  254. }
  255. },
  256. /**
  257. * Event handler for when an app notifies that it needs space
  258. * for viewer mode.
  259. */
  260. _onChangeViewerMode: function(e) {
  261. var state = !!e.viewerModeEnabled;
  262. if (e.viewerModeEnabled) {
  263. OCA.Files.Sidebar.close();
  264. }
  265. $('#app-navigation').toggleClass('hidden', state);
  266. $('.app-files').toggleClass('viewer-mode no-sidebar', state);
  267. },
  268. /**
  269. * Event handler for when the URL changed
  270. */
  271. _onPopState: function(params) {
  272. params = _.extend({
  273. dir: '/',
  274. view: 'files'
  275. }, params);
  276. var lastId = this.getActiveView();
  277. if (!this.navigation.views.find(view => view.id === params.view)) {
  278. params.view = 'files';
  279. }
  280. this.setActiveView(params.view, {silent: true});
  281. if (lastId !== this.getActiveView()) {
  282. this.getCurrentAppContainer().trigger(new $.Event('show', params));
  283. window._nc_event_bus.emit('files:navigation:changed')
  284. }
  285. this.getCurrentAppContainer().trigger(new $.Event('urlChanged', params));
  286. },
  287. /**
  288. * Encode URL params into a string, except for the "dir" attribute
  289. * that gets encoded as path where "/" is not encoded
  290. *
  291. * @param {Object.<string>} params
  292. * @return {string} encoded params
  293. */
  294. _makeUrlParams: function(params) {
  295. var dir = params.dir;
  296. delete params.dir;
  297. return 'dir=' + OC.encodePath(dir) + '&' + OC.buildQueryString(params);
  298. },
  299. /**
  300. * Change the URL to point to the given dir and view
  301. */
  302. _changeUrl: function(view, dir, fileId) {
  303. var params = { dir: dir };
  304. if (view !== 'files') {
  305. params.view = view;
  306. } else if (fileId) {
  307. params.fileid = fileId;
  308. }
  309. var currentParams = OC.Util.History.parseUrlQuery();
  310. if (currentParams.dir === params.dir && currentParams.view === params.view) {
  311. if (currentParams.fileid !== params.fileid) {
  312. // if only fileid changed or was added, replace instead of push
  313. OC.Util.History.replaceState(this._makeUrlParams(params));
  314. return
  315. }
  316. } else {
  317. OC.Util.History.pushState(this._makeUrlParams(params));
  318. return
  319. }
  320. },
  321. /**
  322. * Toggle showing gridview by default or not
  323. *
  324. * @returns {undefined}
  325. */
  326. _onGridviewChange: function() {
  327. const isGridView = this.$showGridView.is(':checked');
  328. // only save state if user is logged in
  329. if (OC.currentUser) {
  330. $.post(OC.generateUrl('/apps/files/api/v1/showgridview'), {
  331. show: isGridView,
  332. });
  333. }
  334. this.$showGridView.next('#view-toggle')
  335. .removeClass('icon-toggle-filelist icon-toggle-pictures')
  336. .addClass(isGridView ? 'icon-toggle-filelist' : 'icon-toggle-pictures')
  337. this.$showGridView.next('#view-toggle')
  338. .attr('title', isGridView ? t('files', 'Show list view') : t('files', 'Show grid view'))
  339. this.$showGridView.attr('aria-label', isGridView ? t('files', 'Show list view') : t('files', 'Show grid view'))
  340. if (this.currentFileList) {
  341. this.currentFileList.setGridView(isGridView);
  342. }
  343. },
  344. };
  345. })();
  346. window.addEventListener('DOMContentLoaded', function() {
  347. // wait for other apps/extensions to register their event handlers and file actions
  348. // in the "ready" clause
  349. _.defer(function() {
  350. OCA.Files.App.initialize();
  351. });
  352. });