uploadProgressManager.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { ReactiveVar } from 'meteor/reactive-var';
  2. import { Tracker } from 'meteor/tracker';
  3. /**
  4. * Global upload progress manager for drag-and-drop file uploads
  5. * Tracks upload progress across all cards and provides reactive data
  6. */
  7. class UploadProgressManager {
  8. constructor() {
  9. // Map of cardId -> array of upload objects
  10. this.cardUploads = new ReactiveVar(new Map());
  11. // Map of uploadId -> upload object for easy lookup
  12. this.uploadMap = new ReactiveVar(new Map());
  13. }
  14. /**
  15. * Add a new upload to track
  16. * @param {string} cardId - The card ID
  17. * @param {Object} uploader - The uploader object from Attachments.insert
  18. * @param {File} file - The file being uploaded
  19. * @returns {string} uploadId - Unique identifier for this upload
  20. */
  21. addUpload(cardId, uploader, file) {
  22. const uploadId = `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  23. const upload = {
  24. id: uploadId,
  25. cardId: cardId,
  26. file: file,
  27. uploader: uploader,
  28. progress: new ReactiveVar(0),
  29. status: new ReactiveVar('uploading'), // 'uploading', 'completed', 'error'
  30. error: new ReactiveVar(null),
  31. startTime: Date.now(),
  32. endTime: null
  33. };
  34. // Update card uploads
  35. const currentCardUploads = this.cardUploads.get();
  36. const cardUploads = currentCardUploads.get(cardId) || [];
  37. cardUploads.push(upload);
  38. currentCardUploads.set(cardId, cardUploads);
  39. this.cardUploads.set(currentCardUploads);
  40. // Update upload map
  41. const currentUploadMap = this.uploadMap.get();
  42. currentUploadMap.set(uploadId, upload);
  43. this.uploadMap.set(currentUploadMap);
  44. // Set up uploader event listeners
  45. uploader.on('progress', (progress) => {
  46. upload.progress.set(progress);
  47. });
  48. uploader.on('uploaded', (error, fileRef) => {
  49. upload.status.set(error ? 'error' : 'completed');
  50. upload.endTime = Date.now();
  51. upload.error.set(error);
  52. if (process.env.DEBUG === 'true') {
  53. console.log(`Upload ${uploadId} completed:`, error ? 'error' : 'success');
  54. }
  55. // Remove from tracking after a delay to show completion
  56. setTimeout(() => {
  57. this.removeUpload(uploadId);
  58. }, 2000);
  59. });
  60. uploader.on('error', (error) => {
  61. upload.status.set('error');
  62. upload.endTime = Date.now();
  63. upload.error.set(error);
  64. if (process.env.DEBUG === 'true') {
  65. console.log(`Upload ${uploadId} failed:`, error);
  66. }
  67. // Remove from tracking after a delay to show error
  68. setTimeout(() => {
  69. this.removeUpload(uploadId);
  70. }, 3000);
  71. });
  72. if (process.env.DEBUG === 'true') {
  73. console.log(`Added upload ${uploadId} for card ${cardId}: ${file.name}`);
  74. }
  75. return uploadId;
  76. }
  77. /**
  78. * Remove an upload from tracking
  79. * @param {string} uploadId - The upload ID to remove
  80. */
  81. removeUpload(uploadId) {
  82. const upload = this.uploadMap.get().get(uploadId);
  83. if (!upload) return;
  84. const cardId = upload.cardId;
  85. // Remove from card uploads
  86. const currentCardUploads = this.cardUploads.get();
  87. const cardUploads = currentCardUploads.get(cardId) || [];
  88. const filteredCardUploads = cardUploads.filter(u => u.id !== uploadId);
  89. if (filteredCardUploads.length === 0) {
  90. currentCardUploads.delete(cardId);
  91. } else {
  92. currentCardUploads.set(cardId, filteredCardUploads);
  93. }
  94. this.cardUploads.set(currentCardUploads);
  95. // Remove from upload map
  96. const currentUploadMap = this.uploadMap.get();
  97. currentUploadMap.delete(uploadId);
  98. this.uploadMap.set(currentUploadMap);
  99. if (process.env.DEBUG === 'true') {
  100. console.log(`Removed upload ${uploadId} from tracking`);
  101. }
  102. }
  103. /**
  104. * Get all uploads for a specific card
  105. * @param {string} cardId - The card ID
  106. * @returns {Array} Array of upload objects
  107. */
  108. getUploadsForCard(cardId) {
  109. return this.cardUploads.get().get(cardId) || [];
  110. }
  111. /**
  112. * Get upload count for a specific card
  113. * @param {string} cardId - The card ID
  114. * @returns {number} Number of active uploads
  115. */
  116. getUploadCountForCard(cardId) {
  117. return this.getUploadsForCard(cardId).length;
  118. }
  119. /**
  120. * Check if a card has any active uploads
  121. * @param {string} cardId - The card ID
  122. * @returns {boolean} True if card has active uploads
  123. */
  124. hasActiveUploads(cardId) {
  125. return this.getUploadCountForCard(cardId) > 0;
  126. }
  127. /**
  128. * Get all uploads across all cards
  129. * @returns {Array} Array of all upload objects
  130. */
  131. getAllUploads() {
  132. const allUploads = [];
  133. this.cardUploads.get().forEach(cardUploads => {
  134. allUploads.push(...cardUploads);
  135. });
  136. return allUploads;
  137. }
  138. /**
  139. * Clear all uploads (useful for cleanup)
  140. */
  141. clearAllUploads() {
  142. this.cardUploads.set(new Map());
  143. this.uploadMap.set(new Map());
  144. }
  145. }
  146. // Create global instance
  147. const uploadProgressManager = new UploadProgressManager();
  148. export default uploadProgressManager;