mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 23:40:13 +01:00
Drag any files from file manager to minicard or opened card. Part 2.
Thanks to xet7 ! Fixes #2936
This commit is contained in:
parent
c1cbcdcc72
commit
cdd7d69c66
8 changed files with 404 additions and 6 deletions
|
|
@ -106,6 +106,142 @@
|
||||||
color: white;
|
color: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 4em;
|
font-size: 4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Upload progress indicators for drag-and-drop uploads */
|
||||||
|
.minicard-upload-progress,
|
||||||
|
.card-details-upload-progress {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 12px;
|
||||||
|
margin: 8px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-header i {
|
||||||
|
margin-right: 8px;
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-item.upload-error {
|
||||||
|
border-color: #dc3545;
|
||||||
|
background: #f8d7da;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-filename {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: #495057;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-bar {
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
background: #e9ecef;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, #007bff, #0056b3);
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-item.upload-error .upload-progress-fill {
|
||||||
|
background: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-error,
|
||||||
|
.upload-progress-success {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-error {
|
||||||
|
color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-success {
|
||||||
|
color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-error i,
|
||||||
|
.upload-progress-success i {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Minicard specific styles */
|
||||||
|
.minicard-upload-progress {
|
||||||
|
margin: 4px 0;
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minicard-upload-progress .upload-progress-item {
|
||||||
|
padding: 6px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minicard-upload-progress .upload-progress-filename {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card details specific styles */
|
||||||
|
.card-details-upload-progress {
|
||||||
|
margin: 12px 0;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-details-upload-progress .upload-progress-header {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-details-upload-progress .upload-progress-item {
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-details-upload-progress .upload-progress-filename {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drag over state for minicards */
|
||||||
|
.minicard.is-dragging-over {
|
||||||
|
border: 2px dashed #007bff !important;
|
||||||
|
background: rgba(0, 123, 255, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drag over state for card details */
|
||||||
|
.js-card-details.is-dragging-over {
|
||||||
|
border: 2px dashed #007bff !important;
|
||||||
|
background: rgba(0, 123, 255, 0.05) !important;
|
||||||
|
}
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { ReactiveCache } from '/imports/reactiveCache';
|
import { ReactiveCache } from '/imports/reactiveCache';
|
||||||
import { ObjectID } from 'bson';
|
import { ObjectID } from 'bson';
|
||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
|
import uploadProgressManager from '/client/lib/uploadProgressManager';
|
||||||
|
|
||||||
const filesize = require('filesize');
|
const filesize = require('filesize');
|
||||||
const prettyMilliseconds = require('pretty-ms');
|
const prettyMilliseconds = require('pretty-ms');
|
||||||
|
|
@ -333,13 +334,17 @@ export function handleFileUpload(card, files) {
|
||||||
// Check if board allows attachments
|
// Check if board allows attachments
|
||||||
const board = card.board();
|
const board = card.board();
|
||||||
if (!board || !board.allowsAttachments) {
|
if (!board || !board.allowsAttachments) {
|
||||||
console.warn('Attachments not allowed on this board');
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.warn('Attachments not allowed on this board');
|
||||||
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user can modify the card
|
// Check if user can modify the card
|
||||||
if (!card.canModifyCard()) {
|
if (!card.canModifyCard()) {
|
||||||
console.warn('User does not have permission to modify this card');
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.warn('User does not have permission to modify this card');
|
||||||
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -348,7 +353,9 @@ export function handleFileUpload(card, files) {
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
// Basic file validation
|
// Basic file validation
|
||||||
if (!file || !file.name) {
|
if (!file || !file.name) {
|
||||||
console.warn('Invalid file object');
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.warn('Invalid file object');
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -381,24 +388,36 @@ export function handleFileUpload(card, files) {
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Add to progress manager for tracking
|
||||||
|
const uploadId = uploadProgressManager.addUpload(card._id, uploader, file);
|
||||||
|
|
||||||
uploader.on('uploaded', (error, fileRef) => {
|
uploader.on('uploaded', (error, fileRef) => {
|
||||||
if (!error) {
|
if (!error) {
|
||||||
if (fileRef.isImage) {
|
if (fileRef.isImage) {
|
||||||
card.setCover(fileRef._id);
|
card.setCover(fileRef._id);
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Set cover image for card ${card._id}: ${fileRef.name}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('Upload error:', error);
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.error('Upload error:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
uploader.on('error', (error) => {
|
uploader.on('error', (error) => {
|
||||||
console.error('Upload error:', error);
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.error('Upload error:', error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
uploads.push(uploader);
|
uploads.push(uploader);
|
||||||
uploader.start();
|
uploader.start();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to create uploader:', error);
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.error('Failed to create uploader:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,26 @@ template(name="cardDetails")
|
||||||
else
|
else
|
||||||
p.warning {{_ 'card-archived'}}
|
p.warning {{_ 'card-archived'}}
|
||||||
|
|
||||||
|
// Upload progress indicator for drag-and-drop uploads
|
||||||
|
if hasActiveUploads
|
||||||
|
.card-details-upload-progress
|
||||||
|
.upload-progress-header
|
||||||
|
i.fa.fa-upload
|
||||||
|
span {{_ 'uploading-files'}} ({{uploadCount}})
|
||||||
|
each uploads
|
||||||
|
.upload-progress-item(class="{{#if $eq status 'error'}}upload-error{{/if}}")
|
||||||
|
.upload-progress-filename {{file.name}}
|
||||||
|
.upload-progress-bar
|
||||||
|
.upload-progress-fill(style="width: {{progress}}%")
|
||||||
|
if $eq status 'error'
|
||||||
|
.upload-progress-error
|
||||||
|
i.fa.fa-exclamation-triangle
|
||||||
|
span {{_ 'upload-failed'}}
|
||||||
|
else if $eq status 'completed'
|
||||||
|
.upload-progress-success
|
||||||
|
i.fa.fa-check
|
||||||
|
span {{_ 'upload-completed'}}
|
||||||
|
|
||||||
.card-details-left
|
.card-details-left
|
||||||
|
|
||||||
.card-details-items
|
.card-details-items
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { ALLOWED_COLORS } from '/config/const';
|
||||||
import { UserAvatar } from '../users/userAvatar';
|
import { UserAvatar } from '../users/userAvatar';
|
||||||
import { DialogWithBoardSwimlaneList } from '/client/lib/dialogWithBoardSwimlaneList';
|
import { DialogWithBoardSwimlaneList } from '/client/lib/dialogWithBoardSwimlaneList';
|
||||||
import { handleFileUpload } from './attachments';
|
import { handleFileUpload } from './attachments';
|
||||||
|
import uploadProgressManager from '/client/lib/uploadProgressManager';
|
||||||
|
|
||||||
const subManager = new SubsManager();
|
const subManager = new SubsManager();
|
||||||
const { calculateIndexData } = Utils;
|
const { calculateIndexData } = Utils;
|
||||||
|
|
@ -544,6 +545,16 @@ Template.cardDetails.helpers({
|
||||||
isPopup() {
|
isPopup() {
|
||||||
let ret = !!Utils.getPopupCardId();
|
let ret = !!Utils.getPopupCardId();
|
||||||
return ret;
|
return ret;
|
||||||
|
},
|
||||||
|
// Upload progress helpers
|
||||||
|
hasActiveUploads() {
|
||||||
|
return uploadProgressManager.hasActiveUploads(this._id);
|
||||||
|
},
|
||||||
|
uploads() {
|
||||||
|
return uploadProgressManager.getUploadsForCard(this._id);
|
||||||
|
},
|
||||||
|
uploadCount() {
|
||||||
|
return uploadProgressManager.getUploadCountForCard(this._id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Template.cardDetailsPopup.onDestroyed(() => {
|
Template.cardDetailsPopup.onDestroyed(() => {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,27 @@ template(name="minicard")
|
||||||
if cover
|
if cover
|
||||||
if currentBoard.allowsCoverAttachmentOnMinicard
|
if currentBoard.allowsCoverAttachmentOnMinicard
|
||||||
.minicard-cover(style="background-image: url('{{cover.link 'original'}}?dummyReloadAfterSessionEstablished={{sess}}');")
|
.minicard-cover(style="background-image: url('{{cover.link 'original'}}?dummyReloadAfterSessionEstablished={{sess}}');")
|
||||||
|
|
||||||
|
// Upload progress indicator for drag-and-drop uploads
|
||||||
|
if hasActiveUploads
|
||||||
|
.minicard-upload-progress
|
||||||
|
.upload-progress-header
|
||||||
|
i.fa.fa-upload
|
||||||
|
span {{_ 'uploading-files'}} ({{uploadCount}})
|
||||||
|
each uploads
|
||||||
|
.upload-progress-item(class="{{#if $eq status 'error'}}upload-error{{/if}}")
|
||||||
|
.upload-progress-filename {{file.name}}
|
||||||
|
.upload-progress-bar
|
||||||
|
.upload-progress-fill(style="width: {{progress}}%")
|
||||||
|
if $eq status 'error'
|
||||||
|
.upload-progress-error
|
||||||
|
i.fa.fa-exclamation-triangle
|
||||||
|
span {{_ 'upload-failed'}}
|
||||||
|
else if $eq status 'completed'
|
||||||
|
.upload-progress-success
|
||||||
|
i.fa.fa-check
|
||||||
|
span {{_ 'upload-completed'}}
|
||||||
|
|
||||||
.minicard-title
|
.minicard-title
|
||||||
if $eq 'prefix-with-full-path' currentBoard.presentParentTask
|
if $eq 'prefix-with-full-path' currentBoard.presentParentTask
|
||||||
.parent-prefix
|
.parent-prefix
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { ReactiveCache } from '/imports/reactiveCache';
|
||||||
import { TAPi18n } from '/imports/i18n';
|
import { TAPi18n } from '/imports/i18n';
|
||||||
import { CustomFieldStringTemplate } from '/client/lib/customFields';
|
import { CustomFieldStringTemplate } from '/client/lib/customFields';
|
||||||
import { handleFileUpload } from './attachments';
|
import { handleFileUpload } from './attachments';
|
||||||
|
import uploadProgressManager from '/client/lib/uploadProgressManager';
|
||||||
|
|
||||||
// Template.cards.events({
|
// Template.cards.events({
|
||||||
// 'click .member': Popup.open('cardMember')
|
// 'click .member': Popup.open('cardMember')
|
||||||
|
|
@ -185,6 +186,16 @@ Template.minicard.helpers({
|
||||||
},
|
},
|
||||||
isWatching() {
|
isWatching() {
|
||||||
return this.findWatcher(Meteor.userId());
|
return this.findWatcher(Meteor.userId());
|
||||||
|
},
|
||||||
|
// Upload progress helpers
|
||||||
|
hasActiveUploads() {
|
||||||
|
return uploadProgressManager.hasActiveUploads(this._id);
|
||||||
|
},
|
||||||
|
uploads() {
|
||||||
|
return uploadProgressManager.getUploadsForCard(this._id);
|
||||||
|
},
|
||||||
|
uploadCount() {
|
||||||
|
return uploadProgressManager.getUploadCountForCard(this._id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
177
client/lib/uploadProgressManager.js
Normal file
177
client/lib/uploadProgressManager.js
Normal file
|
|
@ -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;
|
||||||
|
|
||||||
|
|
@ -631,6 +631,9 @@
|
||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"upload-avatar": "Upload an avatar",
|
"upload-avatar": "Upload an avatar",
|
||||||
"uploaded-avatar": "Uploaded an avatar",
|
"uploaded-avatar": "Uploaded an avatar",
|
||||||
|
"uploading-files": "Uploading files",
|
||||||
|
"upload-failed": "Upload failed",
|
||||||
|
"upload-completed": "Upload completed",
|
||||||
"custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL",
|
"custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL",
|
||||||
"custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL",
|
"custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL",
|
||||||
"custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27",
|
"custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue