attachments.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. const filesize = require('filesize');
  2. const prettyMilliseconds = require('pretty-ms');
  3. Template.attachmentsGalery.events({
  4. 'click .js-add-attachment': Popup.open('cardAttachments'),
  5. // If we let this event bubble, FlowRouter will handle it and empty the page
  6. // content, see #101.
  7. 'click .js-download'(event) {
  8. event.stopPropagation();
  9. },
  10. 'click .js-open-attachment-menu': Popup.open('attachmentActions'),
  11. });
  12. Template.attachmentsGalery.helpers({
  13. isBoardAdmin() {
  14. return Meteor.user().isBoardAdmin();
  15. },
  16. fileSize(size) {
  17. return Math.round(size / 1024);
  18. },
  19. });
  20. Template.cardAttachmentsPopup.onCreated(function() {
  21. this.currentUpload = new ReactiveVar(false);
  22. });
  23. Template.cardAttachmentsPopup.helpers({
  24. getEstimateTime() {
  25. const ret = prettyMilliseconds(Template.instance().currentUpload.get().estimateTime.get());
  26. return ret;
  27. },
  28. getEstimateSpeed() {
  29. const ret = filesize(Template.instance().currentUpload.get().estimateSpeed.get(), {round: 0}) + "/s";
  30. return ret;
  31. },
  32. currentUpload() {
  33. return Template.instance().currentUpload.get();
  34. }
  35. });
  36. Template.cardAttachmentsPopup.events({
  37. 'change .js-attach-file'(event, templateInstance) {
  38. const card = this;
  39. const files = event.currentTarget.files;
  40. if (files) {
  41. let finished = [];
  42. for (const file of files) {
  43. const fileId = Random.id();
  44. const config = {
  45. file: file,
  46. fileId: fileId,
  47. meta: Utils.getCommonAttachmentMetaFrom(card),
  48. chunkSize: 'dynamic',
  49. };
  50. config.meta.fileId = fileId;
  51. const uploader = Attachments.insert(
  52. config,
  53. false,
  54. );
  55. uploader.on('start', function() {
  56. templateInstance.currentUpload.set(this);
  57. });
  58. uploader.on('uploaded', (error, fileRef) => {
  59. if (!error) {
  60. if (fileRef.isImage) {
  61. card.setCover(fileRef._id);
  62. }
  63. }
  64. });
  65. uploader.on('end', (error, fileRef) => {
  66. templateInstance.currentUpload.set(false);
  67. finished.push(fileRef);
  68. if (finished.length == files.length) {
  69. Popup.back();
  70. }
  71. });
  72. uploader.start();
  73. }
  74. }
  75. },
  76. 'click .js-computer-upload'(event, templateInstance) {
  77. templateInstance.find('.js-attach-file').click();
  78. event.preventDefault();
  79. },
  80. 'click .js-upload-clipboard-image': Popup.open('previewClipboardImage'),
  81. });
  82. const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL;
  83. const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
  84. let pastedResults = null;
  85. Template.previewClipboardImagePopup.onRendered(() => {
  86. // we can paste image from clipboard
  87. const handle = results => {
  88. if (results.dataURL.startsWith('data:image/')) {
  89. const direct = results => {
  90. $('img.preview-clipboard-image').attr('src', results.dataURL);
  91. pastedResults = results;
  92. };
  93. if (MAX_IMAGE_PIXEL) {
  94. // if has size limitation on image we shrink it before uploading
  95. Utils.shrinkImage({
  96. dataurl: results.dataURL,
  97. maxSize: MAX_IMAGE_PIXEL,
  98. ratio: COMPRESS_RATIO,
  99. callback(changed) {
  100. if (changed !== false && !!changed) {
  101. results.dataURL = changed;
  102. }
  103. direct(results);
  104. },
  105. });
  106. } else {
  107. direct(results);
  108. }
  109. }
  110. };
  111. $(document.body).pasteImageReader(handle);
  112. // we can also drag & drop image file to it
  113. $(document.body).dropImageReader(handle);
  114. });
  115. Template.previewClipboardImagePopup.events({
  116. 'click .js-upload-pasted-image'() {
  117. const card = this;
  118. if (pastedResults && pastedResults.file) {
  119. const file = pastedResults.file;
  120. window.oPasted = pastedResults;
  121. const fileId = Random.id();
  122. const config = {
  123. file,
  124. fileId: fileId,
  125. meta: Utils.getCommonAttachmentMetaFrom(card),
  126. fileName: file.name || file.type.replace('image/', 'clipboard.'),
  127. chunkSize: 'dynamic',
  128. };
  129. config.meta.fileId = fileId;
  130. const uploader = Attachments.insert(
  131. config,
  132. false,
  133. );
  134. uploader.on('uploaded', (error, fileRef) => {
  135. if (!error) {
  136. if (fileRef.isImage) {
  137. card.setCover(fileRef._id);
  138. }
  139. }
  140. });
  141. uploader.on('end', (error, fileRef) => {
  142. pastedResults = null;
  143. $(document.body).pasteImageReader(() => {});
  144. Popup.back();
  145. });
  146. uploader.start();
  147. }
  148. },
  149. });
  150. BlazeComponent.extendComponent({
  151. isCover() {
  152. const ret = Cards.findOne(this.data().meta.cardId).coverId == this.data()._id;
  153. return ret;
  154. },
  155. events() {
  156. return [
  157. {
  158. 'click .js-rename': Popup.open('attachmentRename'),
  159. 'click .js-confirm-delete': Popup.afterConfirm('attachmentDelete', function() {
  160. Attachments.remove(this._id);
  161. Popup.back(2);
  162. }),
  163. 'click .js-add-cover'() {
  164. Cards.findOne(this.data().meta.cardId).setCover(this.data()._id);
  165. Popup.back();
  166. },
  167. 'click .js-remove-cover'() {
  168. Cards.findOne(this.data().meta.cardId).unsetCover();
  169. Popup.back();
  170. },
  171. 'click .js-move-storage-fs'() {
  172. Meteor.call('moveAttachmentToStorage', this.data()._id, "fs");
  173. Popup.back();
  174. },
  175. 'click .js-move-storage-gridfs'() {
  176. Meteor.call('moveAttachmentToStorage', this.data()._id, "gridfs");
  177. Popup.back();
  178. },
  179. }
  180. ]
  181. }
  182. }).register('attachmentActionsPopup');
  183. BlazeComponent.extendComponent({
  184. getNameWithoutExtension() {
  185. const ret = this.data().name.replace(new RegExp("\." + this.data().extension + "$"), "");
  186. return ret;
  187. },
  188. events() {
  189. return [
  190. {
  191. 'keydown input.js-edit-attachment-name'(evt) {
  192. // enter = save
  193. if (evt.keyCode === 13) {
  194. this.find('button[type=submit]').click();
  195. }
  196. },
  197. 'click button.js-submit-edit-attachment-name'(event) {
  198. // save button pressed
  199. event.preventDefault();
  200. const name = this.$('.js-edit-attachment-name')[0]
  201. .value
  202. .trim() + this.data().extensionWithDot;
  203. Meteor.call('renameAttachment', this.data()._id, name);
  204. Popup.back(2);
  205. },
  206. }
  207. ]
  208. }
  209. }).register('attachmentRenamePopup');