versionstabview.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  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.showTemporary(
  118. t('files_version', 'Failed to revert {file} to revision {timestamp}.', {
  119. file: versionModel.getFullPath(),
  120. timestamp: OC.Util.formatDate(versionModel.get('timestamp') * 1000)
  121. })
  122. );
  123. }
  124. });
  125. // spinner
  126. this._toggleLoading(true);
  127. fileInfoModel.trigger('busy', fileInfoModel, true);
  128. },
  129. _toggleLoading: function(state) {
  130. this._loading = state;
  131. this.$el.find('.loading').toggleClass('hidden', !state);
  132. },
  133. _onRequest: function() {
  134. this._toggleLoading(true);
  135. this.$el.find('.showMoreVersions').addClass('hidden');
  136. },
  137. _onEndRequest: function() {
  138. this._toggleLoading(false);
  139. this.$el.find('.empty').toggleClass('hidden', !!this.collection.length);
  140. this.$el.find('.showMoreVersions').toggleClass('hidden', !this.collection.hasMoreResults());
  141. },
  142. _onAddModel: function(model) {
  143. var $el = $(this.itemTemplate(this._formatItem(model)));
  144. this.$versionsContainer.append($el);
  145. var preview = $el.find('.preview')[0];
  146. this._lazyLoadPreview({
  147. url: model.getPreviewUrl(),
  148. mime: model.get('mimetype'),
  149. callback: function(url) {
  150. preview.src = url;
  151. }
  152. });
  153. $el.find('.has-tooltip').tooltip();
  154. },
  155. template: function(data) {
  156. if (!this._template) {
  157. this._template = Handlebars.compile(TEMPLATE);
  158. }
  159. return this._template(data);
  160. },
  161. itemTemplate: function(data) {
  162. if (!this._itemTemplate) {
  163. this._itemTemplate = Handlebars.compile(TEMPLATE_ITEM);
  164. }
  165. return this._itemTemplate(data);
  166. },
  167. setFileInfo: function(fileInfo) {
  168. if (fileInfo) {
  169. this.render();
  170. this.collection.setFileInfo(fileInfo);
  171. this.collection.reset([], {silent: true});
  172. this.nextPage();
  173. } else {
  174. this.render();
  175. this.collection.reset();
  176. }
  177. },
  178. _formatItem: function(version) {
  179. var timestamp = version.get('timestamp') * 1000;
  180. var size = version.has('size') ? version.get('size') : 0;
  181. return _.extend({
  182. millisecondsTimestamp: timestamp,
  183. formattedTimestamp: OC.Util.formatDate(timestamp),
  184. relativeTimestamp: OC.Util.relativeModifiedDate(timestamp),
  185. humanReadableSize: OC.Util.humanFileSize(size, true),
  186. altSize: n('files', '%n byte', '%n bytes', size),
  187. hasDetails: version.has('size'),
  188. downloadUrl: version.getDownloadUrl(),
  189. downloadIconUrl: OC.imagePath('core', 'actions/download'),
  190. revertIconUrl: OC.imagePath('core', 'actions/history'),
  191. revertLabel: t('files_versions', 'Restore'),
  192. canRevert: (this.collection.getFileInfo().get('permissions') & OC.PERMISSION_UPDATE) !== 0
  193. }, version.attributes);
  194. },
  195. /**
  196. * Renders this details view
  197. */
  198. render: function() {
  199. this.$el.html(this.template({
  200. emptyResultLabel: t('files_versions', 'No versions available'),
  201. moreVersionsLabel: t('files_versions', 'More versions...')
  202. }));
  203. this.$el.find('.has-tooltip').tooltip();
  204. this.$versionsContainer = this.$el.find('ul.versions');
  205. this.delegateEvents();
  206. },
  207. /**
  208. * Returns true for files, false for folders.
  209. *
  210. * @return {bool} true for files, false for folders
  211. */
  212. canDisplay: function(fileInfo) {
  213. if (!fileInfo) {
  214. return false;
  215. }
  216. return !fileInfo.isDirectory();
  217. },
  218. /**
  219. * Lazy load a file's preview.
  220. *
  221. * @param path path of the file
  222. * @param mime mime type
  223. * @param callback callback function to call when the image was loaded
  224. * @param etag file etag (for caching)
  225. */
  226. _lazyLoadPreview : function(options) {
  227. var url = options.url;
  228. var mime = options.mime;
  229. var ready = options.callback;
  230. // get mime icon url
  231. var iconURL = OC.MimeType.getIconUrl(mime);
  232. ready(iconURL); // set mimeicon URL
  233. var img = new Image();
  234. img.onload = function(){
  235. // if loading the preview image failed (no preview for the mimetype) then img.width will < 5
  236. if (img.width > 5) {
  237. ready(url, img);
  238. } else if (options.error) {
  239. options.error();
  240. }
  241. };
  242. if (options.error) {
  243. img.onerror = options.error;
  244. }
  245. img.src = url;
  246. }
  247. });
  248. OCA.Versions = OCA.Versions || {};
  249. OCA.Versions.VersionsTabView = VersionsTabView;
  250. })();