|
@@ -0,0 +1,177 @@
|
|
|
+import { ReactiveVar } from 'meteor/reactive-var';
|
|
|
+import { Tracker } from 'meteor/tracker';
|
|
|
+
|
|
|
+/**
|
|
|
+ * Global upload progress manager for drag-and-drop file uploads
|
|
|
+ * Tracks upload progress across all cards and provides reactive data
|
|
|
+ */
|
|
|
+class UploadProgressManager {
|
|
|
+ constructor() {
|
|
|
+ // Map of cardId -> array of upload objects
|
|
|
+ this.cardUploads = new ReactiveVar(new Map());
|
|
|
+
|
|
|
+ // Map of uploadId -> upload object for easy lookup
|
|
|
+ this.uploadMap = new ReactiveVar(new Map());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Add a new upload to track
|
|
|
+ * @param {string} cardId - The card ID
|
|
|
+ * @param {Object} uploader - The uploader object from Attachments.insert
|
|
|
+ * @param {File} file - The file being uploaded
|
|
|
+ * @returns {string} uploadId - Unique identifier for this upload
|
|
|
+ */
|
|
|
+ addUpload(cardId, uploader, file) {
|
|
|
+ const uploadId = `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
|
+
|
|
|
+ const upload = {
|
|
|
+ id: uploadId,
|
|
|
+ cardId: cardId,
|
|
|
+ file: file,
|
|
|
+ uploader: uploader,
|
|
|
+ progress: new ReactiveVar(0),
|
|
|
+ status: new ReactiveVar('uploading'), // 'uploading', 'completed', 'error'
|
|
|
+ error: new ReactiveVar(null),
|
|
|
+ startTime: Date.now(),
|
|
|
+ endTime: null
|
|
|
+ };
|
|
|
+
|
|
|
+ // Update card uploads
|
|
|
+ const currentCardUploads = this.cardUploads.get();
|
|
|
+ const cardUploads = currentCardUploads.get(cardId) || [];
|
|
|
+ cardUploads.push(upload);
|
|
|
+ currentCardUploads.set(cardId, cardUploads);
|
|
|
+ this.cardUploads.set(currentCardUploads);
|
|
|
+
|
|
|
+ // Update upload map
|
|
|
+ const currentUploadMap = this.uploadMap.get();
|
|
|
+ currentUploadMap.set(uploadId, upload);
|
|
|
+ this.uploadMap.set(currentUploadMap);
|
|
|
+
|
|
|
+ // Set up uploader event listeners
|
|
|
+ uploader.on('progress', (progress) => {
|
|
|
+ upload.progress.set(progress);
|
|
|
+ });
|
|
|
+
|
|
|
+ uploader.on('uploaded', (error, fileRef) => {
|
|
|
+ upload.status.set(error ? 'error' : 'completed');
|
|
|
+ upload.endTime = Date.now();
|
|
|
+ upload.error.set(error);
|
|
|
+
|
|
|
+ if (process.env.DEBUG === 'true') {
|
|
|
+ console.log(`Upload ${uploadId} completed:`, error ? 'error' : 'success');
|
|
|
+ }
|
|
|
+
|
|
|
+ // Remove from tracking after a delay to show completion
|
|
|
+ setTimeout(() => {
|
|
|
+ this.removeUpload(uploadId);
|
|
|
+ }, 2000);
|
|
|
+ });
|
|
|
+
|
|
|
+ uploader.on('error', (error) => {
|
|
|
+ upload.status.set('error');
|
|
|
+ upload.endTime = Date.now();
|
|
|
+ upload.error.set(error);
|
|
|
+
|
|
|
+ if (process.env.DEBUG === 'true') {
|
|
|
+ console.log(`Upload ${uploadId} failed:`, error);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Remove from tracking after a delay to show error
|
|
|
+ setTimeout(() => {
|
|
|
+ this.removeUpload(uploadId);
|
|
|
+ }, 3000);
|
|
|
+ });
|
|
|
+
|
|
|
+ if (process.env.DEBUG === 'true') {
|
|
|
+ console.log(`Added upload ${uploadId} for card ${cardId}: ${file.name}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ return uploadId;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Remove an upload from tracking
|
|
|
+ * @param {string} uploadId - The upload ID to remove
|
|
|
+ */
|
|
|
+ removeUpload(uploadId) {
|
|
|
+ const upload = this.uploadMap.get().get(uploadId);
|
|
|
+ if (!upload) return;
|
|
|
+
|
|
|
+ const cardId = upload.cardId;
|
|
|
+
|
|
|
+ // Remove from card uploads
|
|
|
+ const currentCardUploads = this.cardUploads.get();
|
|
|
+ const cardUploads = currentCardUploads.get(cardId) || [];
|
|
|
+ const filteredCardUploads = cardUploads.filter(u => u.id !== uploadId);
|
|
|
+
|
|
|
+ if (filteredCardUploads.length === 0) {
|
|
|
+ currentCardUploads.delete(cardId);
|
|
|
+ } else {
|
|
|
+ currentCardUploads.set(cardId, filteredCardUploads);
|
|
|
+ }
|
|
|
+ this.cardUploads.set(currentCardUploads);
|
|
|
+
|
|
|
+ // Remove from upload map
|
|
|
+ const currentUploadMap = this.uploadMap.get();
|
|
|
+ currentUploadMap.delete(uploadId);
|
|
|
+ this.uploadMap.set(currentUploadMap);
|
|
|
+
|
|
|
+ if (process.env.DEBUG === 'true') {
|
|
|
+ console.log(`Removed upload ${uploadId} from tracking`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get all uploads for a specific card
|
|
|
+ * @param {string} cardId - The card ID
|
|
|
+ * @returns {Array} Array of upload objects
|
|
|
+ */
|
|
|
+ getUploadsForCard(cardId) {
|
|
|
+ return this.cardUploads.get().get(cardId) || [];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get upload count for a specific card
|
|
|
+ * @param {string} cardId - The card ID
|
|
|
+ * @returns {number} Number of active uploads
|
|
|
+ */
|
|
|
+ getUploadCountForCard(cardId) {
|
|
|
+ return this.getUploadsForCard(cardId).length;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Check if a card has any active uploads
|
|
|
+ * @param {string} cardId - The card ID
|
|
|
+ * @returns {boolean} True if card has active uploads
|
|
|
+ */
|
|
|
+ hasActiveUploads(cardId) {
|
|
|
+ return this.getUploadCountForCard(cardId) > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get all uploads across all cards
|
|
|
+ * @returns {Array} Array of all upload objects
|
|
|
+ */
|
|
|
+ getAllUploads() {
|
|
|
+ const allUploads = [];
|
|
|
+ this.cardUploads.get().forEach(cardUploads => {
|
|
|
+ allUploads.push(...cardUploads);
|
|
|
+ });
|
|
|
+ return allUploads;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Clear all uploads (useful for cleanup)
|
|
|
+ */
|
|
|
+ clearAllUploads() {
|
|
|
+ this.cardUploads.set(new Map());
|
|
|
+ this.uploadMap.set(new Map());
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Create global instance
|
|
|
+const uploadProgressManager = new UploadProgressManager();
|
|
|
+
|
|
|
+export default uploadProgressManager;
|
|
|
+
|