You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

450 lines
12 KiB

  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 control
  28. *
  29. * @member {OCA.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 = new OCA.Files.Navigation($('#app-navigation'));
  48. this.$showHiddenFiles = $('input#showhiddenfilesToggle');
  49. var showHidden = $('#showHiddenFiles').val() === "1";
  50. this.$showHiddenFiles.prop('checked', showHidden);
  51. // crop image previews
  52. this.$cropImagePreviews = $('input#cropimagepreviewsToggle');
  53. var cropImagePreviews = $('#cropImagePreviews').val() === "1";
  54. this.$cropImagePreviews.prop('checked', cropImagePreviews);
  55. // Toggle for grid view
  56. this.$showGridView = $('input#showgridview');
  57. this.$showGridView.on('change', _.bind(this._onGridviewChange, this));
  58. $('#view-toggle').tooltip({placement: 'bottom', trigger: 'hover'});
  59. if ($('#fileNotFound').val() === "1") {
  60. OC.Notification.show(t('files', 'File could not be found'), {type: 'error'});
  61. }
  62. this._filesConfig = new OC.Backbone.Model({
  63. showhidden: showHidden,
  64. cropimagepreviews: cropImagePreviews,
  65. });
  66. var urlParams = OC.Util.History.parseUrlQuery();
  67. var fileActions = new OCA.Files.FileActions();
  68. // default actions
  69. fileActions.registerDefaultActions();
  70. // regular actions
  71. fileActions.merge(OCA.Files.fileActions);
  72. this._onActionsUpdated = _.bind(this._onActionsUpdated, this);
  73. OCA.Files.fileActions.on('setDefault.app-files', this._onActionsUpdated);
  74. OCA.Files.fileActions.on('registerAction.app-files', this._onActionsUpdated);
  75. this.files = OCA.Files.Files;
  76. // TODO: ideally these should be in a separate class / app (the embedded "all files" app)
  77. this.fileList = new OCA.Files.FileList(
  78. $('#app-content-files'), {
  79. dragOptions: dragOptions,
  80. folderDropOptions: folderDropOptions,
  81. fileActions: fileActions,
  82. allowLegacyActions: true,
  83. scrollTo: urlParams.scrollto,
  84. openFile: urlParams.openfile,
  85. filesClient: OC.Files.getClient(),
  86. multiSelectMenu: [
  87. {
  88. name: 'copyMove',
  89. displayName: t('files', 'Move or copy'),
  90. iconClass: 'icon-external',
  91. order: 10,
  92. },
  93. {
  94. name: 'download',
  95. displayName: t('files', 'Download'),
  96. iconClass: 'icon-download',
  97. order: 10,
  98. },
  99. OCA.Files.FileList.MultiSelectMenuActions.ToggleSelectionModeAction,
  100. {
  101. name: 'delete',
  102. displayName: t('files', 'Delete'),
  103. iconClass: 'icon-delete',
  104. order: 99,
  105. },
  106. {
  107. name: 'tags',
  108. displayName: 'Tags',
  109. iconClass: 'icon-tag',
  110. order: 100,
  111. },
  112. ],
  113. sorting: {
  114. mode: $('#defaultFileSorting').val(),
  115. direction: $('#defaultFileSortingDirection').val()
  116. },
  117. config: this._filesConfig,
  118. enableUpload: true,
  119. maxChunkSize: OC.appConfig.files && OC.appConfig.files.max_chunk_size
  120. }
  121. );
  122. this.updateCurrentFileList(this.fileList)
  123. this.files.initialize();
  124. // for backward compatibility, the global FileList will
  125. // refer to the one of the "files" view
  126. window.FileList = this.fileList;
  127. OC.Plugins.attach('OCA.Files.App', this);
  128. this._setupEvents();
  129. // trigger URL change event handlers
  130. this._onPopState(urlParams);
  131. $('#quota.has-tooltip').tooltip({
  132. placement: 'top'
  133. });
  134. this._debouncedPersistShowHiddenFilesState = _.debounce(this._persistShowHiddenFilesState, 1200);
  135. this._debouncedPersistCropImagePreviewsState = _.debounce(this._persistCropImagePreviewsState, 1200);
  136. if (sessionStorage.getItem('WhatsNewServerCheck') < (Date.now() - 3600*1000)) {
  137. OCP.WhatsNew.query(); // for Nextcloud server
  138. sessionStorage.setItem('WhatsNewServerCheck', Date.now());
  139. }
  140. },
  141. /**
  142. * Destroy the app
  143. */
  144. destroy: function() {
  145. this.navigation = null;
  146. this.fileList.destroy();
  147. this.fileList = null;
  148. this.files = null;
  149. OCA.Files.fileActions.off('setDefault.app-files', this._onActionsUpdated);
  150. OCA.Files.fileActions.off('registerAction.app-files', this._onActionsUpdated);
  151. },
  152. _onActionsUpdated: function(ev) {
  153. // forward new action to the file list
  154. if (ev.action) {
  155. this.fileList.fileActions.registerAction(ev.action);
  156. } else if (ev.defaultAction) {
  157. this.fileList.fileActions.setDefault(
  158. ev.defaultAction.mime,
  159. ev.defaultAction.name
  160. );
  161. }
  162. },
  163. /**
  164. * Set the currently active file list
  165. *
  166. * Due to the file list implementations being registered after clicking the
  167. * navigation item for the first time, OCA.Files.App is not aware of those until
  168. * they have initialized themselves. Therefore the files list needs to call this
  169. * method manually
  170. *
  171. * @param {OCA.Files.FileList} newFileList -
  172. */
  173. updateCurrentFileList: function(newFileList) {
  174. if (this.currentFileList === newFileList) {
  175. return
  176. }
  177. this.currentFileList = newFileList;
  178. if (this.currentFileList !== null) {
  179. // update grid view to the current value
  180. const isGridView = this.$showGridView.is(':checked');
  181. this.currentFileList.setGridView(isGridView);
  182. }
  183. },
  184. /**
  185. * Return the currently active file list
  186. * @return {?OCA.Files.FileList}
  187. */
  188. getCurrentFileList: function () {
  189. return this.currentFileList;
  190. },
  191. /**
  192. * Returns the container of the currently visible app.
  193. *
  194. * @return app container
  195. */
  196. getCurrentAppContainer: function() {
  197. return this.navigation.getActiveContainer();
  198. },
  199. /**
  200. * Sets the currently active view
  201. * @param viewId view id
  202. */
  203. setActiveView: function(viewId, options) {
  204. this.navigation.setActiveItem(viewId, options);
  205. },
  206. /**
  207. * Returns the view id of the currently active view
  208. * @return view id
  209. */
  210. getActiveView: function() {
  211. return this.navigation.getActiveItem();
  212. },
  213. /**
  214. *
  215. * @returns {Backbone.Model}
  216. */
  217. getFilesConfig: function() {
  218. return this._filesConfig;
  219. },
  220. /**
  221. * Setup events based on URL changes
  222. */
  223. _setupEvents: function() {
  224. OC.Util.History.addOnPopStateHandler(_.bind(this._onPopState, this));
  225. // detect when app changed their current directory
  226. $('#app-content').delegate('>div', 'changeDirectory', _.bind(this._onDirectoryChanged, this));
  227. $('#app-content').delegate('>div', 'afterChangeDirectory', _.bind(this._onAfterDirectoryChanged, this));
  228. $('#app-content').delegate('>div', 'changeViewerMode', _.bind(this._onChangeViewerMode, this));
  229. $('#app-navigation').on('itemChanged', _.bind(this._onNavigationChanged, this));
  230. this.$showHiddenFiles.on('change', _.bind(this._onShowHiddenFilesChange, this));
  231. this.$cropImagePreviews.on('change', _.bind(this._onCropImagePreviewsChange, this));
  232. },
  233. /**
  234. * Toggle showing hidden files according to the settings checkbox
  235. *
  236. * @returns {undefined}
  237. */
  238. _onShowHiddenFilesChange: function() {
  239. var show = this.$showHiddenFiles.is(':checked');
  240. this._filesConfig.set('showhidden', show);
  241. this._debouncedPersistShowHiddenFilesState();
  242. },
  243. /**
  244. * Persist show hidden preference on the server
  245. *
  246. * @returns {undefined}
  247. */
  248. _persistShowHiddenFilesState: function() {
  249. var show = this._filesConfig.get('showhidden');
  250. $.post(OC.generateUrl('/apps/files/api/v1/showhidden'), {
  251. show: show
  252. });
  253. },
  254. /**
  255. * Toggle cropping image previews according to the settings checkbox
  256. *
  257. * @returns void
  258. */
  259. _onCropImagePreviewsChange: function() {
  260. var crop = this.$cropImagePreviews.is(':checked');
  261. this._filesConfig.set('cropimagepreviews', crop);
  262. this._debouncedPersistCropImagePreviewsState();
  263. },
  264. /**
  265. * Persist crop image previews preference on the server
  266. *
  267. * @returns void
  268. */
  269. _persistCropImagePreviewsState: function() {
  270. var crop = this._filesConfig.get('cropimagepreviews');
  271. $.post(OC.generateUrl('/apps/files/api/v1/cropimagepreviews'), {
  272. crop: crop
  273. });
  274. },
  275. /**
  276. * Event handler for when the current navigation item has changed
  277. */
  278. _onNavigationChanged: function(e) {
  279. var params;
  280. if (e && e.itemId) {
  281. params = {
  282. view: typeof e.view === 'string' && e.view !== '' ? e.view : e.itemId,
  283. dir: e.dir ? e.dir : '/'
  284. };
  285. this._changeUrl(params.view, params.dir);
  286. OC.Apps.hideAppSidebar($('.detailsView'));
  287. this.navigation.getActiveContainer().trigger(new $.Event('urlChanged', params));
  288. window._nc_event_bus.emit('files:navigation:changed')
  289. }
  290. },
  291. /**
  292. * Event handler for when an app notified that its directory changed
  293. */
  294. _onDirectoryChanged: function(e) {
  295. if (e.dir && !e.changedThroughUrl) {
  296. this._changeUrl(this.navigation.getActiveItem(), e.dir, e.fileId);
  297. }
  298. },
  299. /**
  300. * Event handler for when an app notified that its directory changed
  301. */
  302. _onAfterDirectoryChanged: function(e) {
  303. if (e.dir && e.fileId) {
  304. this._changeUrl(this.navigation.getActiveItem(), e.dir, e.fileId);
  305. }
  306. },
  307. /**
  308. * Event handler for when an app notifies that it needs space
  309. * for viewer mode.
  310. */
  311. _onChangeViewerMode: function(e) {
  312. var state = !!e.viewerModeEnabled;
  313. if (e.viewerModeEnabled) {
  314. OC.Apps.hideAppSidebar($('.detailsView'));
  315. }
  316. $('#app-navigation').toggleClass('hidden', state);
  317. $('.app-files').toggleClass('viewer-mode no-sidebar', state);
  318. },
  319. /**
  320. * Event handler for when the URL changed
  321. */
  322. _onPopState: function(params) {
  323. params = _.extend({
  324. dir: '/',
  325. view: 'files'
  326. }, params);
  327. var lastId = this.navigation.getActiveItem();
  328. if (!this.navigation.itemExists(params.view)) {
  329. params.view = 'files';
  330. }
  331. this.navigation.setActiveItem(params.view, {silent: true});
  332. if (lastId !== this.navigation.getActiveItem()) {
  333. this.navigation.getActiveContainer().trigger(new $.Event('show'));
  334. }
  335. this.navigation.getActiveContainer().trigger(new $.Event('urlChanged', params));
  336. window._nc_event_bus.emit('files:navigation:changed')
  337. },
  338. /**
  339. * Encode URL params into a string, except for the "dir" attribute
  340. * that gets encoded as path where "/" is not encoded
  341. *
  342. * @param {Object.<string>} params
  343. * @return {string} encoded params
  344. */
  345. _makeUrlParams: function(params) {
  346. var dir = params.dir;
  347. delete params.dir;
  348. return 'dir=' + OC.encodePath(dir) + '&' + OC.buildQueryString(params);
  349. },
  350. /**
  351. * Change the URL to point to the given dir and view
  352. */
  353. _changeUrl: function(view, dir, fileId) {
  354. var params = {dir: dir};
  355. if (view !== 'files') {
  356. params.view = view;
  357. } else if (fileId) {
  358. params.fileid = fileId;
  359. }
  360. var currentParams = OC.Util.History.parseUrlQuery();
  361. if (currentParams.dir === params.dir && currentParams.view === params.view) {
  362. if (currentParams.fileid !== params.fileid) {
  363. // if only fileid changed or was added, replace instead of push
  364. OC.Util.History.replaceState(this._makeUrlParams(params));
  365. }
  366. } else {
  367. OC.Util.History.pushState(this._makeUrlParams(params));
  368. }
  369. },
  370. /**
  371. * Toggle showing gridview by default or not
  372. *
  373. * @returns {undefined}
  374. */
  375. _onGridviewChange: function() {
  376. const isGridView = this.$showGridView.is(':checked');
  377. // only save state if user is logged in
  378. if (OC.currentUser) {
  379. $.post(OC.generateUrl('/apps/files/api/v1/showgridview'), {
  380. show: isGridView,
  381. });
  382. }
  383. this.$showGridView.next('#view-toggle')
  384. .removeClass('icon-toggle-filelist icon-toggle-pictures')
  385. .addClass(isGridView ? 'icon-toggle-filelist' : 'icon-toggle-pictures')
  386. this.$showGridView.next('#view-toggle').attr(
  387. 'data-original-title',
  388. isGridView ? t('files', 'Show list view') : t('files', 'Show grid view'),
  389. )
  390. if (this.currentFileList) {
  391. this.currentFileList.setGridView(isGridView);
  392. }
  393. },
  394. };
  395. })();
  396. window.addEventListener('DOMContentLoaded', function() {
  397. // wait for other apps/extensions to register their event handlers and file actions
  398. // in the "ready" clause
  399. _.defer(function() {
  400. OCA.Files.App.initialize();
  401. });
  402. });