1
0

app.js 11 KB

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