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.

app.js 11KB

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