versionstabview.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. /*
  2. * Copyright (c) 2015
  3. *
  4. * This file is licensed under the Affero General Public License version 3
  5. * or later.
  6. *
  7. * See the COPYING-README file.
  8. *
  9. */
  10. /* @global Handlebars */
  11. (function() {
  12. var TEMPLATE_ITEM =
  13. '<li data-revision="{{timestamp}}">' +
  14. '<div>' +
  15. '<div class="preview-container">' +
  16. '<img class="preview" src="{{previewUrl}}" width="44" height="44"/>' +
  17. '</div>' +
  18. '<div class="version-container">' +
  19. '<div>' +
  20. '<a href="{{downloadUrl}}" class="downloadVersion"><img src="{{downloadIconUrl}}" />' +
  21. '<span class="versiondate has-tooltip live-relative-timestamp" data-timestamp="{{millisecondsTimestamp}}" title="{{formattedTimestamp}}">{{relativeTimestamp}}</span>' +
  22. '</a>' +
  23. '</div>' +
  24. '{{#hasDetails}}' +
  25. '<div class="version-details">' +
  26. '<span class="size has-tooltip" title="{{altSize}}">{{humanReadableSize}}</span>' +
  27. '</div>' +
  28. '{{/hasDetails}}' +
  29. '</div>' +
  30. '{{#canRevert}}' +
  31. '<a href="#" class="revertVersion" title="{{revertLabel}}"><img src="{{revertIconUrl}}" /></a>' +
  32. '{{/canRevert}}' +
  33. '</div>' +
  34. '</li>';
  35. var TEMPLATE =
  36. '<ul class="versions"></ul>' +
  37. '<div class="clear-float"></div>' +
  38. '<div class="empty hidden">' +
  39. '<div class="emptycontent">' +
  40. '<div class="icon-history"></div>' +
  41. '<p>{{emptyResultLabel}}</p>' +
  42. '</div></div>' +
  43. '<input type="button" class="showMoreVersions hidden" value="{{moreVersionsLabel}}"' +
  44. ' name="show-more-versions" id="show-more-versions" />' +
  45. '<div class="loading hidden" style="height: 50px"></div>';
  46. /**
  47. * @memberof OCA.Versions
  48. */
  49. var VersionsTabView = OCA.Files.DetailTabView.extend(
  50. /** @lends OCA.Versions.VersionsTabView.prototype */ {
  51. id: 'versionsTabView',
  52. className: 'tab versionsTabView',
  53. _template: null,
  54. $versionsContainer: null,
  55. events: {
  56. 'click .revertVersion': '_onClickRevertVersion',
  57. 'click .showMoreVersions': '_onClickShowMoreVersions'
  58. },
  59. initialize: function() {
  60. OCA.Files.DetailTabView.prototype.initialize.apply(this, arguments);
  61. this.collection = new OCA.Versions.VersionCollection();
  62. this.collection.on('request', this._onRequest, this);
  63. this.collection.on('sync', this._onEndRequest, this);
  64. this.collection.on('update', this._onUpdate, this);
  65. this.collection.on('error', this._onError, this);
  66. this.collection.on('add', this._onAddModel, this);
  67. },
  68. getLabel: function() {
  69. return t('files_versions', 'Versions');
  70. },
  71. nextPage: function() {
  72. if (this._loading || !this.collection.hasMoreResults()) {
  73. return;
  74. }
  75. if (this.collection.getFileInfo() && this.collection.getFileInfo().isDirectory()) {
  76. return;
  77. }
  78. this.collection.fetchNext();
  79. },
  80. _onClickShowMoreVersions: function(ev) {
  81. ev.preventDefault();
  82. this.nextPage();
  83. },
  84. _onClickRevertVersion: function(ev) {
  85. var self = this;
  86. var $target = $(ev.target);
  87. var fileInfoModel = this.collection.getFileInfo();
  88. var revision;
  89. if (!$target.is('li')) {
  90. $target = $target.closest('li');
  91. }
  92. ev.preventDefault();
  93. revision = $target.attr('data-revision');
  94. this.$el.find('.versions, .showMoreVersions').addClass('hidden');
  95. var versionModel = this.collection.get(revision);
  96. versionModel.revert({
  97. success: function() {
  98. // reset and re-fetch the updated collection
  99. self.$versionsContainer.empty();
  100. self.collection.setFileInfo(fileInfoModel);
  101. self.collection.reset([], {silent: true});
  102. self.collection.fetchNext();
  103. self.$el.find('.versions').removeClass('hidden');
  104. // update original model
  105. fileInfoModel.trigger('busy', fileInfoModel, false);
  106. fileInfoModel.set({
  107. size: versionModel.get('size'),
  108. mtime: versionModel.get('timestamp') * 1000,
  109. // temp dummy, until we can do a PROPFIND
  110. etag: versionModel.get('id') + versionModel.get('timestamp')
  111. });
  112. },
  113. error: function() {
  114. fileInfoModel.trigger('busy', fileInfoModel, false);
  115. self.$el.find('.versions').removeClass('hidden');
  116. self._toggleLoading(false);
  117. OC.Notification.show(t('files_version', 'Failed to revert {file} to revision {timestamp}.',
  118. {
  119. file: versionModel.getFullPath(),
  120. timestamp: OC.Util.formatDate(versionModel.get('timestamp') * 1000)
  121. }),
  122. {
  123. type: 'error'
  124. }
  125. );
  126. }
  127. });
  128. // spinner
  129. this._toggleLoading(true);
  130. fileInfoModel.trigger('busy', fileInfoModel, true);
  131. },
  132. _toggleLoading: function(state) {
  133. this._loading = state;
  134. this.$el.find('.loading').toggleClass('hidden', !state);
  135. },
  136. _onRequest: function() {
  137. this._toggleLoading(true);
  138. this.$el.find('.showMoreVersions').addClass('hidden');
  139. },
  140. _onEndRequest: function() {
  141. this._toggleLoading(false);
  142. this.$el.find('.empty').toggleClass('hidden', !!this.collection.length);
  143. this.$el.find('.showMoreVersions').toggleClass('hidden', !this.collection.hasMoreResults());
  144. },
  145. _onAddModel: function(model) {
  146. var $el = $(this.itemTemplate(this._formatItem(model)));
  147. this.$versionsContainer.append($el);
  148. var preview = $el.find('.preview')[0];
  149. this._lazyLoadPreview({
  150. url: model.getPreviewUrl(),
  151. mime: model.get('mimetype'),
  152. callback: function(url) {
  153. preview.src = url;
  154. }
  155. });
  156. $el.find('.has-tooltip').tooltip();
  157. },
  158. template: function(data) {
  159. if (!this._template) {
  160. this._template = Handlebars.compile(TEMPLATE);
  161. }
  162. return this._template(data);
  163. },
  164. itemTemplate: function(data) {
  165. if (!this._itemTemplate) {
  166. this._itemTemplate = Handlebars.compile(TEMPLATE_ITEM);
  167. }
  168. return this._itemTemplate(data);
  169. },
  170. setFileInfo: function(fileInfo) {
  171. if (fileInfo) {
  172. this.render();
  173. this.collection.setFileInfo(fileInfo);
  174. this.collection.reset([], {silent: true});
  175. this.nextPage();
  176. } else {
  177. this.render();
  178. this.collection.reset();
  179. }
  180. },
  181. _formatItem: function(version) {
  182. var timestamp = version.get('timestamp') * 1000;
  183. var size = version.has('size') ? version.get('size') : 0;
  184. return _.extend({
  185. millisecondsTimestamp: timestamp,
  186. formattedTimestamp: OC.Util.formatDate(timestamp),
  187. relativeTimestamp: OC.Util.relativeModifiedDate(timestamp),
  188. humanReadableSize: OC.Util.humanFileSize(size, true),
  189. altSize: n('files', '%n byte', '%n bytes', size),
  190. hasDetails: version.has('size'),
  191. downloadUrl: version.getDownloadUrl(),
  192. downloadIconUrl: OC.imagePath('core', 'actions/download'),
  193. revertIconUrl: OC.imagePath('core', 'actions/history'),
  194. revertLabel: t('files_versions', 'Restore'),
  195. canRevert: (this.collection.getFileInfo().get('permissions') & OC.PERMISSION_UPDATE) !== 0
  196. }, version.attributes);
  197. },
  198. /**
  199. * Renders this details view
  200. */
  201. render: function() {
  202. this.$el.html(this.template({
  203. emptyResultLabel: t('files_versions', 'No earlier versions available'),
  204. moreVersionsLabel: t('files_versions', 'More versions …')
  205. }));
  206. this.$el.find('.has-tooltip').tooltip();
  207. this.$versionsContainer = this.$el.find('ul.versions');
  208. this.delegateEvents();
  209. },
  210. /**
  211. * Returns true for files, false for folders.
  212. *
  213. * @return {bool} true for files, false for folders
  214. */
  215. canDisplay: function(fileInfo) {
  216. if (!fileInfo) {
  217. return false;
  218. }
  219. return !fileInfo.isDirectory();
  220. },
  221. /**
  222. * Lazy load a file's preview.
  223. *
  224. * @param path path of the file
  225. * @param mime mime type
  226. * @param callback callback function to call when the image was loaded
  227. * @param etag file etag (for caching)
  228. */
  229. _lazyLoadPreview : function(options) {
  230. var url = options.url;
  231. var mime = options.mime;
  232. var ready = options.callback;
  233. // get mime icon url
  234. var iconURL = OC.MimeType.getIconUrl(mime);
  235. ready(iconURL); // set mimeicon URL
  236. var img = new Image();
  237. img.onload = function(){
  238. // if loading the preview image failed (no preview for the mimetype) then img.width will < 5
  239. if (img.width > 5) {
  240. ready(url, img);
  241. } else if (options.error) {
  242. options.error();
  243. }
  244. };
  245. if (options.error) {
  246. img.onerror = options.error;
  247. }
  248. img.src = url;
  249. }
  250. });
  251. OCA.Versions = OCA.Versions || {};
  252. OCA.Versions.VersionsTabView = VersionsTabView;
  253. })();