cloud-ide-element 1.0.80 → 1.0.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/cloud-ide-element.mjs +1602 -1469
- package/fesm2022/cloud-ide-element.mjs.map +1 -1
- package/index.d.ts +50 -24
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i1 from '@angular/common';
|
|
2
2
|
import { CommonModule, NgTemplateOutlet } from '@angular/common';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { Pipe, Injectable, inject, EventEmitter, ViewContainerRef, forwardRef, ViewChild, Output, Input, Component, HostListener, ContentChildren, signal, DestroyRef, computed, afterRenderEffect, afterNextRender,
|
|
4
|
+
import { Pipe, Injectable, inject, EventEmitter, ViewContainerRef, forwardRef, ViewChild, Output, Input, Component, HostListener, ContentChildren, signal, DestroyRef, computed, effect, afterRenderEffect, afterNextRender, ElementRef, Directive, viewChild } from '@angular/core';
|
|
5
5
|
import * as i2 from '@angular/forms';
|
|
6
6
|
import { FormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
|
|
7
7
|
import { BehaviorSubject, Subject, debounceTime, takeUntil, distinctUntilChanged, Observable, retry, catchError, finalize, throwError } from 'rxjs';
|
|
@@ -3148,1583 +3148,1644 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
3148
3148
|
}]
|
|
3149
3149
|
}], ctorParameters: () => [] });
|
|
3150
3150
|
|
|
3151
|
-
class
|
|
3152
|
-
fileManagerService = inject(CideEleFileManagerService);
|
|
3153
|
-
notificationService = inject(NotificationService);
|
|
3154
|
-
elementService = inject(CideElementsService);
|
|
3151
|
+
class CideEleFloatingFileUploaderComponent {
|
|
3155
3152
|
destroyRef = inject(DestroyRef);
|
|
3156
|
-
|
|
3157
|
-
//
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
uploadProgressChange = new EventEmitter();
|
|
3179
|
-
// Readable signals created from @Input() decorator values
|
|
3180
|
-
labelSignal = signal(this.label, ...(ngDevMode ? [{ debugName: "labelSignal" }] : []));
|
|
3181
|
-
acceptSignal = signal(this.accept, ...(ngDevMode ? [{ debugName: "acceptSignal" }] : []));
|
|
3182
|
-
multipleSignal = signal(this.multiple, ...(ngDevMode ? [{ debugName: "multipleSignal" }] : []));
|
|
3183
|
-
disabledSignal = signal(this.disabled, ...(ngDevMode ? [{ debugName: "disabledSignal" }] : []));
|
|
3184
|
-
requiredSignal = signal(this.required, ...(ngDevMode ? [{ debugName: "requiredSignal" }] : []));
|
|
3185
|
-
helperTextSignal = signal(this.helperText, ...(ngDevMode ? [{ debugName: "helperTextSignal" }] : []));
|
|
3186
|
-
errorTextSignal = signal(this.errorText, ...(ngDevMode ? [{ debugName: "errorTextSignal" }] : []));
|
|
3187
|
-
showPreviewSignal = signal(this.showPreview, ...(ngDevMode ? [{ debugName: "showPreviewSignal" }] : []));
|
|
3188
|
-
previewWidthSignal = signal(this.previewWidth, ...(ngDevMode ? [{ debugName: "previewWidthSignal" }] : []));
|
|
3189
|
-
previewHeightSignal = signal(this.previewHeight, ...(ngDevMode ? [{ debugName: "previewHeightSignal" }] : []));
|
|
3190
|
-
previewBoxModeSignal = signal(this.previewBoxMode, ...(ngDevMode ? [{ debugName: "previewBoxModeSignal" }] : []));
|
|
3191
|
-
showFileNameSignal = signal(this.showFileName, ...(ngDevMode ? [{ debugName: "showFileNameSignal" }] : []));
|
|
3192
|
-
placeholderTextSignal = signal(this.placeholderText, ...(ngDevMode ? [{ debugName: "placeholderTextSignal" }] : []));
|
|
3193
|
-
placeholderIconSignal = signal(this.placeholderIcon, ...(ngDevMode ? [{ debugName: "placeholderIconSignal" }] : []));
|
|
3194
|
-
autoUploadSignal = signal(this.autoUpload, ...(ngDevMode ? [{ debugName: "autoUploadSignal" }] : []));
|
|
3195
|
-
uploadDataSignal = signal(this.uploadData, ...(ngDevMode ? [{ debugName: "uploadDataSignal" }] : []));
|
|
3196
|
-
// Reactive state with signals
|
|
3197
|
-
id = signal(Math.random().toString(36).substring(2, 10), ...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
3198
|
-
isUploading = signal(false, ...(ngDevMode ? [{ debugName: "isUploading" }] : []));
|
|
3199
|
-
uploadProgress = signal(0, ...(ngDevMode ? [{ debugName: "uploadProgress" }] : []));
|
|
3200
|
-
uploadStatus = signal('idle', ...(ngDevMode ? [{ debugName: "uploadStatus" }] : []));
|
|
3201
|
-
files = signal(null, ...(ngDevMode ? [{ debugName: "files" }] : []));
|
|
3202
|
-
fileNames = signal([], ...(ngDevMode ? [{ debugName: "fileNames" }] : []));
|
|
3203
|
-
previewUrls = signal([], ...(ngDevMode ? [{ debugName: "previewUrls" }] : []));
|
|
3204
|
-
uploadNotificationId = signal(null, ...(ngDevMode ? [{ debugName: "uploadNotificationId" }] : []));
|
|
3205
|
-
isDragOver = signal(false, ...(ngDevMode ? [{ debugName: "isDragOver" }] : []));
|
|
3206
|
-
groupId = signal(null, ...(ngDevMode ? [{ debugName: "groupId" }] : [])); // Group ID for multiple file uploads
|
|
3207
|
-
isMultipleUploadMode = signal(false, ...(ngDevMode ? [{ debugName: "isMultipleUploadMode" }] : [])); // Flag to track if we're in multiple upload mode
|
|
3208
|
-
// Computed signals for better relationships
|
|
3209
|
-
hasFiles = computed(() => this.files() !== null && this.files().length > 0, ...(ngDevMode ? [{ debugName: "hasFiles" }] : []));
|
|
3210
|
-
canUpload = computed(() => this.hasFiles() && !this.isUploading() && !this.disabledSignal(), ...(ngDevMode ? [{ debugName: "canUpload" }] : []));
|
|
3211
|
-
isInErrorState = computed(() => this.uploadStatus() === 'error', ...(ngDevMode ? [{ debugName: "isInErrorState" }] : []));
|
|
3212
|
-
isInSuccessState = computed(() => this.uploadStatus() === 'success', ...(ngDevMode ? [{ debugName: "isInSuccessState" }] : []));
|
|
3213
|
-
// Angular 20: Computed values using new features
|
|
3214
|
-
totalFileSize = computed(() => {
|
|
3215
|
-
if (!this.files())
|
|
3216
|
-
return 0;
|
|
3217
|
-
return Array.from(this.files()).reduce((total, file) => total + file.size, 0);
|
|
3218
|
-
}, ...(ngDevMode ? [{ debugName: "totalFileSize" }] : []));
|
|
3219
|
-
fileSizeInMB = computed(() => {
|
|
3220
|
-
// Angular 20: Using ** operator for exponentiation
|
|
3221
|
-
return (this.totalFileSize() / (1024 ** 2)).toFixed(2);
|
|
3222
|
-
}, ...(ngDevMode ? [{ debugName: "fileSizeInMB" }] : []));
|
|
3223
|
-
uploadProgressPercentage = computed(() => {
|
|
3224
|
-
// Angular 20: Using ** operator for exponentiation
|
|
3225
|
-
return Math.round(this.uploadProgress() ** 1); // Simple power operation
|
|
3226
|
-
}, ...(ngDevMode ? [{ debugName: "uploadProgressPercentage" }] : []));
|
|
3227
|
-
// ControlValueAccessor callbacks
|
|
3228
|
-
onChange = (value) => { };
|
|
3229
|
-
onTouched = () => { };
|
|
3230
|
-
onValidatorChange = () => { };
|
|
3231
|
-
// Computed values
|
|
3232
|
-
hasImages = computed(() => this.previewUrls().length > 0, ...(ngDevMode ? [{ debugName: "hasImages" }] : []));
|
|
3233
|
-
isPreviewBoxMode = computed(() => this.previewBoxModeSignal() && this.showPreviewSignal(), ...(ngDevMode ? [{ debugName: "isPreviewBoxMode" }] : []));
|
|
3234
|
-
isImagePreviewAvailable = computed(() => this.showPreviewSignal() && this.previewUrls().length > 0, ...(ngDevMode ? [{ debugName: "isImagePreviewAvailable" }] : []));
|
|
3153
|
+
fileManagerService = inject(CideEleFileManagerService);
|
|
3154
|
+
// Signals for reactive state
|
|
3155
|
+
isVisible = signal(false, ...(ngDevMode ? [{ debugName: "isVisible" }] : []));
|
|
3156
|
+
isMinimized = signal(false, ...(ngDevMode ? [{ debugName: "isMinimized" }] : []));
|
|
3157
|
+
currentUserId = signal('', ...(ngDevMode ? [{ debugName: "currentUserId" }] : []));
|
|
3158
|
+
currentGroupId = signal(null, ...(ngDevMode ? [{ debugName: "currentGroupId" }] : []));
|
|
3159
|
+
// Use file manager service's upload queue as the single source of truth
|
|
3160
|
+
uploadQueue = computed(() => this.fileManagerService.uploadQueue(), ...(ngDevMode ? [{ debugName: "uploadQueue" }] : []));
|
|
3161
|
+
activeUploads = computed(() => this.fileManagerService.activeUploads(), ...(ngDevMode ? [{ debugName: "activeUploads" }] : []));
|
|
3162
|
+
// Computed values based on file manager service state
|
|
3163
|
+
hasUploads = computed(() => this.uploadQueue().length > 0, ...(ngDevMode ? [{ debugName: "hasUploads" }] : []));
|
|
3164
|
+
pendingUploads = computed(() => this.uploadQueue().length > 0, ...(ngDevMode ? [{ debugName: "pendingUploads" }] : [])); // All files in queue are pending
|
|
3165
|
+
completedUploads = computed(() => {
|
|
3166
|
+
const active = this.activeUploads();
|
|
3167
|
+
return Array.from(active.values()).filter(upload => upload.stage === 'complete');
|
|
3168
|
+
}, ...(ngDevMode ? [{ debugName: "completedUploads" }] : []));
|
|
3169
|
+
failedUploads = computed(() => {
|
|
3170
|
+
const active = this.activeUploads();
|
|
3171
|
+
return Array.from(active.values()).filter(upload => upload.stage === 'error');
|
|
3172
|
+
}, ...(ngDevMode ? [{ debugName: "failedUploads" }] : []));
|
|
3173
|
+
// Animation states
|
|
3174
|
+
isAnimating = signal(false, ...(ngDevMode ? [{ debugName: "isAnimating" }] : []));
|
|
3235
3175
|
constructor() {
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3176
|
+
console.log('🚀 [FloatingFileUploader] Component initialized');
|
|
3177
|
+
// Set up effect to monitor file manager service's upload queue
|
|
3178
|
+
effect(() => {
|
|
3179
|
+
const uploadQueue = this.fileManagerService.uploadQueue();
|
|
3180
|
+
const activeUploads = this.fileManagerService.activeUploads();
|
|
3181
|
+
const hasUploads = uploadQueue.length > 0 || activeUploads.size > 0;
|
|
3182
|
+
console.log('🔄 [FloatingFileUploader] File manager service queue changed:', {
|
|
3183
|
+
queueLength: uploadQueue.length,
|
|
3184
|
+
activeUploadsCount: activeUploads.size,
|
|
3185
|
+
hasUploads,
|
|
3186
|
+
currentVisible: this.isVisible(),
|
|
3187
|
+
queueFiles: uploadQueue
|
|
3188
|
+
});
|
|
3189
|
+
// Show floating uploader when files are added to the file manager service queue
|
|
3190
|
+
// But don't auto-hide when uploads complete - let user manually close
|
|
3191
|
+
if (hasUploads && !this.isVisible()) {
|
|
3192
|
+
console.log('👁️ [FloatingFileUploader] Showing floating uploader due to file manager queue');
|
|
3193
|
+
this.showWithAnimation();
|
|
3245
3194
|
}
|
|
3246
|
-
|
|
3247
|
-
// Angular 20: afterNextRender for one-time DOM operations
|
|
3248
|
-
afterNextRender(() => {
|
|
3249
|
-
console.log('🎯 [FileInput] Component rendered and DOM is ready');
|
|
3195
|
+
// Note: Removed auto-hide logic - floating uploader stays visible until manually closed
|
|
3250
3196
|
});
|
|
3251
3197
|
}
|
|
3252
3198
|
ngOnInit() {
|
|
3253
|
-
//
|
|
3254
|
-
this.
|
|
3255
|
-
|
|
3256
|
-
this.
|
|
3257
|
-
this.disabledSignal.set(this.disabled);
|
|
3258
|
-
this.requiredSignal.set(this.required);
|
|
3259
|
-
this.helperTextSignal.set(this.helperText);
|
|
3260
|
-
this.errorTextSignal.set(this.errorText);
|
|
3261
|
-
this.showPreviewSignal.set(this.showPreview);
|
|
3262
|
-
this.previewWidthSignal.set(this.previewWidth);
|
|
3263
|
-
this.previewHeightSignal.set(this.previewHeight);
|
|
3264
|
-
this.previewBoxModeSignal.set(this.previewBoxMode);
|
|
3265
|
-
this.showFileNameSignal.set(this.showFileName);
|
|
3266
|
-
this.placeholderTextSignal.set(this.placeholderText);
|
|
3267
|
-
this.placeholderIconSignal.set(this.placeholderIcon);
|
|
3268
|
-
this.autoUploadSignal.set(this.autoUpload);
|
|
3269
|
-
this.uploadDataSignal.set(this.uploadData);
|
|
3199
|
+
// Set up drag and drop listeners
|
|
3200
|
+
this.setupDragAndDrop();
|
|
3201
|
+
// Set up file input change listeners
|
|
3202
|
+
this.setupFileInputListeners();
|
|
3270
3203
|
}
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
if (changes['accept'])
|
|
3276
|
-
this.acceptSignal.set(this.accept);
|
|
3277
|
-
if (changes['multiple'])
|
|
3278
|
-
this.multipleSignal.set(this.multiple);
|
|
3279
|
-
if (changes['disabled'])
|
|
3280
|
-
this.disabledSignal.set(this.disabled);
|
|
3281
|
-
if (changes['required'])
|
|
3282
|
-
this.requiredSignal.set(this.required);
|
|
3283
|
-
if (changes['helperText'])
|
|
3284
|
-
this.helperTextSignal.set(this.helperText);
|
|
3285
|
-
if (changes['errorText'])
|
|
3286
|
-
this.errorTextSignal.set(this.errorText);
|
|
3287
|
-
if (changes['showPreview'])
|
|
3288
|
-
this.showPreviewSignal.set(this.showPreview);
|
|
3289
|
-
if (changes['previewWidth'])
|
|
3290
|
-
this.previewWidthSignal.set(this.previewWidth);
|
|
3291
|
-
if (changes['previewHeight'])
|
|
3292
|
-
this.previewHeightSignal.set(this.previewHeight);
|
|
3293
|
-
if (changes['previewBoxMode'])
|
|
3294
|
-
this.previewBoxModeSignal.set(this.previewBoxMode);
|
|
3295
|
-
if (changes['showFileName'])
|
|
3296
|
-
this.showFileNameSignal.set(this.showFileName);
|
|
3297
|
-
if (changes['placeholderText'])
|
|
3298
|
-
this.placeholderTextSignal.set(this.placeholderText);
|
|
3299
|
-
if (changes['placeholderIcon'])
|
|
3300
|
-
this.placeholderIconSignal.set(this.placeholderIcon);
|
|
3301
|
-
if (changes['autoUpload'])
|
|
3302
|
-
this.autoUploadSignal.set(this.autoUpload);
|
|
3303
|
-
if (changes['uploadData'])
|
|
3304
|
-
this.uploadDataSignal.set(this.uploadData);
|
|
3204
|
+
ngOnDestroy() {
|
|
3205
|
+
console.log('🧹 [FloatingFileUploader] Component destroyed');
|
|
3206
|
+
this.removeDragAndDropListeners();
|
|
3207
|
+
this.removeFileInputListeners();
|
|
3305
3208
|
}
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
this.clearPreviews();
|
|
3314
|
-
// Fetch file details to get base64 and set preview
|
|
3315
|
-
this.loadFileDetailsFromId(value);
|
|
3316
|
-
}
|
|
3317
|
-
else if (value instanceof FileList) {
|
|
3318
|
-
// Value is a FileList
|
|
3319
|
-
console.log('📝 [FileInput] Value is FileList:', Array.from(value).map(f => f.name));
|
|
3320
|
-
this.files.set(value);
|
|
3321
|
-
this.fileNames.set(Array.from(value).map(f => f.name));
|
|
3322
|
-
this.generatePreviews();
|
|
3323
|
-
}
|
|
3324
|
-
else {
|
|
3325
|
-
// Value is null
|
|
3326
|
-
console.log('📝 [FileInput] Value is null');
|
|
3327
|
-
this.files.set(null);
|
|
3328
|
-
this.fileNames.set([]);
|
|
3329
|
-
this.clearPreviews();
|
|
3330
|
-
}
|
|
3209
|
+
/**
|
|
3210
|
+
* Set up drag and drop functionality
|
|
3211
|
+
*/
|
|
3212
|
+
setupDragAndDrop() {
|
|
3213
|
+
document.addEventListener('dragover', this.handleDragOver.bind(this));
|
|
3214
|
+
document.addEventListener('dragleave', this.handleDragLeave.bind(this));
|
|
3215
|
+
document.addEventListener('drop', this.handleDrop.bind(this));
|
|
3331
3216
|
}
|
|
3332
|
-
|
|
3333
|
-
|
|
3217
|
+
/**
|
|
3218
|
+
* Remove drag and drop listeners
|
|
3219
|
+
*/
|
|
3220
|
+
removeDragAndDropListeners() {
|
|
3221
|
+
document.removeEventListener('dragover', this.handleDragOver.bind(this));
|
|
3222
|
+
document.removeEventListener('dragleave', this.handleDragLeave.bind(this));
|
|
3223
|
+
document.removeEventListener('drop', this.handleDrop.bind(this));
|
|
3334
3224
|
}
|
|
3335
|
-
|
|
3336
|
-
|
|
3225
|
+
/**
|
|
3226
|
+
* Set up file input change listeners
|
|
3227
|
+
*/
|
|
3228
|
+
setupFileInputListeners() {
|
|
3229
|
+
// Listen for file input change events globally
|
|
3230
|
+
document.addEventListener('change', this.handleFileInputChange.bind(this));
|
|
3337
3231
|
}
|
|
3338
|
-
|
|
3339
|
-
|
|
3232
|
+
/**
|
|
3233
|
+
* Remove file input listeners
|
|
3234
|
+
*/
|
|
3235
|
+
removeFileInputListeners() {
|
|
3236
|
+
document.removeEventListener('change', this.handleFileInputChange.bind(this));
|
|
3340
3237
|
}
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
this
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
this.uploadMultipleFiles(Array.from(selectedFiles));
|
|
3365
|
-
}
|
|
3366
|
-
else {
|
|
3367
|
-
console.log('🚀 [FileInput] Auto upload enabled for single file mode:', selectedFiles[0].name);
|
|
3368
|
-
this.uploadFile(selectedFiles[0]);
|
|
3369
|
-
}
|
|
3370
|
-
}
|
|
3371
|
-
else {
|
|
3372
|
-
console.log('⏸️ [FileInput] Auto upload disabled or no files');
|
|
3373
|
-
}
|
|
3374
|
-
}
|
|
3375
|
-
clearFiles() {
|
|
3376
|
-
console.log('🗑️ [FileInput] clearFiles called');
|
|
3377
|
-
this.files.set(null);
|
|
3378
|
-
this.fileNames.set([]);
|
|
3379
|
-
this.clearPreviews();
|
|
3380
|
-
this.uploadStatus.set('idle');
|
|
3381
|
-
console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus());
|
|
3382
|
-
this.onChange(null);
|
|
3383
|
-
this.fileChange.emit(null);
|
|
3384
|
-
}
|
|
3385
|
-
uploadFile(file) {
|
|
3386
|
-
console.log('📤 [FileInput] uploadFile called for:', file.name, 'Size:', file.size, 'bytes');
|
|
3387
|
-
// Angular 20: Use PendingTasks for better loading state management
|
|
3388
|
-
// const uploadTask = this.pendingTasks.add(); // TODO: Fix PendingTasks API usage
|
|
3389
|
-
// console.log('⏳ [FileInput] Pending task added for upload tracking');
|
|
3390
|
-
// Set upload status to 'start' before starting upload
|
|
3391
|
-
this.uploadStatus.set('start');
|
|
3392
|
-
console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
|
|
3393
|
-
this.isUploading.set(true);
|
|
3394
|
-
this.uploadProgress.set(0);
|
|
3395
|
-
this.uploadProgressChange.emit(0);
|
|
3396
|
-
console.log('📊 [FileInput] Upload progress initialized to 0%');
|
|
3397
|
-
// Make form control invalid during upload - this prevents form submission
|
|
3398
|
-
this.onChange(null);
|
|
3399
|
-
console.log('🚫 [FileInput] Form control value set to null to prevent submission');
|
|
3400
|
-
// Show initial progress notification with spinner (persistent - no auto-dismiss)
|
|
3401
|
-
const notificationId = this.notificationService.showProgress('🔄 Preparing file upload...', 0, { duration: 0 });
|
|
3402
|
-
this.uploadNotificationId.set(notificationId);
|
|
3403
|
-
console.log('🔔 [FileInput] Progress notification started with ID:', notificationId);
|
|
3404
|
-
this.fileManagerService.uploadFile(file, this.uploadDataSignal(), (progress) => {
|
|
3405
|
-
// Real progress callback from file manager service
|
|
3406
|
-
this.uploadProgress.set(progress);
|
|
3407
|
-
this.uploadProgressChange.emit(progress);
|
|
3408
|
-
console.log('📈 [FileInput] Upload progress:', Math.round(progress) + '%');
|
|
3409
|
-
// Set upload status to 'uploading' when progress starts
|
|
3410
|
-
if (this.uploadStatus() === 'start') {
|
|
3411
|
-
this.uploadStatus.set('uploading');
|
|
3412
|
-
console.log('🔄 [FileInput] Upload status changed to:', this.uploadStatus());
|
|
3413
|
-
}
|
|
3414
|
-
// Update progress notification with spinner
|
|
3415
|
-
const notificationId = this.uploadNotificationId();
|
|
3416
|
-
if (notificationId) {
|
|
3417
|
-
let progressMessage = '';
|
|
3418
|
-
if (progress < 10) {
|
|
3419
|
-
progressMessage = '🔄 Starting upload...';
|
|
3420
|
-
}
|
|
3421
|
-
else if (progress < 25) {
|
|
3422
|
-
progressMessage = '🔄 Uploading file...';
|
|
3423
|
-
}
|
|
3424
|
-
else if (progress < 50) {
|
|
3425
|
-
progressMessage = '🔄 Upload in progress...';
|
|
3426
|
-
}
|
|
3427
|
-
else if (progress < 75) {
|
|
3428
|
-
progressMessage = '🔄 Almost done...';
|
|
3429
|
-
}
|
|
3430
|
-
else if (progress < 95) {
|
|
3431
|
-
progressMessage = '🔄 Finishing upload...';
|
|
3432
|
-
}
|
|
3433
|
-
else {
|
|
3434
|
-
progressMessage = '🔄 Finalizing...';
|
|
3435
|
-
}
|
|
3436
|
-
this.notificationService.updateProgress(notificationId, progress, progressMessage);
|
|
3437
|
-
}
|
|
3438
|
-
}).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
|
3439
|
-
next: (response) => {
|
|
3440
|
-
console.log('🎉 [FileInput] Upload SUCCESS - Response received:', response);
|
|
3441
|
-
// Angular 20: Complete the pending task
|
|
3442
|
-
// this.pendingTasks.complete(uploadTask); // TODO: Fix PendingTasks API usage
|
|
3443
|
-
// console.log('✅ [FileInput] Pending task completed for successful upload');
|
|
3444
|
-
// Set upload status to 'success'
|
|
3445
|
-
this.uploadStatus.set('success');
|
|
3446
|
-
console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
|
|
3447
|
-
// Complete the progress
|
|
3448
|
-
this.uploadProgress.set(100);
|
|
3449
|
-
this.uploadProgressChange.emit(100);
|
|
3450
|
-
console.log('📊 [FileInput] Upload progress completed: 100%');
|
|
3451
|
-
// Update progress notification to complete
|
|
3452
|
-
const notificationId = this.uploadNotificationId();
|
|
3453
|
-
if (notificationId) {
|
|
3454
|
-
this.notificationService.remove(notificationId);
|
|
3455
|
-
console.log('🔔 [FileInput] Progress notification removed');
|
|
3456
|
-
}
|
|
3457
|
-
this.notificationService.success('✅ File uploaded successfully!', { duration: 0 });
|
|
3458
|
-
this.uploadNotificationId.set(null);
|
|
3459
|
-
// Extract ID from CoreFileManagerInsertUpdateResponse
|
|
3460
|
-
const uploadedId = response?.data?.core_file_manager?.[0]?.cyfm_id;
|
|
3461
|
-
if (uploadedId) {
|
|
3462
|
-
console.log('✅ [FileInput] File uploaded successfully with ID:', uploadedId);
|
|
3463
|
-
// Set the uploaded ID as the form control value
|
|
3464
|
-
this.onChange(uploadedId);
|
|
3465
|
-
console.log('📝 [FileInput] Form control value set to uploaded ID:', uploadedId);
|
|
3466
|
-
// Only emit individual uploadSuccess if not in multiple upload mode
|
|
3467
|
-
if (!this.isMultipleUploadMode()) {
|
|
3468
|
-
this.uploadSuccess.emit(uploadedId);
|
|
3469
|
-
console.log('📝 [FileInput] Upload success event emitted with file ID:', uploadedId);
|
|
3470
|
-
}
|
|
3471
|
-
else {
|
|
3472
|
-
console.log('📝 [FileInput] Individual upload success suppressed (multiple upload mode) - file ID:', uploadedId);
|
|
3473
|
-
}
|
|
3474
|
-
}
|
|
3475
|
-
else {
|
|
3476
|
-
console.error('❌ [FileInput] Upload successful but no ID returned:', response);
|
|
3477
|
-
this.uploadError.emit('Upload successful but no ID returned');
|
|
3478
|
-
}
|
|
3479
|
-
this.isUploading.set(false);
|
|
3480
|
-
console.log('🔄 [FileInput] isUploading set to false');
|
|
3481
|
-
},
|
|
3482
|
-
error: (error) => {
|
|
3483
|
-
console.error('💥 [FileInput] Upload FAILED:', error);
|
|
3484
|
-
// Angular 20: Complete the pending task even on error
|
|
3485
|
-
// this.pendingTasks.complete(uploadTask); // TODO: Fix PendingTasks API usage
|
|
3486
|
-
// console.log('❌ [FileInput] Pending task completed for failed upload');
|
|
3487
|
-
// Set upload status to 'error' and remove upload validation error
|
|
3488
|
-
this.uploadStatus.set('error');
|
|
3489
|
-
console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
|
|
3490
|
-
// Remove progress notification and show error
|
|
3491
|
-
const notificationId = this.uploadNotificationId();
|
|
3492
|
-
if (notificationId) {
|
|
3493
|
-
this.notificationService.remove(notificationId);
|
|
3494
|
-
console.log('🔔 [FileInput] Progress notification removed due to error');
|
|
3495
|
-
}
|
|
3496
|
-
this.notificationService.error(`❌ File upload failed: ${error.message || error.error?.message || 'Unknown error occurred'}`, { duration: 0 });
|
|
3497
|
-
this.uploadNotificationId.set(null);
|
|
3498
|
-
this.uploadError.emit(error.message || error.error?.message || 'Upload failed');
|
|
3499
|
-
this.isUploading.set(false);
|
|
3500
|
-
this.uploadProgress.set(0);
|
|
3501
|
-
this.uploadProgressChange.emit(0);
|
|
3502
|
-
console.log('🔄 [FileInput] Upload state reset - isUploading: false, progress: 0%');
|
|
3503
|
-
}
|
|
3504
|
-
});
|
|
3505
|
-
}
|
|
3506
|
-
/**
|
|
3507
|
-
* Upload multiple files with group ID support
|
|
3508
|
-
* FLOW: 1) Generate group ID first, 2) Upload all files with same group ID, 3) Emit group ID on completion
|
|
3509
|
-
*/
|
|
3510
|
-
uploadMultipleFiles(files) {
|
|
3511
|
-
console.log('📤 [FileInput] uploadMultipleFiles called for:', files.length, 'files');
|
|
3512
|
-
console.log('🔄 [FileInput] STEP 1: Generate group ID before starting any file uploads');
|
|
3513
|
-
// Set multiple upload mode flag
|
|
3514
|
-
this.isMultipleUploadMode.set(true);
|
|
3515
|
-
// Set upload status to 'start' before starting upload
|
|
3516
|
-
this.uploadStatus.set('start');
|
|
3517
|
-
this.isUploading.set(true);
|
|
3518
|
-
this.uploadProgress.set(0);
|
|
3519
|
-
this.uploadProgressChange.emit(0);
|
|
3520
|
-
// Make form control invalid during upload
|
|
3521
|
-
this.onChange(null);
|
|
3522
|
-
// Show initial progress notification
|
|
3523
|
-
const notificationId = this.notificationService.showProgress('🔄 Preparing multiple file upload...', 0, { duration: 0 });
|
|
3524
|
-
this.uploadNotificationId.set(notificationId);
|
|
3525
|
-
// STEP 1: Generate or get group ID BEFORE starting any file uploads
|
|
3526
|
-
const existingGroupId = this.uploadDataSignal().groupId;
|
|
3527
|
-
if (existingGroupId) {
|
|
3528
|
-
console.log('🆔 [FileInput] STEP 1 COMPLETE: Using existing group ID:', existingGroupId);
|
|
3529
|
-
console.log('🔄 [FileInput] STEP 2: Starting file uploads with group ID:', existingGroupId);
|
|
3530
|
-
this.groupId.set(existingGroupId);
|
|
3531
|
-
this.startMulti(files, existingGroupId);
|
|
3532
|
-
}
|
|
3533
|
-
else {
|
|
3534
|
-
console.log('🆔 [FileInput] No existing group ID, generating new one...');
|
|
3535
|
-
// Generate group ID BEFORE starting any file uploads
|
|
3536
|
-
this.fileManagerService.generateObjectId().subscribe({
|
|
3537
|
-
next: (response) => {
|
|
3538
|
-
const newGroupId = response.data?.objectId;
|
|
3539
|
-
console.log('🆔 [FileInput] STEP 1 COMPLETE: Generated new group ID:', newGroupId);
|
|
3540
|
-
console.log('🔄 [FileInput] STEP 2: Starting file uploads with group ID:', newGroupId);
|
|
3541
|
-
this.groupId.set(newGroupId);
|
|
3542
|
-
this.startMulti(files, newGroupId);
|
|
3543
|
-
},
|
|
3544
|
-
error: (error) => {
|
|
3545
|
-
console.error('❌ [FileInput] Failed to generate group ID:', error);
|
|
3546
|
-
this.uploadError.emit('Failed to generate group ID');
|
|
3547
|
-
this.isUploading.set(false);
|
|
3548
|
-
this.uploadStatus.set('error');
|
|
3549
|
-
const notificationId = this.uploadNotificationId();
|
|
3550
|
-
if (notificationId) {
|
|
3551
|
-
this.notificationService.remove(notificationId);
|
|
3552
|
-
}
|
|
3553
|
-
this.notificationService.error('❌ Failed to generate group ID for multiple file upload', { duration: 0 });
|
|
3554
|
-
this.uploadNotificationId.set(null);
|
|
3555
|
-
}
|
|
3556
|
-
});
|
|
3557
|
-
}
|
|
3238
|
+
/**
|
|
3239
|
+
* Handle file input change events
|
|
3240
|
+
*/
|
|
3241
|
+
handleFileInputChange(event) {
|
|
3242
|
+
const target = event.target;
|
|
3243
|
+
console.log('🔍 [FloatingFileUploader] File input change event detected:', {
|
|
3244
|
+
type: target.type,
|
|
3245
|
+
filesLength: target.files?.length || 0,
|
|
3246
|
+
element: target
|
|
3247
|
+
});
|
|
3248
|
+
// Check if this is a file input with files
|
|
3249
|
+
if (target.type === 'file' && target.files && target.files.length > 0) {
|
|
3250
|
+
console.log('📁 [FloatingFileUploader] File input change detected:', target.files.length, 'files');
|
|
3251
|
+
// Check if the input has a data-user-id attribute for user context
|
|
3252
|
+
const userId = target.getAttribute('data-user-id');
|
|
3253
|
+
if (userId && userId !== this.currentUserId()) {
|
|
3254
|
+
this.setCurrentUserId(userId);
|
|
3255
|
+
}
|
|
3256
|
+
// Handle the files
|
|
3257
|
+
this.handleFiles(Array.from(target.files));
|
|
3258
|
+
// Reset the input to allow selecting the same files again
|
|
3259
|
+
target.value = '';
|
|
3260
|
+
}
|
|
3558
3261
|
}
|
|
3559
3262
|
/**
|
|
3560
|
-
*
|
|
3561
|
-
* All files will be uploaded with the SAME group ID that was generated before this method
|
|
3263
|
+
* Handle drag over event
|
|
3562
3264
|
*/
|
|
3563
|
-
|
|
3564
|
-
console.log('🚀 [FileInput] STEP 2: Starting upload for', files.length, 'files with group ID:', groupId);
|
|
3565
|
-
console.log('📋 [FileInput] All files will use the same group ID:', groupId);
|
|
3566
|
-
let completedUploads = 0;
|
|
3567
|
-
let failedUploads = 0;
|
|
3568
|
-
const totalFiles = files.length;
|
|
3569
|
-
// IMPORTANT: All files use the SAME group ID that was generated before starting uploads
|
|
3570
|
-
const uploadDataWithGroupId = {
|
|
3571
|
-
...this.uploadDataSignal(),
|
|
3572
|
-
groupId: groupId
|
|
3573
|
-
};
|
|
3574
|
-
files.forEach((file, index) => {
|
|
3575
|
-
console.log(`📤 [FileInput] Uploading file ${index + 1}/${totalFiles}: "${file.name}" with group ID: ${groupId}`);
|
|
3576
|
-
this.fileManagerService.uploadFile(file, uploadDataWithGroupId, (progress) => {
|
|
3577
|
-
// Calculate overall progress
|
|
3578
|
-
const fileProgress = progress / totalFiles;
|
|
3579
|
-
const overallProgress = ((completedUploads * 100) + fileProgress) / totalFiles;
|
|
3580
|
-
this.uploadProgress.set(overallProgress);
|
|
3581
|
-
this.uploadProgressChange.emit(overallProgress);
|
|
3582
|
-
// Update progress notification
|
|
3583
|
-
const notificationId = this.uploadNotificationId();
|
|
3584
|
-
if (notificationId) {
|
|
3585
|
-
const progressMessage = `🔄 Uploading file ${index + 1} of ${totalFiles}...`;
|
|
3586
|
-
this.notificationService.updateProgress(notificationId, overallProgress, progressMessage);
|
|
3587
|
-
}
|
|
3588
|
-
}).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
|
3589
|
-
next: (response) => {
|
|
3590
|
-
completedUploads++;
|
|
3591
|
-
console.log(`✅ [FileInput] File ${index + 1}/${totalFiles} uploaded successfully`);
|
|
3592
|
-
// Check if all files are completed
|
|
3593
|
-
if (completedUploads + failedUploads === totalFiles) {
|
|
3594
|
-
this.handleMultipleUploadComplete(completedUploads, failedUploads, totalFiles, groupId);
|
|
3595
|
-
}
|
|
3596
|
-
},
|
|
3597
|
-
error: (error) => {
|
|
3598
|
-
failedUploads++;
|
|
3599
|
-
console.error(`❌ [FileInput] File ${index + 1}/${totalFiles} upload failed:`, error);
|
|
3600
|
-
// Check if all files are completed
|
|
3601
|
-
if (completedUploads + failedUploads === totalFiles) {
|
|
3602
|
-
this.handleMultipleUploadComplete(completedUploads, failedUploads, totalFiles, groupId);
|
|
3603
|
-
}
|
|
3604
|
-
}
|
|
3605
|
-
});
|
|
3606
|
-
});
|
|
3607
|
-
}
|
|
3608
|
-
/**
|
|
3609
|
-
* Handle completion of multiple file upload
|
|
3610
|
-
*/
|
|
3611
|
-
handleMultipleUploadComplete(completed, failed, total, groupId) {
|
|
3612
|
-
console.log(`📊 [FileInput] Multiple upload complete: ${completed}/${total} successful, ${failed} failed`);
|
|
3613
|
-
this.isUploading.set(false);
|
|
3614
|
-
this.uploadProgress.set(100);
|
|
3615
|
-
this.uploadProgressChange.emit(100);
|
|
3616
|
-
// Remove progress notification
|
|
3617
|
-
const notificationId = this.uploadNotificationId();
|
|
3618
|
-
if (notificationId) {
|
|
3619
|
-
this.notificationService.remove(notificationId);
|
|
3620
|
-
}
|
|
3621
|
-
this.uploadNotificationId.set(null);
|
|
3622
|
-
if (failed === 0) {
|
|
3623
|
-
// All files uploaded successfully
|
|
3624
|
-
this.uploadStatus.set('success');
|
|
3625
|
-
this.notificationService.success(`✅ All ${total} files uploaded successfully!`, { duration: 0 });
|
|
3626
|
-
// STEP 3: For multiple file upload, emit the group ID (not individual file IDs)
|
|
3627
|
-
this.onChange(groupId);
|
|
3628
|
-
this.uploadSuccess.emit(groupId);
|
|
3629
|
-
console.log('📝 [FileInput] STEP 3 COMPLETE: Form control value set to group ID:', groupId);
|
|
3630
|
-
console.log('✅ [FileInput] Multiple upload SUCCESS - Group ID emitted:', groupId);
|
|
3631
|
-
}
|
|
3632
|
-
else if (completed > 0) {
|
|
3633
|
-
// Some files uploaded successfully
|
|
3634
|
-
this.uploadStatus.set('error');
|
|
3635
|
-
this.notificationService.warning(`⚠️ ${completed}/${total} files uploaded. ${failed} failed.`, { duration: 0 });
|
|
3636
|
-
this.uploadError.emit(`${failed} out of ${total} files failed to upload`);
|
|
3637
|
-
}
|
|
3638
|
-
else {
|
|
3639
|
-
// All files failed
|
|
3640
|
-
this.uploadStatus.set('error');
|
|
3641
|
-
this.notificationService.error(`❌ All ${total} files failed to upload.`, { duration: 0 });
|
|
3642
|
-
this.uploadError.emit('All files failed to upload');
|
|
3643
|
-
}
|
|
3644
|
-
// Reset multiple upload mode flag
|
|
3645
|
-
this.isMultipleUploadMode.set(false);
|
|
3646
|
-
}
|
|
3647
|
-
generatePreviews() {
|
|
3648
|
-
// Clear existing previews
|
|
3649
|
-
this.clearPreviews();
|
|
3650
|
-
if (!this.showPreviewSignal() || !this.files()) {
|
|
3651
|
-
return;
|
|
3652
|
-
}
|
|
3653
|
-
Array.from(this.files()).forEach(file => {
|
|
3654
|
-
if (this.isImageFile(file)) {
|
|
3655
|
-
const reader = new FileReader();
|
|
3656
|
-
reader.onload = (e) => {
|
|
3657
|
-
if (e.target?.result) {
|
|
3658
|
-
this.previewUrls.update(urls => [...urls, e.target.result]);
|
|
3659
|
-
}
|
|
3660
|
-
};
|
|
3661
|
-
reader.readAsDataURL(file);
|
|
3662
|
-
}
|
|
3663
|
-
});
|
|
3664
|
-
}
|
|
3665
|
-
clearPreviews() {
|
|
3666
|
-
// Revoke object URLs to prevent memory leaks
|
|
3667
|
-
this.previewUrls().forEach(url => {
|
|
3668
|
-
if (url.startsWith('blob:')) {
|
|
3669
|
-
URL.revokeObjectURL(url);
|
|
3670
|
-
}
|
|
3671
|
-
});
|
|
3672
|
-
this.previewUrls.set([]);
|
|
3673
|
-
}
|
|
3674
|
-
isImageFile(file) {
|
|
3675
|
-
return file.type.startsWith('image/');
|
|
3676
|
-
}
|
|
3677
|
-
loadFileDetailsFromId(fileId) {
|
|
3678
|
-
console.log('🔍 [FileInput] Loading file details for ID:', fileId);
|
|
3679
|
-
if (!fileId)
|
|
3680
|
-
return;
|
|
3681
|
-
this.fileManagerService?.getFileDetails({ cyfm_id: fileId })?.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
|
3682
|
-
next: (fileDetails) => {
|
|
3683
|
-
console.log('📋 [FileInput] File details received:', fileDetails);
|
|
3684
|
-
if (fileDetails?.data?.length) {
|
|
3685
|
-
const fileData = fileDetails.data[0];
|
|
3686
|
-
console.log('📁 [FileInput] File data:', fileData);
|
|
3687
|
-
// Set file name from the details
|
|
3688
|
-
if (fileData.cyfm_name) {
|
|
3689
|
-
this.fileNames.set([fileData.cyfm_name]);
|
|
3690
|
-
console.log('📝 [FileInput] File name set:', fileData.cyfm_name);
|
|
3691
|
-
}
|
|
3692
|
-
// If it's an image and we have base64 data, set preview
|
|
3693
|
-
if (this.showPreviewSignal() && fileData.cyfm_file_base64) {
|
|
3694
|
-
// Check if it's an image file based on file name or type
|
|
3695
|
-
const isImage = this.isImageFileFromName(fileData.cyfm_name || '') ||
|
|
3696
|
-
this.isImageFileFromType(fileData.cyfm_type || '');
|
|
3697
|
-
if (isImage) {
|
|
3698
|
-
// Add data URL prefix if not already present
|
|
3699
|
-
let base64Data = fileData.cyfm_file_base64;
|
|
3700
|
-
if (!base64Data.startsWith('data:')) {
|
|
3701
|
-
const mimeType = fileData.cyfm_type || 'image/jpeg';
|
|
3702
|
-
base64Data = `data:${mimeType};base64,${base64Data}`;
|
|
3703
|
-
}
|
|
3704
|
-
this.previewUrls.set([base64Data]);
|
|
3705
|
-
console.log('🖼️ [FileInput] Preview set from base64 data');
|
|
3706
|
-
}
|
|
3707
|
-
}
|
|
3708
|
-
}
|
|
3709
|
-
else {
|
|
3710
|
-
console.warn('⚠️ [FileInput] No file data found for ID:', fileId);
|
|
3711
|
-
}
|
|
3712
|
-
},
|
|
3713
|
-
error: (error) => {
|
|
3714
|
-
console.error('❌ [FileInput] Error loading file details:', error);
|
|
3715
|
-
this.notificationService.error(`Failed to load file details: ${error.message || 'Unknown error'}`, { duration: 0 });
|
|
3716
|
-
}
|
|
3717
|
-
});
|
|
3718
|
-
}
|
|
3719
|
-
isImageFileFromName(fileName) {
|
|
3720
|
-
if (!fileName)
|
|
3721
|
-
return false;
|
|
3722
|
-
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg'];
|
|
3723
|
-
const lowerFileName = fileName.toLowerCase();
|
|
3724
|
-
return imageExtensions.some(ext => lowerFileName.endsWith(ext));
|
|
3725
|
-
}
|
|
3726
|
-
isImageFileFromType(fileType) {
|
|
3727
|
-
if (!fileType)
|
|
3728
|
-
return false;
|
|
3729
|
-
return fileType.startsWith('image/');
|
|
3730
|
-
}
|
|
3731
|
-
removePreview(index) {
|
|
3732
|
-
const currentFiles = this.files();
|
|
3733
|
-
const currentUrls = this.previewUrls();
|
|
3734
|
-
if (currentFiles && currentFiles.length > index) {
|
|
3735
|
-
// Handle FileList case - remove file from FileList
|
|
3736
|
-
const dt = new DataTransfer();
|
|
3737
|
-
Array.from(currentFiles).forEach((file, i) => {
|
|
3738
|
-
if (i !== index) {
|
|
3739
|
-
dt.items.add(file);
|
|
3740
|
-
}
|
|
3741
|
-
});
|
|
3742
|
-
const newFiles = dt.files;
|
|
3743
|
-
this.files.set(newFiles);
|
|
3744
|
-
this.fileNames.set(Array.from(newFiles).map(f => f.name));
|
|
3745
|
-
// Remove the preview URL
|
|
3746
|
-
if (currentUrls[index] && currentUrls[index].startsWith('blob:')) {
|
|
3747
|
-
URL.revokeObjectURL(currentUrls[index]);
|
|
3748
|
-
}
|
|
3749
|
-
this.previewUrls.update(urls => urls.filter((_, i) => i !== index));
|
|
3750
|
-
this.onChange(newFiles);
|
|
3751
|
-
this.fileChange.emit(newFiles);
|
|
3752
|
-
}
|
|
3753
|
-
else if (currentUrls.length > index) {
|
|
3754
|
-
// Handle uploaded file ID case - clear the preview and set control value to null
|
|
3755
|
-
console.log('🗑️ [FileInput] Removing preview for uploaded file ID');
|
|
3756
|
-
// Clear preview
|
|
3757
|
-
this.previewUrls.update(urls => urls.filter((_, i) => i !== index));
|
|
3758
|
-
this.fileNames.set([]);
|
|
3759
|
-
// Set control value to null since we're removing the uploaded file
|
|
3760
|
-
this.onChange(null);
|
|
3761
|
-
this.fileChange.emit(null);
|
|
3762
|
-
}
|
|
3763
|
-
}
|
|
3764
|
-
ngOnDestroy() {
|
|
3765
|
-
// Clean up preview URLs to prevent memory leaks
|
|
3766
|
-
this.clearPreviews();
|
|
3767
|
-
// Clean up any active upload notification
|
|
3768
|
-
const notificationId = this.uploadNotificationId();
|
|
3769
|
-
if (notificationId) {
|
|
3770
|
-
this.notificationService.remove(notificationId);
|
|
3771
|
-
this.uploadNotificationId.set(null);
|
|
3772
|
-
}
|
|
3773
|
-
}
|
|
3774
|
-
triggerFileSelect() {
|
|
3775
|
-
const fileInput = document.getElementById('cide-file-input-' + this.id());
|
|
3776
|
-
if (fileInput && !this.disabledSignal()) {
|
|
3777
|
-
fileInput.click();
|
|
3778
|
-
}
|
|
3779
|
-
}
|
|
3780
|
-
// Drag and Drop Event Handlers
|
|
3781
|
-
onDragOver(event) {
|
|
3782
|
-
event.preventDefault();
|
|
3783
|
-
event.stopPropagation();
|
|
3784
|
-
if (!this.disabledSignal()) {
|
|
3785
|
-
this.isDragOver.set(true);
|
|
3786
|
-
console.log('🔄 [FileInput] Drag over detected');
|
|
3787
|
-
}
|
|
3788
|
-
}
|
|
3789
|
-
onDragLeave(event) {
|
|
3790
|
-
event.preventDefault();
|
|
3791
|
-
event.stopPropagation();
|
|
3792
|
-
this.isDragOver.set(false);
|
|
3793
|
-
console.log('🔄 [FileInput] Drag leave detected');
|
|
3794
|
-
}
|
|
3795
|
-
onDragEnter(event) {
|
|
3796
|
-
event.preventDefault();
|
|
3797
|
-
event.stopPropagation();
|
|
3798
|
-
if (!this.disabledSignal()) {
|
|
3799
|
-
this.isDragOver.set(true);
|
|
3800
|
-
console.log('🔄 [FileInput] Drag enter detected');
|
|
3801
|
-
}
|
|
3802
|
-
}
|
|
3803
|
-
onDrop(event) {
|
|
3265
|
+
handleDragOver(event) {
|
|
3804
3266
|
event.preventDefault();
|
|
3805
|
-
event.stopPropagation();
|
|
3806
|
-
|
|
3807
|
-
if (
|
|
3808
|
-
|
|
3809
|
-
return;
|
|
3810
|
-
}
|
|
3811
|
-
const files = event.dataTransfer?.files;
|
|
3812
|
-
if (files && files.length > 0) {
|
|
3813
|
-
console.log('📁 [FileInput] Files dropped:', Array.from(files).map(f => f.name));
|
|
3814
|
-
// Validate file types if accept is specified
|
|
3815
|
-
if (this.acceptSignal() && !this.validateFileTypes(files)) {
|
|
3816
|
-
console.log('❌ [FileInput] Invalid file types dropped');
|
|
3817
|
-
this.notificationService.error('❌ Invalid file type. Please select files of the correct type.', { duration: 0 });
|
|
3818
|
-
return;
|
|
3819
|
-
}
|
|
3820
|
-
// Handle single vs multiple files
|
|
3821
|
-
if (!this.multipleSignal() && files.length > 1) {
|
|
3822
|
-
console.log('⚠️ [FileInput] Multiple files dropped but multiple is disabled');
|
|
3823
|
-
this.notificationService.warning('⚠️ Only one file is allowed. Using the first file.', { duration: 0 });
|
|
3824
|
-
// Create a new FileList with only the first file
|
|
3825
|
-
const dt = new DataTransfer();
|
|
3826
|
-
dt.items.add(files[0]);
|
|
3827
|
-
this.handleFileSelection(dt.files);
|
|
3828
|
-
}
|
|
3829
|
-
else {
|
|
3830
|
-
this.handleFileSelection(files);
|
|
3831
|
-
}
|
|
3832
|
-
}
|
|
3833
|
-
}
|
|
3834
|
-
validateFileTypes(files) {
|
|
3835
|
-
const acceptTypes = this.acceptSignal().split(',').map(type => type.trim());
|
|
3836
|
-
if (acceptTypes.length === 0 || acceptTypes[0] === '')
|
|
3837
|
-
return true;
|
|
3838
|
-
return Array.from(files).every(file => {
|
|
3839
|
-
return acceptTypes.some(acceptType => {
|
|
3840
|
-
if (acceptType.startsWith('.')) {
|
|
3841
|
-
// Extension-based validation
|
|
3842
|
-
return file.name.toLowerCase().endsWith(acceptType.toLowerCase());
|
|
3843
|
-
}
|
|
3844
|
-
else if (acceptType.includes('/')) {
|
|
3845
|
-
// MIME type validation
|
|
3846
|
-
return file.type === acceptType || file.type.startsWith(acceptType.replace('*', ''));
|
|
3847
|
-
}
|
|
3848
|
-
return false;
|
|
3849
|
-
});
|
|
3850
|
-
});
|
|
3851
|
-
}
|
|
3852
|
-
handleFileSelection(files) {
|
|
3853
|
-
this.files.set(files);
|
|
3854
|
-
this.fileNames.set(Array.from(files).map(f => f.name));
|
|
3855
|
-
console.log('📁 [FileInput] Files selected via drag & drop:', this.fileNames());
|
|
3856
|
-
this.generatePreviews();
|
|
3857
|
-
// Reset upload status when new file is selected
|
|
3858
|
-
this.uploadStatus.set('idle');
|
|
3859
|
-
console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus());
|
|
3860
|
-
this.onChange(files);
|
|
3861
|
-
this.fileChange.emit(files);
|
|
3862
|
-
this.onTouched();
|
|
3863
|
-
// Auto upload if enabled
|
|
3864
|
-
if (this.autoUploadSignal() && files.length > 0) {
|
|
3865
|
-
if (this.multipleSignal()) {
|
|
3866
|
-
console.log('🚀 [FileInput] Auto upload enabled for multiple files mode (drag & drop):', files.length, 'files');
|
|
3867
|
-
this.uploadMultipleFiles(Array.from(files));
|
|
3868
|
-
}
|
|
3869
|
-
else {
|
|
3870
|
-
console.log('🚀 [FileInput] Auto upload enabled for single file mode (drag & drop):', files[0].name);
|
|
3871
|
-
this.uploadFile(files[0]);
|
|
3872
|
-
}
|
|
3267
|
+
event.stopPropagation();
|
|
3268
|
+
// Show floating uploader when files are dragged over
|
|
3269
|
+
if (event.dataTransfer?.types.includes('Files')) {
|
|
3270
|
+
this.showWithAnimation();
|
|
3873
3271
|
}
|
|
3874
|
-
|
|
3875
|
-
|
|
3272
|
+
}
|
|
3273
|
+
/**
|
|
3274
|
+
* Handle drag leave event
|
|
3275
|
+
*/
|
|
3276
|
+
handleDragLeave(event) {
|
|
3277
|
+
event.preventDefault();
|
|
3278
|
+
event.stopPropagation();
|
|
3279
|
+
// Only hide if leaving the entire document
|
|
3280
|
+
if (!event.relatedTarget || event.relatedTarget === document.body) {
|
|
3281
|
+
this.updateVisibility();
|
|
3876
3282
|
}
|
|
3877
3283
|
}
|
|
3878
|
-
|
|
3879
|
-
|
|
3284
|
+
/**
|
|
3285
|
+
* Handle drop event
|
|
3286
|
+
*/
|
|
3287
|
+
handleDrop(event) {
|
|
3288
|
+
event.preventDefault();
|
|
3289
|
+
event.stopPropagation();
|
|
3290
|
+
const files = event.dataTransfer?.files;
|
|
3291
|
+
if (files && files.length > 0) {
|
|
3292
|
+
this.handleFiles(Array.from(files));
|
|
3293
|
+
}
|
|
3880
3294
|
}
|
|
3881
3295
|
/**
|
|
3882
|
-
*
|
|
3883
|
-
* @returns Properly typed upload data
|
|
3296
|
+
* Handle files from drag and drop or file input
|
|
3884
3297
|
*/
|
|
3885
|
-
|
|
3886
|
-
|
|
3298
|
+
handleFiles(files) {
|
|
3299
|
+
console.log('📁 [FloatingFileUploader] Handling files:', files.length);
|
|
3300
|
+
// Use handleExternalFiles to process the files
|
|
3301
|
+
this.handleExternalFiles(files, this.currentUserId());
|
|
3887
3302
|
}
|
|
3888
3303
|
/**
|
|
3889
|
-
*
|
|
3890
|
-
* @param data Partial upload data to merge with existing data
|
|
3304
|
+
* Update visibility - simplified for notification only
|
|
3891
3305
|
*/
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
// Note: This would require the uploadData to be a writable signal
|
|
3896
|
-
// For now, this method serves as a type-safe way to work with upload data
|
|
3897
|
-
console.log('📝 [FileInput] Upload data updated:', updatedData);
|
|
3306
|
+
updateVisibility() {
|
|
3307
|
+
// This is just a notification component now
|
|
3308
|
+
// The actual uploads are handled by the global uploader
|
|
3898
3309
|
}
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
uploadProgress: this.uploadProgress(),
|
|
3912
|
-
files: this.files() ? Array.from(this.files()).map(f => ({ name: f.name, size: f.size, type: f.type })) : null,
|
|
3913
|
-
fileNames: this.fileNames(),
|
|
3914
|
-
previewUrls: this.previewUrls().length,
|
|
3915
|
-
helperText: this.helperTextSignal(),
|
|
3916
|
-
errorText: this.errorTextSignal(),
|
|
3917
|
-
placeholderText: this.placeholderTextSignal(),
|
|
3918
|
-
placeholderIcon: this.placeholderIconSignal(),
|
|
3919
|
-
previewWidth: this.previewWidthSignal(),
|
|
3920
|
-
previewHeight: this.previewHeightSignal(),
|
|
3921
|
-
previewBoxMode: this.previewBoxModeSignal(),
|
|
3922
|
-
showFileName: this.showFileNameSignal(),
|
|
3923
|
-
uploadData: this.uploadDataSignal()
|
|
3924
|
-
};
|
|
3310
|
+
/**
|
|
3311
|
+
* Show with animation
|
|
3312
|
+
*/
|
|
3313
|
+
showWithAnimation() {
|
|
3314
|
+
console.log('🎬 [FloatingFileUploader] showWithAnimation called - setting isVisible to true');
|
|
3315
|
+
this.isAnimating.set(true);
|
|
3316
|
+
this.isVisible.set(true);
|
|
3317
|
+
// Remove animation class after animation completes
|
|
3318
|
+
setTimeout(() => {
|
|
3319
|
+
this.isAnimating.set(false);
|
|
3320
|
+
console.log('🎬 [FloatingFileUploader] Animation completed, isVisible:', this.isVisible());
|
|
3321
|
+
}, 300);
|
|
3925
3322
|
}
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3323
|
+
/**
|
|
3324
|
+
* Hide with animation
|
|
3325
|
+
*/
|
|
3326
|
+
hideWithAnimation() {
|
|
3327
|
+
this.isAnimating.set(true);
|
|
3328
|
+
// Wait for animation to complete before hiding
|
|
3329
|
+
setTimeout(() => {
|
|
3330
|
+
this.isVisible.set(false);
|
|
3331
|
+
this.isAnimating.set(false);
|
|
3332
|
+
}, 300);
|
|
3333
|
+
}
|
|
3334
|
+
/**
|
|
3335
|
+
* Toggle minimize state
|
|
3336
|
+
*/
|
|
3337
|
+
toggleMinimize() {
|
|
3338
|
+
this.isMinimized.set(!this.isMinimized());
|
|
3339
|
+
}
|
|
3340
|
+
/**
|
|
3341
|
+
* Close the floating uploader
|
|
3342
|
+
*/
|
|
3343
|
+
close() {
|
|
3344
|
+
this.hideWithAnimation();
|
|
3345
|
+
}
|
|
3346
|
+
/**
|
|
3347
|
+
* Get upload summary text
|
|
3348
|
+
*/
|
|
3349
|
+
getUploadSummary() {
|
|
3350
|
+
const queueLength = this.uploadQueue().length;
|
|
3351
|
+
const activeCount = this.activeUploads().size;
|
|
3352
|
+
const completed = this.completedUploads().length;
|
|
3353
|
+
const failed = this.failedUploads().length;
|
|
3354
|
+
if (activeCount > 0) {
|
|
3355
|
+
return `${activeCount} uploading`;
|
|
3938
3356
|
}
|
|
3939
|
-
else {
|
|
3940
|
-
|
|
3357
|
+
else if (completed > 0 && failed === 0) {
|
|
3358
|
+
return `${completed} completed`;
|
|
3941
3359
|
}
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
validate(control) {
|
|
3945
|
-
console.log('🔍 [FileInput] validate() called - uploadStatus:', this.uploadStatus(), 'required:', this.requiredSignal(), 'files:', !!this.files(), 'control.value:', control.value);
|
|
3946
|
-
// If upload is in progress (start or uploading status), return validation error
|
|
3947
|
-
if (this.uploadStatus() === 'start' || this.uploadStatus() === 'uploading') {
|
|
3948
|
-
console.log('⚠️ [FileInput] Validation ERROR: Upload in progress');
|
|
3949
|
-
return { 'uploadInProgress': { message: 'File upload in progress. Please wait...' } };
|
|
3360
|
+
else if (failed > 0) {
|
|
3361
|
+
return `${completed} completed, ${failed} failed`;
|
|
3950
3362
|
}
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
console.log('⚠️ [FileInput] Validation ERROR: File required');
|
|
3954
|
-
return { 'required': { message: 'Please select a file to upload.' } };
|
|
3363
|
+
else if (queueLength > 0) {
|
|
3364
|
+
return `${queueLength} pending`;
|
|
3955
3365
|
}
|
|
3956
|
-
|
|
3957
|
-
return null; // No validation errors
|
|
3366
|
+
return 'No uploads';
|
|
3958
3367
|
}
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
], usesOnChanges: true, ngImport: i0, template: "<div class=\"cide-file-input\">\n <!-- Label (shown when not in preview box mode or when preview box mode but no label override) -->\n @if (labelSignal() && !isPreviewBoxMode()) {\n <label class=\"cide-file-input-label\" [attr.for]=\"'cide-file-input-' + id()\">\n {{ labelSignal() }}@if (requiredSignal()) {<span class=\"cide-file-input-required\"> *</span>}\n </label>\n }\n \n <!-- Preview Box Mode -->\n @if (isPreviewBoxMode()) {\n <div class=\"cide-file-input-preview-box-container\">\n <!-- Hidden file input -->\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id()\"\n [attr.accept]=\"acceptSignal()\"\n [attr.multiple]=\"multipleSignal() ? true : null\"\n [disabled]=\"disabledSignal()\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-hidden\"\n />\n \n <!-- Preview Box -->\n <div \n class=\"cide-file-input-preview-box\"\n [class.cide-file-input-preview-box-disabled]=\"disabledSignal()\"\n [class.cide-file-input-preview-box-has-image]=\"hasImages()\"\n [class.cide-file-input-preview-box-drag-over]=\"isDragOver()\"\n [style.width]=\"previewWidthSignal()\"\n [style.height]=\"previewHeightSignal()\"\n (click)=\"triggerFileSelect()\"\n (dragover)=\"onDragOver($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [attr.title]=\"disabledSignal() ? 'File selection disabled' : placeholderTextSignal()\">\n \n <!-- No Image State -->\n @if (!hasImages()) {\n <div class=\"cide-file-input-preview-box-placeholder\">\n <div class=\"cide-file-input-preview-box-icon\">\n <cide-ele-icon>{{ isDragOver() ? '\uD83D\uDCC1' : placeholderIconSignal() }}</cide-ele-icon>\n </div>\n <div class=\"cide-file-input-preview-box-text\">\n {{ isDragOver() ? 'Drop files here...' : placeholderTextSignal() }}\n </div>\n </div>\n }\n \n <!-- Image Preview State -->\n @if (hasImages()) {\n <div class=\"cide-file-input-preview-box-content\">\n <img \n [src]=\"previewUrls()[0]\" \n [alt]=\"fileNames()[0] || 'Preview image'\"\n class=\"cide-file-input-preview-box-image\">\n <div class=\"cide-file-input-preview-box-overlay\">\n <div class=\"cide-file-input-preview-box-overlay-text\">Click to change</div>\n </div>\n @if (!disabledSignal()) {\n <button \n type=\"button\" \n class=\"cide-file-input-preview-box-remove\"\n (click)=\"clearFiles(); $event.stopPropagation()\"\n title=\"Remove image\">\n \u00D7\n </button>\n }\n </div>\n }\n </div>\n \n <!-- File name display for preview box mode -->\n @if (hasImages() && fileNames().length && showFileNameSignal()) {\n <div class=\"cide-file-input-preview-box-filename\">\n {{ fileNames()[0] }}\n </div>\n }\n </div>\n }\n\n <!-- Standard Mode -->\n @if (!isPreviewBoxMode()) {\n <div \n class=\"cide-file-input-wrapper\"\n [class.cide-file-input-drag-over]=\"isDragOver()\"\n (dragover)=\"onDragOver($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id()\"\n [attr.accept]=\"acceptSignal()\"\n [attr.multiple]=\"multipleSignal() ? true : null\"\n [disabled]=\"disabledSignal()\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-element\"\n />\n @if (hasFiles()) {\n <button type=\"button\" class=\"cide-file-input-clear\" (click)=\"clearFiles()\">\n Clear\n </button>\n }\n </div>\n @if (hasFiles() && !isPreviewBoxMode()) {\n <div class=\"cide-file-input-files\">\n @for (name of fileNames(); track name) {\n <span>{{ name }}</span>\n }\n <!-- Angular 20: Display file size using new computed values -->\n @if (totalFileSize() > 0) {\n <div class=\"cide-file-input-size\">\n Total size: {{ fileSizeInMB() }} MB\n </div>\n }\n </div>\n }\n }\n \n <!-- Image Preview Section (only for standard mode) -->\n @if (isImagePreviewAvailable() && !isPreviewBoxMode()) {\n <div class=\"cide-file-input-preview\">\n <div class=\"cide-file-input-preview-label\">Preview:</div>\n <div class=\"cide-file-input-preview-container\">\n @for (previewUrl of previewUrls(); track previewUrl; let i = $index) {\n <div \n class=\"cide-file-input-preview-item\"\n [style.width]=\"previewWidthSignal()\"\n [style.height]=\"previewHeightSignal()\">\n <button \n type=\"button\" \n class=\"cide-file-input-preview-remove\"\n (click)=\"removePreview(i)\"\n title=\"Remove image\">\n \u00D7\n </button>\n <img \n [src]=\"previewUrl\" \n [alt]=\"fileNames()[i] || 'Preview image'\"\n class=\"cide-file-input-preview-image\"\n loading=\"lazy\">\n <div class=\"cide-file-input-preview-filename\">{{ fileNames()[i] }}</div>\n </div>\n }\n </div>\n </div>\n }\n \n @if (errorTextSignal()) {\n <div class=\"cide-file-input-error\">{{ errorTextSignal() }}</div>\n }\n @if (helperTextSignal() && !errorTextSignal()) {\n <div class=\"cide-file-input-helper\">{{ helperTextSignal() }}</div>\n }\n</div> ", styles: [".cide-file-input{display:flex;flex-direction:column;gap:.5rem}.cide-file-input-label{font-weight:500;margin-bottom:.25rem}.cide-file-input-required{color:#d32f2f;font-weight:700}.cide-file-input-wrapper{display:flex;align-items:center;gap:.5rem;border:2px dashed transparent;border-radius:.5rem;padding:.5rem;transition:all .2s ease-in-out}.cide-file-input-wrapper.cide-file-input-drag-over{border-color:#3b82f6;background-color:#eff6ff;transform:scale(1.02)}.cide-file-input-element{flex:1}.cide-file-input-clear{background:none;border:none;color:#d32f2f;cursor:pointer;font-size:.9rem}.cide-file-input-files{font-size:.95rem;color:#333;margin-top:.25rem}.cide-file-input-size{margin-top:.5rem;padding:.25rem .5rem;background-color:#f3f4f6;border-radius:.25rem;font-size:.75rem;color:#4b5563;font-weight:500}.cide-file-input-error{color:#d32f2f;font-size:.9rem}.cide-file-input-helper{color:#666;font-size:.9rem}.cide-file-input-preview{margin-top:.75rem;padding:.75rem;background-color:#f8f9fa;border:1px solid #e1e5e9;border-radius:.375rem}.cide-file-input-preview-label{font-weight:500;margin-bottom:.5rem;color:#374151;font-size:.875rem}.cide-file-input-preview-container{display:flex;flex-wrap:wrap;gap:.75rem}.cide-file-input-preview-item{position:relative;display:flex;flex-direction:column;border:1px solid #d1d5db;border-radius:.5rem;overflow:hidden;background-color:#fff;box-shadow:0 1px 3px #0000001a;transition:box-shadow .2s ease-in-out}.cide-file-input-preview-item:hover{box-shadow:0 4px 6px -1px #0000001a}.cide-file-input-preview-image{width:100%;height:calc(100% - 2rem);object-fit:cover;object-position:center;background-color:#f3f4f6}.cide-file-input-preview-filename{padding:.375rem .5rem;background-color:#f9fafbf2;border-top:1px solid #e5e7eb;font-size:.75rem;color:#374151;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-height:2rem;display:flex;align-items:center;justify-content:center}.cide-file-input-preview-remove{position:absolute;top:.25rem;right:.25rem;width:1.5rem;height:1.5rem;background-color:#ef4444e6;color:#fff;border:none;border-radius:50%;font-size:1rem;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:10;transition:all .2s ease-in-out}.cide-file-input-preview-remove:hover{background-color:#dc2626f2;transform:scale(1.1)}.cide-file-input-preview-remove:focus{outline:2px solid #3b82f6;outline-offset:2px}.cide-file-input-hidden{display:none}.cide-file-input-preview-box-container{display:flex;flex-direction:column;gap:.5rem}.cide-file-input-preview-box{position:relative;border:2px dashed #d1d5db;border-radius:.5rem;cursor:pointer;background-color:#f9fafb;display:flex;align-items:center;justify-content:center;overflow:hidden;transition:all .2s ease-in-out}.cide-file-input-preview-box:hover{border-color:#3b82f6;background-color:#eff6ff}.cide-file-input-preview-box.cide-file-input-preview-box-disabled{cursor:not-allowed;opacity:.6;background-color:#f3f4f6}.cide-file-input-preview-box.cide-file-input-preview-box-disabled:hover{border-color:#d1d5db;background-color:#f3f4f6}.cide-file-input-preview-box.cide-file-input-preview-box-has-image{border-style:solid;border-color:#e5e7eb;padding:0}.cide-file-input-preview-box.cide-file-input-preview-box-has-image:hover{border-color:#3b82f6}.cide-file-input-preview-box.cide-file-input-preview-box-has-image:hover .cide-file-input-preview-box-overlay{opacity:1}.cide-file-input-preview-box.cide-file-input-preview-box-drag-over{border-color:#3b82f6!important;background-color:#eff6ff!important;transform:scale(1.02);box-shadow:0 0 0 4px #3b82f61a}.cide-file-input-preview-box.cide-file-input-preview-box-drag-over .cide-file-input-preview-box-placeholder .cide-file-input-preview-box-icon{color:#3b82f6;transform:scale(1.1)}.cide-file-input-preview-box.cide-file-input-preview-box-drag-over .cide-file-input-preview-box-placeholder .cide-file-input-preview-box-text{color:#3b82f6;font-weight:600}.cide-file-input-preview-box-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:1rem;text-align:center}.cide-file-input-preview-box-icon{font-size:2rem;color:#6b7280}.cide-file-input-preview-box-text{font-size:.875rem;color:#6b7280;font-weight:500}.cide-file-input-preview-box-content{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center}.cide-file-input-preview-box-image{width:100%;height:100%;object-fit:cover;object-position:center}.cide-file-input-preview-box-overlay{position:absolute;inset:0;background-color:#0009;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s ease-in-out}.cide-file-input-preview-box-overlay-text{color:#fff;font-size:.875rem;font-weight:500;text-align:center}.cide-file-input-preview-box-remove{position:absolute;top:.375rem;right:.375rem;width:1.5rem;height:1.5rem;background-color:#ef4444e6;color:#fff;border:none;border-radius:50%;font-size:1rem;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:20;transition:all .2s ease-in-out}.cide-file-input-preview-box-remove:hover{background-color:#dc2626f2;transform:scale(1.1)}.cide-file-input-preview-box-remove:focus{outline:2px solid #3b82f6;outline-offset:2px}.cide-file-input-preview-box-filename{font-size:.75rem;color:#374151;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:.25rem .5rem;background-color:#f3f4f6;border-radius:.25rem;margin-top:.25rem}@media (max-width: 640px){.cide-file-input-preview-container{justify-content:center}.cide-file-input-preview-item{min-width:120px;max-width:150px}.cide-file-input-preview-box-icon{font-size:1.5rem}.cide-file-input-preview-box-text{font-size:.75rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }] });
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
},
|
|
3981
|
-
{
|
|
3982
|
-
provide: NG_VALIDATORS,
|
|
3983
|
-
useExisting: CideEleFileInputComponent,
|
|
3984
|
-
multi: true
|
|
3985
|
-
}
|
|
3986
|
-
], template: "<div class=\"cide-file-input\">\n <!-- Label (shown when not in preview box mode or when preview box mode but no label override) -->\n @if (labelSignal() && !isPreviewBoxMode()) {\n <label class=\"cide-file-input-label\" [attr.for]=\"'cide-file-input-' + id()\">\n {{ labelSignal() }}@if (requiredSignal()) {<span class=\"cide-file-input-required\"> *</span>}\n </label>\n }\n \n <!-- Preview Box Mode -->\n @if (isPreviewBoxMode()) {\n <div class=\"cide-file-input-preview-box-container\">\n <!-- Hidden file input -->\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id()\"\n [attr.accept]=\"acceptSignal()\"\n [attr.multiple]=\"multipleSignal() ? true : null\"\n [disabled]=\"disabledSignal()\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-hidden\"\n />\n \n <!-- Preview Box -->\n <div \n class=\"cide-file-input-preview-box\"\n [class.cide-file-input-preview-box-disabled]=\"disabledSignal()\"\n [class.cide-file-input-preview-box-has-image]=\"hasImages()\"\n [class.cide-file-input-preview-box-drag-over]=\"isDragOver()\"\n [style.width]=\"previewWidthSignal()\"\n [style.height]=\"previewHeightSignal()\"\n (click)=\"triggerFileSelect()\"\n (dragover)=\"onDragOver($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [attr.title]=\"disabledSignal() ? 'File selection disabled' : placeholderTextSignal()\">\n \n <!-- No Image State -->\n @if (!hasImages()) {\n <div class=\"cide-file-input-preview-box-placeholder\">\n <div class=\"cide-file-input-preview-box-icon\">\n <cide-ele-icon>{{ isDragOver() ? '\uD83D\uDCC1' : placeholderIconSignal() }}</cide-ele-icon>\n </div>\n <div class=\"cide-file-input-preview-box-text\">\n {{ isDragOver() ? 'Drop files here...' : placeholderTextSignal() }}\n </div>\n </div>\n }\n \n <!-- Image Preview State -->\n @if (hasImages()) {\n <div class=\"cide-file-input-preview-box-content\">\n <img \n [src]=\"previewUrls()[0]\" \n [alt]=\"fileNames()[0] || 'Preview image'\"\n class=\"cide-file-input-preview-box-image\">\n <div class=\"cide-file-input-preview-box-overlay\">\n <div class=\"cide-file-input-preview-box-overlay-text\">Click to change</div>\n </div>\n @if (!disabledSignal()) {\n <button \n type=\"button\" \n class=\"cide-file-input-preview-box-remove\"\n (click)=\"clearFiles(); $event.stopPropagation()\"\n title=\"Remove image\">\n \u00D7\n </button>\n }\n </div>\n }\n </div>\n \n <!-- File name display for preview box mode -->\n @if (hasImages() && fileNames().length && showFileNameSignal()) {\n <div class=\"cide-file-input-preview-box-filename\">\n {{ fileNames()[0] }}\n </div>\n }\n </div>\n }\n\n <!-- Standard Mode -->\n @if (!isPreviewBoxMode()) {\n <div \n class=\"cide-file-input-wrapper\"\n [class.cide-file-input-drag-over]=\"isDragOver()\"\n (dragover)=\"onDragOver($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id()\"\n [attr.accept]=\"acceptSignal()\"\n [attr.multiple]=\"multipleSignal() ? true : null\"\n [disabled]=\"disabledSignal()\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-element\"\n />\n @if (hasFiles()) {\n <button type=\"button\" class=\"cide-file-input-clear\" (click)=\"clearFiles()\">\n Clear\n </button>\n }\n </div>\n @if (hasFiles() && !isPreviewBoxMode()) {\n <div class=\"cide-file-input-files\">\n @for (name of fileNames(); track name) {\n <span>{{ name }}</span>\n }\n <!-- Angular 20: Display file size using new computed values -->\n @if (totalFileSize() > 0) {\n <div class=\"cide-file-input-size\">\n Total size: {{ fileSizeInMB() }} MB\n </div>\n }\n </div>\n }\n }\n \n <!-- Image Preview Section (only for standard mode) -->\n @if (isImagePreviewAvailable() && !isPreviewBoxMode()) {\n <div class=\"cide-file-input-preview\">\n <div class=\"cide-file-input-preview-label\">Preview:</div>\n <div class=\"cide-file-input-preview-container\">\n @for (previewUrl of previewUrls(); track previewUrl; let i = $index) {\n <div \n class=\"cide-file-input-preview-item\"\n [style.width]=\"previewWidthSignal()\"\n [style.height]=\"previewHeightSignal()\">\n <button \n type=\"button\" \n class=\"cide-file-input-preview-remove\"\n (click)=\"removePreview(i)\"\n title=\"Remove image\">\n \u00D7\n </button>\n <img \n [src]=\"previewUrl\" \n [alt]=\"fileNames()[i] || 'Preview image'\"\n class=\"cide-file-input-preview-image\"\n loading=\"lazy\">\n <div class=\"cide-file-input-preview-filename\">{{ fileNames()[i] }}</div>\n </div>\n }\n </div>\n </div>\n }\n \n @if (errorTextSignal()) {\n <div class=\"cide-file-input-error\">{{ errorTextSignal() }}</div>\n }\n @if (helperTextSignal() && !errorTextSignal()) {\n <div class=\"cide-file-input-helper\">{{ helperTextSignal() }}</div>\n }\n</div> ", styles: [".cide-file-input{display:flex;flex-direction:column;gap:.5rem}.cide-file-input-label{font-weight:500;margin-bottom:.25rem}.cide-file-input-required{color:#d32f2f;font-weight:700}.cide-file-input-wrapper{display:flex;align-items:center;gap:.5rem;border:2px dashed transparent;border-radius:.5rem;padding:.5rem;transition:all .2s ease-in-out}.cide-file-input-wrapper.cide-file-input-drag-over{border-color:#3b82f6;background-color:#eff6ff;transform:scale(1.02)}.cide-file-input-element{flex:1}.cide-file-input-clear{background:none;border:none;color:#d32f2f;cursor:pointer;font-size:.9rem}.cide-file-input-files{font-size:.95rem;color:#333;margin-top:.25rem}.cide-file-input-size{margin-top:.5rem;padding:.25rem .5rem;background-color:#f3f4f6;border-radius:.25rem;font-size:.75rem;color:#4b5563;font-weight:500}.cide-file-input-error{color:#d32f2f;font-size:.9rem}.cide-file-input-helper{color:#666;font-size:.9rem}.cide-file-input-preview{margin-top:.75rem;padding:.75rem;background-color:#f8f9fa;border:1px solid #e1e5e9;border-radius:.375rem}.cide-file-input-preview-label{font-weight:500;margin-bottom:.5rem;color:#374151;font-size:.875rem}.cide-file-input-preview-container{display:flex;flex-wrap:wrap;gap:.75rem}.cide-file-input-preview-item{position:relative;display:flex;flex-direction:column;border:1px solid #d1d5db;border-radius:.5rem;overflow:hidden;background-color:#fff;box-shadow:0 1px 3px #0000001a;transition:box-shadow .2s ease-in-out}.cide-file-input-preview-item:hover{box-shadow:0 4px 6px -1px #0000001a}.cide-file-input-preview-image{width:100%;height:calc(100% - 2rem);object-fit:cover;object-position:center;background-color:#f3f4f6}.cide-file-input-preview-filename{padding:.375rem .5rem;background-color:#f9fafbf2;border-top:1px solid #e5e7eb;font-size:.75rem;color:#374151;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-height:2rem;display:flex;align-items:center;justify-content:center}.cide-file-input-preview-remove{position:absolute;top:.25rem;right:.25rem;width:1.5rem;height:1.5rem;background-color:#ef4444e6;color:#fff;border:none;border-radius:50%;font-size:1rem;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:10;transition:all .2s ease-in-out}.cide-file-input-preview-remove:hover{background-color:#dc2626f2;transform:scale(1.1)}.cide-file-input-preview-remove:focus{outline:2px solid #3b82f6;outline-offset:2px}.cide-file-input-hidden{display:none}.cide-file-input-preview-box-container{display:flex;flex-direction:column;gap:.5rem}.cide-file-input-preview-box{position:relative;border:2px dashed #d1d5db;border-radius:.5rem;cursor:pointer;background-color:#f9fafb;display:flex;align-items:center;justify-content:center;overflow:hidden;transition:all .2s ease-in-out}.cide-file-input-preview-box:hover{border-color:#3b82f6;background-color:#eff6ff}.cide-file-input-preview-box.cide-file-input-preview-box-disabled{cursor:not-allowed;opacity:.6;background-color:#f3f4f6}.cide-file-input-preview-box.cide-file-input-preview-box-disabled:hover{border-color:#d1d5db;background-color:#f3f4f6}.cide-file-input-preview-box.cide-file-input-preview-box-has-image{border-style:solid;border-color:#e5e7eb;padding:0}.cide-file-input-preview-box.cide-file-input-preview-box-has-image:hover{border-color:#3b82f6}.cide-file-input-preview-box.cide-file-input-preview-box-has-image:hover .cide-file-input-preview-box-overlay{opacity:1}.cide-file-input-preview-box.cide-file-input-preview-box-drag-over{border-color:#3b82f6!important;background-color:#eff6ff!important;transform:scale(1.02);box-shadow:0 0 0 4px #3b82f61a}.cide-file-input-preview-box.cide-file-input-preview-box-drag-over .cide-file-input-preview-box-placeholder .cide-file-input-preview-box-icon{color:#3b82f6;transform:scale(1.1)}.cide-file-input-preview-box.cide-file-input-preview-box-drag-over .cide-file-input-preview-box-placeholder .cide-file-input-preview-box-text{color:#3b82f6;font-weight:600}.cide-file-input-preview-box-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:1rem;text-align:center}.cide-file-input-preview-box-icon{font-size:2rem;color:#6b7280}.cide-file-input-preview-box-text{font-size:.875rem;color:#6b7280;font-weight:500}.cide-file-input-preview-box-content{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center}.cide-file-input-preview-box-image{width:100%;height:100%;object-fit:cover;object-position:center}.cide-file-input-preview-box-overlay{position:absolute;inset:0;background-color:#0009;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s ease-in-out}.cide-file-input-preview-box-overlay-text{color:#fff;font-size:.875rem;font-weight:500;text-align:center}.cide-file-input-preview-box-remove{position:absolute;top:.375rem;right:.375rem;width:1.5rem;height:1.5rem;background-color:#ef4444e6;color:#fff;border:none;border-radius:50%;font-size:1rem;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:20;transition:all .2s ease-in-out}.cide-file-input-preview-box-remove:hover{background-color:#dc2626f2;transform:scale(1.1)}.cide-file-input-preview-box-remove:focus{outline:2px solid #3b82f6;outline-offset:2px}.cide-file-input-preview-box-filename{font-size:.75rem;color:#374151;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:.25rem .5rem;background-color:#f3f4f6;border-radius:.25rem;margin-top:.25rem}@media (max-width: 640px){.cide-file-input-preview-container{justify-content:center}.cide-file-input-preview-item{min-width:120px;max-width:150px}.cide-file-input-preview-box-icon{font-size:1.5rem}.cide-file-input-preview-box-text{font-size:.75rem}}\n"] }]
|
|
3987
|
-
}], ctorParameters: () => [], propDecorators: { label: [{
|
|
3988
|
-
type: Input
|
|
3989
|
-
}], accept: [{
|
|
3990
|
-
type: Input
|
|
3991
|
-
}], multiple: [{
|
|
3992
|
-
type: Input
|
|
3993
|
-
}], disabled: [{
|
|
3994
|
-
type: Input
|
|
3995
|
-
}], required: [{
|
|
3996
|
-
type: Input
|
|
3997
|
-
}], helperText: [{
|
|
3998
|
-
type: Input
|
|
3999
|
-
}], errorText: [{
|
|
4000
|
-
type: Input
|
|
4001
|
-
}], showPreview: [{
|
|
4002
|
-
type: Input
|
|
4003
|
-
}], previewWidth: [{
|
|
4004
|
-
type: Input
|
|
4005
|
-
}], previewHeight: [{
|
|
4006
|
-
type: Input
|
|
4007
|
-
}], previewBoxMode: [{
|
|
4008
|
-
type: Input
|
|
4009
|
-
}], showFileName: [{
|
|
4010
|
-
type: Input
|
|
4011
|
-
}], placeholderText: [{
|
|
4012
|
-
type: Input
|
|
4013
|
-
}], placeholderIcon: [{
|
|
4014
|
-
type: Input
|
|
4015
|
-
}], autoUpload: [{
|
|
4016
|
-
type: Input
|
|
4017
|
-
}], uploadData: [{
|
|
4018
|
-
type: Input
|
|
4019
|
-
}], fileChange: [{
|
|
4020
|
-
type: Output
|
|
4021
|
-
}], uploadSuccess: [{
|
|
4022
|
-
type: Output
|
|
4023
|
-
}], uploadError: [{
|
|
4024
|
-
type: Output
|
|
4025
|
-
}], uploadProgressChange: [{
|
|
4026
|
-
type: Output
|
|
4027
|
-
}] } });
|
|
4028
|
-
|
|
4029
|
-
class CideEleGlobalFileUploaderComponent {
|
|
4030
|
-
// Dependency injection
|
|
4031
|
-
destroyRef = inject(DestroyRef);
|
|
4032
|
-
fileService = inject(CideEleFileManagerService);
|
|
4033
|
-
// Input properties
|
|
4034
|
-
userId = '';
|
|
4035
|
-
multiple = true;
|
|
4036
|
-
accept = '';
|
|
4037
|
-
maxFileSize = 10; // MB
|
|
4038
|
-
allowedTypes = [];
|
|
4039
|
-
// Output events
|
|
4040
|
-
uploadComplete = new EventEmitter();
|
|
4041
|
-
uploadError = new EventEmitter();
|
|
4042
|
-
uploadCancelled = new EventEmitter();
|
|
4043
|
-
allUploadsComplete = new EventEmitter();
|
|
4044
|
-
// Signals for reactive state management
|
|
4045
|
-
isDragOver = signal(false, ...(ngDevMode ? [{ debugName: "isDragOver" }] : []));
|
|
4046
|
-
isUploading = signal(false, ...(ngDevMode ? [{ debugName: "isUploading" }] : []));
|
|
4047
|
-
uploadQueue = signal([], ...(ngDevMode ? [{ debugName: "uploadQueue" }] : []));
|
|
4048
|
-
currentGroupId = signal(null, ...(ngDevMode ? [{ debugName: "currentGroupId" }] : []));
|
|
4049
|
-
// Computed values
|
|
4050
|
-
hasUploads = computed(() => this.uploadQueue().length > 0, ...(ngDevMode ? [{ debugName: "hasUploads" }] : []));
|
|
4051
|
-
pendingUploads = computed(() => this.uploadQueue().filter(upload => upload.status === 'pending'), ...(ngDevMode ? [{ debugName: "pendingUploads" }] : []));
|
|
4052
|
-
activeUploads = computed(() => this.uploadQueue().filter(upload => upload.status === 'uploading'), ...(ngDevMode ? [{ debugName: "activeUploads" }] : []));
|
|
4053
|
-
completedUploads = computed(() => this.uploadQueue().filter(upload => upload.status === 'completed'), ...(ngDevMode ? [{ debugName: "completedUploads" }] : []));
|
|
4054
|
-
failedUploads = computed(() => this.uploadQueue().filter(upload => upload.status === 'error'), ...(ngDevMode ? [{ debugName: "failedUploads" }] : []));
|
|
4055
|
-
constructor() {
|
|
4056
|
-
console.log('🚀 [GlobalFileUploader] Component initialized');
|
|
3368
|
+
/**
|
|
3369
|
+
* Get overall progress percentage
|
|
3370
|
+
*/
|
|
3371
|
+
getOverallProgress() {
|
|
3372
|
+
const activeUploads = this.activeUploads();
|
|
3373
|
+
if (activeUploads.size === 0)
|
|
3374
|
+
return 0;
|
|
3375
|
+
const totalProgress = Array.from(activeUploads.values()).reduce((sum, upload) => sum + upload.percentage, 0);
|
|
3376
|
+
return Math.round(totalProgress / activeUploads.size);
|
|
3377
|
+
}
|
|
3378
|
+
/**
|
|
3379
|
+
* Get status icon based on upload stage
|
|
3380
|
+
*/
|
|
3381
|
+
getStatusIcon(stage) {
|
|
3382
|
+
switch (stage) {
|
|
3383
|
+
case 'reading': return 'schedule';
|
|
3384
|
+
case 'uploading': return 'cloud_upload';
|
|
3385
|
+
case 'complete': return 'check_circle';
|
|
3386
|
+
case 'error': return 'error';
|
|
3387
|
+
default: return 'help';
|
|
3388
|
+
}
|
|
4057
3389
|
}
|
|
4058
3390
|
/**
|
|
4059
|
-
*
|
|
3391
|
+
* Get status class based on upload stage
|
|
4060
3392
|
*/
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
3393
|
+
getStatusClass(stage) {
|
|
3394
|
+
switch (stage) {
|
|
3395
|
+
case 'reading': return 'status-pending';
|
|
3396
|
+
case 'uploading': return 'status-uploading';
|
|
3397
|
+
case 'complete': return 'status-completed';
|
|
3398
|
+
case 'error': return 'status-error';
|
|
3399
|
+
default: return 'status-unknown';
|
|
3400
|
+
}
|
|
4065
3401
|
}
|
|
4066
3402
|
/**
|
|
4067
|
-
*
|
|
3403
|
+
* Cancel upload
|
|
4068
3404
|
*/
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
this.isDragOver.set(false);
|
|
3405
|
+
cancelUpload(fileId) {
|
|
3406
|
+
console.log('🚫 [FloatingFileUploader] Cancelling upload:', fileId);
|
|
3407
|
+
this.fileManagerService.cancelUpload(fileId);
|
|
4073
3408
|
}
|
|
4074
3409
|
/**
|
|
4075
|
-
*
|
|
3410
|
+
* Get file name from file ID (extract from the ID format)
|
|
4076
3411
|
*/
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
this.addFilesToQueue(Array.from(files));
|
|
3412
|
+
getFileNameFromId(fileId) {
|
|
3413
|
+
// Extract filename from the fileId format: filename_size_timestamp
|
|
3414
|
+
const parts = fileId.split('_');
|
|
3415
|
+
if (parts.length >= 3) {
|
|
3416
|
+
// Remove the last two parts (size and timestamp) to get the filename
|
|
3417
|
+
return parts.slice(0, -2).join('_');
|
|
4084
3418
|
}
|
|
3419
|
+
return fileId;
|
|
4085
3420
|
}
|
|
4086
3421
|
/**
|
|
4087
|
-
*
|
|
3422
|
+
* Set current user ID
|
|
4088
3423
|
*/
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
3424
|
+
setCurrentUserId(userId) {
|
|
3425
|
+
this.currentUserId.set(userId);
|
|
3426
|
+
this.fileManagerService.setUserId(userId);
|
|
3427
|
+
}
|
|
3428
|
+
/**
|
|
3429
|
+
* Public method to handle files from external sources
|
|
3430
|
+
* This can be called by other components to trigger the floating uploader
|
|
3431
|
+
*/
|
|
3432
|
+
handleExternalFiles(files, userId, groupId) {
|
|
3433
|
+
console.log('📁 [FloatingFileUploader] External files received:', files.length, 'files');
|
|
3434
|
+
// Set user ID if provided
|
|
3435
|
+
if (userId && userId !== this.currentUserId()) {
|
|
3436
|
+
this.setCurrentUserId(userId);
|
|
4094
3437
|
}
|
|
4095
|
-
//
|
|
4096
|
-
|
|
3438
|
+
// Set group ID if provided
|
|
3439
|
+
if (groupId) {
|
|
3440
|
+
this.currentGroupId.set(groupId);
|
|
3441
|
+
}
|
|
3442
|
+
// Upload files using file manager service
|
|
3443
|
+
// The file manager service will handle adding to its queue and the effect will show the floating uploader
|
|
3444
|
+
files.forEach((file, index) => {
|
|
3445
|
+
console.log(`📁 [FloatingFileUploader] Starting upload for file ${index + 1}/${files.length}:`, file.name);
|
|
3446
|
+
this.fileManagerService.uploadFile(file, {
|
|
3447
|
+
userId: this.currentUserId(),
|
|
3448
|
+
groupId: groupId,
|
|
3449
|
+
permissions: ['read', 'write'],
|
|
3450
|
+
tags: []
|
|
3451
|
+
})
|
|
3452
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
3453
|
+
.subscribe({
|
|
3454
|
+
next: (response) => {
|
|
3455
|
+
console.log('✅ [FloatingFileUploader] Upload completed:', response);
|
|
3456
|
+
},
|
|
3457
|
+
error: (error) => {
|
|
3458
|
+
console.error('❌ [FloatingFileUploader] Upload failed:', error);
|
|
3459
|
+
}
|
|
3460
|
+
});
|
|
3461
|
+
});
|
|
4097
3462
|
}
|
|
4098
3463
|
/**
|
|
4099
|
-
*
|
|
3464
|
+
* Manually show the floating uploader
|
|
3465
|
+
* This can be called from file input components or other triggers
|
|
4100
3466
|
*/
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
console.warn('⚠️ [GlobalFileUploader] No valid files to upload');
|
|
4106
|
-
return;
|
|
3467
|
+
showUploader(groupId) {
|
|
3468
|
+
console.log('👁️ [FloatingFileUploader] Manually showing uploader', groupId ? `for group: ${groupId}` : '');
|
|
3469
|
+
if (groupId) {
|
|
3470
|
+
this.currentGroupId.set(groupId);
|
|
4107
3471
|
}
|
|
4108
|
-
|
|
4109
|
-
fileId: this.generateFileId(file),
|
|
4110
|
-
fileName: file.name,
|
|
4111
|
-
progress: 0,
|
|
4112
|
-
status: 'pending',
|
|
4113
|
-
file: file
|
|
4114
|
-
}));
|
|
4115
|
-
this.uploadQueue.update(queue => [...queue, ...newUploads]);
|
|
4116
|
-
this.processUploadQueue();
|
|
3472
|
+
this.showWithAnimation();
|
|
4117
3473
|
}
|
|
4118
3474
|
/**
|
|
4119
|
-
*
|
|
3475
|
+
* Check if there are any uploads for the current group
|
|
4120
3476
|
*/
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
3477
|
+
hasUploadsForCurrentGroup() {
|
|
3478
|
+
const groupId = this.currentGroupId();
|
|
3479
|
+
if (!groupId) {
|
|
3480
|
+
// If no group filter, show all uploads
|
|
3481
|
+
return this.hasUploads();
|
|
3482
|
+
}
|
|
3483
|
+
// Check if any uploads belong to the current group
|
|
3484
|
+
// Note: This would need to be enhanced based on how group IDs are stored in the file manager service
|
|
3485
|
+
return this.hasUploads();
|
|
3486
|
+
}
|
|
3487
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFloatingFileUploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3488
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: CideEleFloatingFileUploaderComponent, isStandalone: true, selector: "cide-ele-floating-file-uploader", ngImport: i0, template: "<!-- Floating File Uploader Container -->\r\n@if (isVisible()) {\r\n<div class=\"floating-uploader\" \r\n [class.minimized]=\"isMinimized()\" \r\n [class.animating]=\"isAnimating()\">\r\n\r\n <!-- Header -->\r\n <div class=\"uploader-header\">\r\n <div class=\"header-left\">\r\n <div class=\"upload-icon\">\r\n <cide-ele-icon size=\"sm\">cloud_upload</cide-ele-icon>\r\n </div>\r\n <div class=\"upload-info\">\r\n <div class=\"upload-title\">File Upload</div>\r\n <div class=\"upload-summary\">{{ getUploadSummary() }}</div>\r\n </div>\r\n </div>\r\n \r\n <div class=\"header-actions\">\r\n <button class=\"action-btn minimize-btn\" (click)=\"toggleMinimize()\" [title]=\"isMinimized() ? 'Expand' : 'Minimize'\">\r\n <cide-ele-icon size=\"xs\">{{ isMinimized() ? 'expand_more' : 'expand_less' }}</cide-ele-icon>\r\n </button>\r\n <button class=\"action-btn close-btn\" (click)=\"close()\" title=\"Close\">\r\n <cide-ele-icon size=\"xs\">close</cide-ele-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Content (hidden when minimized) -->\r\n @if (!isMinimized()) {\r\n <div class=\"uploader-content\">\r\n \r\n <!-- Overall Progress Bar -->\r\n @if (uploadQueue().length > 0 || activeUploads().size > 0) {\r\n <div class=\"overall-progress\">\r\n <div class=\"progress-bar\">\r\n <div class=\"progress-fill\" [style.width.%]=\"getOverallProgress()\"></div>\r\n </div>\r\n <div class=\"progress-text\">{{ getOverallProgress() }}%</div>\r\n </div>\r\n }\r\n\r\n <!-- Upload Queue - Show files from file manager service -->\r\n @if (uploadQueue().length > 0 || activeUploads().size > 0) {\r\n <div class=\"upload-queue\">\r\n <!-- Show files in queue (pending) -->\r\n @for (fileId of uploadQueue(); track fileId) {\r\n <div class=\"upload-item status-pending\">\r\n <div class=\"file-info\">\r\n <cide-ele-icon class=\"status-icon\" size=\"xs\">schedule</cide-ele-icon>\r\n <div class=\"file-details\">\r\n <div class=\"file-name\">{{ getFileNameFromId(fileId) }}</div>\r\n <div class=\"file-status\">\r\n <span class=\"text-yellow-600\">Waiting...</span>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"upload-actions\">\r\n <button class=\"action-btn cancel-btn\" (click)=\"cancelUpload(fileId)\" title=\"Cancel\">\r\n <cide-ele-icon size=\"xs\">cancel</cide-ele-icon>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n \r\n <!-- Show active uploads -->\r\n @for (entry of activeUploads() | keyvalue; track entry.key) {\r\n <div class=\"upload-item\" [class]=\"getStatusClass(entry.value.stage)\">\r\n <div class=\"file-info\">\r\n <cide-ele-icon class=\"status-icon\" size=\"xs\">{{ getStatusIcon(entry.value.stage) }}</cide-ele-icon>\r\n <div class=\"file-details\">\r\n <div class=\"file-name\">{{ getFileNameFromId(entry.key) }}</div>\r\n <div class=\"file-status\">\r\n @switch (entry.value.stage) {\r\n @case ('reading') {\r\n <span class=\"text-yellow-600\">Reading...</span>\r\n }\r\n @case ('uploading') {\r\n <span class=\"text-blue-600\">Uploading...</span>\r\n }\r\n @case ('complete') {\r\n <span class=\"text-green-600\">Completed</span>\r\n }\r\n @case ('error') {\r\n <span class=\"text-red-600\">Failed</span>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Progress Bar (for uploading files) -->\r\n @if (entry.value.stage === 'uploading') {\r\n <div class=\"file-progress\">\r\n <div class=\"progress-bar\">\r\n <div class=\"progress-fill\" [style.width.%]=\"entry.value.percentage\"></div>\r\n </div>\r\n <span class=\"progress-text\">{{ entry.value.percentage }}%</span>\r\n </div>\r\n }\r\n\r\n <!-- Actions -->\r\n <div class=\"upload-actions\">\r\n @switch (entry.value.stage) {\r\n @case ('reading') {\r\n <button class=\"action-btn cancel-btn\" (click)=\"cancelUpload(entry.key)\" title=\"Cancel\">\r\n <cide-ele-icon size=\"xs\">cancel</cide-ele-icon>\r\n </button>\r\n }\r\n @case ('uploading') {\r\n <button class=\"action-btn cancel-btn\" (click)=\"cancelUpload(entry.key)\" title=\"Cancel\">\r\n <cide-ele-icon size=\"xs\">cancel</cide-ele-icon>\r\n </button>\r\n }\r\n @case ('complete') {\r\n <button class=\"action-btn success-btn\" title=\"Completed\">\r\n <cide-ele-icon size=\"xs\">check_circle</cide-ele-icon>\r\n </button>\r\n }\r\n @case ('error') {\r\n <button class=\"action-btn retry-btn\" title=\"Retry\">\r\n <cide-ele-icon size=\"xs\">refresh</cide-ele-icon>\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- No uploads message when manually opened -->\r\n <div class=\"no-uploads-message\">\r\n <div class=\"message-content\">\r\n <cide-ele-icon size=\"md\" class=\"message-icon\">cloud_upload</cide-ele-icon>\r\n <div class=\"message-text\">\r\n <h4>No active uploads</h4>\r\n <p>Upload files to see their progress here</p>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n</div>\r\n}\r\n", styles: [".floating-uploader{position:fixed;bottom:20px;right:20px;width:320px;max-height:500px;background:#fff;border-radius:12px;box-shadow:0 8px 32px #0000001f;border:1px solid rgba(0,0,0,.08);z-index:1000;overflow:hidden;transition:all .3s cubic-bezier(.4,0,.2,1);transform:translateY(0);opacity:1}.floating-uploader.animating{transition:all .3s cubic-bezier(.4,0,.2,1)}.floating-uploader.minimized .uploader-content{display:none}.floating-uploader.minimized .uploader-footer{border-top:none}.floating-uploader.uploading{border-color:#3b82f6;box-shadow:0 8px 32px #3b82f626}.floating-uploader .uploader-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#f8fafc;border-bottom:1px solid #e2e8f0}.floating-uploader .uploader-header .header-left{display:flex;align-items:center;gap:8px}.floating-uploader .uploader-header .header-left .upload-icon{display:flex;align-items:center;justify-content:center;width:24px;height:24px;background:#3b82f6;border-radius:6px;color:#fff}.floating-uploader .uploader-header .header-left .upload-info .upload-title{font-size:14px;font-weight:600;color:#1e293b;margin:0}.floating-uploader .uploader-header .header-left .upload-info .upload-summary{font-size:12px;color:#64748b;margin:0}.floating-uploader .uploader-header .header-actions{display:flex;gap:4px}.floating-uploader .uploader-header .header-actions .action-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;background:transparent;border-radius:4px;cursor:pointer;transition:background-color .2s;color:#64748b}.floating-uploader .uploader-header .header-actions .action-btn:hover{background:#e2e8f0;color:#1e293b}.floating-uploader .uploader-header .header-actions .action-btn.close-btn:hover{background:#fef2f2;color:#dc2626}.floating-uploader .uploader-content{max-height:400px;overflow-y:auto}.floating-uploader .uploader-content .overall-progress{padding:12px 16px;border-bottom:1px solid #e2e8f0}.floating-uploader .uploader-content .overall-progress .progress-bar{width:100%;height:4px;background:#e2e8f0;border-radius:2px;overflow:hidden;margin-bottom:4px}.floating-uploader .uploader-content .overall-progress .progress-bar .progress-fill{height:100%;background:#3b82f6;transition:width .3s ease}.floating-uploader .uploader-content .overall-progress .progress-text{font-size:12px;color:#64748b;text-align:center;display:block}.floating-uploader .uploader-content .upload-queue .upload-item{display:flex;align-items:center;padding:8px 16px;border-bottom:1px solid #f1f5f9;transition:background-color .2s}.floating-uploader .uploader-content .upload-queue .upload-item:last-child{border-bottom:none}.floating-uploader .uploader-content .upload-queue .upload-item.status-uploading{background:#f0f9ff}.floating-uploader .uploader-content .upload-queue .upload-item.status-completed{background:#f0fdf4}.floating-uploader .uploader-content .upload-queue .upload-item.status-error{background:#fef2f2}.floating-uploader .uploader-content .upload-queue .upload-item .file-info{display:flex;align-items:center;gap:8px;flex:1;min-width:0}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .status-icon{flex-shrink:0}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details{min-width:0;flex:1}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details .file-name{font-size:13px;font-weight:500;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details .file-status{font-size:11px;margin:0}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details .file-status span{font-weight:500}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress{display:flex;align-items:center;gap:8px;margin:0 8px;min-width:80px}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-bar{flex:1;height:3px;background:#e2e8f0;border-radius:2px;overflow:hidden}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-bar .progress-fill{height:100%;background:#3b82f6;transition:width .3s ease}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-text{font-size:10px;color:#64748b;min-width:24px;text-align:right}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions{display:flex;gap:4px}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;background:transparent;border-radius:4px;cursor:pointer;transition:all .2s;color:#64748b}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn:hover{background:#e2e8f0}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.cancel-btn:hover{background:#fef2f2;color:#dc2626}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.retry-btn:hover{background:#f0f9ff;color:#3b82f6}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.success-btn{color:#16a34a}.floating-uploader .uploader-content .hidden-uploader{display:none}.floating-uploader .uploader-footer{padding:8px 16px;background:#f8fafc;border-top:1px solid #e2e8f0}.floating-uploader .uploader-footer .footer-stats{display:flex;gap:12px;font-size:11px}.floating-uploader .uploader-footer .footer-stats .stat{display:flex;align-items:center;gap:4px;color:#64748b}.floating-uploader .uploader-footer .footer-stats .stat.uploading{color:#3b82f6}.floating-uploader .uploader-footer .footer-stats .stat.completed{color:#16a34a}.floating-uploader .uploader-footer .footer-stats .stat.failed{color:#dc2626}@media (max-width: 640px){.floating-uploader{bottom:10px;right:10px;left:10px;width:auto;max-width:none}}@media (prefers-color-scheme: dark){.floating-uploader{background:#1e293b;border-color:#334155;box-shadow:0 8px 32px #0000004d}.floating-uploader.uploading{border-color:#3b82f6;box-shadow:0 8px 32px #3b82f633}.floating-uploader .uploader-header{background:#334155;border-bottom-color:#475569}.floating-uploader .uploader-header .header-left .upload-icon{background:#3b82f6}.floating-uploader .uploader-header .header-left .upload-info .upload-title{color:#f1f5f9}.floating-uploader .uploader-header .header-left .upload-info .upload-summary,.floating-uploader .uploader-header .header-actions .action-btn{color:#94a3b8}.floating-uploader .uploader-header .header-actions .action-btn:hover{background:#475569;color:#f1f5f9}.floating-uploader .uploader-header .header-actions .action-btn.close-btn:hover{background:#7f1d1d;color:#fca5a5}.floating-uploader .uploader-content .overall-progress{border-bottom-color:#475569}.floating-uploader .uploader-content .overall-progress .progress-bar{background:#475569}.floating-uploader .uploader-content .overall-progress .progress-bar .progress-fill{background:#3b82f6}.floating-uploader .uploader-content .overall-progress .progress-text{color:#94a3b8}.floating-uploader .uploader-content .upload-queue .upload-item{border-bottom-color:#334155}.floating-uploader .uploader-content .upload-queue .upload-item.status-uploading{background:#1e3a8a}.floating-uploader .uploader-content .upload-queue .upload-item.status-completed{background:#14532d}.floating-uploader .uploader-content .upload-queue .upload-item.status-error{background:#7f1d1d}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details .file-name{color:#f1f5f9}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-bar{background:#475569}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-bar .progress-fill{background:#3b82f6}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-text,.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn{color:#94a3b8}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn:hover{background:#475569}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.cancel-btn:hover{background:#7f1d1d;color:#fca5a5}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.retry-btn:hover{background:#1e3a8a;color:#60a5fa}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.success-btn{color:#4ade80}.floating-uploader .uploader-footer{background:#334155;border-top-color:#475569}.floating-uploader .uploader-footer .footer-stats .stat{color:#94a3b8}.floating-uploader .uploader-footer .footer-stats .stat.uploading{color:#60a5fa}.floating-uploader .uploader-footer .footer-stats .stat.completed{color:#4ade80}.floating-uploader .uploader-footer .footer-stats .stat.failed{color:#fca5a5}}@keyframes slideInUp{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes slideOutDown{0%{transform:translateY(0);opacity:1}to{transform:translateY(100%);opacity:0}}.floating-uploader.animating{animation:slideInUp .3s cubic-bezier(.4,0,.2,1)}.floating-uploader.animating.hiding{animation:slideOutDown .3s cubic-bezier(.4,0,.2,1)}.no-uploads-message{padding:2rem;text-align:center;color:#6b7280}.no-uploads-message .message-content{display:flex;flex-direction:column;align-items:center;gap:1rem}.no-uploads-message .message-content .message-icon{color:#9ca3af;opacity:.7}.no-uploads-message .message-content .message-text h4{margin:0 0 .5rem;font-size:1.1rem;font-weight:600;color:#374151}.no-uploads-message .message-content .message-text p{margin:0;font-size:.9rem;color:#6b7280}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }, { kind: "pipe", type: i1.KeyValuePipe, name: "keyvalue" }] });
|
|
3489
|
+
}
|
|
3490
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFloatingFileUploaderComponent, decorators: [{
|
|
3491
|
+
type: Component,
|
|
3492
|
+
args: [{ selector: 'cide-ele-floating-file-uploader', standalone: true, imports: [
|
|
3493
|
+
CommonModule,
|
|
3494
|
+
CideIconComponent
|
|
3495
|
+
], template: "<!-- Floating File Uploader Container -->\r\n@if (isVisible()) {\r\n<div class=\"floating-uploader\" \r\n [class.minimized]=\"isMinimized()\" \r\n [class.animating]=\"isAnimating()\">\r\n\r\n <!-- Header -->\r\n <div class=\"uploader-header\">\r\n <div class=\"header-left\">\r\n <div class=\"upload-icon\">\r\n <cide-ele-icon size=\"sm\">cloud_upload</cide-ele-icon>\r\n </div>\r\n <div class=\"upload-info\">\r\n <div class=\"upload-title\">File Upload</div>\r\n <div class=\"upload-summary\">{{ getUploadSummary() }}</div>\r\n </div>\r\n </div>\r\n \r\n <div class=\"header-actions\">\r\n <button class=\"action-btn minimize-btn\" (click)=\"toggleMinimize()\" [title]=\"isMinimized() ? 'Expand' : 'Minimize'\">\r\n <cide-ele-icon size=\"xs\">{{ isMinimized() ? 'expand_more' : 'expand_less' }}</cide-ele-icon>\r\n </button>\r\n <button class=\"action-btn close-btn\" (click)=\"close()\" title=\"Close\">\r\n <cide-ele-icon size=\"xs\">close</cide-ele-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Content (hidden when minimized) -->\r\n @if (!isMinimized()) {\r\n <div class=\"uploader-content\">\r\n \r\n <!-- Overall Progress Bar -->\r\n @if (uploadQueue().length > 0 || activeUploads().size > 0) {\r\n <div class=\"overall-progress\">\r\n <div class=\"progress-bar\">\r\n <div class=\"progress-fill\" [style.width.%]=\"getOverallProgress()\"></div>\r\n </div>\r\n <div class=\"progress-text\">{{ getOverallProgress() }}%</div>\r\n </div>\r\n }\r\n\r\n <!-- Upload Queue - Show files from file manager service -->\r\n @if (uploadQueue().length > 0 || activeUploads().size > 0) {\r\n <div class=\"upload-queue\">\r\n <!-- Show files in queue (pending) -->\r\n @for (fileId of uploadQueue(); track fileId) {\r\n <div class=\"upload-item status-pending\">\r\n <div class=\"file-info\">\r\n <cide-ele-icon class=\"status-icon\" size=\"xs\">schedule</cide-ele-icon>\r\n <div class=\"file-details\">\r\n <div class=\"file-name\">{{ getFileNameFromId(fileId) }}</div>\r\n <div class=\"file-status\">\r\n <span class=\"text-yellow-600\">Waiting...</span>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"upload-actions\">\r\n <button class=\"action-btn cancel-btn\" (click)=\"cancelUpload(fileId)\" title=\"Cancel\">\r\n <cide-ele-icon size=\"xs\">cancel</cide-ele-icon>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n \r\n <!-- Show active uploads -->\r\n @for (entry of activeUploads() | keyvalue; track entry.key) {\r\n <div class=\"upload-item\" [class]=\"getStatusClass(entry.value.stage)\">\r\n <div class=\"file-info\">\r\n <cide-ele-icon class=\"status-icon\" size=\"xs\">{{ getStatusIcon(entry.value.stage) }}</cide-ele-icon>\r\n <div class=\"file-details\">\r\n <div class=\"file-name\">{{ getFileNameFromId(entry.key) }}</div>\r\n <div class=\"file-status\">\r\n @switch (entry.value.stage) {\r\n @case ('reading') {\r\n <span class=\"text-yellow-600\">Reading...</span>\r\n }\r\n @case ('uploading') {\r\n <span class=\"text-blue-600\">Uploading...</span>\r\n }\r\n @case ('complete') {\r\n <span class=\"text-green-600\">Completed</span>\r\n }\r\n @case ('error') {\r\n <span class=\"text-red-600\">Failed</span>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Progress Bar (for uploading files) -->\r\n @if (entry.value.stage === 'uploading') {\r\n <div class=\"file-progress\">\r\n <div class=\"progress-bar\">\r\n <div class=\"progress-fill\" [style.width.%]=\"entry.value.percentage\"></div>\r\n </div>\r\n <span class=\"progress-text\">{{ entry.value.percentage }}%</span>\r\n </div>\r\n }\r\n\r\n <!-- Actions -->\r\n <div class=\"upload-actions\">\r\n @switch (entry.value.stage) {\r\n @case ('reading') {\r\n <button class=\"action-btn cancel-btn\" (click)=\"cancelUpload(entry.key)\" title=\"Cancel\">\r\n <cide-ele-icon size=\"xs\">cancel</cide-ele-icon>\r\n </button>\r\n }\r\n @case ('uploading') {\r\n <button class=\"action-btn cancel-btn\" (click)=\"cancelUpload(entry.key)\" title=\"Cancel\">\r\n <cide-ele-icon size=\"xs\">cancel</cide-ele-icon>\r\n </button>\r\n }\r\n @case ('complete') {\r\n <button class=\"action-btn success-btn\" title=\"Completed\">\r\n <cide-ele-icon size=\"xs\">check_circle</cide-ele-icon>\r\n </button>\r\n }\r\n @case ('error') {\r\n <button class=\"action-btn retry-btn\" title=\"Retry\">\r\n <cide-ele-icon size=\"xs\">refresh</cide-ele-icon>\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- No uploads message when manually opened -->\r\n <div class=\"no-uploads-message\">\r\n <div class=\"message-content\">\r\n <cide-ele-icon size=\"md\" class=\"message-icon\">cloud_upload</cide-ele-icon>\r\n <div class=\"message-text\">\r\n <h4>No active uploads</h4>\r\n <p>Upload files to see their progress here</p>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n</div>\r\n}\r\n", styles: [".floating-uploader{position:fixed;bottom:20px;right:20px;width:320px;max-height:500px;background:#fff;border-radius:12px;box-shadow:0 8px 32px #0000001f;border:1px solid rgba(0,0,0,.08);z-index:1000;overflow:hidden;transition:all .3s cubic-bezier(.4,0,.2,1);transform:translateY(0);opacity:1}.floating-uploader.animating{transition:all .3s cubic-bezier(.4,0,.2,1)}.floating-uploader.minimized .uploader-content{display:none}.floating-uploader.minimized .uploader-footer{border-top:none}.floating-uploader.uploading{border-color:#3b82f6;box-shadow:0 8px 32px #3b82f626}.floating-uploader .uploader-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#f8fafc;border-bottom:1px solid #e2e8f0}.floating-uploader .uploader-header .header-left{display:flex;align-items:center;gap:8px}.floating-uploader .uploader-header .header-left .upload-icon{display:flex;align-items:center;justify-content:center;width:24px;height:24px;background:#3b82f6;border-radius:6px;color:#fff}.floating-uploader .uploader-header .header-left .upload-info .upload-title{font-size:14px;font-weight:600;color:#1e293b;margin:0}.floating-uploader .uploader-header .header-left .upload-info .upload-summary{font-size:12px;color:#64748b;margin:0}.floating-uploader .uploader-header .header-actions{display:flex;gap:4px}.floating-uploader .uploader-header .header-actions .action-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;background:transparent;border-radius:4px;cursor:pointer;transition:background-color .2s;color:#64748b}.floating-uploader .uploader-header .header-actions .action-btn:hover{background:#e2e8f0;color:#1e293b}.floating-uploader .uploader-header .header-actions .action-btn.close-btn:hover{background:#fef2f2;color:#dc2626}.floating-uploader .uploader-content{max-height:400px;overflow-y:auto}.floating-uploader .uploader-content .overall-progress{padding:12px 16px;border-bottom:1px solid #e2e8f0}.floating-uploader .uploader-content .overall-progress .progress-bar{width:100%;height:4px;background:#e2e8f0;border-radius:2px;overflow:hidden;margin-bottom:4px}.floating-uploader .uploader-content .overall-progress .progress-bar .progress-fill{height:100%;background:#3b82f6;transition:width .3s ease}.floating-uploader .uploader-content .overall-progress .progress-text{font-size:12px;color:#64748b;text-align:center;display:block}.floating-uploader .uploader-content .upload-queue .upload-item{display:flex;align-items:center;padding:8px 16px;border-bottom:1px solid #f1f5f9;transition:background-color .2s}.floating-uploader .uploader-content .upload-queue .upload-item:last-child{border-bottom:none}.floating-uploader .uploader-content .upload-queue .upload-item.status-uploading{background:#f0f9ff}.floating-uploader .uploader-content .upload-queue .upload-item.status-completed{background:#f0fdf4}.floating-uploader .uploader-content .upload-queue .upload-item.status-error{background:#fef2f2}.floating-uploader .uploader-content .upload-queue .upload-item .file-info{display:flex;align-items:center;gap:8px;flex:1;min-width:0}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .status-icon{flex-shrink:0}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details{min-width:0;flex:1}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details .file-name{font-size:13px;font-weight:500;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details .file-status{font-size:11px;margin:0}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details .file-status span{font-weight:500}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress{display:flex;align-items:center;gap:8px;margin:0 8px;min-width:80px}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-bar{flex:1;height:3px;background:#e2e8f0;border-radius:2px;overflow:hidden}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-bar .progress-fill{height:100%;background:#3b82f6;transition:width .3s ease}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-text{font-size:10px;color:#64748b;min-width:24px;text-align:right}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions{display:flex;gap:4px}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;background:transparent;border-radius:4px;cursor:pointer;transition:all .2s;color:#64748b}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn:hover{background:#e2e8f0}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.cancel-btn:hover{background:#fef2f2;color:#dc2626}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.retry-btn:hover{background:#f0f9ff;color:#3b82f6}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.success-btn{color:#16a34a}.floating-uploader .uploader-content .hidden-uploader{display:none}.floating-uploader .uploader-footer{padding:8px 16px;background:#f8fafc;border-top:1px solid #e2e8f0}.floating-uploader .uploader-footer .footer-stats{display:flex;gap:12px;font-size:11px}.floating-uploader .uploader-footer .footer-stats .stat{display:flex;align-items:center;gap:4px;color:#64748b}.floating-uploader .uploader-footer .footer-stats .stat.uploading{color:#3b82f6}.floating-uploader .uploader-footer .footer-stats .stat.completed{color:#16a34a}.floating-uploader .uploader-footer .footer-stats .stat.failed{color:#dc2626}@media (max-width: 640px){.floating-uploader{bottom:10px;right:10px;left:10px;width:auto;max-width:none}}@media (prefers-color-scheme: dark){.floating-uploader{background:#1e293b;border-color:#334155;box-shadow:0 8px 32px #0000004d}.floating-uploader.uploading{border-color:#3b82f6;box-shadow:0 8px 32px #3b82f633}.floating-uploader .uploader-header{background:#334155;border-bottom-color:#475569}.floating-uploader .uploader-header .header-left .upload-icon{background:#3b82f6}.floating-uploader .uploader-header .header-left .upload-info .upload-title{color:#f1f5f9}.floating-uploader .uploader-header .header-left .upload-info .upload-summary,.floating-uploader .uploader-header .header-actions .action-btn{color:#94a3b8}.floating-uploader .uploader-header .header-actions .action-btn:hover{background:#475569;color:#f1f5f9}.floating-uploader .uploader-header .header-actions .action-btn.close-btn:hover{background:#7f1d1d;color:#fca5a5}.floating-uploader .uploader-content .overall-progress{border-bottom-color:#475569}.floating-uploader .uploader-content .overall-progress .progress-bar{background:#475569}.floating-uploader .uploader-content .overall-progress .progress-bar .progress-fill{background:#3b82f6}.floating-uploader .uploader-content .overall-progress .progress-text{color:#94a3b8}.floating-uploader .uploader-content .upload-queue .upload-item{border-bottom-color:#334155}.floating-uploader .uploader-content .upload-queue .upload-item.status-uploading{background:#1e3a8a}.floating-uploader .uploader-content .upload-queue .upload-item.status-completed{background:#14532d}.floating-uploader .uploader-content .upload-queue .upload-item.status-error{background:#7f1d1d}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details .file-name{color:#f1f5f9}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-bar{background:#475569}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-bar .progress-fill{background:#3b82f6}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-text,.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn{color:#94a3b8}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn:hover{background:#475569}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.cancel-btn:hover{background:#7f1d1d;color:#fca5a5}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.retry-btn:hover{background:#1e3a8a;color:#60a5fa}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.success-btn{color:#4ade80}.floating-uploader .uploader-footer{background:#334155;border-top-color:#475569}.floating-uploader .uploader-footer .footer-stats .stat{color:#94a3b8}.floating-uploader .uploader-footer .footer-stats .stat.uploading{color:#60a5fa}.floating-uploader .uploader-footer .footer-stats .stat.completed{color:#4ade80}.floating-uploader .uploader-footer .footer-stats .stat.failed{color:#fca5a5}}@keyframes slideInUp{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes slideOutDown{0%{transform:translateY(0);opacity:1}to{transform:translateY(100%);opacity:0}}.floating-uploader.animating{animation:slideInUp .3s cubic-bezier(.4,0,.2,1)}.floating-uploader.animating.hiding{animation:slideOutDown .3s cubic-bezier(.4,0,.2,1)}.no-uploads-message{padding:2rem;text-align:center;color:#6b7280}.no-uploads-message .message-content{display:flex;flex-direction:column;align-items:center;gap:1rem}.no-uploads-message .message-content .message-icon{color:#9ca3af;opacity:.7}.no-uploads-message .message-content .message-text h4{margin:0 0 .5rem;font-size:1.1rem;font-weight:600;color:#374151}.no-uploads-message .message-content .message-text p{margin:0;font-size:.9rem;color:#6b7280}\n"] }]
|
|
3496
|
+
}], ctorParameters: () => [] });
|
|
3497
|
+
|
|
3498
|
+
class CideEleFileInputComponent {
|
|
3499
|
+
fileManagerService = inject(CideEleFileManagerService);
|
|
3500
|
+
notificationService = inject(NotificationService);
|
|
3501
|
+
elementService = inject(CideElementsService);
|
|
3502
|
+
destroyRef = inject(DestroyRef);
|
|
3503
|
+
floatingUploader = inject(CideEleFloatingFileUploaderComponent, { optional: true });
|
|
3504
|
+
// private readonly pendingTasks = inject(PendingTasks); // TODO: Fix PendingTasks API usage
|
|
3505
|
+
// Traditional @Input() decorators
|
|
3506
|
+
label = 'Choose file';
|
|
3507
|
+
accept = '';
|
|
3508
|
+
multiple = false;
|
|
3509
|
+
disabled = false;
|
|
3510
|
+
required = false;
|
|
3511
|
+
helperText = '';
|
|
3512
|
+
errorText = '';
|
|
3513
|
+
showPreview = false;
|
|
3514
|
+
previewWidth = '200px';
|
|
3515
|
+
previewHeight = '200px';
|
|
3516
|
+
previewBoxMode = false;
|
|
3517
|
+
showFileName = true;
|
|
3518
|
+
placeholderText = 'Click to select image';
|
|
3519
|
+
placeholderIcon = '📷';
|
|
3520
|
+
autoUpload = false;
|
|
3521
|
+
uploadData = {};
|
|
3522
|
+
showFloatingUploader = true;
|
|
3523
|
+
floatingUploaderGroupId;
|
|
3524
|
+
// Traditional @Output() decorators
|
|
3525
|
+
fileChange = new EventEmitter();
|
|
3526
|
+
uploadSuccess = new EventEmitter();
|
|
3527
|
+
uploadError = new EventEmitter();
|
|
3528
|
+
uploadProgressChange = new EventEmitter();
|
|
3529
|
+
// Readable signals created from @Input() decorator values
|
|
3530
|
+
labelSignal = signal(this.label, ...(ngDevMode ? [{ debugName: "labelSignal" }] : []));
|
|
3531
|
+
acceptSignal = signal(this.accept, ...(ngDevMode ? [{ debugName: "acceptSignal" }] : []));
|
|
3532
|
+
multipleSignal = signal(this.multiple, ...(ngDevMode ? [{ debugName: "multipleSignal" }] : []));
|
|
3533
|
+
disabledSignal = signal(this.disabled, ...(ngDevMode ? [{ debugName: "disabledSignal" }] : []));
|
|
3534
|
+
requiredSignal = signal(this.required, ...(ngDevMode ? [{ debugName: "requiredSignal" }] : []));
|
|
3535
|
+
helperTextSignal = signal(this.helperText, ...(ngDevMode ? [{ debugName: "helperTextSignal" }] : []));
|
|
3536
|
+
errorTextSignal = signal(this.errorText, ...(ngDevMode ? [{ debugName: "errorTextSignal" }] : []));
|
|
3537
|
+
showPreviewSignal = signal(this.showPreview, ...(ngDevMode ? [{ debugName: "showPreviewSignal" }] : []));
|
|
3538
|
+
previewWidthSignal = signal(this.previewWidth, ...(ngDevMode ? [{ debugName: "previewWidthSignal" }] : []));
|
|
3539
|
+
previewHeightSignal = signal(this.previewHeight, ...(ngDevMode ? [{ debugName: "previewHeightSignal" }] : []));
|
|
3540
|
+
previewBoxModeSignal = signal(this.previewBoxMode, ...(ngDevMode ? [{ debugName: "previewBoxModeSignal" }] : []));
|
|
3541
|
+
showFileNameSignal = signal(this.showFileName, ...(ngDevMode ? [{ debugName: "showFileNameSignal" }] : []));
|
|
3542
|
+
placeholderTextSignal = signal(this.placeholderText, ...(ngDevMode ? [{ debugName: "placeholderTextSignal" }] : []));
|
|
3543
|
+
placeholderIconSignal = signal(this.placeholderIcon, ...(ngDevMode ? [{ debugName: "placeholderIconSignal" }] : []));
|
|
3544
|
+
autoUploadSignal = signal(this.autoUpload, ...(ngDevMode ? [{ debugName: "autoUploadSignal" }] : []));
|
|
3545
|
+
uploadDataSignal = signal(this.uploadData, ...(ngDevMode ? [{ debugName: "uploadDataSignal" }] : []));
|
|
3546
|
+
showFloatingUploaderSignal = signal(this.showFloatingUploader, ...(ngDevMode ? [{ debugName: "showFloatingUploaderSignal" }] : []));
|
|
3547
|
+
floatingUploaderGroupIdSignal = signal(this.floatingUploaderGroupId, ...(ngDevMode ? [{ debugName: "floatingUploaderGroupIdSignal" }] : []));
|
|
3548
|
+
// Reactive state with signals
|
|
3549
|
+
id = signal(Math.random().toString(36).substring(2, 10), ...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
3550
|
+
isUploading = signal(false, ...(ngDevMode ? [{ debugName: "isUploading" }] : []));
|
|
3551
|
+
uploadProgress = signal(0, ...(ngDevMode ? [{ debugName: "uploadProgress" }] : []));
|
|
3552
|
+
uploadStatus = signal('idle', ...(ngDevMode ? [{ debugName: "uploadStatus" }] : []));
|
|
3553
|
+
files = signal(null, ...(ngDevMode ? [{ debugName: "files" }] : []));
|
|
3554
|
+
fileNames = signal([], ...(ngDevMode ? [{ debugName: "fileNames" }] : []));
|
|
3555
|
+
previewUrls = signal([], ...(ngDevMode ? [{ debugName: "previewUrls" }] : []));
|
|
3556
|
+
uploadNotificationId = signal(null, ...(ngDevMode ? [{ debugName: "uploadNotificationId" }] : []));
|
|
3557
|
+
isDragOver = signal(false, ...(ngDevMode ? [{ debugName: "isDragOver" }] : []));
|
|
3558
|
+
groupId = signal(null, ...(ngDevMode ? [{ debugName: "groupId" }] : [])); // Group ID for multiple file uploads
|
|
3559
|
+
isMultipleUploadMode = signal(false, ...(ngDevMode ? [{ debugName: "isMultipleUploadMode" }] : [])); // Flag to track if we're in multiple upload mode
|
|
3560
|
+
// Computed signals for better relationships
|
|
3561
|
+
hasFiles = computed(() => this.files() !== null && this.files().length > 0, ...(ngDevMode ? [{ debugName: "hasFiles" }] : []));
|
|
3562
|
+
canUpload = computed(() => this.hasFiles() && !this.isUploading() && !this.disabledSignal(), ...(ngDevMode ? [{ debugName: "canUpload" }] : []));
|
|
3563
|
+
isInErrorState = computed(() => this.uploadStatus() === 'error', ...(ngDevMode ? [{ debugName: "isInErrorState" }] : []));
|
|
3564
|
+
isInSuccessState = computed(() => this.uploadStatus() === 'success', ...(ngDevMode ? [{ debugName: "isInSuccessState" }] : []));
|
|
3565
|
+
// Angular 20: Computed values using new features
|
|
3566
|
+
totalFileSize = computed(() => {
|
|
3567
|
+
if (!this.files())
|
|
3568
|
+
return 0;
|
|
3569
|
+
return Array.from(this.files()).reduce((total, file) => total + file.size, 0);
|
|
3570
|
+
}, ...(ngDevMode ? [{ debugName: "totalFileSize" }] : []));
|
|
3571
|
+
fileSizeInMB = computed(() => {
|
|
3572
|
+
// Angular 20: Using ** operator for exponentiation
|
|
3573
|
+
return (this.totalFileSize() / (1024 ** 2)).toFixed(2);
|
|
3574
|
+
}, ...(ngDevMode ? [{ debugName: "fileSizeInMB" }] : []));
|
|
3575
|
+
uploadProgressPercentage = computed(() => {
|
|
3576
|
+
// Angular 20: Using ** operator for exponentiation
|
|
3577
|
+
return Math.round(this.uploadProgress() ** 1); // Simple power operation
|
|
3578
|
+
}, ...(ngDevMode ? [{ debugName: "uploadProgressPercentage" }] : []));
|
|
3579
|
+
// ControlValueAccessor callbacks
|
|
3580
|
+
onChange = (value) => { };
|
|
3581
|
+
onTouched = () => { };
|
|
3582
|
+
onValidatorChange = () => { };
|
|
3583
|
+
// Computed values
|
|
3584
|
+
hasImages = computed(() => this.previewUrls().length > 0, ...(ngDevMode ? [{ debugName: "hasImages" }] : []));
|
|
3585
|
+
isPreviewBoxMode = computed(() => this.previewBoxModeSignal() && this.showPreviewSignal(), ...(ngDevMode ? [{ debugName: "isPreviewBoxMode" }] : []));
|
|
3586
|
+
isImagePreviewAvailable = computed(() => this.showPreviewSignal() && this.previewUrls().length > 0, ...(ngDevMode ? [{ debugName: "isImagePreviewAvailable" }] : []));
|
|
3587
|
+
constructor() {
|
|
3588
|
+
// Angular 20: afterRenderEffect for DOM operations
|
|
3589
|
+
afterRenderEffect(() => {
|
|
3590
|
+
// Update file input element when files change
|
|
3591
|
+
if (this.files()) {
|
|
3592
|
+
const fileInput = document.getElementById('cide-file-input-' + this.id());
|
|
3593
|
+
if (fileInput) {
|
|
3594
|
+
// Ensure the input reflects the current state
|
|
3595
|
+
fileInput.files = this.files();
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
});
|
|
3599
|
+
// Angular 20: afterNextRender for one-time DOM operations
|
|
3600
|
+
afterNextRender(() => {
|
|
3601
|
+
console.log('🎯 [FileInput] Component rendered and DOM is ready');
|
|
3602
|
+
});
|
|
3603
|
+
}
|
|
3604
|
+
ngOnInit() {
|
|
3605
|
+
// Update signals with initial @Input() values
|
|
3606
|
+
this.labelSignal.set(this.label);
|
|
3607
|
+
this.acceptSignal.set(this.accept);
|
|
3608
|
+
this.multipleSignal.set(this.multiple);
|
|
3609
|
+
this.disabledSignal.set(this.disabled);
|
|
3610
|
+
this.requiredSignal.set(this.required);
|
|
3611
|
+
this.helperTextSignal.set(this.helperText);
|
|
3612
|
+
this.errorTextSignal.set(this.errorText);
|
|
3613
|
+
this.showPreviewSignal.set(this.showPreview);
|
|
3614
|
+
this.previewWidthSignal.set(this.previewWidth);
|
|
3615
|
+
this.previewHeightSignal.set(this.previewHeight);
|
|
3616
|
+
this.previewBoxModeSignal.set(this.previewBoxMode);
|
|
3617
|
+
this.showFileNameSignal.set(this.showFileName);
|
|
3618
|
+
this.placeholderTextSignal.set(this.placeholderText);
|
|
3619
|
+
this.placeholderIconSignal.set(this.placeholderIcon);
|
|
3620
|
+
this.autoUploadSignal.set(this.autoUpload);
|
|
3621
|
+
this.uploadDataSignal.set(this.uploadData);
|
|
3622
|
+
}
|
|
3623
|
+
ngOnChanges(changes) {
|
|
3624
|
+
// Angular 20: Update signals when @Input() values change
|
|
3625
|
+
if (changes['label'])
|
|
3626
|
+
this.labelSignal.set(this.label);
|
|
3627
|
+
if (changes['accept'])
|
|
3628
|
+
this.acceptSignal.set(this.accept);
|
|
3629
|
+
if (changes['multiple'])
|
|
3630
|
+
this.multipleSignal.set(this.multiple);
|
|
3631
|
+
if (changes['disabled'])
|
|
3632
|
+
this.disabledSignal.set(this.disabled);
|
|
3633
|
+
if (changes['required'])
|
|
3634
|
+
this.requiredSignal.set(this.required);
|
|
3635
|
+
if (changes['helperText'])
|
|
3636
|
+
this.helperTextSignal.set(this.helperText);
|
|
3637
|
+
if (changes['errorText'])
|
|
3638
|
+
this.errorTextSignal.set(this.errorText);
|
|
3639
|
+
if (changes['showPreview'])
|
|
3640
|
+
this.showPreviewSignal.set(this.showPreview);
|
|
3641
|
+
if (changes['previewWidth'])
|
|
3642
|
+
this.previewWidthSignal.set(this.previewWidth);
|
|
3643
|
+
if (changes['previewHeight'])
|
|
3644
|
+
this.previewHeightSignal.set(this.previewHeight);
|
|
3645
|
+
if (changes['previewBoxMode'])
|
|
3646
|
+
this.previewBoxModeSignal.set(this.previewBoxMode);
|
|
3647
|
+
if (changes['showFileName'])
|
|
3648
|
+
this.showFileNameSignal.set(this.showFileName);
|
|
3649
|
+
if (changes['placeholderText'])
|
|
3650
|
+
this.placeholderTextSignal.set(this.placeholderText);
|
|
3651
|
+
if (changes['placeholderIcon'])
|
|
3652
|
+
this.placeholderIconSignal.set(this.placeholderIcon);
|
|
3653
|
+
if (changes['autoUpload'])
|
|
3654
|
+
this.autoUploadSignal.set(this.autoUpload);
|
|
3655
|
+
if (changes['uploadData'])
|
|
3656
|
+
this.uploadDataSignal.set(this.uploadData);
|
|
3657
|
+
}
|
|
3658
|
+
writeValue(value) {
|
|
3659
|
+
console.log('📝 [FileInput] writeValue called with:', value);
|
|
3660
|
+
if (typeof value === 'string') {
|
|
3661
|
+
// Value is an uploaded file ID - fetch file details and set preview
|
|
3662
|
+
console.log('📝 [FileInput] Value is uploaded file ID:', value);
|
|
3663
|
+
this.files.set(null);
|
|
3664
|
+
this.fileNames.set([]);
|
|
3665
|
+
this.clearPreviews();
|
|
3666
|
+
// Fetch file details to get base64 and set preview
|
|
3667
|
+
this.loadFileDetailsFromId(value);
|
|
4127
3668
|
}
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
console.
|
|
4131
|
-
|
|
3669
|
+
else if (value instanceof FileList) {
|
|
3670
|
+
// Value is a FileList
|
|
3671
|
+
console.log('📝 [FileInput] Value is FileList:', Array.from(value).map(f => f.name));
|
|
3672
|
+
this.files.set(value);
|
|
3673
|
+
this.fileNames.set(Array.from(value).map(f => f.name));
|
|
3674
|
+
this.generatePreviews();
|
|
3675
|
+
}
|
|
3676
|
+
else {
|
|
3677
|
+
// Value is null
|
|
3678
|
+
console.log('📝 [FileInput] Value is null');
|
|
3679
|
+
this.files.set(null);
|
|
3680
|
+
this.fileNames.set([]);
|
|
3681
|
+
this.clearPreviews();
|
|
4132
3682
|
}
|
|
4133
|
-
return true;
|
|
4134
3683
|
}
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
3684
|
+
registerOnChange(fn) {
|
|
3685
|
+
this.onChange = fn;
|
|
3686
|
+
}
|
|
3687
|
+
registerOnTouched(fn) {
|
|
3688
|
+
this.onTouched = fn;
|
|
3689
|
+
}
|
|
3690
|
+
registerOnValidatorChange(fn) {
|
|
3691
|
+
this.onValidatorChange = fn;
|
|
3692
|
+
}
|
|
3693
|
+
setDisabledState(isDisabled) {
|
|
3694
|
+
// Note: With input signals, disabled state is controlled by the parent component
|
|
3695
|
+
// This method is kept for ControlValueAccessor compatibility but doesn't modify the signal
|
|
3696
|
+
console.log('🔧 [FileInput] setDisabledState called with:', isDisabled, '(controlled by parent component)');
|
|
3697
|
+
}
|
|
3698
|
+
onFileSelected(event) {
|
|
3699
|
+
console.log('🔍 [FileInput] onFileSelected called');
|
|
3700
|
+
const input = event.target;
|
|
3701
|
+
const selectedFiles = input.files;
|
|
3702
|
+
this.files.set(selectedFiles);
|
|
3703
|
+
this.fileNames.set(selectedFiles ? Array.from(selectedFiles).map(f => f.name) : []);
|
|
3704
|
+
console.log('📁 [FileInput] Files selected:', this.fileNames());
|
|
3705
|
+
this.generatePreviews();
|
|
3706
|
+
// Reset upload status when new file is selected
|
|
3707
|
+
this.uploadStatus.set('idle');
|
|
3708
|
+
console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus());
|
|
3709
|
+
this.onChange(selectedFiles);
|
|
3710
|
+
this.fileChange.emit(selectedFiles);
|
|
3711
|
+
this.onTouched();
|
|
3712
|
+
// Show floating uploader if enabled and files are selected
|
|
3713
|
+
if (this.showFloatingUploaderSignal() && selectedFiles && selectedFiles.length > 0 && this.floatingUploader) {
|
|
3714
|
+
console.log('👁️ [FileInput] Showing floating uploader for', selectedFiles.length, 'files');
|
|
3715
|
+
this.floatingUploader.showUploader(this.floatingUploaderGroupIdSignal());
|
|
3716
|
+
}
|
|
3717
|
+
// Auto upload if enabled
|
|
3718
|
+
if (this.autoUploadSignal() && selectedFiles && selectedFiles.length > 0) {
|
|
3719
|
+
if (this.multipleSignal()) {
|
|
3720
|
+
console.log('🚀 [FileInput] Auto upload enabled for multiple files mode:', selectedFiles.length, 'files');
|
|
3721
|
+
this.uploadMultipleFiles(Array.from(selectedFiles));
|
|
4145
3722
|
}
|
|
4146
3723
|
else {
|
|
4147
|
-
|
|
4148
|
-
this.
|
|
3724
|
+
console.log('🚀 [FileInput] Auto upload enabled for single file mode:', selectedFiles[0].name);
|
|
3725
|
+
this.uploadFile(selectedFiles[0]);
|
|
4149
3726
|
}
|
|
4150
3727
|
}
|
|
3728
|
+
else {
|
|
3729
|
+
console.log('⏸️ [FileInput] Auto upload disabled or no files');
|
|
3730
|
+
}
|
|
4151
3731
|
}
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
this.
|
|
4159
|
-
|
|
4160
|
-
|
|
3732
|
+
clearFiles() {
|
|
3733
|
+
console.log('🗑️ [FileInput] clearFiles called');
|
|
3734
|
+
this.files.set(null);
|
|
3735
|
+
this.fileNames.set([]);
|
|
3736
|
+
this.clearPreviews();
|
|
3737
|
+
this.uploadStatus.set('idle');
|
|
3738
|
+
console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus());
|
|
3739
|
+
this.onChange(null);
|
|
3740
|
+
this.fileChange.emit(null);
|
|
3741
|
+
}
|
|
3742
|
+
uploadFile(file) {
|
|
3743
|
+
console.log('📤 [FileInput] uploadFile called for:', file.name, 'Size:', file.size, 'bytes');
|
|
3744
|
+
// Angular 20: Use PendingTasks for better loading state management
|
|
3745
|
+
// const uploadTask = this.pendingTasks.add(); // TODO: Fix PendingTasks API usage
|
|
3746
|
+
// console.log('⏳ [FileInput] Pending task added for upload tracking');
|
|
3747
|
+
// Set upload status to 'start' before starting upload
|
|
3748
|
+
this.uploadStatus.set('start');
|
|
3749
|
+
console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
|
|
3750
|
+
this.isUploading.set(true);
|
|
3751
|
+
this.uploadProgress.set(0);
|
|
3752
|
+
this.uploadProgressChange.emit(0);
|
|
3753
|
+
console.log('📊 [FileInput] Upload progress initialized to 0%');
|
|
3754
|
+
// Make form control invalid during upload - this prevents form submission
|
|
3755
|
+
this.onChange(null);
|
|
3756
|
+
console.log('🚫 [FileInput] Form control value set to null to prevent submission');
|
|
3757
|
+
// Show initial progress notification with spinner (persistent - no auto-dismiss)
|
|
3758
|
+
const notificationId = this.notificationService.showProgress('🔄 Preparing file upload...', 0, { duration: 0 });
|
|
3759
|
+
this.uploadNotificationId.set(notificationId);
|
|
3760
|
+
console.log('🔔 [FileInput] Progress notification started with ID:', notificationId);
|
|
3761
|
+
this.fileManagerService.uploadFile(file, this.uploadDataSignal(), (progress) => {
|
|
3762
|
+
// Real progress callback from file manager service
|
|
3763
|
+
this.uploadProgress.set(progress);
|
|
3764
|
+
this.uploadProgressChange.emit(progress);
|
|
3765
|
+
console.log('📈 [FileInput] Upload progress:', Math.round(progress) + '%');
|
|
3766
|
+
// Set upload status to 'uploading' when progress starts
|
|
3767
|
+
if (this.uploadStatus() === 'start') {
|
|
3768
|
+
this.uploadStatus.set('uploading');
|
|
3769
|
+
console.log('🔄 [FileInput] Upload status changed to:', this.uploadStatus());
|
|
3770
|
+
}
|
|
3771
|
+
// Update progress notification with spinner
|
|
3772
|
+
const notificationId = this.uploadNotificationId();
|
|
3773
|
+
if (notificationId) {
|
|
3774
|
+
let progressMessage = '';
|
|
3775
|
+
if (progress < 10) {
|
|
3776
|
+
progressMessage = '🔄 Starting upload...';
|
|
3777
|
+
}
|
|
3778
|
+
else if (progress < 25) {
|
|
3779
|
+
progressMessage = '🔄 Uploading file...';
|
|
3780
|
+
}
|
|
3781
|
+
else if (progress < 50) {
|
|
3782
|
+
progressMessage = '🔄 Upload in progress...';
|
|
3783
|
+
}
|
|
3784
|
+
else if (progress < 75) {
|
|
3785
|
+
progressMessage = '🔄 Almost done...';
|
|
3786
|
+
}
|
|
3787
|
+
else if (progress < 95) {
|
|
3788
|
+
progressMessage = '🔄 Finishing upload...';
|
|
3789
|
+
}
|
|
3790
|
+
else {
|
|
3791
|
+
progressMessage = '🔄 Finalizing...';
|
|
3792
|
+
}
|
|
3793
|
+
this.notificationService.updateProgress(notificationId, progress, progressMessage);
|
|
3794
|
+
}
|
|
3795
|
+
}).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
|
4161
3796
|
next: (response) => {
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
this.
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
3797
|
+
console.log('🎉 [FileInput] Upload SUCCESS - Response received:', response);
|
|
3798
|
+
// Angular 20: Complete the pending task
|
|
3799
|
+
// this.pendingTasks.complete(uploadTask); // TODO: Fix PendingTasks API usage
|
|
3800
|
+
// console.log('✅ [FileInput] Pending task completed for successful upload');
|
|
3801
|
+
// Set upload status to 'success'
|
|
3802
|
+
this.uploadStatus.set('success');
|
|
3803
|
+
console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
|
|
3804
|
+
// Complete the progress
|
|
3805
|
+
this.uploadProgress.set(100);
|
|
3806
|
+
this.uploadProgressChange.emit(100);
|
|
3807
|
+
console.log('📊 [FileInput] Upload progress completed: 100%');
|
|
3808
|
+
// Update progress notification to complete
|
|
3809
|
+
const notificationId = this.uploadNotificationId();
|
|
3810
|
+
if (notificationId) {
|
|
3811
|
+
this.notificationService.remove(notificationId);
|
|
3812
|
+
console.log('🔔 [FileInput] Progress notification removed');
|
|
3813
|
+
}
|
|
3814
|
+
// Success notification removed for cleaner UX
|
|
3815
|
+
this.uploadNotificationId.set(null);
|
|
3816
|
+
// Extract ID from CoreFileManagerInsertUpdateResponse
|
|
3817
|
+
const uploadedId = response?.data?.core_file_manager?.[0]?.cyfm_id;
|
|
3818
|
+
if (uploadedId) {
|
|
3819
|
+
console.log('✅ [FileInput] File uploaded successfully with ID:', uploadedId);
|
|
3820
|
+
// Set the uploaded ID as the form control value
|
|
3821
|
+
this.onChange(uploadedId);
|
|
3822
|
+
console.log('📝 [FileInput] Form control value set to uploaded ID:', uploadedId);
|
|
3823
|
+
// Only emit individual uploadSuccess if not in multiple upload mode
|
|
3824
|
+
if (!this.isMultipleUploadMode()) {
|
|
3825
|
+
this.uploadSuccess.emit(uploadedId);
|
|
3826
|
+
console.log('📝 [FileInput] Upload success event emitted with file ID:', uploadedId);
|
|
3827
|
+
}
|
|
3828
|
+
else {
|
|
3829
|
+
console.log('📝 [FileInput] Individual upload success suppressed (multiple upload mode) - file ID:', uploadedId);
|
|
3830
|
+
}
|
|
4168
3831
|
}
|
|
4169
3832
|
else {
|
|
4170
|
-
console.error('❌ [
|
|
4171
|
-
this.
|
|
3833
|
+
console.error('❌ [FileInput] Upload successful but no ID returned:', response);
|
|
3834
|
+
this.uploadError.emit('Upload successful but no ID returned');
|
|
3835
|
+
}
|
|
3836
|
+
this.isUploading.set(false);
|
|
3837
|
+
console.log('🔄 [FileInput] isUploading set to false');
|
|
3838
|
+
},
|
|
3839
|
+
error: (error) => {
|
|
3840
|
+
console.error('💥 [FileInput] Upload FAILED:', error);
|
|
3841
|
+
// Angular 20: Complete the pending task even on error
|
|
3842
|
+
// this.pendingTasks.complete(uploadTask); // TODO: Fix PendingTasks API usage
|
|
3843
|
+
// console.log('❌ [FileInput] Pending task completed for failed upload');
|
|
3844
|
+
// Set upload status to 'error' and remove upload validation error
|
|
3845
|
+
this.uploadStatus.set('error');
|
|
3846
|
+
console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
|
|
3847
|
+
// Remove progress notification and show error
|
|
3848
|
+
const notificationId = this.uploadNotificationId();
|
|
3849
|
+
if (notificationId) {
|
|
3850
|
+
this.notificationService.remove(notificationId);
|
|
3851
|
+
console.log('🔔 [FileInput] Progress notification removed due to error');
|
|
3852
|
+
}
|
|
3853
|
+
this.notificationService.error(`❌ File upload failed: ${error.message || error.error?.message || 'Unknown error occurred'}`, { duration: 0 });
|
|
3854
|
+
this.uploadNotificationId.set(null);
|
|
3855
|
+
this.uploadError.emit(error.message || error.error?.message || 'Upload failed');
|
|
3856
|
+
this.isUploading.set(false);
|
|
3857
|
+
this.uploadProgress.set(0);
|
|
3858
|
+
this.uploadProgressChange.emit(0);
|
|
3859
|
+
console.log('🔄 [FileInput] Upload state reset - isUploading: false, progress: 0%');
|
|
3860
|
+
}
|
|
3861
|
+
});
|
|
3862
|
+
}
|
|
3863
|
+
/**
|
|
3864
|
+
* Upload multiple files with group ID support
|
|
3865
|
+
* FLOW: 1) Generate group ID first, 2) Upload all files with same group ID, 3) Emit group ID on completion
|
|
3866
|
+
*/
|
|
3867
|
+
uploadMultipleFiles(files) {
|
|
3868
|
+
console.log('📤 [FileInput] uploadMultipleFiles called for:', files.length, 'files');
|
|
3869
|
+
console.log('🔄 [FileInput] STEP 1: Generate group ID before starting any file uploads');
|
|
3870
|
+
// Set multiple upload mode flag
|
|
3871
|
+
this.isMultipleUploadMode.set(true);
|
|
3872
|
+
// Set upload status to 'start' before starting upload
|
|
3873
|
+
this.uploadStatus.set('start');
|
|
3874
|
+
this.isUploading.set(true);
|
|
3875
|
+
this.uploadProgress.set(0);
|
|
3876
|
+
this.uploadProgressChange.emit(0);
|
|
3877
|
+
// Make form control invalid during upload
|
|
3878
|
+
this.onChange(null);
|
|
3879
|
+
// Show initial progress notification
|
|
3880
|
+
const notificationId = this.notificationService.showProgress('🔄 Preparing multiple file upload...', 0, { duration: 0 });
|
|
3881
|
+
this.uploadNotificationId.set(notificationId);
|
|
3882
|
+
// STEP 1: Generate or get group ID BEFORE starting any file uploads
|
|
3883
|
+
const existingGroupId = this.uploadDataSignal().groupId;
|
|
3884
|
+
if (existingGroupId) {
|
|
3885
|
+
console.log('🆔 [FileInput] STEP 1 COMPLETE: Using existing group ID:', existingGroupId);
|
|
3886
|
+
console.log('🔄 [FileInput] STEP 2: Starting file uploads with group ID:', existingGroupId);
|
|
3887
|
+
this.groupId.set(existingGroupId);
|
|
3888
|
+
this.startMulti(files, existingGroupId);
|
|
3889
|
+
}
|
|
3890
|
+
else {
|
|
3891
|
+
console.log('🆔 [FileInput] No existing group ID, generating new one...');
|
|
3892
|
+
// Generate group ID BEFORE starting any file uploads
|
|
3893
|
+
this.fileManagerService.generateObjectId().subscribe({
|
|
3894
|
+
next: (response) => {
|
|
3895
|
+
const newGroupId = response.data?.objectId;
|
|
3896
|
+
console.log('🆔 [FileInput] STEP 1 COMPLETE: Generated new group ID:', newGroupId);
|
|
3897
|
+
console.log('🔄 [FileInput] STEP 2: Starting file uploads with group ID:', newGroupId);
|
|
3898
|
+
this.groupId.set(newGroupId);
|
|
3899
|
+
this.startMulti(files, newGroupId);
|
|
3900
|
+
},
|
|
3901
|
+
error: (error) => {
|
|
3902
|
+
console.error('❌ [FileInput] Failed to generate group ID:', error);
|
|
3903
|
+
this.uploadError.emit('Failed to generate group ID');
|
|
3904
|
+
this.isUploading.set(false);
|
|
3905
|
+
this.uploadStatus.set('error');
|
|
3906
|
+
const notificationId = this.uploadNotificationId();
|
|
3907
|
+
if (notificationId) {
|
|
3908
|
+
this.notificationService.remove(notificationId);
|
|
3909
|
+
}
|
|
3910
|
+
this.notificationService.error('❌ Failed to generate group ID for multiple file upload', { duration: 0 });
|
|
3911
|
+
this.uploadNotificationId.set(null);
|
|
4172
3912
|
}
|
|
4173
|
-
}
|
|
4174
|
-
|
|
4175
|
-
console.error('❌ [GlobalFileUploader] Failed to generate group ID:', error);
|
|
4176
|
-
this.handleUploadError('Failed to generate group ID', uploads[0]?.fileId);
|
|
4177
|
-
}
|
|
4178
|
-
});
|
|
3913
|
+
});
|
|
3914
|
+
}
|
|
4179
3915
|
}
|
|
4180
3916
|
/**
|
|
4181
|
-
*
|
|
3917
|
+
* Start uploading multiple files with the provided group ID
|
|
3918
|
+
* All files will be uploaded with the SAME group ID that was generated before this method
|
|
4182
3919
|
*/
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
this.
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
.
|
|
3920
|
+
startMulti(files, groupId) {
|
|
3921
|
+
console.log('🚀 [FileInput] STEP 2: Starting upload for', files.length, 'files with group ID:', groupId);
|
|
3922
|
+
console.log('📋 [FileInput] All files will use the same group ID:', groupId);
|
|
3923
|
+
let completedUploads = 0;
|
|
3924
|
+
let failedUploads = 0;
|
|
3925
|
+
const totalFiles = files.length;
|
|
3926
|
+
// IMPORTANT: All files use the SAME group ID that was generated before starting uploads
|
|
3927
|
+
const uploadDataWithGroupId = {
|
|
3928
|
+
...this.uploadDataSignal(),
|
|
3929
|
+
groupId: groupId
|
|
3930
|
+
};
|
|
3931
|
+
files.forEach((file, index) => {
|
|
3932
|
+
console.log(`📤 [FileInput] Uploading file ${index + 1}/${totalFiles}: "${file.name}" with group ID: ${groupId}`);
|
|
3933
|
+
this.fileManagerService.uploadFile(file, uploadDataWithGroupId, (progress) => {
|
|
3934
|
+
// Calculate overall progress
|
|
3935
|
+
const fileProgress = progress / totalFiles;
|
|
3936
|
+
const overallProgress = ((completedUploads * 100) + fileProgress) / totalFiles;
|
|
3937
|
+
this.uploadProgress.set(overallProgress);
|
|
3938
|
+
this.uploadProgressChange.emit(overallProgress);
|
|
3939
|
+
// Update progress notification
|
|
3940
|
+
const notificationId = this.uploadNotificationId();
|
|
3941
|
+
if (notificationId) {
|
|
3942
|
+
const progressMessage = `🔄 Uploading file ${index + 1} of ${totalFiles}...`;
|
|
3943
|
+
this.notificationService.updateProgress(notificationId, overallProgress, progressMessage);
|
|
3944
|
+
}
|
|
3945
|
+
}).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
|
4204
3946
|
next: (response) => {
|
|
4205
|
-
|
|
4206
|
-
console.log(`✅ [
|
|
4207
|
-
// Find the upload item by cyfm_temp_unique_id from response
|
|
4208
|
-
const responseFile = response?.data?.core_file_manager?.[0];
|
|
4209
|
-
const tempUniqueId = responseFile?.cyfm_temp_unique_id;
|
|
4210
|
-
console.log('🔍 [GlobalFileUploader] Response tempUniqueId:', tempUniqueId);
|
|
4211
|
-
console.log('🔍 [GlobalFileUploader] Original upload fileId:', upload.fileId);
|
|
4212
|
-
// Use the tempUniqueId to find and update the correct upload item
|
|
4213
|
-
if (tempUniqueId) {
|
|
4214
|
-
this.updateUploadStatusByTempId(tempUniqueId, 'completed', 100, undefined, response);
|
|
4215
|
-
}
|
|
4216
|
-
else {
|
|
4217
|
-
// Fallback to original fileId
|
|
4218
|
-
this.updateUploadStatus(upload.fileId, 'completed', 100, undefined, response);
|
|
4219
|
-
}
|
|
4220
|
-
this.uploadComplete.emit({
|
|
4221
|
-
fileId: tempUniqueId || upload.fileId,
|
|
4222
|
-
fileName: upload.fileName,
|
|
4223
|
-
progress: 100,
|
|
4224
|
-
status: 'completed',
|
|
4225
|
-
uploadedFile: response
|
|
4226
|
-
});
|
|
4227
|
-
if (responseFile) {
|
|
4228
|
-
uploadedFiles.push(responseFile);
|
|
4229
|
-
}
|
|
4230
|
-
console.log('🔍 [GlobalFileUploader] Upload response received:', response);
|
|
4231
|
-
console.log('🔍 [GlobalFileUploader] Response data structure:', response?.data);
|
|
3947
|
+
completedUploads++;
|
|
3948
|
+
console.log(`✅ [FileInput] File ${index + 1}/${totalFiles} uploaded`);
|
|
4232
3949
|
// Check if all files are completed
|
|
4233
|
-
if (
|
|
4234
|
-
this.handleMultipleUploadComplete(
|
|
3950
|
+
if (completedUploads + failedUploads === totalFiles) {
|
|
3951
|
+
this.handleMultipleUploadComplete(completedUploads, failedUploads, totalFiles, groupId);
|
|
4235
3952
|
}
|
|
4236
3953
|
},
|
|
4237
3954
|
error: (error) => {
|
|
4238
|
-
|
|
4239
|
-
console.error(`❌ [
|
|
4240
|
-
this.updateUploadStatus(upload.fileId, 'error', 0, error.message || 'Upload failed');
|
|
4241
|
-
this.uploadError.emit({
|
|
4242
|
-
fileId: upload.fileId,
|
|
4243
|
-
error: error.message || 'Upload failed'
|
|
4244
|
-
});
|
|
3955
|
+
failedUploads++;
|
|
3956
|
+
console.error(`❌ [FileInput] File ${index + 1}/${totalFiles} upload failed:`, error);
|
|
4245
3957
|
// Check if all files are completed
|
|
4246
|
-
if (
|
|
4247
|
-
this.handleMultipleUploadComplete(
|
|
3958
|
+
if (completedUploads + failedUploads === totalFiles) {
|
|
3959
|
+
this.handleMultipleUploadComplete(completedUploads, failedUploads, totalFiles, groupId);
|
|
4248
3960
|
}
|
|
4249
3961
|
}
|
|
4250
3962
|
});
|
|
4251
3963
|
});
|
|
4252
3964
|
}
|
|
4253
3965
|
/**
|
|
4254
|
-
* Handle multiple upload
|
|
3966
|
+
* Handle completion of multiple file upload
|
|
4255
3967
|
*/
|
|
4256
|
-
handleMultipleUploadComplete(completed, failed,
|
|
4257
|
-
console.log(`📊 [
|
|
3968
|
+
handleMultipleUploadComplete(completed, failed, total, groupId) {
|
|
3969
|
+
console.log(`📊 [FileInput] Multiple upload complete: ${completed}/${total} successful, ${failed} failed`);
|
|
4258
3970
|
this.isUploading.set(false);
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
3971
|
+
this.uploadProgress.set(100);
|
|
3972
|
+
this.uploadProgressChange.emit(100);
|
|
3973
|
+
// Remove progress notification
|
|
3974
|
+
const notificationId = this.uploadNotificationId();
|
|
3975
|
+
if (notificationId) {
|
|
3976
|
+
this.notificationService.remove(notificationId);
|
|
3977
|
+
}
|
|
3978
|
+
this.uploadNotificationId.set(null);
|
|
3979
|
+
if (failed === 0) {
|
|
3980
|
+
// All files uploaded successfully
|
|
3981
|
+
this.uploadStatus.set('success');
|
|
3982
|
+
// Success notification removed for cleaner UX
|
|
3983
|
+
// STEP 3: For multiple file upload, emit the group ID (not individual file IDs)
|
|
3984
|
+
this.onChange(groupId);
|
|
3985
|
+
this.uploadSuccess.emit(groupId);
|
|
3986
|
+
console.log('📝 [FileInput] Multiple upload completed with group ID:', groupId);
|
|
3987
|
+
}
|
|
3988
|
+
else if (completed > 0) {
|
|
3989
|
+
// Some files uploaded successfully
|
|
3990
|
+
this.uploadStatus.set('error');
|
|
3991
|
+
this.notificationService.warning(`⚠️ ${completed}/${total} files uploaded. ${failed} failed.`, { duration: 0 });
|
|
3992
|
+
this.uploadError.emit(`${failed} out of ${total} files failed to upload`);
|
|
3993
|
+
}
|
|
3994
|
+
else {
|
|
3995
|
+
// All files failed
|
|
3996
|
+
this.uploadStatus.set('error');
|
|
3997
|
+
this.notificationService.error(`❌ All ${total} files failed to upload.`, { duration: 0 });
|
|
3998
|
+
this.uploadError.emit('All files failed to upload');
|
|
4264
3999
|
}
|
|
4000
|
+
// Reset multiple upload mode flag
|
|
4001
|
+
this.isMultipleUploadMode.set(false);
|
|
4265
4002
|
}
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
const pendingUpload = this.uploadQueue().find(upload => upload.status === 'pending');
|
|
4271
|
-
if (!pendingUpload) {
|
|
4272
|
-
this.isUploading.set(false);
|
|
4003
|
+
generatePreviews() {
|
|
4004
|
+
// Clear existing previews
|
|
4005
|
+
this.clearPreviews();
|
|
4006
|
+
if (!this.showPreviewSignal() || !this.files()) {
|
|
4273
4007
|
return;
|
|
4274
4008
|
}
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
})
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4009
|
+
Array.from(this.files()).forEach(file => {
|
|
4010
|
+
if (this.isImageFile(file)) {
|
|
4011
|
+
const reader = new FileReader();
|
|
4012
|
+
reader.onload = (e) => {
|
|
4013
|
+
if (e.target?.result) {
|
|
4014
|
+
this.previewUrls.update(urls => [...urls, e.target.result]);
|
|
4015
|
+
}
|
|
4016
|
+
};
|
|
4017
|
+
reader.readAsDataURL(file);
|
|
4018
|
+
}
|
|
4019
|
+
});
|
|
4020
|
+
}
|
|
4021
|
+
clearPreviews() {
|
|
4022
|
+
// Revoke object URLs to prevent memory leaks
|
|
4023
|
+
this.previewUrls().forEach(url => {
|
|
4024
|
+
if (url.startsWith('blob:')) {
|
|
4025
|
+
URL.revokeObjectURL(url);
|
|
4026
|
+
}
|
|
4027
|
+
});
|
|
4028
|
+
this.previewUrls.set([]);
|
|
4029
|
+
}
|
|
4030
|
+
isImageFile(file) {
|
|
4031
|
+
return file.type.startsWith('image/');
|
|
4032
|
+
}
|
|
4033
|
+
loadFileDetailsFromId(fileId) {
|
|
4034
|
+
console.log('🔍 [FileInput] Loading file details for ID:', fileId);
|
|
4035
|
+
if (!fileId)
|
|
4036
|
+
return;
|
|
4037
|
+
this.fileManagerService?.getFileDetails({ cyfm_id: fileId })?.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
|
4038
|
+
next: (fileDetails) => {
|
|
4039
|
+
console.log('📋 [FileInput] File details received:', fileDetails);
|
|
4040
|
+
if (fileDetails?.data?.length) {
|
|
4041
|
+
const fileData = fileDetails.data[0];
|
|
4042
|
+
console.log('📁 [FileInput] File data:', fileData);
|
|
4043
|
+
// Set file name from the details
|
|
4044
|
+
if (fileData.cyfm_name) {
|
|
4045
|
+
this.fileNames.set([fileData.cyfm_name]);
|
|
4046
|
+
console.log('📝 [FileInput] File name set:', fileData.cyfm_name);
|
|
4047
|
+
}
|
|
4048
|
+
// If it's an image and we have base64 data, set preview
|
|
4049
|
+
if (this.showPreviewSignal() && fileData.cyfm_file_base64) {
|
|
4050
|
+
// Check if it's an image file based on file name or type
|
|
4051
|
+
const isImage = this.isImageFileFromName(fileData.cyfm_name || '') ||
|
|
4052
|
+
this.isImageFileFromType(fileData.cyfm_type || '');
|
|
4053
|
+
if (isImage) {
|
|
4054
|
+
// Add data URL prefix if not already present
|
|
4055
|
+
let base64Data = fileData.cyfm_file_base64;
|
|
4056
|
+
if (!base64Data.startsWith('data:')) {
|
|
4057
|
+
const mimeType = fileData.cyfm_type || 'image/jpeg';
|
|
4058
|
+
base64Data = `data:${mimeType};base64,${base64Data}`;
|
|
4059
|
+
}
|
|
4060
|
+
this.previewUrls.set([base64Data]);
|
|
4061
|
+
console.log('🖼️ [FileInput] Preview set from base64 data');
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
else {
|
|
4066
|
+
console.warn('⚠️ [FileInput] No file data found for ID:', fileId);
|
|
4067
|
+
}
|
|
4312
4068
|
},
|
|
4313
4069
|
error: (error) => {
|
|
4314
|
-
console.error('❌ [
|
|
4315
|
-
this.
|
|
4316
|
-
this.uploadError.emit({
|
|
4317
|
-
fileId: pendingUpload.fileId,
|
|
4318
|
-
error: error.message || 'Upload failed'
|
|
4319
|
-
});
|
|
4320
|
-
// Process next file
|
|
4321
|
-
setTimeout(() => this.uploadNextFile(), 500);
|
|
4070
|
+
console.error('❌ [FileInput] Error loading file details:', error);
|
|
4071
|
+
this.notificationService.error(`Failed to load file details: ${error.message || 'Unknown error'}`, { duration: 0 });
|
|
4322
4072
|
}
|
|
4323
4073
|
});
|
|
4324
4074
|
}
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
: upload));
|
|
4075
|
+
isImageFileFromName(fileName) {
|
|
4076
|
+
if (!fileName)
|
|
4077
|
+
return false;
|
|
4078
|
+
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg'];
|
|
4079
|
+
const lowerFileName = fileName.toLowerCase();
|
|
4080
|
+
return imageExtensions.some(ext => lowerFileName.endsWith(ext));
|
|
4332
4081
|
}
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
console.log('🔄 [GlobalFileUploader] Updating upload status:', { fileId, status, progress, error: !!error, uploadedFile: !!uploadedFile });
|
|
4338
|
-
this.uploadQueue.update(queue => {
|
|
4339
|
-
const updatedQueue = queue.map(upload => {
|
|
4340
|
-
if (upload.fileId === fileId) {
|
|
4341
|
-
console.log('🔄 [GlobalFileUploader] Found upload to update:', upload.fileName, 'from', upload.status, 'to', status);
|
|
4342
|
-
return { ...upload, status, progress, error, uploadedFile };
|
|
4343
|
-
}
|
|
4344
|
-
return upload;
|
|
4345
|
-
});
|
|
4346
|
-
console.log('🔄 [GlobalFileUploader] Updated queue:', updatedQueue.map(u => ({ fileName: u.fileName, status: u.status })));
|
|
4347
|
-
return updatedQueue;
|
|
4348
|
-
});
|
|
4082
|
+
isImageFileFromType(fileType) {
|
|
4083
|
+
if (!fileType)
|
|
4084
|
+
return false;
|
|
4085
|
+
return fileType.startsWith('image/');
|
|
4349
4086
|
}
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
console.log('🔄 [GlobalFileUploader] Found upload by tempId to update:', upload.fileName, 'from', upload.status, 'to', status);
|
|
4360
|
-
return { ...upload, status, progress, error, uploadedFile };
|
|
4087
|
+
removePreview(index) {
|
|
4088
|
+
const currentFiles = this.files();
|
|
4089
|
+
const currentUrls = this.previewUrls();
|
|
4090
|
+
if (currentFiles && currentFiles.length > index) {
|
|
4091
|
+
// Handle FileList case - remove file from FileList
|
|
4092
|
+
const dt = new DataTransfer();
|
|
4093
|
+
Array.from(currentFiles).forEach((file, i) => {
|
|
4094
|
+
if (i !== index) {
|
|
4095
|
+
dt.items.add(file);
|
|
4361
4096
|
}
|
|
4362
|
-
return upload;
|
|
4363
4097
|
});
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4098
|
+
const newFiles = dt.files;
|
|
4099
|
+
this.files.set(newFiles);
|
|
4100
|
+
this.fileNames.set(Array.from(newFiles).map(f => f.name));
|
|
4101
|
+
// Remove the preview URL
|
|
4102
|
+
if (currentUrls[index] && currentUrls[index].startsWith('blob:')) {
|
|
4103
|
+
URL.revokeObjectURL(currentUrls[index]);
|
|
4104
|
+
}
|
|
4105
|
+
this.previewUrls.update(urls => urls.filter((_, i) => i !== index));
|
|
4106
|
+
this.onChange(newFiles);
|
|
4107
|
+
this.fileChange.emit(newFiles);
|
|
4108
|
+
}
|
|
4109
|
+
else if (currentUrls.length > index) {
|
|
4110
|
+
// Handle uploaded file ID case - clear the preview and set control value to null
|
|
4111
|
+
console.log('🗑️ [FileInput] Removing preview for uploaded file ID');
|
|
4112
|
+
// Clear preview
|
|
4113
|
+
this.previewUrls.update(urls => urls.filter((_, i) => i !== index));
|
|
4114
|
+
this.fileNames.set([]);
|
|
4115
|
+
// Set control value to null since we're removing the uploaded file
|
|
4116
|
+
this.onChange(null);
|
|
4117
|
+
this.fileChange.emit(null);
|
|
4118
|
+
}
|
|
4367
4119
|
}
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
this.
|
|
4120
|
+
ngOnDestroy() {
|
|
4121
|
+
// Clean up preview URLs to prevent memory leaks
|
|
4122
|
+
this.clearPreviews();
|
|
4123
|
+
// Clean up any active upload notification
|
|
4124
|
+
const notificationId = this.uploadNotificationId();
|
|
4125
|
+
if (notificationId) {
|
|
4126
|
+
this.notificationService.remove(notificationId);
|
|
4127
|
+
this.uploadNotificationId.set(null);
|
|
4375
4128
|
}
|
|
4376
|
-
this.isUploading.set(false);
|
|
4377
4129
|
}
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
this.uploadCancelled.emit(fileId);
|
|
4384
|
-
// Remove from queue after a delay
|
|
4385
|
-
setTimeout(() => {
|
|
4386
|
-
this.uploadQueue.update(queue => queue.filter(upload => upload.fileId !== fileId));
|
|
4387
|
-
}, 1000);
|
|
4130
|
+
triggerFileSelect() {
|
|
4131
|
+
const fileInput = document.getElementById('cide-file-input-' + this.id());
|
|
4132
|
+
if (fileInput && !this.disabledSignal()) {
|
|
4133
|
+
fileInput.click();
|
|
4134
|
+
}
|
|
4388
4135
|
}
|
|
4389
4136
|
/**
|
|
4390
|
-
*
|
|
4137
|
+
* Show floating uploader manually
|
|
4138
|
+
* This can be called to show the floating uploader even when no files are selected
|
|
4391
4139
|
*/
|
|
4392
|
-
|
|
4393
|
-
this.
|
|
4394
|
-
|
|
4140
|
+
showUploader() {
|
|
4141
|
+
if (this.floatingUploader && this.showFloatingUploaderSignal()) {
|
|
4142
|
+
console.log('👁️ [FileInput] Manually showing floating uploader');
|
|
4143
|
+
this.floatingUploader.showUploader(this.floatingUploaderGroupIdSignal());
|
|
4144
|
+
}
|
|
4395
4145
|
}
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
this.
|
|
4146
|
+
// Drag and Drop Event Handlers
|
|
4147
|
+
onDragOver(event) {
|
|
4148
|
+
event.preventDefault();
|
|
4149
|
+
event.stopPropagation();
|
|
4150
|
+
if (!this.disabledSignal()) {
|
|
4151
|
+
this.isDragOver.set(true);
|
|
4152
|
+
console.log('🔄 [FileInput] Drag over detected');
|
|
4153
|
+
}
|
|
4401
4154
|
}
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4155
|
+
onDragLeave(event) {
|
|
4156
|
+
event.preventDefault();
|
|
4157
|
+
event.stopPropagation();
|
|
4158
|
+
this.isDragOver.set(false);
|
|
4159
|
+
console.log('🔄 [FileInput] Drag leave detected');
|
|
4407
4160
|
}
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4161
|
+
onDragEnter(event) {
|
|
4162
|
+
event.preventDefault();
|
|
4163
|
+
event.stopPropagation();
|
|
4164
|
+
if (!this.disabledSignal()) {
|
|
4165
|
+
this.isDragOver.set(true);
|
|
4166
|
+
console.log('🔄 [FileInput] Drag enter detected');
|
|
4167
|
+
}
|
|
4414
4168
|
}
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4169
|
+
onDrop(event) {
|
|
4170
|
+
event.preventDefault();
|
|
4171
|
+
event.stopPropagation();
|
|
4172
|
+
this.isDragOver.set(false);
|
|
4173
|
+
if (this.disabledSignal()) {
|
|
4174
|
+
console.log('⏸️ [FileInput] Drop ignored - component is disabled');
|
|
4175
|
+
return;
|
|
4176
|
+
}
|
|
4177
|
+
const files = event.dataTransfer?.files;
|
|
4178
|
+
if (files && files.length > 0) {
|
|
4179
|
+
console.log('📁 [FileInput] Files dropped:', Array.from(files).map(f => f.name));
|
|
4180
|
+
// Validate file types if accept is specified
|
|
4181
|
+
if (this.acceptSignal() && !this.validateFileTypes(files)) {
|
|
4182
|
+
console.log('❌ [FileInput] Invalid file types dropped');
|
|
4183
|
+
this.notificationService.error('❌ Invalid file type. Please select files of the correct type.', { duration: 0 });
|
|
4184
|
+
return;
|
|
4185
|
+
}
|
|
4186
|
+
// Handle single vs multiple files
|
|
4187
|
+
if (!this.multipleSignal() && files.length > 1) {
|
|
4188
|
+
console.log('⚠️ [FileInput] Multiple files dropped but multiple is disabled');
|
|
4189
|
+
this.notificationService.warning('⚠️ Only one file is allowed. Using the first file.', { duration: 0 });
|
|
4190
|
+
// Create a new FileList with only the first file
|
|
4191
|
+
const dt = new DataTransfer();
|
|
4192
|
+
dt.items.add(files[0]);
|
|
4193
|
+
this.handleFileSelection(dt.files);
|
|
4194
|
+
}
|
|
4195
|
+
else {
|
|
4196
|
+
this.handleFileSelection(files);
|
|
4197
|
+
}
|
|
4426
4198
|
}
|
|
4427
4199
|
}
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4200
|
+
validateFileTypes(files) {
|
|
4201
|
+
const acceptTypes = this.acceptSignal().split(',').map(type => type.trim());
|
|
4202
|
+
if (acceptTypes.length === 0 || acceptTypes[0] === '')
|
|
4203
|
+
return true;
|
|
4204
|
+
return Array.from(files).every(file => {
|
|
4205
|
+
return acceptTypes.some(acceptType => {
|
|
4206
|
+
if (acceptType.startsWith('.')) {
|
|
4207
|
+
// Extension-based validation
|
|
4208
|
+
return file.name.toLowerCase().endsWith(acceptType.toLowerCase());
|
|
4209
|
+
}
|
|
4210
|
+
else if (acceptType.includes('/')) {
|
|
4211
|
+
// MIME type validation
|
|
4212
|
+
return file.type === acceptType || file.type.startsWith(acceptType.replace('*', ''));
|
|
4213
|
+
}
|
|
4214
|
+
return false;
|
|
4215
|
+
});
|
|
4216
|
+
});
|
|
4217
|
+
}
|
|
4218
|
+
handleFileSelection(files) {
|
|
4219
|
+
this.files.set(files);
|
|
4220
|
+
this.fileNames.set(Array.from(files).map(f => f.name));
|
|
4221
|
+
console.log('📁 [FileInput] Files selected via drag & drop:', this.fileNames());
|
|
4222
|
+
this.generatePreviews();
|
|
4223
|
+
// Reset upload status when new file is selected
|
|
4224
|
+
this.uploadStatus.set('idle');
|
|
4225
|
+
console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus());
|
|
4226
|
+
this.onChange(files);
|
|
4227
|
+
this.fileChange.emit(files);
|
|
4228
|
+
this.onTouched();
|
|
4229
|
+
// Show floating uploader if enabled and files are selected
|
|
4230
|
+
if (this.showFloatingUploaderSignal() && files.length > 0 && this.floatingUploader) {
|
|
4231
|
+
console.log('👁️ [FileInput] Showing floating uploader for dropped files:', files.length, 'files');
|
|
4232
|
+
this.floatingUploader.showUploader(this.floatingUploaderGroupIdSignal());
|
|
4233
|
+
}
|
|
4234
|
+
// Auto upload if enabled
|
|
4235
|
+
if (this.autoUploadSignal() && files.length > 0) {
|
|
4236
|
+
if (this.multipleSignal()) {
|
|
4237
|
+
console.log('🚀 [FileInput] Auto upload enabled for multiple files mode (drag & drop):', files.length, 'files');
|
|
4238
|
+
this.uploadMultipleFiles(Array.from(files));
|
|
4239
|
+
}
|
|
4240
|
+
else {
|
|
4241
|
+
console.log('🚀 [FileInput] Auto upload enabled for single file mode (drag & drop):', files[0].name);
|
|
4242
|
+
this.uploadFile(files[0]);
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
else {
|
|
4246
|
+
console.log('⏸️ [FileInput] Auto upload disabled or no files');
|
|
4439
4247
|
}
|
|
4440
4248
|
}
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
*/
|
|
4444
|
-
getFileSizeDisplay(file) {
|
|
4445
|
-
const bytes = file.size;
|
|
4446
|
-
if (bytes === 0)
|
|
4447
|
-
return '0 Bytes';
|
|
4448
|
-
const k = 1024;
|
|
4449
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
4450
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
4451
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
4249
|
+
isRequired() {
|
|
4250
|
+
return this.requiredSignal();
|
|
4452
4251
|
}
|
|
4453
4252
|
/**
|
|
4454
|
-
*
|
|
4455
|
-
*
|
|
4253
|
+
* Angular 20: Utility method to get upload data with proper typing
|
|
4254
|
+
* @returns Properly typed upload data
|
|
4456
4255
|
*/
|
|
4457
|
-
|
|
4458
|
-
return
|
|
4256
|
+
getUploadData() {
|
|
4257
|
+
return this.uploadDataSignal();
|
|
4459
4258
|
}
|
|
4460
4259
|
/**
|
|
4461
|
-
*
|
|
4260
|
+
* Angular 20: Utility method to update upload data with type safety
|
|
4261
|
+
* @param data Partial upload data to merge with existing data
|
|
4462
4262
|
*/
|
|
4463
|
-
|
|
4263
|
+
updateUploadData(data) {
|
|
4264
|
+
const currentData = this.uploadDataSignal();
|
|
4265
|
+
const updatedData = { ...currentData, ...data };
|
|
4266
|
+
// Note: This would require the uploadData to be a writable signal
|
|
4267
|
+
// For now, this method serves as a type-safe way to work with upload data
|
|
4268
|
+
console.log('📝 [FileInput] Upload data updated:', updatedData);
|
|
4269
|
+
}
|
|
4270
|
+
getCurrentState() {
|
|
4464
4271
|
return {
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4272
|
+
id: this.id(),
|
|
4273
|
+
label: this.labelSignal(),
|
|
4274
|
+
required: this.requiredSignal(),
|
|
4275
|
+
disabled: this.disabledSignal(),
|
|
4276
|
+
accept: this.acceptSignal(),
|
|
4277
|
+
multiple: this.multipleSignal(),
|
|
4278
|
+
showPreview: this.showPreviewSignal(),
|
|
4279
|
+
autoUpload: this.autoUploadSignal(),
|
|
4280
|
+
uploadStatus: this.uploadStatus(),
|
|
4281
|
+
isUploading: this.isUploading(),
|
|
4282
|
+
uploadProgress: this.uploadProgress(),
|
|
4283
|
+
files: this.files() ? Array.from(this.files()).map(f => ({ name: f.name, size: f.size, type: f.type })) : null,
|
|
4284
|
+
fileNames: this.fileNames(),
|
|
4285
|
+
previewUrls: this.previewUrls().length,
|
|
4286
|
+
helperText: this.helperTextSignal(),
|
|
4287
|
+
errorText: this.errorTextSignal(),
|
|
4288
|
+
placeholderText: this.placeholderTextSignal(),
|
|
4289
|
+
placeholderIcon: this.placeholderIconSignal(),
|
|
4290
|
+
previewWidth: this.previewWidthSignal(),
|
|
4291
|
+
previewHeight: this.previewHeightSignal(),
|
|
4292
|
+
previewBoxMode: this.previewBoxModeSignal(),
|
|
4293
|
+
showFileName: this.showFileNameSignal(),
|
|
4294
|
+
uploadData: this.uploadDataSignal()
|
|
4470
4295
|
};
|
|
4471
4296
|
}
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4297
|
+
async getControlData() {
|
|
4298
|
+
console.log('🔍 [FileInput] getControlData called');
|
|
4299
|
+
const cide_element_data = await this.elementService?.getElementData({ sype_key: this.id() });
|
|
4300
|
+
if (cide_element_data) {
|
|
4301
|
+
console.log('📋 [FileInput] Element data loaded:', cide_element_data);
|
|
4302
|
+
// Note: Since we're using input signals, we can't directly set their values
|
|
4303
|
+
// This method would need to be refactored to work with the new signal-based approach
|
|
4304
|
+
// For now, we'll log the data and trigger validation
|
|
4305
|
+
console.log('✅ [FileInput] Control data received from element service');
|
|
4306
|
+
console.log('⚠️ [FileInput] Note: Input signals cannot be modified after component initialization');
|
|
4307
|
+
// Trigger validation update
|
|
4308
|
+
this.onValidatorChange();
|
|
4309
|
+
}
|
|
4310
|
+
else {
|
|
4311
|
+
console.log('⚠️ [FileInput] No element data found for key:', this.id());
|
|
4479
4312
|
}
|
|
4480
4313
|
}
|
|
4481
|
-
|
|
4482
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: CideEleGlobalFileUploaderComponent, isStandalone: true, selector: "cide-ele-global-file-uploader", inputs: { userId: "userId", multiple: "multiple", accept: "accept", maxFileSize: "maxFileSize", allowedTypes: "allowedTypes" }, outputs: { uploadComplete: "uploadComplete", uploadError: "uploadError", uploadCancelled: "uploadCancelled", allUploadsComplete: "allUploadsComplete" }, ngImport: i0, template: "<!-- Global File Uploader Component -->\r\n<div class=\"global-file-uploader\">\r\n\r\n <!-- Hidden file input -->\r\n <input id=\"global-file-input\" type=\"file\" [multiple]=\"multiple\" [accept]=\"accept\" \r\n (change)=\"onFileInputChange($event)\" style=\"display: none;\">\r\n\r\n <!-- Drag and Drop Zone -->\r\n <div class=\"upload-zone\" [class.drag-over]=\"isDragOver()\" (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\" (drop)=\"onDrop($event)\" (click)=\"triggerFileInput()\">\r\n\r\n <div class=\"upload-zone-content\">\r\n <cide-ele-icon name=\"cloud_upload\" class=\"upload-icon\" size=\"lg\">\r\n </cide-ele-icon>\r\n\r\n <h3 class=\"upload-title\">\r\n {{ isDragOver() ? 'Drop files here' : 'Upload Files' }}\r\n </h3>\r\n\r\n <p class=\"upload-subtitle\">\r\n {{ isDragOver() ? 'Release to upload' : 'Drag and drop files here or click to browse' }}\r\n </p>\r\n\r\n <button cideEleButton cideEleButton variant=\"outline\" size=\"sm\"\r\n (click)=\"triggerFileInput(); $event.stopPropagation()\">\r\n <cide-ele-icon name=\"add\" size=\"sm\"></cide-ele-icon>\r\n Choose Files\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Upload Queue -->\r\n @if (hasUploads()) {\r\n <div class=\"upload-queue\">\r\n <div class=\"queue-header\">\r\n <h4 class=\"queue-title\">Upload Queue</h4>\r\n <div class=\"queue-actions\">\r\n @if (completedUploads().length > 0) {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"clearCompleted()\">\r\n Clear Completed\r\n </button>\r\n }\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"clearAll()\">\r\n Clear All\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"queue-items\">\r\n @for (upload of uploadQueue(); track upload.fileId) {\r\n <div class=\"upload-item\" [class]=\"getStatusClass(upload.status)\">\r\n\r\n <!-- File Info -->\r\n <div class=\"file-info\">\r\n <cide-ele-icon class=\"status-icon\" size=\"sm\">\r\n {{getStatusIcon(upload.status)}}\r\n </cide-ele-icon>\r\n\r\n <div class=\"file-details\">\r\n <div class=\"file-name\">{{ upload.fileName }}</div>\r\n @if (upload.file) {\r\n <div class=\"file-size\">{{ getFileSizeDisplay(upload.file) }}</div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Progress Bar -->\r\n @if (upload.status === 'uploading') {\r\n <div class=\"progress-container\">\r\n <div class=\"progress-bar\">\r\n <div class=\"progress-fill\" [style.width.%]=\"upload.progress\">\r\n </div>\r\n </div>\r\n <span class=\"progress-text\">{{ upload.progress }}%</span>\r\n </div>\r\n }\r\n\r\n <!-- Status Text -->\r\n @if (upload.status !== 'uploading') {\r\n <div class=\"status-text\">\r\n @switch (upload.status) {\r\n @case ('pending') {\r\n <span class=\"text-yellow-600\">Waiting...</span>\r\n }\r\n @case ('completed') {\r\n <span class=\"text-green-600\">Uploaded successfully</span>\r\n }\r\n @case ('error') {\r\n <span class=\"text-red-600\">{{ upload.error || 'Upload failed' }}</span>\r\n }\r\n @case ('cancelled') {\r\n <span class=\"text-gray-600\">Cancelled</span>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Actions -->\r\n <div class=\"upload-actions\">\r\n @switch (upload.status) {\r\n @case ('pending') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"cancelUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"cancel\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n @case ('uploading') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"cancelUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"cancel\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n @case ('completed') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"removeUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"close\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n @case ('error') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"retryUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"refresh\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"removeUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"close\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n @case ('cancelled') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"removeUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"close\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Queue Summary -->\r\n <div class=\"queue-summary\">\r\n <div class=\"summary-stats\">\r\n <span class=\"stat\">\r\n <cide-ele-icon name=\"schedule\" size=\"xs\"></cide-ele-icon>\r\n {{ pendingUploads().length }} pending\r\n </span>\r\n <span class=\"stat\">\r\n <cide-ele-icon name=\"cloud_upload\" size=\"xs\"></cide-ele-icon>\r\n {{ activeUploads().length }} uploading\r\n </span>\r\n <span class=\"stat\">\r\n <cide-ele-icon name=\"check_circle\" size=\"xs\"></cide-ele-icon>\r\n {{ completedUploads().length }} completed\r\n </span>\r\n @if (failedUploads().length > 0) {\r\n <span class=\"stat error\">\r\n <cide-ele-icon name=\"error\" size=\"xs\"></cide-ele-icon>\r\n {{ failedUploads().length }} failed\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n\r\n", styles: [".global-file-uploader{@apply tw-w-full tw-max-w-2xl tw-mx-auto tw-p-4;}.global-file-uploader .upload-zone{@apply tw-border-2 tw-border-dashed tw-border-gray-300 tw-rounded-lg tw-p-8 tw-text-center tw-cursor-pointer tw-transition-all tw-duration-200 tw-ease-in-out;@apply hover:tw-border-blue-400 hover:tw-bg-blue-50;}.global-file-uploader .upload-zone.drag-over{@apply tw-border-blue-500 tw-bg-blue-100 tw-scale-105;}.global-file-uploader .upload-zone .upload-zone-content{@apply tw-space-y-4;}.global-file-uploader .upload-zone .upload-zone-content .upload-icon{@apply tw-text-gray-400 tw-mx-auto;}.global-file-uploader .upload-zone .upload-zone-content .upload-title{@apply tw-text-xl tw-font-semibold tw-text-gray-700 tw-m-0;}.global-file-uploader .upload-zone .upload-zone-content .upload-subtitle{@apply tw-text-sm tw-text-gray-500 tw-m-0;}.global-file-uploader .upload-queue{@apply tw-mt-6 tw-bg-white tw-border tw-border-gray-200 tw-rounded-lg tw-shadow-sm;}.global-file-uploader .upload-queue .queue-header{@apply tw-flex tw-justify-between tw-items-center tw-p-4 tw-border-b tw-border-gray-200;}.global-file-uploader .upload-queue .queue-header .queue-title{@apply tw-text-lg tw-font-medium tw-text-gray-700 tw-m-0;}.global-file-uploader .upload-queue .queue-header .queue-actions{@apply tw-flex tw-space-x-2;}.global-file-uploader .upload-queue .queue-items{@apply tw-max-h-96 tw-overflow-y-auto;}.global-file-uploader .upload-queue .queue-items .upload-item{@apply tw-flex tw-items-center tw-justify-between tw-p-4 tw-border-b tw-border-gray-100 tw-transition-colors tw-duration-200;}.global-file-uploader .upload-queue .queue-items .upload-item:last-child{@apply tw-border-b-0;}.global-file-uploader .upload-queue .queue-items .upload-item.status-pending{@apply tw-bg-yellow-50;}.global-file-uploader .upload-queue .queue-items .upload-item.status-uploading{@apply tw-bg-blue-50;}.global-file-uploader .upload-queue .queue-items .upload-item.status-completed{@apply tw-bg-green-50;}.global-file-uploader .upload-queue .queue-items .upload-item.status-error{@apply tw-bg-red-50;}.global-file-uploader .upload-queue .queue-items .upload-item.status-cancelled{@apply tw-bg-gray-50;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info{@apply tw-flex tw-items-center tw-flex-1 tw-min-w-0;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .status-icon{@apply tw-mr-3 tw-flex-shrink-0;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details{@apply tw-min-w-0 tw-flex-1;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details .file-name{@apply tw-text-sm tw-font-medium tw-text-gray-700 tw-truncate;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details .file-size{@apply tw-text-xs tw-text-gray-500 tw-mt-1;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container{@apply tw-flex tw-items-center tw-space-x-3 tw-flex-1 tw-max-w-xs tw-mx-4;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-bar{@apply tw-flex-1 tw-h-2 tw-bg-gray-200 tw-rounded-full tw-overflow-hidden;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-bar .progress-fill{@apply tw-h-full tw-bg-blue-500 tw-transition-all tw-duration-300 tw-ease-out;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-text{@apply tw-text-xs tw-font-medium tw-text-gray-600 tw-min-w-12 tw-text-right;}.global-file-uploader .upload-queue .queue-items .upload-item .status-text{@apply tw-flex-1 tw-text-sm tw-text-center;}.global-file-uploader .upload-queue .queue-items .upload-item .status-text span{@apply tw-font-medium;}.global-file-uploader .upload-queue .queue-items .upload-item .upload-actions{@apply tw-flex tw-space-x-1 tw-ml-4;}.global-file-uploader .upload-queue .queue-summary{@apply tw-p-4 tw-bg-gray-50 tw-border-t tw-border-gray-200;}.global-file-uploader .upload-queue .queue-summary .summary-stats{@apply tw-flex tw-flex-wrap tw-gap-4 tw-text-sm tw-text-gray-600;}.global-file-uploader .upload-queue .queue-summary .summary-stats .stat{@apply tw-flex tw-items-center tw-space-x-1;}.global-file-uploader .upload-queue .queue-summary .summary-stats .stat.error{@apply tw-text-red-600;}@media (max-width: 640px){.global-file-uploader{@apply tw-p-2;}.global-file-uploader .upload-zone{@apply tw-p-6;}.global-file-uploader .upload-zone .upload-zone-content .upload-title{@apply tw-text-lg;}.global-file-uploader .upload-zone .upload-zone-content .upload-subtitle{@apply tw-text-xs;}.global-file-uploader .upload-queue .queue-header{@apply tw-p-3 tw-flex-col tw-space-y-2 tw-items-start;}.global-file-uploader .upload-queue .queue-header .queue-actions{@apply tw-w-full tw-justify-end;}.global-file-uploader .upload-queue .queue-items .upload-item{@apply tw-p-3 tw-flex-col tw-space-y-3 tw-items-start;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info{@apply tw-w-full;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container{@apply tw-w-full tw-mx-0;}.global-file-uploader .upload-queue .queue-items .upload-item .status-text{@apply tw-w-full tw-text-left;}.global-file-uploader .upload-queue .queue-items .upload-item .upload-actions{@apply tw-w-full tw-justify-end tw-ml-0;}}@media (prefers-color-scheme: dark){.global-file-uploader .upload-zone{@apply tw-border-gray-600 hover:tw-border-blue-400 hover:tw-bg-gray-800;}.global-file-uploader .upload-zone.drag-over{@apply tw-bg-blue-900;}.global-file-uploader .upload-zone .upload-zone-content .upload-icon{@apply tw-text-gray-500;}.global-file-uploader .upload-zone .upload-zone-content .upload-title{@apply tw-text-gray-200;}.global-file-uploader .upload-zone .upload-zone-content .upload-subtitle{@apply tw-text-gray-400;}.global-file-uploader .upload-queue{@apply tw-bg-gray-800 tw-border-gray-700;}.global-file-uploader .upload-queue .queue-header{@apply tw-border-gray-700;}.global-file-uploader .upload-queue .queue-header .queue-title{@apply tw-text-gray-200;}.global-file-uploader .upload-queue .queue-items .upload-item{@apply tw-border-gray-700;}.global-file-uploader .upload-queue .queue-items .upload-item.status-pending{@apply tw-bg-yellow-900;}.global-file-uploader .upload-queue .queue-items .upload-item.status-uploading{@apply tw-bg-blue-900;}.global-file-uploader .upload-queue .queue-items .upload-item.status-completed{@apply tw-bg-green-900;}.global-file-uploader .upload-queue .queue-items .upload-item.status-error{@apply tw-bg-red-900;}.global-file-uploader .upload-queue .queue-items .upload-item.status-cancelled{@apply tw-bg-gray-700;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details .file-name{@apply tw-text-gray-200;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details .file-size{@apply tw-text-gray-400;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-bar{@apply tw-bg-gray-600;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-text{@apply tw-text-gray-300;}.global-file-uploader .upload-queue .queue-summary{@apply tw-bg-gray-700 tw-border-gray-600;}.global-file-uploader .upload-queue .queue-summary .summary-stats{@apply tw-text-gray-300;}.global-file-uploader .upload-queue .queue-summary .summary-stats .stat.error{@apply tw-text-red-400;}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CideEleButtonComponent, selector: "button[cideEleButton], a[cideEleButton]", inputs: ["label", "variant", "size", "type", "shape", "elevation", "disabled", "id", "loading", "fullWidth", "leftIcon", "rightIcon", "customClass", "tooltip", "ariaLabel", "testId", "routerLink", "routerExtras", "preventDoubleClick", "animated"], outputs: ["btnClick", "doubleClick"] }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }] });
|
|
4314
|
+
// Validator implementation
|
|
4315
|
+
validate(control) {
|
|
4316
|
+
console.log('🔍 [FileInput] validate() called - uploadStatus:', this.uploadStatus(), 'required:', this.requiredSignal(), 'files:', !!this.files(), 'control.value:', control.value);
|
|
4317
|
+
// If upload is in progress (start or uploading status), return validation error
|
|
4318
|
+
if (this.uploadStatus() === 'start' || this.uploadStatus() === 'uploading') {
|
|
4319
|
+
console.log('⚠️ [FileInput] Validation ERROR: Upload in progress');
|
|
4320
|
+
return { 'uploadInProgress': { message: 'File upload in progress. Please wait...' } };
|
|
4321
|
+
}
|
|
4322
|
+
// If required and no file is selected and no control value (uploaded file ID), return validation error
|
|
4323
|
+
if (this.requiredSignal() && !this.files() && !control.value) {
|
|
4324
|
+
console.log('⚠️ [FileInput] Validation ERROR: File required');
|
|
4325
|
+
return { 'required': { message: 'Please select a file to upload.' } };
|
|
4326
|
+
}
|
|
4327
|
+
console.log('✅ [FileInput] Validation PASSED: No errors');
|
|
4328
|
+
return null; // No validation errors
|
|
4329
|
+
}
|
|
4330
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4331
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: CideEleFileInputComponent, isStandalone: true, selector: "cide-ele-file-input", inputs: { label: "label", accept: "accept", multiple: "multiple", disabled: "disabled", required: "required", helperText: "helperText", errorText: "errorText", showPreview: "showPreview", previewWidth: "previewWidth", previewHeight: "previewHeight", previewBoxMode: "previewBoxMode", showFileName: "showFileName", placeholderText: "placeholderText", placeholderIcon: "placeholderIcon", autoUpload: "autoUpload", uploadData: "uploadData", showFloatingUploader: "showFloatingUploader", floatingUploaderGroupId: "floatingUploaderGroupId" }, outputs: { fileChange: "fileChange", uploadSuccess: "uploadSuccess", uploadError: "uploadError", uploadProgressChange: "uploadProgressChange" }, providers: [
|
|
4332
|
+
{
|
|
4333
|
+
provide: NG_VALUE_ACCESSOR,
|
|
4334
|
+
useExisting: CideEleFileInputComponent,
|
|
4335
|
+
multi: true
|
|
4336
|
+
},
|
|
4337
|
+
{
|
|
4338
|
+
provide: NG_VALIDATORS,
|
|
4339
|
+
useExisting: CideEleFileInputComponent,
|
|
4340
|
+
multi: true
|
|
4341
|
+
}
|
|
4342
|
+
], usesOnChanges: true, ngImport: i0, template: "<div class=\"cide-file-input\">\n <!-- Label (shown when not in preview box mode or when preview box mode but no label override) -->\n @if (labelSignal() && !isPreviewBoxMode()) {\n <label class=\"cide-file-input-label\" [attr.for]=\"'cide-file-input-' + id()\">\n {{ labelSignal() }}@if (requiredSignal()) {<span class=\"cide-file-input-required\"> *</span>}\n </label>\n }\n \n <!-- Preview Box Mode -->\n @if (isPreviewBoxMode()) {\n <div class=\"cide-file-input-preview-box-container\">\n <!-- Hidden file input -->\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id()\"\n [attr.accept]=\"acceptSignal()\"\n [attr.multiple]=\"multipleSignal() ? true : null\"\n [disabled]=\"disabledSignal()\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-hidden\"\n />\n \n <!-- Preview Box -->\n <div \n class=\"cide-file-input-preview-box\"\n [class.cide-file-input-preview-box-disabled]=\"disabledSignal()\"\n [class.cide-file-input-preview-box-has-image]=\"hasImages()\"\n [class.cide-file-input-preview-box-drag-over]=\"isDragOver()\"\n [style.width]=\"previewWidthSignal()\"\n [style.height]=\"previewHeightSignal()\"\n (click)=\"triggerFileSelect()\"\n (dragover)=\"onDragOver($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [attr.title]=\"disabledSignal() ? 'File selection disabled' : placeholderTextSignal()\">\n \n <!-- No Image State -->\n @if (!hasImages()) {\n <div class=\"cide-file-input-preview-box-placeholder\">\n <div class=\"cide-file-input-preview-box-icon\">\n <cide-ele-icon>{{ isDragOver() ? '\uD83D\uDCC1' : placeholderIconSignal() }}</cide-ele-icon>\n </div>\n <div class=\"cide-file-input-preview-box-text\">\n {{ isDragOver() ? 'Drop files here...' : placeholderTextSignal() }}\n </div>\n </div>\n }\n \n <!-- Image Preview State -->\n @if (hasImages()) {\n <div class=\"cide-file-input-preview-box-content\">\n <img \n [src]=\"previewUrls()[0]\" \n [alt]=\"fileNames()[0] || 'Preview image'\"\n class=\"cide-file-input-preview-box-image\">\n <div class=\"cide-file-input-preview-box-overlay\">\n <div class=\"cide-file-input-preview-box-overlay-text\">Click to change</div>\n </div>\n @if (!disabledSignal()) {\n <button \n type=\"button\" \n class=\"cide-file-input-preview-box-remove\"\n (click)=\"clearFiles(); $event.stopPropagation()\"\n title=\"Remove image\">\n \u00D7\n </button>\n }\n </div>\n }\n </div>\n \n <!-- File name display for preview box mode -->\n @if (hasImages() && fileNames().length && showFileNameSignal()) {\n <div class=\"cide-file-input-preview-box-filename\">\n {{ fileNames()[0] }}\n </div>\n }\n </div>\n }\n\n <!-- Standard Mode -->\n @if (!isPreviewBoxMode()) {\n <div \n class=\"cide-file-input-wrapper\"\n [class.cide-file-input-drag-over]=\"isDragOver()\"\n (dragover)=\"onDragOver($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id()\"\n [attr.accept]=\"acceptSignal()\"\n [attr.multiple]=\"multipleSignal() ? true : null\"\n [disabled]=\"disabledSignal()\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-element\"\n />\n @if (hasFiles()) {\n <button type=\"button\" class=\"cide-file-input-clear\" (click)=\"clearFiles()\">\n Clear\n </button>\n }\n </div>\n @if (hasFiles() && !isPreviewBoxMode()) {\n <div class=\"cide-file-input-files\">\n @if (multipleSignal() && fileNames().length > 1) {\n <!-- Show file count for multiple files -->\n <div class=\"cide-file-input-multiple-count\">\n {{ fileNames().length }} files selected\n </div>\n } @else {\n <!-- Show individual file names for single file or when only one file selected -->\n @for (name of fileNames(); track name) {\n <span>{{ name }}</span>\n }\n }\n <!-- Angular 20: Display file size using new computed values -->\n @if (totalFileSize() > 0) {\n <div class=\"cide-file-input-size\">\n Total size: {{ fileSizeInMB() }} MB\n </div>\n }\n </div>\n }\n }\n \n <!-- Image Preview Section (only for standard mode) -->\n @if (isImagePreviewAvailable() && !isPreviewBoxMode()) {\n <div class=\"cide-file-input-preview\">\n <div class=\"cide-file-input-preview-label\">Preview:</div>\n <div class=\"cide-file-input-preview-container\">\n @for (previewUrl of previewUrls(); track previewUrl; let i = $index) {\n <div \n class=\"cide-file-input-preview-item\"\n [style.width]=\"previewWidthSignal()\"\n [style.height]=\"previewHeightSignal()\">\n <button \n type=\"button\" \n class=\"cide-file-input-preview-remove\"\n (click)=\"removePreview(i)\"\n title=\"Remove image\">\n \u00D7\n </button>\n <img \n [src]=\"previewUrl\" \n [alt]=\"fileNames()[i] || 'Preview image'\"\n class=\"cide-file-input-preview-image\"\n loading=\"lazy\">\n <div class=\"cide-file-input-preview-filename\">{{ fileNames()[i] }}</div>\n </div>\n }\n </div>\n </div>\n }\n \n @if (errorTextSignal()) {\n <div class=\"cide-file-input-error\">{{ errorTextSignal() }}</div>\n }\n @if (helperTextSignal() && !errorTextSignal()) {\n <div class=\"cide-file-input-helper\">{{ helperTextSignal() }}</div>\n }\n</div> ", styles: [".cide-file-input{display:flex;flex-direction:column;gap:.5rem}.cide-file-input-label{font-weight:500;margin-bottom:.25rem}.cide-file-input-required{color:#d32f2f;font-weight:700}.cide-file-input-wrapper{display:flex;align-items:center;gap:.5rem;border:2px dashed transparent;border-radius:.5rem;padding:.5rem;transition:all .2s ease-in-out}.cide-file-input-wrapper.cide-file-input-drag-over{border-color:#3b82f6;background-color:#eff6ff;transform:scale(1.02)}.cide-file-input-element{flex:1}.cide-file-input-clear{background:none;border:none;color:#d32f2f;cursor:pointer;font-size:.9rem}.cide-file-input-files{font-size:.95rem;color:#333;margin-top:.25rem}.cide-file-input-multiple-count{display:inline-flex;align-items:center;gap:.5rem;padding:.5rem 1rem;background-color:#f0f9ff;border:1px solid #0ea5e9;border-radius:.5rem;color:#0369a1;font-weight:500;font-size:.9rem}.cide-file-input-size{margin-top:.5rem;padding:.25rem .5rem;background-color:#f3f4f6;border-radius:.25rem;font-size:.75rem;color:#4b5563;font-weight:500}.cide-file-input-error{color:#d32f2f;font-size:.9rem}.cide-file-input-helper{color:#666;font-size:.9rem}.cide-file-input-preview{margin-top:.75rem;padding:.75rem;background-color:#f8f9fa;border:1px solid #e1e5e9;border-radius:.375rem}.cide-file-input-preview-label{font-weight:500;margin-bottom:.5rem;color:#374151;font-size:.875rem}.cide-file-input-preview-container{display:flex;flex-wrap:wrap;gap:.75rem}.cide-file-input-preview-item{position:relative;display:flex;flex-direction:column;border:1px solid #d1d5db;border-radius:.5rem;overflow:hidden;background-color:#fff;box-shadow:0 1px 3px #0000001a;transition:box-shadow .2s ease-in-out}.cide-file-input-preview-item:hover{box-shadow:0 4px 6px -1px #0000001a}.cide-file-input-preview-image{width:100%;height:calc(100% - 2rem);object-fit:cover;object-position:center;background-color:#f3f4f6}.cide-file-input-preview-filename{padding:.375rem .5rem;background-color:#f9fafbf2;border-top:1px solid #e5e7eb;font-size:.75rem;color:#374151;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-height:2rem;display:flex;align-items:center;justify-content:center}.cide-file-input-preview-remove{position:absolute;top:.25rem;right:.25rem;width:1.5rem;height:1.5rem;background-color:#ef4444e6;color:#fff;border:none;border-radius:50%;font-size:1rem;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:10;transition:all .2s ease-in-out}.cide-file-input-preview-remove:hover{background-color:#dc2626f2;transform:scale(1.1)}.cide-file-input-preview-remove:focus{outline:2px solid #3b82f6;outline-offset:2px}.cide-file-input-hidden{display:none}.cide-file-input-preview-box-container{display:flex;flex-direction:column;gap:.5rem}.cide-file-input-preview-box{position:relative;border:2px dashed #d1d5db;border-radius:.5rem;cursor:pointer;background-color:#f9fafb;display:flex;align-items:center;justify-content:center;overflow:hidden;transition:all .2s ease-in-out}.cide-file-input-preview-box:hover{border-color:#3b82f6;background-color:#eff6ff}.cide-file-input-preview-box.cide-file-input-preview-box-disabled{cursor:not-allowed;opacity:.6;background-color:#f3f4f6}.cide-file-input-preview-box.cide-file-input-preview-box-disabled:hover{border-color:#d1d5db;background-color:#f3f4f6}.cide-file-input-preview-box.cide-file-input-preview-box-has-image{border-style:solid;border-color:#e5e7eb;padding:0}.cide-file-input-preview-box.cide-file-input-preview-box-has-image:hover{border-color:#3b82f6}.cide-file-input-preview-box.cide-file-input-preview-box-has-image:hover .cide-file-input-preview-box-overlay{opacity:1}.cide-file-input-preview-box.cide-file-input-preview-box-drag-over{border-color:#3b82f6!important;background-color:#eff6ff!important;transform:scale(1.02);box-shadow:0 0 0 4px #3b82f61a}.cide-file-input-preview-box.cide-file-input-preview-box-drag-over .cide-file-input-preview-box-placeholder .cide-file-input-preview-box-icon{color:#3b82f6;transform:scale(1.1)}.cide-file-input-preview-box.cide-file-input-preview-box-drag-over .cide-file-input-preview-box-placeholder .cide-file-input-preview-box-text{color:#3b82f6;font-weight:600}.cide-file-input-preview-box-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:1rem;text-align:center}.cide-file-input-preview-box-icon{font-size:2rem;color:#6b7280}.cide-file-input-preview-box-text{font-size:.875rem;color:#6b7280;font-weight:500}.cide-file-input-preview-box-content{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center}.cide-file-input-preview-box-image{width:100%;height:100%;object-fit:cover;object-position:center}.cide-file-input-preview-box-overlay{position:absolute;inset:0;background-color:#0009;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s ease-in-out}.cide-file-input-preview-box-overlay-text{color:#fff;font-size:.875rem;font-weight:500;text-align:center}.cide-file-input-preview-box-remove{position:absolute;top:.375rem;right:.375rem;width:1.5rem;height:1.5rem;background-color:#ef4444e6;color:#fff;border:none;border-radius:50%;font-size:1rem;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:20;transition:all .2s ease-in-out}.cide-file-input-preview-box-remove:hover{background-color:#dc2626f2;transform:scale(1.1)}.cide-file-input-preview-box-remove:focus{outline:2px solid #3b82f6;outline-offset:2px}.cide-file-input-preview-box-filename{font-size:.75rem;color:#374151;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:.25rem .5rem;background-color:#f3f4f6;border-radius:.25rem;margin-top:.25rem}@media (max-width: 640px){.cide-file-input-preview-container{justify-content:center}.cide-file-input-preview-item{min-width:120px;max-width:150px}.cide-file-input-preview-box-icon{font-size:1.5rem}.cide-file-input-preview-box-text{font-size:.75rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }] });
|
|
4483
4343
|
}
|
|
4484
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
4344
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileInputComponent, decorators: [{
|
|
4485
4345
|
type: Component,
|
|
4486
|
-
args: [{ selector: 'cide-ele-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
], template: "<!-- Global File Uploader Component -->\r\n<div class=\"global-file-uploader\">\r\n\r\n <!-- Hidden file input -->\r\n <input id=\"global-file-input\" type=\"file\" [multiple]=\"multiple\" [accept]=\"accept\" \r\n (change)=\"onFileInputChange($event)\" style=\"display: none;\">\r\n\r\n <!-- Drag and Drop Zone -->\r\n <div class=\"upload-zone\" [class.drag-over]=\"isDragOver()\" (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\" (drop)=\"onDrop($event)\" (click)=\"triggerFileInput()\">\r\n\r\n <div class=\"upload-zone-content\">\r\n <cide-ele-icon name=\"cloud_upload\" class=\"upload-icon\" size=\"lg\">\r\n </cide-ele-icon>\r\n\r\n <h3 class=\"upload-title\">\r\n {{ isDragOver() ? 'Drop files here' : 'Upload Files' }}\r\n </h3>\r\n\r\n <p class=\"upload-subtitle\">\r\n {{ isDragOver() ? 'Release to upload' : 'Drag and drop files here or click to browse' }}\r\n </p>\r\n\r\n <button cideEleButton cideEleButton variant=\"outline\" size=\"sm\"\r\n (click)=\"triggerFileInput(); $event.stopPropagation()\">\r\n <cide-ele-icon name=\"add\" size=\"sm\"></cide-ele-icon>\r\n Choose Files\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Upload Queue -->\r\n @if (hasUploads()) {\r\n <div class=\"upload-queue\">\r\n <div class=\"queue-header\">\r\n <h4 class=\"queue-title\">Upload Queue</h4>\r\n <div class=\"queue-actions\">\r\n @if (completedUploads().length > 0) {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"clearCompleted()\">\r\n Clear Completed\r\n </button>\r\n }\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"clearAll()\">\r\n Clear All\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"queue-items\">\r\n @for (upload of uploadQueue(); track upload.fileId) {\r\n <div class=\"upload-item\" [class]=\"getStatusClass(upload.status)\">\r\n\r\n <!-- File Info -->\r\n <div class=\"file-info\">\r\n <cide-ele-icon class=\"status-icon\" size=\"sm\">\r\n {{getStatusIcon(upload.status)}}\r\n </cide-ele-icon>\r\n\r\n <div class=\"file-details\">\r\n <div class=\"file-name\">{{ upload.fileName }}</div>\r\n @if (upload.file) {\r\n <div class=\"file-size\">{{ getFileSizeDisplay(upload.file) }}</div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Progress Bar -->\r\n @if (upload.status === 'uploading') {\r\n <div class=\"progress-container\">\r\n <div class=\"progress-bar\">\r\n <div class=\"progress-fill\" [style.width.%]=\"upload.progress\">\r\n </div>\r\n </div>\r\n <span class=\"progress-text\">{{ upload.progress }}%</span>\r\n </div>\r\n }\r\n\r\n <!-- Status Text -->\r\n @if (upload.status !== 'uploading') {\r\n <div class=\"status-text\">\r\n @switch (upload.status) {\r\n @case ('pending') {\r\n <span class=\"text-yellow-600\">Waiting...</span>\r\n }\r\n @case ('completed') {\r\n <span class=\"text-green-600\">Uploaded successfully</span>\r\n }\r\n @case ('error') {\r\n <span class=\"text-red-600\">{{ upload.error || 'Upload failed' }}</span>\r\n }\r\n @case ('cancelled') {\r\n <span class=\"text-gray-600\">Cancelled</span>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Actions -->\r\n <div class=\"upload-actions\">\r\n @switch (upload.status) {\r\n @case ('pending') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"cancelUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"cancel\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n @case ('uploading') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"cancelUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"cancel\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n @case ('completed') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"removeUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"close\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n @case ('error') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"retryUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"refresh\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"removeUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"close\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n @case ('cancelled') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"removeUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"close\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Queue Summary -->\r\n <div class=\"queue-summary\">\r\n <div class=\"summary-stats\">\r\n <span class=\"stat\">\r\n <cide-ele-icon name=\"schedule\" size=\"xs\"></cide-ele-icon>\r\n {{ pendingUploads().length }} pending\r\n </span>\r\n <span class=\"stat\">\r\n <cide-ele-icon name=\"cloud_upload\" size=\"xs\"></cide-ele-icon>\r\n {{ activeUploads().length }} uploading\r\n </span>\r\n <span class=\"stat\">\r\n <cide-ele-icon name=\"check_circle\" size=\"xs\"></cide-ele-icon>\r\n {{ completedUploads().length }} completed\r\n </span>\r\n @if (failedUploads().length > 0) {\r\n <span class=\"stat error\">\r\n <cide-ele-icon name=\"error\" size=\"xs\"></cide-ele-icon>\r\n {{ failedUploads().length }} failed\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n\r\n", styles: [".global-file-uploader{@apply tw-w-full tw-max-w-2xl tw-mx-auto tw-p-4;}.global-file-uploader .upload-zone{@apply tw-border-2 tw-border-dashed tw-border-gray-300 tw-rounded-lg tw-p-8 tw-text-center tw-cursor-pointer tw-transition-all tw-duration-200 tw-ease-in-out;@apply hover:tw-border-blue-400 hover:tw-bg-blue-50;}.global-file-uploader .upload-zone.drag-over{@apply tw-border-blue-500 tw-bg-blue-100 tw-scale-105;}.global-file-uploader .upload-zone .upload-zone-content{@apply tw-space-y-4;}.global-file-uploader .upload-zone .upload-zone-content .upload-icon{@apply tw-text-gray-400 tw-mx-auto;}.global-file-uploader .upload-zone .upload-zone-content .upload-title{@apply tw-text-xl tw-font-semibold tw-text-gray-700 tw-m-0;}.global-file-uploader .upload-zone .upload-zone-content .upload-subtitle{@apply tw-text-sm tw-text-gray-500 tw-m-0;}.global-file-uploader .upload-queue{@apply tw-mt-6 tw-bg-white tw-border tw-border-gray-200 tw-rounded-lg tw-shadow-sm;}.global-file-uploader .upload-queue .queue-header{@apply tw-flex tw-justify-between tw-items-center tw-p-4 tw-border-b tw-border-gray-200;}.global-file-uploader .upload-queue .queue-header .queue-title{@apply tw-text-lg tw-font-medium tw-text-gray-700 tw-m-0;}.global-file-uploader .upload-queue .queue-header .queue-actions{@apply tw-flex tw-space-x-2;}.global-file-uploader .upload-queue .queue-items{@apply tw-max-h-96 tw-overflow-y-auto;}.global-file-uploader .upload-queue .queue-items .upload-item{@apply tw-flex tw-items-center tw-justify-between tw-p-4 tw-border-b tw-border-gray-100 tw-transition-colors tw-duration-200;}.global-file-uploader .upload-queue .queue-items .upload-item:last-child{@apply tw-border-b-0;}.global-file-uploader .upload-queue .queue-items .upload-item.status-pending{@apply tw-bg-yellow-50;}.global-file-uploader .upload-queue .queue-items .upload-item.status-uploading{@apply tw-bg-blue-50;}.global-file-uploader .upload-queue .queue-items .upload-item.status-completed{@apply tw-bg-green-50;}.global-file-uploader .upload-queue .queue-items .upload-item.status-error{@apply tw-bg-red-50;}.global-file-uploader .upload-queue .queue-items .upload-item.status-cancelled{@apply tw-bg-gray-50;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info{@apply tw-flex tw-items-center tw-flex-1 tw-min-w-0;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .status-icon{@apply tw-mr-3 tw-flex-shrink-0;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details{@apply tw-min-w-0 tw-flex-1;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details .file-name{@apply tw-text-sm tw-font-medium tw-text-gray-700 tw-truncate;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details .file-size{@apply tw-text-xs tw-text-gray-500 tw-mt-1;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container{@apply tw-flex tw-items-center tw-space-x-3 tw-flex-1 tw-max-w-xs tw-mx-4;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-bar{@apply tw-flex-1 tw-h-2 tw-bg-gray-200 tw-rounded-full tw-overflow-hidden;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-bar .progress-fill{@apply tw-h-full tw-bg-blue-500 tw-transition-all tw-duration-300 tw-ease-out;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-text{@apply tw-text-xs tw-font-medium tw-text-gray-600 tw-min-w-12 tw-text-right;}.global-file-uploader .upload-queue .queue-items .upload-item .status-text{@apply tw-flex-1 tw-text-sm tw-text-center;}.global-file-uploader .upload-queue .queue-items .upload-item .status-text span{@apply tw-font-medium;}.global-file-uploader .upload-queue .queue-items .upload-item .upload-actions{@apply tw-flex tw-space-x-1 tw-ml-4;}.global-file-uploader .upload-queue .queue-summary{@apply tw-p-4 tw-bg-gray-50 tw-border-t tw-border-gray-200;}.global-file-uploader .upload-queue .queue-summary .summary-stats{@apply tw-flex tw-flex-wrap tw-gap-4 tw-text-sm tw-text-gray-600;}.global-file-uploader .upload-queue .queue-summary .summary-stats .stat{@apply tw-flex tw-items-center tw-space-x-1;}.global-file-uploader .upload-queue .queue-summary .summary-stats .stat.error{@apply tw-text-red-600;}@media (max-width: 640px){.global-file-uploader{@apply tw-p-2;}.global-file-uploader .upload-zone{@apply tw-p-6;}.global-file-uploader .upload-zone .upload-zone-content .upload-title{@apply tw-text-lg;}.global-file-uploader .upload-zone .upload-zone-content .upload-subtitle{@apply tw-text-xs;}.global-file-uploader .upload-queue .queue-header{@apply tw-p-3 tw-flex-col tw-space-y-2 tw-items-start;}.global-file-uploader .upload-queue .queue-header .queue-actions{@apply tw-w-full tw-justify-end;}.global-file-uploader .upload-queue .queue-items .upload-item{@apply tw-p-3 tw-flex-col tw-space-y-3 tw-items-start;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info{@apply tw-w-full;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container{@apply tw-w-full tw-mx-0;}.global-file-uploader .upload-queue .queue-items .upload-item .status-text{@apply tw-w-full tw-text-left;}.global-file-uploader .upload-queue .queue-items .upload-item .upload-actions{@apply tw-w-full tw-justify-end tw-ml-0;}}@media (prefers-color-scheme: dark){.global-file-uploader .upload-zone{@apply tw-border-gray-600 hover:tw-border-blue-400 hover:tw-bg-gray-800;}.global-file-uploader .upload-zone.drag-over{@apply tw-bg-blue-900;}.global-file-uploader .upload-zone .upload-zone-content .upload-icon{@apply tw-text-gray-500;}.global-file-uploader .upload-zone .upload-zone-content .upload-title{@apply tw-text-gray-200;}.global-file-uploader .upload-zone .upload-zone-content .upload-subtitle{@apply tw-text-gray-400;}.global-file-uploader .upload-queue{@apply tw-bg-gray-800 tw-border-gray-700;}.global-file-uploader .upload-queue .queue-header{@apply tw-border-gray-700;}.global-file-uploader .upload-queue .queue-header .queue-title{@apply tw-text-gray-200;}.global-file-uploader .upload-queue .queue-items .upload-item{@apply tw-border-gray-700;}.global-file-uploader .upload-queue .queue-items .upload-item.status-pending{@apply tw-bg-yellow-900;}.global-file-uploader .upload-queue .queue-items .upload-item.status-uploading{@apply tw-bg-blue-900;}.global-file-uploader .upload-queue .queue-items .upload-item.status-completed{@apply tw-bg-green-900;}.global-file-uploader .upload-queue .queue-items .upload-item.status-error{@apply tw-bg-red-900;}.global-file-uploader .upload-queue .queue-items .upload-item.status-cancelled{@apply tw-bg-gray-700;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details .file-name{@apply tw-text-gray-200;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details .file-size{@apply tw-text-gray-400;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-bar{@apply tw-bg-gray-600;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-text{@apply tw-text-gray-300;}.global-file-uploader .upload-queue .queue-summary{@apply tw-bg-gray-700 tw-border-gray-600;}.global-file-uploader .upload-queue .queue-summary .summary-stats{@apply tw-text-gray-300;}.global-file-uploader .upload-queue .queue-summary .summary-stats .stat.error{@apply tw-text-red-400;}}\n"] }]
|
|
4491
|
-
|
|
4346
|
+
args: [{ selector: 'cide-ele-file-input', standalone: true, imports: [CommonModule, FormsModule, CideIconComponent], providers: [
|
|
4347
|
+
{
|
|
4348
|
+
provide: NG_VALUE_ACCESSOR,
|
|
4349
|
+
useExisting: CideEleFileInputComponent,
|
|
4350
|
+
multi: true
|
|
4351
|
+
},
|
|
4352
|
+
{
|
|
4353
|
+
provide: NG_VALIDATORS,
|
|
4354
|
+
useExisting: CideEleFileInputComponent,
|
|
4355
|
+
multi: true
|
|
4356
|
+
}
|
|
4357
|
+
], template: "<div class=\"cide-file-input\">\n <!-- Label (shown when not in preview box mode or when preview box mode but no label override) -->\n @if (labelSignal() && !isPreviewBoxMode()) {\n <label class=\"cide-file-input-label\" [attr.for]=\"'cide-file-input-' + id()\">\n {{ labelSignal() }}@if (requiredSignal()) {<span class=\"cide-file-input-required\"> *</span>}\n </label>\n }\n \n <!-- Preview Box Mode -->\n @if (isPreviewBoxMode()) {\n <div class=\"cide-file-input-preview-box-container\">\n <!-- Hidden file input -->\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id()\"\n [attr.accept]=\"acceptSignal()\"\n [attr.multiple]=\"multipleSignal() ? true : null\"\n [disabled]=\"disabledSignal()\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-hidden\"\n />\n \n <!-- Preview Box -->\n <div \n class=\"cide-file-input-preview-box\"\n [class.cide-file-input-preview-box-disabled]=\"disabledSignal()\"\n [class.cide-file-input-preview-box-has-image]=\"hasImages()\"\n [class.cide-file-input-preview-box-drag-over]=\"isDragOver()\"\n [style.width]=\"previewWidthSignal()\"\n [style.height]=\"previewHeightSignal()\"\n (click)=\"triggerFileSelect()\"\n (dragover)=\"onDragOver($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [attr.title]=\"disabledSignal() ? 'File selection disabled' : placeholderTextSignal()\">\n \n <!-- No Image State -->\n @if (!hasImages()) {\n <div class=\"cide-file-input-preview-box-placeholder\">\n <div class=\"cide-file-input-preview-box-icon\">\n <cide-ele-icon>{{ isDragOver() ? '\uD83D\uDCC1' : placeholderIconSignal() }}</cide-ele-icon>\n </div>\n <div class=\"cide-file-input-preview-box-text\">\n {{ isDragOver() ? 'Drop files here...' : placeholderTextSignal() }}\n </div>\n </div>\n }\n \n <!-- Image Preview State -->\n @if (hasImages()) {\n <div class=\"cide-file-input-preview-box-content\">\n <img \n [src]=\"previewUrls()[0]\" \n [alt]=\"fileNames()[0] || 'Preview image'\"\n class=\"cide-file-input-preview-box-image\">\n <div class=\"cide-file-input-preview-box-overlay\">\n <div class=\"cide-file-input-preview-box-overlay-text\">Click to change</div>\n </div>\n @if (!disabledSignal()) {\n <button \n type=\"button\" \n class=\"cide-file-input-preview-box-remove\"\n (click)=\"clearFiles(); $event.stopPropagation()\"\n title=\"Remove image\">\n \u00D7\n </button>\n }\n </div>\n }\n </div>\n \n <!-- File name display for preview box mode -->\n @if (hasImages() && fileNames().length && showFileNameSignal()) {\n <div class=\"cide-file-input-preview-box-filename\">\n {{ fileNames()[0] }}\n </div>\n }\n </div>\n }\n\n <!-- Standard Mode -->\n @if (!isPreviewBoxMode()) {\n <div \n class=\"cide-file-input-wrapper\"\n [class.cide-file-input-drag-over]=\"isDragOver()\"\n (dragover)=\"onDragOver($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id()\"\n [attr.accept]=\"acceptSignal()\"\n [attr.multiple]=\"multipleSignal() ? true : null\"\n [disabled]=\"disabledSignal()\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-element\"\n />\n @if (hasFiles()) {\n <button type=\"button\" class=\"cide-file-input-clear\" (click)=\"clearFiles()\">\n Clear\n </button>\n }\n </div>\n @if (hasFiles() && !isPreviewBoxMode()) {\n <div class=\"cide-file-input-files\">\n @if (multipleSignal() && fileNames().length > 1) {\n <!-- Show file count for multiple files -->\n <div class=\"cide-file-input-multiple-count\">\n {{ fileNames().length }} files selected\n </div>\n } @else {\n <!-- Show individual file names for single file or when only one file selected -->\n @for (name of fileNames(); track name) {\n <span>{{ name }}</span>\n }\n }\n <!-- Angular 20: Display file size using new computed values -->\n @if (totalFileSize() > 0) {\n <div class=\"cide-file-input-size\">\n Total size: {{ fileSizeInMB() }} MB\n </div>\n }\n </div>\n }\n }\n \n <!-- Image Preview Section (only for standard mode) -->\n @if (isImagePreviewAvailable() && !isPreviewBoxMode()) {\n <div class=\"cide-file-input-preview\">\n <div class=\"cide-file-input-preview-label\">Preview:</div>\n <div class=\"cide-file-input-preview-container\">\n @for (previewUrl of previewUrls(); track previewUrl; let i = $index) {\n <div \n class=\"cide-file-input-preview-item\"\n [style.width]=\"previewWidthSignal()\"\n [style.height]=\"previewHeightSignal()\">\n <button \n type=\"button\" \n class=\"cide-file-input-preview-remove\"\n (click)=\"removePreview(i)\"\n title=\"Remove image\">\n \u00D7\n </button>\n <img \n [src]=\"previewUrl\" \n [alt]=\"fileNames()[i] || 'Preview image'\"\n class=\"cide-file-input-preview-image\"\n loading=\"lazy\">\n <div class=\"cide-file-input-preview-filename\">{{ fileNames()[i] }}</div>\n </div>\n }\n </div>\n </div>\n }\n \n @if (errorTextSignal()) {\n <div class=\"cide-file-input-error\">{{ errorTextSignal() }}</div>\n }\n @if (helperTextSignal() && !errorTextSignal()) {\n <div class=\"cide-file-input-helper\">{{ helperTextSignal() }}</div>\n }\n</div> ", styles: [".cide-file-input{display:flex;flex-direction:column;gap:.5rem}.cide-file-input-label{font-weight:500;margin-bottom:.25rem}.cide-file-input-required{color:#d32f2f;font-weight:700}.cide-file-input-wrapper{display:flex;align-items:center;gap:.5rem;border:2px dashed transparent;border-radius:.5rem;padding:.5rem;transition:all .2s ease-in-out}.cide-file-input-wrapper.cide-file-input-drag-over{border-color:#3b82f6;background-color:#eff6ff;transform:scale(1.02)}.cide-file-input-element{flex:1}.cide-file-input-clear{background:none;border:none;color:#d32f2f;cursor:pointer;font-size:.9rem}.cide-file-input-files{font-size:.95rem;color:#333;margin-top:.25rem}.cide-file-input-multiple-count{display:inline-flex;align-items:center;gap:.5rem;padding:.5rem 1rem;background-color:#f0f9ff;border:1px solid #0ea5e9;border-radius:.5rem;color:#0369a1;font-weight:500;font-size:.9rem}.cide-file-input-size{margin-top:.5rem;padding:.25rem .5rem;background-color:#f3f4f6;border-radius:.25rem;font-size:.75rem;color:#4b5563;font-weight:500}.cide-file-input-error{color:#d32f2f;font-size:.9rem}.cide-file-input-helper{color:#666;font-size:.9rem}.cide-file-input-preview{margin-top:.75rem;padding:.75rem;background-color:#f8f9fa;border:1px solid #e1e5e9;border-radius:.375rem}.cide-file-input-preview-label{font-weight:500;margin-bottom:.5rem;color:#374151;font-size:.875rem}.cide-file-input-preview-container{display:flex;flex-wrap:wrap;gap:.75rem}.cide-file-input-preview-item{position:relative;display:flex;flex-direction:column;border:1px solid #d1d5db;border-radius:.5rem;overflow:hidden;background-color:#fff;box-shadow:0 1px 3px #0000001a;transition:box-shadow .2s ease-in-out}.cide-file-input-preview-item:hover{box-shadow:0 4px 6px -1px #0000001a}.cide-file-input-preview-image{width:100%;height:calc(100% - 2rem);object-fit:cover;object-position:center;background-color:#f3f4f6}.cide-file-input-preview-filename{padding:.375rem .5rem;background-color:#f9fafbf2;border-top:1px solid #e5e7eb;font-size:.75rem;color:#374151;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-height:2rem;display:flex;align-items:center;justify-content:center}.cide-file-input-preview-remove{position:absolute;top:.25rem;right:.25rem;width:1.5rem;height:1.5rem;background-color:#ef4444e6;color:#fff;border:none;border-radius:50%;font-size:1rem;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:10;transition:all .2s ease-in-out}.cide-file-input-preview-remove:hover{background-color:#dc2626f2;transform:scale(1.1)}.cide-file-input-preview-remove:focus{outline:2px solid #3b82f6;outline-offset:2px}.cide-file-input-hidden{display:none}.cide-file-input-preview-box-container{display:flex;flex-direction:column;gap:.5rem}.cide-file-input-preview-box{position:relative;border:2px dashed #d1d5db;border-radius:.5rem;cursor:pointer;background-color:#f9fafb;display:flex;align-items:center;justify-content:center;overflow:hidden;transition:all .2s ease-in-out}.cide-file-input-preview-box:hover{border-color:#3b82f6;background-color:#eff6ff}.cide-file-input-preview-box.cide-file-input-preview-box-disabled{cursor:not-allowed;opacity:.6;background-color:#f3f4f6}.cide-file-input-preview-box.cide-file-input-preview-box-disabled:hover{border-color:#d1d5db;background-color:#f3f4f6}.cide-file-input-preview-box.cide-file-input-preview-box-has-image{border-style:solid;border-color:#e5e7eb;padding:0}.cide-file-input-preview-box.cide-file-input-preview-box-has-image:hover{border-color:#3b82f6}.cide-file-input-preview-box.cide-file-input-preview-box-has-image:hover .cide-file-input-preview-box-overlay{opacity:1}.cide-file-input-preview-box.cide-file-input-preview-box-drag-over{border-color:#3b82f6!important;background-color:#eff6ff!important;transform:scale(1.02);box-shadow:0 0 0 4px #3b82f61a}.cide-file-input-preview-box.cide-file-input-preview-box-drag-over .cide-file-input-preview-box-placeholder .cide-file-input-preview-box-icon{color:#3b82f6;transform:scale(1.1)}.cide-file-input-preview-box.cide-file-input-preview-box-drag-over .cide-file-input-preview-box-placeholder .cide-file-input-preview-box-text{color:#3b82f6;font-weight:600}.cide-file-input-preview-box-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:1rem;text-align:center}.cide-file-input-preview-box-icon{font-size:2rem;color:#6b7280}.cide-file-input-preview-box-text{font-size:.875rem;color:#6b7280;font-weight:500}.cide-file-input-preview-box-content{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center}.cide-file-input-preview-box-image{width:100%;height:100%;object-fit:cover;object-position:center}.cide-file-input-preview-box-overlay{position:absolute;inset:0;background-color:#0009;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s ease-in-out}.cide-file-input-preview-box-overlay-text{color:#fff;font-size:.875rem;font-weight:500;text-align:center}.cide-file-input-preview-box-remove{position:absolute;top:.375rem;right:.375rem;width:1.5rem;height:1.5rem;background-color:#ef4444e6;color:#fff;border:none;border-radius:50%;font-size:1rem;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:20;transition:all .2s ease-in-out}.cide-file-input-preview-box-remove:hover{background-color:#dc2626f2;transform:scale(1.1)}.cide-file-input-preview-box-remove:focus{outline:2px solid #3b82f6;outline-offset:2px}.cide-file-input-preview-box-filename{font-size:.75rem;color:#374151;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:.25rem .5rem;background-color:#f3f4f6;border-radius:.25rem;margin-top:.25rem}@media (max-width: 640px){.cide-file-input-preview-container{justify-content:center}.cide-file-input-preview-item{min-width:120px;max-width:150px}.cide-file-input-preview-box-icon{font-size:1.5rem}.cide-file-input-preview-box-text{font-size:.75rem}}\n"] }]
|
|
4358
|
+
}], ctorParameters: () => [], propDecorators: { label: [{
|
|
4359
|
+
type: Input
|
|
4360
|
+
}], accept: [{
|
|
4492
4361
|
type: Input
|
|
4493
4362
|
}], multiple: [{
|
|
4494
4363
|
type: Input
|
|
4495
|
-
}],
|
|
4364
|
+
}], disabled: [{
|
|
4496
4365
|
type: Input
|
|
4497
|
-
}],
|
|
4366
|
+
}], required: [{
|
|
4498
4367
|
type: Input
|
|
4499
|
-
}],
|
|
4368
|
+
}], helperText: [{
|
|
4500
4369
|
type: Input
|
|
4501
|
-
}],
|
|
4370
|
+
}], errorText: [{
|
|
4371
|
+
type: Input
|
|
4372
|
+
}], showPreview: [{
|
|
4373
|
+
type: Input
|
|
4374
|
+
}], previewWidth: [{
|
|
4375
|
+
type: Input
|
|
4376
|
+
}], previewHeight: [{
|
|
4377
|
+
type: Input
|
|
4378
|
+
}], previewBoxMode: [{
|
|
4379
|
+
type: Input
|
|
4380
|
+
}], showFileName: [{
|
|
4381
|
+
type: Input
|
|
4382
|
+
}], placeholderText: [{
|
|
4383
|
+
type: Input
|
|
4384
|
+
}], placeholderIcon: [{
|
|
4385
|
+
type: Input
|
|
4386
|
+
}], autoUpload: [{
|
|
4387
|
+
type: Input
|
|
4388
|
+
}], uploadData: [{
|
|
4389
|
+
type: Input
|
|
4390
|
+
}], showFloatingUploader: [{
|
|
4391
|
+
type: Input
|
|
4392
|
+
}], floatingUploaderGroupId: [{
|
|
4393
|
+
type: Input
|
|
4394
|
+
}], fileChange: [{
|
|
4502
4395
|
type: Output
|
|
4503
|
-
}],
|
|
4396
|
+
}], uploadSuccess: [{
|
|
4504
4397
|
type: Output
|
|
4505
|
-
}],
|
|
4398
|
+
}], uploadError: [{
|
|
4506
4399
|
type: Output
|
|
4507
|
-
}],
|
|
4400
|
+
}], uploadProgressChange: [{
|
|
4508
4401
|
type: Output
|
|
4509
4402
|
}] } });
|
|
4510
4403
|
|
|
4511
|
-
class
|
|
4404
|
+
class CideEleGlobalFileUploaderComponent {
|
|
4405
|
+
// Dependency injection
|
|
4512
4406
|
destroyRef = inject(DestroyRef);
|
|
4513
|
-
|
|
4514
|
-
//
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4407
|
+
fileService = inject(CideEleFileManagerService);
|
|
4408
|
+
// Input properties
|
|
4409
|
+
userId = '';
|
|
4410
|
+
multiple = true;
|
|
4411
|
+
accept = '';
|
|
4412
|
+
maxFileSize = 10; // MB
|
|
4413
|
+
allowedTypes = [];
|
|
4414
|
+
// Output events
|
|
4415
|
+
uploadComplete = new EventEmitter();
|
|
4416
|
+
uploadError = new EventEmitter();
|
|
4417
|
+
uploadCancelled = new EventEmitter();
|
|
4418
|
+
allUploadsComplete = new EventEmitter();
|
|
4419
|
+
// Signals for reactive state management
|
|
4420
|
+
isDragOver = signal(false, ...(ngDevMode ? [{ debugName: "isDragOver" }] : []));
|
|
4421
|
+
isUploading = signal(false, ...(ngDevMode ? [{ debugName: "isUploading" }] : []));
|
|
4518
4422
|
uploadQueue = signal([], ...(ngDevMode ? [{ debugName: "uploadQueue" }] : []));
|
|
4423
|
+
currentGroupId = signal(null, ...(ngDevMode ? [{ debugName: "currentGroupId" }] : []));
|
|
4519
4424
|
// Computed values
|
|
4520
4425
|
hasUploads = computed(() => this.uploadQueue().length > 0, ...(ngDevMode ? [{ debugName: "hasUploads" }] : []));
|
|
4521
4426
|
pendingUploads = computed(() => this.uploadQueue().filter(upload => upload.status === 'pending'), ...(ngDevMode ? [{ debugName: "pendingUploads" }] : []));
|
|
4522
4427
|
activeUploads = computed(() => this.uploadQueue().filter(upload => upload.status === 'uploading'), ...(ngDevMode ? [{ debugName: "activeUploads" }] : []));
|
|
4523
4428
|
completedUploads = computed(() => this.uploadQueue().filter(upload => upload.status === 'completed'), ...(ngDevMode ? [{ debugName: "completedUploads" }] : []));
|
|
4524
4429
|
failedUploads = computed(() => this.uploadQueue().filter(upload => upload.status === 'error'), ...(ngDevMode ? [{ debugName: "failedUploads" }] : []));
|
|
4525
|
-
// Animation states
|
|
4526
|
-
isAnimating = signal(false, ...(ngDevMode ? [{ debugName: "isAnimating" }] : []));
|
|
4527
4430
|
constructor() {
|
|
4528
|
-
console.log('🚀 [
|
|
4529
|
-
// Set up effect to monitor local upload queue
|
|
4530
|
-
effect(() => {
|
|
4531
|
-
const uploadQueue = this.uploadQueue();
|
|
4532
|
-
const hasUploads = uploadQueue.length > 0;
|
|
4533
|
-
console.log('🔄 [FloatingFileUploader] Upload queue changed:', {
|
|
4534
|
-
queueLength: uploadQueue.length,
|
|
4535
|
-
hasUploads,
|
|
4536
|
-
currentVisible: this.isVisible()
|
|
4537
|
-
});
|
|
4538
|
-
// Show floating uploader when files are added to the queue
|
|
4539
|
-
if (hasUploads && !this.isVisible()) {
|
|
4540
|
-
console.log('👁️ [FloatingFileUploader] Showing floating uploader due to upload queue');
|
|
4541
|
-
this.showWithAnimation();
|
|
4542
|
-
}
|
|
4543
|
-
else if (!hasUploads && this.isVisible()) {
|
|
4544
|
-
console.log('👁️ [FloatingFileUploader] Hiding floating uploader - no files in queue');
|
|
4545
|
-
this.hideWithAnimation();
|
|
4546
|
-
}
|
|
4547
|
-
});
|
|
4431
|
+
console.log('🚀 [GlobalFileUploader] Component initialized');
|
|
4548
4432
|
}
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4433
|
+
/**
|
|
4434
|
+
* Handle drag over event
|
|
4435
|
+
*/
|
|
4436
|
+
onDragOver(event) {
|
|
4437
|
+
event.preventDefault();
|
|
4438
|
+
event.stopPropagation();
|
|
4439
|
+
this.isDragOver.set(true);
|
|
4554
4440
|
}
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4441
|
+
/**
|
|
4442
|
+
* Handle drag leave event
|
|
4443
|
+
*/
|
|
4444
|
+
onDragLeave(event) {
|
|
4445
|
+
event.preventDefault();
|
|
4446
|
+
event.stopPropagation();
|
|
4447
|
+
this.isDragOver.set(false);
|
|
4448
|
+
}
|
|
4449
|
+
/**
|
|
4450
|
+
* Handle drop event
|
|
4451
|
+
*/
|
|
4452
|
+
onDrop(event) {
|
|
4453
|
+
event.preventDefault();
|
|
4454
|
+
event.stopPropagation();
|
|
4455
|
+
this.isDragOver.set(false);
|
|
4456
|
+
const files = event.dataTransfer?.files;
|
|
4457
|
+
if (files && files.length > 0) {
|
|
4458
|
+
this.addFilesToQueue(Array.from(files));
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
/**
|
|
4462
|
+
* Handle file input change
|
|
4463
|
+
*/
|
|
4464
|
+
onFileInputChange(event) {
|
|
4465
|
+
const target = event.target;
|
|
4466
|
+
const files = target.files;
|
|
4467
|
+
if (files && files.length > 0) {
|
|
4468
|
+
this.addFilesToQueue(Array.from(files));
|
|
4469
|
+
}
|
|
4470
|
+
// Reset input value to allow selecting the same file again
|
|
4471
|
+
target.value = '';
|
|
4559
4472
|
}
|
|
4560
4473
|
/**
|
|
4561
|
-
*
|
|
4474
|
+
* Add files to upload queue
|
|
4562
4475
|
*/
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4476
|
+
addFilesToQueue(files) {
|
|
4477
|
+
// Validate files
|
|
4478
|
+
const validFiles = files.filter(file => this.validateFile(file));
|
|
4479
|
+
if (validFiles.length === 0) {
|
|
4480
|
+
console.warn('⚠️ [GlobalFileUploader] No valid files to upload');
|
|
4481
|
+
return;
|
|
4482
|
+
}
|
|
4483
|
+
const newUploads = validFiles.map(file => ({
|
|
4484
|
+
fileId: this.generateFileId(file),
|
|
4485
|
+
fileName: file.name,
|
|
4486
|
+
progress: 0,
|
|
4487
|
+
status: 'pending',
|
|
4488
|
+
file: file
|
|
4489
|
+
}));
|
|
4490
|
+
this.uploadQueue.update(queue => [...queue, ...newUploads]);
|
|
4491
|
+
this.processUploadQueue();
|
|
4567
4492
|
}
|
|
4568
4493
|
/**
|
|
4569
|
-
*
|
|
4494
|
+
* Validate file
|
|
4570
4495
|
*/
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4496
|
+
validateFile(file) {
|
|
4497
|
+
// Check file size
|
|
4498
|
+
const maxSizeBytes = this.maxFileSize * 1024 * 1024;
|
|
4499
|
+
if (file.size > maxSizeBytes) {
|
|
4500
|
+
console.warn(`⚠️ [GlobalFileUploader] File ${file.name} exceeds size limit`);
|
|
4501
|
+
return false;
|
|
4502
|
+
}
|
|
4503
|
+
// Check file type
|
|
4504
|
+
if (this.allowedTypes.length > 0 && !this.allowedTypes.includes(file.type)) {
|
|
4505
|
+
console.warn(`⚠️ [GlobalFileUploader] File type ${file.type} not allowed`);
|
|
4506
|
+
return false;
|
|
4507
|
+
}
|
|
4508
|
+
return true;
|
|
4575
4509
|
}
|
|
4576
4510
|
/**
|
|
4577
|
-
*
|
|
4511
|
+
* Process upload queue
|
|
4578
4512
|
*/
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4513
|
+
processUploadQueue() {
|
|
4514
|
+
const pendingUploads = this.uploadQueue().filter(upload => upload.status === 'pending');
|
|
4515
|
+
if (pendingUploads.length > 0 && !this.isUploading()) {
|
|
4516
|
+
this.isUploading.set(true);
|
|
4517
|
+
if (this.multiple && pendingUploads.length > 1) {
|
|
4518
|
+
// Multiple file upload with group ID
|
|
4519
|
+
this.uploadMultipleFiles(pendingUploads);
|
|
4520
|
+
}
|
|
4521
|
+
else {
|
|
4522
|
+
// Single file upload
|
|
4523
|
+
this.uploadNextFile();
|
|
4524
|
+
}
|
|
4525
|
+
}
|
|
4582
4526
|
}
|
|
4583
4527
|
/**
|
|
4584
|
-
*
|
|
4528
|
+
* Upload multiple files with group ID
|
|
4585
4529
|
*/
|
|
4586
|
-
|
|
4587
|
-
|
|
4530
|
+
uploadMultipleFiles(uploads) {
|
|
4531
|
+
console.log('📤 [GlobalFileUploader] Starting multiple file upload for:', uploads.length, 'files');
|
|
4532
|
+
// Generate group ID first
|
|
4533
|
+
this.fileService.generateObjectId()
|
|
4534
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4535
|
+
.subscribe({
|
|
4536
|
+
next: (response) => {
|
|
4537
|
+
const groupId = response.data?.objectId;
|
|
4538
|
+
console.log('🆔 [GlobalFileUploader] Generated group ID:', groupId);
|
|
4539
|
+
this.currentGroupId.set(groupId || null);
|
|
4540
|
+
if (groupId) {
|
|
4541
|
+
// Upload all files with the same group ID
|
|
4542
|
+
this.uploadFilesWithGroupId(uploads, groupId);
|
|
4543
|
+
}
|
|
4544
|
+
else {
|
|
4545
|
+
console.error('❌ [GlobalFileUploader] No group ID received');
|
|
4546
|
+
this.handleUploadError('Failed to generate group ID', uploads[0]?.fileId);
|
|
4547
|
+
}
|
|
4548
|
+
},
|
|
4549
|
+
error: (error) => {
|
|
4550
|
+
console.error('❌ [GlobalFileUploader] Failed to generate group ID:', error);
|
|
4551
|
+
this.handleUploadError('Failed to generate group ID', uploads[0]?.fileId);
|
|
4552
|
+
}
|
|
4553
|
+
});
|
|
4588
4554
|
}
|
|
4589
4555
|
/**
|
|
4590
|
-
*
|
|
4556
|
+
* Upload files with group ID
|
|
4591
4557
|
*/
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4558
|
+
uploadFilesWithGroupId(uploads, groupId) {
|
|
4559
|
+
let completedCount = 0;
|
|
4560
|
+
let failedCount = 0;
|
|
4561
|
+
const totalFiles = uploads.length;
|
|
4562
|
+
const uploadedFiles = [];
|
|
4563
|
+
uploads.forEach((upload, index) => {
|
|
4564
|
+
// Update status to uploading
|
|
4565
|
+
this.updateUploadStatus(upload.fileId, 'uploading', 0);
|
|
4566
|
+
const uploadRequest = {
|
|
4567
|
+
file: upload.file,
|
|
4568
|
+
userId: this.userId,
|
|
4569
|
+
groupId: groupId,
|
|
4570
|
+
tags: [],
|
|
4571
|
+
permissions: ['read', 'write']
|
|
4572
|
+
};
|
|
4573
|
+
this.fileService.uploadFile(upload.file, this.convertToFileUploadOptions(uploadRequest), (progress) => {
|
|
4574
|
+
// Use the original fileId for progress updates during upload
|
|
4575
|
+
this.updateUploadProgress(upload.fileId, progress);
|
|
4576
|
+
})
|
|
4577
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4578
|
+
.subscribe({
|
|
4579
|
+
next: (response) => {
|
|
4580
|
+
completedCount++;
|
|
4581
|
+
console.log(`✅ [GlobalFileUploader] File ${index + 1}/${totalFiles} uploaded successfully`);
|
|
4582
|
+
// Find the upload item by cyfm_temp_unique_id from response
|
|
4583
|
+
const responseFile = response?.data?.core_file_manager?.[0];
|
|
4584
|
+
const tempUniqueId = responseFile?.cyfm_temp_unique_id;
|
|
4585
|
+
console.log('🔍 [GlobalFileUploader] Response tempUniqueId:', tempUniqueId);
|
|
4586
|
+
console.log('🔍 [GlobalFileUploader] Original upload fileId:', upload.fileId);
|
|
4587
|
+
// Use the tempUniqueId to find and update the correct upload item
|
|
4588
|
+
if (tempUniqueId) {
|
|
4589
|
+
this.updateUploadStatusByTempId(tempUniqueId, 'completed', 100, undefined, response);
|
|
4590
|
+
}
|
|
4591
|
+
else {
|
|
4592
|
+
// Fallback to original fileId
|
|
4593
|
+
this.updateUploadStatus(upload.fileId, 'completed', 100, undefined, response);
|
|
4594
|
+
}
|
|
4595
|
+
this.uploadComplete.emit({
|
|
4596
|
+
fileId: tempUniqueId || upload.fileId,
|
|
4597
|
+
fileName: upload.fileName,
|
|
4598
|
+
progress: 100,
|
|
4599
|
+
status: 'completed',
|
|
4600
|
+
uploadedFile: response
|
|
4601
|
+
});
|
|
4602
|
+
if (responseFile) {
|
|
4603
|
+
uploadedFiles.push(responseFile);
|
|
4604
|
+
}
|
|
4605
|
+
console.log('🔍 [GlobalFileUploader] Upload response received:', response);
|
|
4606
|
+
console.log('🔍 [GlobalFileUploader] Response data structure:', response?.data);
|
|
4607
|
+
// Check if all files are completed
|
|
4608
|
+
if (completedCount + failedCount === totalFiles) {
|
|
4609
|
+
this.handleMultipleUploadComplete(completedCount, failedCount, groupId, uploadedFiles);
|
|
4610
|
+
}
|
|
4611
|
+
},
|
|
4612
|
+
error: (error) => {
|
|
4613
|
+
failedCount++;
|
|
4614
|
+
console.error(`❌ [GlobalFileUploader] File ${index + 1}/${totalFiles} upload failed:`, error);
|
|
4615
|
+
this.updateUploadStatus(upload.fileId, 'error', 0, error.message || 'Upload failed');
|
|
4616
|
+
this.uploadError.emit({
|
|
4617
|
+
fileId: upload.fileId,
|
|
4618
|
+
error: error.message || 'Upload failed'
|
|
4619
|
+
});
|
|
4620
|
+
// Check if all files are completed
|
|
4621
|
+
if (completedCount + failedCount === totalFiles) {
|
|
4622
|
+
this.handleMultipleUploadComplete(completedCount, failedCount, groupId, uploadedFiles);
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
});
|
|
4598
4626
|
});
|
|
4599
|
-
// Check if this is a file input with files
|
|
4600
|
-
if (target.type === 'file' && target.files && target.files.length > 0) {
|
|
4601
|
-
console.log('📁 [FloatingFileUploader] File input change detected:', target.files.length, 'files');
|
|
4602
|
-
// Check if the input has a data-user-id attribute for user context
|
|
4603
|
-
const userId = target.getAttribute('data-user-id');
|
|
4604
|
-
if (userId && userId !== this.currentUserId()) {
|
|
4605
|
-
this.setCurrentUserId(userId);
|
|
4606
|
-
}
|
|
4607
|
-
// Handle the files
|
|
4608
|
-
this.handleFiles(Array.from(target.files));
|
|
4609
|
-
// Reset the input to allow selecting the same files again
|
|
4610
|
-
target.value = '';
|
|
4611
|
-
}
|
|
4612
4627
|
}
|
|
4613
4628
|
/**
|
|
4614
|
-
* Handle
|
|
4629
|
+
* Handle multiple upload completion
|
|
4615
4630
|
*/
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4631
|
+
handleMultipleUploadComplete(completed, failed, groupId, uploadedFiles) {
|
|
4632
|
+
console.log(`📊 [GlobalFileUploader] Multiple upload complete: ${completed}/${completed + failed} successful`);
|
|
4633
|
+
this.isUploading.set(false);
|
|
4634
|
+
if (completed > 0) {
|
|
4635
|
+
this.allUploadsComplete.emit({
|
|
4636
|
+
groupId: groupId,
|
|
4637
|
+
uploadedFiles: uploadedFiles
|
|
4638
|
+
});
|
|
4622
4639
|
}
|
|
4623
4640
|
}
|
|
4624
4641
|
/**
|
|
4625
|
-
*
|
|
4642
|
+
* Upload next file in queue (single file mode)
|
|
4626
4643
|
*/
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
this.updateVisibility();
|
|
4644
|
+
uploadNextFile() {
|
|
4645
|
+
const pendingUpload = this.uploadQueue().find(upload => upload.status === 'pending');
|
|
4646
|
+
if (!pendingUpload) {
|
|
4647
|
+
this.isUploading.set(false);
|
|
4648
|
+
return;
|
|
4633
4649
|
}
|
|
4650
|
+
// Update status to uploading
|
|
4651
|
+
this.updateUploadStatus(pendingUpload.fileId, 'uploading', 0);
|
|
4652
|
+
const uploadRequest = {
|
|
4653
|
+
file: pendingUpload.file,
|
|
4654
|
+
userId: this.userId,
|
|
4655
|
+
tags: [],
|
|
4656
|
+
permissions: ['read', 'write']
|
|
4657
|
+
};
|
|
4658
|
+
this.fileService.uploadFile(pendingUpload.file, this.convertToFileUploadOptions(uploadRequest), (progress) => {
|
|
4659
|
+
this.updateUploadProgress(pendingUpload.fileId, progress);
|
|
4660
|
+
})
|
|
4661
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4662
|
+
.subscribe({
|
|
4663
|
+
next: (response) => {
|
|
4664
|
+
console.log('✅ [GlobalFileUploader] Single file upload successful:', response);
|
|
4665
|
+
// Find the upload item by cyfm_temp_unique_id from response
|
|
4666
|
+
const responseFile = response?.data?.core_file_manager?.[0];
|
|
4667
|
+
const tempUniqueId = responseFile?.cyfm_temp_unique_id;
|
|
4668
|
+
console.log('🔍 [GlobalFileUploader] Single upload - Response tempUniqueId:', tempUniqueId);
|
|
4669
|
+
console.log('🔍 [GlobalFileUploader] Single upload - Original upload fileId:', pendingUpload.fileId);
|
|
4670
|
+
// Use the tempUniqueId to find and update the correct upload item
|
|
4671
|
+
if (tempUniqueId) {
|
|
4672
|
+
this.updateUploadStatusByTempId(tempUniqueId, 'completed', 100, undefined, response);
|
|
4673
|
+
}
|
|
4674
|
+
else {
|
|
4675
|
+
// Fallback to original fileId
|
|
4676
|
+
this.updateUploadStatus(pendingUpload.fileId, 'completed', 100, undefined, response);
|
|
4677
|
+
}
|
|
4678
|
+
this.uploadComplete.emit({
|
|
4679
|
+
fileId: tempUniqueId || pendingUpload.fileId,
|
|
4680
|
+
fileName: pendingUpload.fileName,
|
|
4681
|
+
progress: 100,
|
|
4682
|
+
status: 'completed',
|
|
4683
|
+
uploadedFile: response
|
|
4684
|
+
});
|
|
4685
|
+
// Process next file
|
|
4686
|
+
setTimeout(() => this.uploadNextFile(), 500);
|
|
4687
|
+
},
|
|
4688
|
+
error: (error) => {
|
|
4689
|
+
console.error('❌ [GlobalFileUploader] Upload error:', error);
|
|
4690
|
+
this.updateUploadStatus(pendingUpload.fileId, 'error', 0, error.message || 'Upload failed');
|
|
4691
|
+
this.uploadError.emit({
|
|
4692
|
+
fileId: pendingUpload.fileId,
|
|
4693
|
+
error: error.message || 'Upload failed'
|
|
4694
|
+
});
|
|
4695
|
+
// Process next file
|
|
4696
|
+
setTimeout(() => this.uploadNextFile(), 500);
|
|
4697
|
+
}
|
|
4698
|
+
});
|
|
4634
4699
|
}
|
|
4635
4700
|
/**
|
|
4636
|
-
*
|
|
4701
|
+
* Update upload progress
|
|
4637
4702
|
*/
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
if (files && files.length > 0) {
|
|
4643
|
-
this.handleFiles(Array.from(files));
|
|
4644
|
-
}
|
|
4703
|
+
updateUploadProgress(fileId, progress) {
|
|
4704
|
+
this.uploadQueue.update(queue => queue.map(upload => upload.fileId === fileId
|
|
4705
|
+
? { ...upload, progress }
|
|
4706
|
+
: upload));
|
|
4645
4707
|
}
|
|
4646
4708
|
/**
|
|
4647
|
-
*
|
|
4709
|
+
* Update upload status
|
|
4648
4710
|
*/
|
|
4649
|
-
|
|
4650
|
-
console.log('
|
|
4651
|
-
|
|
4652
|
-
|
|
4711
|
+
updateUploadStatus(fileId, status, progress, error, uploadedFile) {
|
|
4712
|
+
console.log('🔄 [GlobalFileUploader] Updating upload status:', { fileId, status, progress, error: !!error, uploadedFile: !!uploadedFile });
|
|
4713
|
+
this.uploadQueue.update(queue => {
|
|
4714
|
+
const updatedQueue = queue.map(upload => {
|
|
4715
|
+
if (upload.fileId === fileId) {
|
|
4716
|
+
console.log('🔄 [GlobalFileUploader] Found upload to update:', upload.fileName, 'from', upload.status, 'to', status);
|
|
4717
|
+
return { ...upload, status, progress, error, uploadedFile };
|
|
4718
|
+
}
|
|
4719
|
+
return upload;
|
|
4720
|
+
});
|
|
4721
|
+
console.log('🔄 [GlobalFileUploader] Updated queue:', updatedQueue.map(u => ({ fileName: u.fileName, status: u.status })));
|
|
4722
|
+
return updatedQueue;
|
|
4723
|
+
});
|
|
4653
4724
|
}
|
|
4654
4725
|
/**
|
|
4655
|
-
* Update
|
|
4726
|
+
* Update upload status by cyfm_temp_unique_id
|
|
4656
4727
|
*/
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4728
|
+
updateUploadStatusByTempId(tempUniqueId, status, progress, error, uploadedFile) {
|
|
4729
|
+
console.log('🔄 [GlobalFileUploader] Updating upload status by tempId:', { tempUniqueId, status, progress, error: !!error, uploadedFile: !!uploadedFile });
|
|
4730
|
+
this.uploadQueue.update(queue => {
|
|
4731
|
+
const updatedQueue = queue.map(upload => {
|
|
4732
|
+
// Check if this upload's fileId matches the tempUniqueId
|
|
4733
|
+
if (upload.fileId === tempUniqueId) {
|
|
4734
|
+
console.log('🔄 [GlobalFileUploader] Found upload by tempId to update:', upload.fileName, 'from', upload.status, 'to', status);
|
|
4735
|
+
return { ...upload, status, progress, error, uploadedFile };
|
|
4736
|
+
}
|
|
4737
|
+
return upload;
|
|
4738
|
+
});
|
|
4739
|
+
console.log('🔄 [GlobalFileUploader] Updated queue by tempId:', updatedQueue.map(u => ({ fileName: u.fileName, status: u.status, fileId: u.fileId })));
|
|
4740
|
+
return updatedQueue;
|
|
4741
|
+
});
|
|
4660
4742
|
}
|
|
4661
4743
|
/**
|
|
4662
|
-
*
|
|
4744
|
+
* Handle upload error
|
|
4663
4745
|
*/
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
this.isAnimating.set(false);
|
|
4671
|
-
console.log('🎬 [FloatingFileUploader] Animation completed, isVisible:', this.isVisible());
|
|
4672
|
-
}, 300);
|
|
4746
|
+
handleUploadError(error, fileId) {
|
|
4747
|
+
if (fileId) {
|
|
4748
|
+
this.updateUploadStatus(fileId, 'error', 0, error);
|
|
4749
|
+
this.uploadError.emit({ fileId, error });
|
|
4750
|
+
}
|
|
4751
|
+
this.isUploading.set(false);
|
|
4673
4752
|
}
|
|
4674
4753
|
/**
|
|
4675
|
-
*
|
|
4754
|
+
* Cancel upload
|
|
4676
4755
|
*/
|
|
4677
|
-
|
|
4678
|
-
this.
|
|
4679
|
-
|
|
4756
|
+
cancelUpload(fileId) {
|
|
4757
|
+
this.updateUploadStatus(fileId, 'cancelled', 0);
|
|
4758
|
+
this.uploadCancelled.emit(fileId);
|
|
4759
|
+
// Remove from queue after a delay
|
|
4680
4760
|
setTimeout(() => {
|
|
4681
|
-
this.
|
|
4682
|
-
|
|
4683
|
-
}, 300);
|
|
4761
|
+
this.uploadQueue.update(queue => queue.filter(upload => upload.fileId !== fileId));
|
|
4762
|
+
}, 1000);
|
|
4684
4763
|
}
|
|
4685
4764
|
/**
|
|
4686
|
-
*
|
|
4765
|
+
* Retry upload
|
|
4687
4766
|
*/
|
|
4688
|
-
|
|
4689
|
-
this.
|
|
4767
|
+
retryUpload(fileId) {
|
|
4768
|
+
this.updateUploadStatus(fileId, 'pending', 0);
|
|
4769
|
+
this.processUploadQueue();
|
|
4690
4770
|
}
|
|
4691
4771
|
/**
|
|
4692
|
-
*
|
|
4772
|
+
* Remove upload from queue
|
|
4693
4773
|
*/
|
|
4694
|
-
|
|
4695
|
-
this.
|
|
4774
|
+
removeUpload(fileId) {
|
|
4775
|
+
this.uploadQueue.update(queue => queue.filter(upload => upload.fileId !== fileId));
|
|
4696
4776
|
}
|
|
4697
4777
|
/**
|
|
4698
|
-
*
|
|
4778
|
+
* Clear completed uploads
|
|
4699
4779
|
*/
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
const completed = this.completedUploads().length;
|
|
4703
|
-
const failed = this.failedUploads().length;
|
|
4704
|
-
const pending = this.pendingUploads().length;
|
|
4705
|
-
if (active > 0) {
|
|
4706
|
-
return `${active} uploading`;
|
|
4707
|
-
}
|
|
4708
|
-
else if (completed > 0 && failed === 0) {
|
|
4709
|
-
return `${completed} completed`;
|
|
4710
|
-
}
|
|
4711
|
-
else if (failed > 0) {
|
|
4712
|
-
return `${completed} completed, ${failed} failed`;
|
|
4713
|
-
}
|
|
4714
|
-
else if (pending > 0) {
|
|
4715
|
-
return `${pending} pending`;
|
|
4716
|
-
}
|
|
4717
|
-
return 'No uploads';
|
|
4780
|
+
clearCompleted() {
|
|
4781
|
+
this.uploadQueue.update(queue => queue.filter(upload => upload.status !== 'completed'));
|
|
4718
4782
|
}
|
|
4719
4783
|
/**
|
|
4720
|
-
*
|
|
4784
|
+
* Clear all uploads
|
|
4721
4785
|
*/
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
return 0;
|
|
4726
|
-
const totalProgress = uploads.reduce((sum, upload) => sum + upload.progress, 0);
|
|
4727
|
-
return Math.round(totalProgress / uploads.length);
|
|
4786
|
+
clearAll() {
|
|
4787
|
+
this.uploadQueue.set([]);
|
|
4788
|
+
this.currentGroupId.set(null);
|
|
4728
4789
|
}
|
|
4729
4790
|
/**
|
|
4730
4791
|
* Get status icon
|
|
@@ -4753,110 +4814,182 @@ class CideEleFloatingFileUploaderComponent {
|
|
|
4753
4814
|
}
|
|
4754
4815
|
}
|
|
4755
4816
|
/**
|
|
4756
|
-
*
|
|
4757
|
-
*/
|
|
4758
|
-
cancelUpload(fileId) {
|
|
4759
|
-
console.log('🚫 [FloatingFileUploader] Cancelling upload:', fileId);
|
|
4760
|
-
this.fileManagerService.cancelUpload(fileId);
|
|
4761
|
-
this.updateUploadStatus(fileId, 'cancelled', 0);
|
|
4762
|
-
}
|
|
4763
|
-
/**
|
|
4764
|
-
* Retry upload
|
|
4765
|
-
*/
|
|
4766
|
-
retryUpload(fileId) {
|
|
4767
|
-
console.log('🔄 [FloatingFileUploader] Retrying upload:', fileId);
|
|
4768
|
-
this.updateUploadStatus(fileId, 'pending', 0);
|
|
4769
|
-
// Note: Actual retry logic would need to be implemented based on stored file data
|
|
4770
|
-
}
|
|
4771
|
-
/**
|
|
4772
|
-
* Set current user ID
|
|
4773
|
-
*/
|
|
4774
|
-
setCurrentUserId(userId) {
|
|
4775
|
-
this.currentUserId.set(userId);
|
|
4776
|
-
this.fileManagerService.setUserId(userId);
|
|
4777
|
-
}
|
|
4778
|
-
/**
|
|
4779
|
-
* Public method to handle files from external sources
|
|
4780
|
-
* This can be called by other components to trigger the floating uploader
|
|
4817
|
+
* Get file size display
|
|
4781
4818
|
*/
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
files.forEach((file, index) => {
|
|
4791
|
-
const fileId = this.generateFileId(file);
|
|
4792
|
-
// Create upload queue item
|
|
4793
|
-
const uploadItem = {
|
|
4794
|
-
fileId,
|
|
4795
|
-
fileName: file.name,
|
|
4796
|
-
progress: 0,
|
|
4797
|
-
status: 'pending',
|
|
4798
|
-
file: file
|
|
4799
|
-
};
|
|
4800
|
-
console.log(`📁 [FloatingFileUploader] Adding file ${index + 1}/${files.length} to upload queue:`, {
|
|
4801
|
-
fileId,
|
|
4802
|
-
fileName: file.name,
|
|
4803
|
-
status: 'pending'
|
|
4804
|
-
});
|
|
4805
|
-
// Add to local upload queue
|
|
4806
|
-
this.uploadQueue.update(queue => [...queue, uploadItem]);
|
|
4807
|
-
// Start upload using file manager service
|
|
4808
|
-
this.fileManagerService.uploadFile(file, {
|
|
4809
|
-
userId: this.currentUserId(),
|
|
4810
|
-
permissions: ['read', 'write'],
|
|
4811
|
-
tags: []
|
|
4812
|
-
}, (progress) => {
|
|
4813
|
-
this.updateUploadProgress(fileId, progress);
|
|
4814
|
-
})
|
|
4815
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4816
|
-
.subscribe({
|
|
4817
|
-
next: (response) => {
|
|
4818
|
-
console.log('✅ [FloatingFileUploader] Upload completed:', response);
|
|
4819
|
-
this.updateUploadStatus(fileId, 'completed', 100, undefined, response);
|
|
4820
|
-
},
|
|
4821
|
-
error: (error) => {
|
|
4822
|
-
console.error('❌ [FloatingFileUploader] Upload failed:', error);
|
|
4823
|
-
this.updateUploadStatus(fileId, 'error', 0, error.message || 'Upload failed');
|
|
4824
|
-
}
|
|
4825
|
-
});
|
|
4826
|
-
});
|
|
4819
|
+
getFileSizeDisplay(file) {
|
|
4820
|
+
const bytes = file.size;
|
|
4821
|
+
if (bytes === 0)
|
|
4822
|
+
return '0 Bytes';
|
|
4823
|
+
const k = 1024;
|
|
4824
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
4825
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
4826
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
4827
4827
|
}
|
|
4828
4828
|
/**
|
|
4829
|
-
* Generate unique file ID
|
|
4829
|
+
* Generate unique file ID - this should match the service's generateFileId method
|
|
4830
|
+
* The service uses: ${file.name}_${file.size}_${Date.now()}
|
|
4830
4831
|
*/
|
|
4831
4832
|
generateFileId(file) {
|
|
4832
4833
|
return `${file.name}_${file.size}_${Date.now()}`;
|
|
4833
4834
|
}
|
|
4834
4835
|
/**
|
|
4835
|
-
*
|
|
4836
|
+
* Convert IFileUploadRequest to FileUploadOptions
|
|
4836
4837
|
*/
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
:
|
|
4838
|
+
convertToFileUploadOptions(request) {
|
|
4839
|
+
return {
|
|
4840
|
+
altText: request.altText,
|
|
4841
|
+
userId: request.userId,
|
|
4842
|
+
permissions: request.permissions,
|
|
4843
|
+
tags: request.tags,
|
|
4844
|
+
groupId: request.groupId
|
|
4845
|
+
};
|
|
4841
4846
|
}
|
|
4842
4847
|
/**
|
|
4843
|
-
*
|
|
4848
|
+
* Trigger file input click
|
|
4844
4849
|
*/
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4850
|
+
triggerFileInput() {
|
|
4851
|
+
const fileInput = document.getElementById('global-file-input');
|
|
4852
|
+
if (fileInput) {
|
|
4853
|
+
fileInput.click();
|
|
4854
|
+
}
|
|
4849
4855
|
}
|
|
4850
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
4851
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: CideEleFloatingFileUploaderComponent, isStandalone: true, selector: "cide-ele-floating-file-uploader", ngImport: i0, template: "<!-- Floating File Uploader Container -->\r\n@if (isVisible()) {\r\n<div class=\"floating-uploader\" \r\n [class.minimized]=\"isMinimized()\" \r\n [class.animating]=\"isAnimating()\">\r\n\r\n <!-- Header -->\r\n <div class=\"uploader-header\">\r\n <div class=\"header-left\">\r\n <div class=\"upload-icon\">\r\n <cide-ele-icon size=\"sm\">cloud_upload</cide-ele-icon>\r\n </div>\r\n <div class=\"upload-info\">\r\n <div class=\"upload-title\">File Upload</div>\r\n <div class=\"upload-summary\">{{ getUploadSummary() }}</div>\r\n </div>\r\n </div>\r\n \r\n <div class=\"header-actions\">\r\n <button class=\"action-btn minimize-btn\" (click)=\"toggleMinimize()\" [title]=\"isMinimized() ? 'Expand' : 'Minimize'\">\r\n <cide-ele-icon size=\"xs\">{{ isMinimized() ? 'expand_more' : 'expand_less' }}</cide-ele-icon>\r\n </button>\r\n <button class=\"action-btn close-btn\" (click)=\"close()\" title=\"Close\">\r\n <cide-ele-icon size=\"xs\">close</cide-ele-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Content (hidden when minimized) -->\r\n @if (!isMinimized()) {\r\n <div class=\"uploader-content\">\r\n \r\n <!-- Overall Progress Bar -->\r\n @if (uploadQueue().length > 0) {\r\n <div class=\"overall-progress\">\r\n <div class=\"progress-bar\">\r\n <div class=\"progress-fill\" [style.width.%]=\"getOverallProgress()\"></div>\r\n </div>\r\n <div class=\"progress-text\">{{ getOverallProgress() }}%</div>\r\n </div>\r\n }\r\n\r\n <!-- Upload Queue -->\r\n @if (uploadQueue().length > 0) {\r\n <div class=\"upload-queue\">\r\n @for (upload of uploadQueue(); track upload.fileId) {\r\n <div class=\"upload-item\" [class]=\"getStatusClass(upload.status)\">\r\n \r\n <!-- File Info -->\r\n <div class=\"file-info\">\r\n <cide-ele-icon class=\"status-icon\" size=\"xs\">{{ getStatusIcon(upload.status) }}</cide-ele-icon>\r\n <div class=\"file-details\">\r\n <div class=\"file-name\">{{ upload.fileName }}</div>\r\n <div class=\"file-status\">\r\n @switch (upload.status) {\r\n @case ('pending') {\r\n <span class=\"text-yellow-600\">Waiting...</span>\r\n }\r\n @case ('uploading') {\r\n <span class=\"text-blue-600\">Uploading...</span>\r\n }\r\n @case ('completed') {\r\n <span class=\"text-green-600\">Completed</span>\r\n }\r\n @case ('error') {\r\n <span class=\"text-red-600\">{{ upload.error || 'Failed' }}</span>\r\n }\r\n @case ('cancelled') {\r\n <span class=\"text-gray-600\">Cancelled</span>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Progress Bar (for uploading files) -->\r\n @if (upload.status === 'uploading') {\r\n <div class=\"file-progress\">\r\n <div class=\"progress-bar\">\r\n <div class=\"progress-fill\" [style.width.%]=\"upload.progress\"></div>\r\n </div>\r\n <span class=\"progress-text\">{{ upload.progress }}%</span>\r\n </div>\r\n }\r\n\r\n <!-- Actions -->\r\n <div class=\"upload-actions\">\r\n @switch (upload.status) {\r\n @case ('pending') {\r\n <button class=\"action-btn cancel-btn\" (click)=\"cancelUpload(upload.fileId)\" title=\"Cancel\">\r\n <cide-ele-icon size=\"xs\">cancel</cide-ele-icon>\r\n </button>\r\n }\r\n @case ('uploading') {\r\n <button class=\"action-btn cancel-btn\" (click)=\"cancelUpload(upload.fileId)\" title=\"Cancel\">\r\n <cide-ele-icon size=\"xs\">cancel</cide-ele-icon>\r\n </button>\r\n }\r\n @case ('completed') {\r\n <button class=\"action-btn success-btn\" title=\"Completed\">\r\n <cide-ele-icon size=\"xs\">check_circle</cide-ele-icon>\r\n </button>\r\n }\r\n @case ('error') {\r\n <button class=\"action-btn retry-btn\" (click)=\"retryUpload(upload.fileId)\" title=\"Retry\">\r\n <cide-ele-icon size=\"xs\">refresh</cide-ele-icon>\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n</div>\r\n}\r\n", styles: [".floating-uploader{position:fixed;bottom:20px;right:20px;width:320px;max-height:500px;background:#fff;border-radius:12px;box-shadow:0 8px 32px #0000001f;border:1px solid rgba(0,0,0,.08);z-index:1000;overflow:hidden;transition:all .3s cubic-bezier(.4,0,.2,1);transform:translateY(0);opacity:1}.floating-uploader.animating{transition:all .3s cubic-bezier(.4,0,.2,1)}.floating-uploader.minimized .uploader-content{display:none}.floating-uploader.minimized .uploader-footer{border-top:none}.floating-uploader.uploading{border-color:#3b82f6;box-shadow:0 8px 32px #3b82f626}.floating-uploader .uploader-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#f8fafc;border-bottom:1px solid #e2e8f0}.floating-uploader .uploader-header .header-left{display:flex;align-items:center;gap:8px}.floating-uploader .uploader-header .header-left .upload-icon{display:flex;align-items:center;justify-content:center;width:24px;height:24px;background:#3b82f6;border-radius:6px;color:#fff}.floating-uploader .uploader-header .header-left .upload-info .upload-title{font-size:14px;font-weight:600;color:#1e293b;margin:0}.floating-uploader .uploader-header .header-left .upload-info .upload-summary{font-size:12px;color:#64748b;margin:0}.floating-uploader .uploader-header .header-actions{display:flex;gap:4px}.floating-uploader .uploader-header .header-actions .action-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;background:transparent;border-radius:4px;cursor:pointer;transition:background-color .2s;color:#64748b}.floating-uploader .uploader-header .header-actions .action-btn:hover{background:#e2e8f0;color:#1e293b}.floating-uploader .uploader-header .header-actions .action-btn.close-btn:hover{background:#fef2f2;color:#dc2626}.floating-uploader .uploader-content{max-height:400px;overflow-y:auto}.floating-uploader .uploader-content .overall-progress{padding:12px 16px;border-bottom:1px solid #e2e8f0}.floating-uploader .uploader-content .overall-progress .progress-bar{width:100%;height:4px;background:#e2e8f0;border-radius:2px;overflow:hidden;margin-bottom:4px}.floating-uploader .uploader-content .overall-progress .progress-bar .progress-fill{height:100%;background:#3b82f6;transition:width .3s ease}.floating-uploader .uploader-content .overall-progress .progress-text{font-size:12px;color:#64748b;text-align:center;display:block}.floating-uploader .uploader-content .upload-queue .upload-item{display:flex;align-items:center;padding:8px 16px;border-bottom:1px solid #f1f5f9;transition:background-color .2s}.floating-uploader .uploader-content .upload-queue .upload-item:last-child{border-bottom:none}.floating-uploader .uploader-content .upload-queue .upload-item.status-uploading{background:#f0f9ff}.floating-uploader .uploader-content .upload-queue .upload-item.status-completed{background:#f0fdf4}.floating-uploader .uploader-content .upload-queue .upload-item.status-error{background:#fef2f2}.floating-uploader .uploader-content .upload-queue .upload-item .file-info{display:flex;align-items:center;gap:8px;flex:1;min-width:0}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .status-icon{flex-shrink:0}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details{min-width:0;flex:1}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details .file-name{font-size:13px;font-weight:500;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details .file-status{font-size:11px;margin:0}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details .file-status span{font-weight:500}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress{display:flex;align-items:center;gap:8px;margin:0 8px;min-width:80px}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-bar{flex:1;height:3px;background:#e2e8f0;border-radius:2px;overflow:hidden}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-bar .progress-fill{height:100%;background:#3b82f6;transition:width .3s ease}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-text{font-size:10px;color:#64748b;min-width:24px;text-align:right}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions{display:flex;gap:4px}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;background:transparent;border-radius:4px;cursor:pointer;transition:all .2s;color:#64748b}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn:hover{background:#e2e8f0}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.cancel-btn:hover{background:#fef2f2;color:#dc2626}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.retry-btn:hover{background:#f0f9ff;color:#3b82f6}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.success-btn{color:#16a34a}.floating-uploader .uploader-content .hidden-uploader{display:none}.floating-uploader .uploader-footer{padding:8px 16px;background:#f8fafc;border-top:1px solid #e2e8f0}.floating-uploader .uploader-footer .footer-stats{display:flex;gap:12px;font-size:11px}.floating-uploader .uploader-footer .footer-stats .stat{display:flex;align-items:center;gap:4px;color:#64748b}.floating-uploader .uploader-footer .footer-stats .stat.uploading{color:#3b82f6}.floating-uploader .uploader-footer .footer-stats .stat.completed{color:#16a34a}.floating-uploader .uploader-footer .footer-stats .stat.failed{color:#dc2626}@media (max-width: 640px){.floating-uploader{bottom:10px;right:10px;left:10px;width:auto;max-width:none}}@media (prefers-color-scheme: dark){.floating-uploader{background:#1e293b;border-color:#334155;box-shadow:0 8px 32px #0000004d}.floating-uploader.uploading{border-color:#3b82f6;box-shadow:0 8px 32px #3b82f633}.floating-uploader .uploader-header{background:#334155;border-bottom-color:#475569}.floating-uploader .uploader-header .header-left .upload-icon{background:#3b82f6}.floating-uploader .uploader-header .header-left .upload-info .upload-title{color:#f1f5f9}.floating-uploader .uploader-header .header-left .upload-info .upload-summary,.floating-uploader .uploader-header .header-actions .action-btn{color:#94a3b8}.floating-uploader .uploader-header .header-actions .action-btn:hover{background:#475569;color:#f1f5f9}.floating-uploader .uploader-header .header-actions .action-btn.close-btn:hover{background:#7f1d1d;color:#fca5a5}.floating-uploader .uploader-content .overall-progress{border-bottom-color:#475569}.floating-uploader .uploader-content .overall-progress .progress-bar{background:#475569}.floating-uploader .uploader-content .overall-progress .progress-bar .progress-fill{background:#3b82f6}.floating-uploader .uploader-content .overall-progress .progress-text{color:#94a3b8}.floating-uploader .uploader-content .upload-queue .upload-item{border-bottom-color:#334155}.floating-uploader .uploader-content .upload-queue .upload-item.status-uploading{background:#1e3a8a}.floating-uploader .uploader-content .upload-queue .upload-item.status-completed{background:#14532d}.floating-uploader .uploader-content .upload-queue .upload-item.status-error{background:#7f1d1d}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details .file-name{color:#f1f5f9}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-bar{background:#475569}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-bar .progress-fill{background:#3b82f6}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-text,.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn{color:#94a3b8}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn:hover{background:#475569}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.cancel-btn:hover{background:#7f1d1d;color:#fca5a5}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.retry-btn:hover{background:#1e3a8a;color:#60a5fa}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.success-btn{color:#4ade80}.floating-uploader .uploader-footer{background:#334155;border-top-color:#475569}.floating-uploader .uploader-footer .footer-stats .stat{color:#94a3b8}.floating-uploader .uploader-footer .footer-stats .stat.uploading{color:#60a5fa}.floating-uploader .uploader-footer .footer-stats .stat.completed{color:#4ade80}.floating-uploader .uploader-footer .footer-stats .stat.failed{color:#fca5a5}}@keyframes slideInUp{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes slideOutDown{0%{transform:translateY(0);opacity:1}to{transform:translateY(100%);opacity:0}}.floating-uploader.animating{animation:slideInUp .3s cubic-bezier(.4,0,.2,1)}.floating-uploader.animating.hiding{animation:slideOutDown .3s cubic-bezier(.4,0,.2,1)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }] });
|
|
4856
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleGlobalFileUploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4857
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: CideEleGlobalFileUploaderComponent, isStandalone: true, selector: "cide-ele-global-file-uploader", inputs: { userId: "userId", multiple: "multiple", accept: "accept", maxFileSize: "maxFileSize", allowedTypes: "allowedTypes" }, outputs: { uploadComplete: "uploadComplete", uploadError: "uploadError", uploadCancelled: "uploadCancelled", allUploadsComplete: "allUploadsComplete" }, ngImport: i0, template: "<!-- Global File Uploader Component -->\r\n<div class=\"global-file-uploader\">\r\n\r\n <!-- Hidden file input -->\r\n <input id=\"global-file-input\" type=\"file\" [multiple]=\"multiple\" [accept]=\"accept\" \r\n (change)=\"onFileInputChange($event)\" style=\"display: none;\">\r\n\r\n <!-- Drag and Drop Zone -->\r\n <div class=\"upload-zone\" [class.drag-over]=\"isDragOver()\" (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\" (drop)=\"onDrop($event)\" (click)=\"triggerFileInput()\">\r\n\r\n <div class=\"upload-zone-content\">\r\n <cide-ele-icon name=\"cloud_upload\" class=\"upload-icon\" size=\"lg\">\r\n </cide-ele-icon>\r\n\r\n <h3 class=\"upload-title\">\r\n {{ isDragOver() ? 'Drop files here' : 'Upload Files' }}\r\n </h3>\r\n\r\n <p class=\"upload-subtitle\">\r\n {{ isDragOver() ? 'Release to upload' : 'Drag and drop files here or click to browse' }}\r\n </p>\r\n\r\n <button cideEleButton cideEleButton variant=\"outline\" size=\"sm\"\r\n (click)=\"triggerFileInput(); $event.stopPropagation()\">\r\n <cide-ele-icon name=\"add\" size=\"sm\"></cide-ele-icon>\r\n Choose Files\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Upload Queue -->\r\n @if (hasUploads()) {\r\n <div class=\"upload-queue\">\r\n <div class=\"queue-header\">\r\n <h4 class=\"queue-title\">Upload Queue</h4>\r\n <div class=\"queue-actions\">\r\n @if (completedUploads().length > 0) {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"clearCompleted()\">\r\n Clear Completed\r\n </button>\r\n }\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"clearAll()\">\r\n Clear All\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"queue-items\">\r\n @for (upload of uploadQueue(); track upload.fileId) {\r\n <div class=\"upload-item\" [class]=\"getStatusClass(upload.status)\">\r\n\r\n <!-- File Info -->\r\n <div class=\"file-info\">\r\n <cide-ele-icon class=\"status-icon\" size=\"sm\">\r\n {{getStatusIcon(upload.status)}}\r\n </cide-ele-icon>\r\n\r\n <div class=\"file-details\">\r\n <div class=\"file-name\">{{ upload.fileName }}</div>\r\n @if (upload.file) {\r\n <div class=\"file-size\">{{ getFileSizeDisplay(upload.file) }}</div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Progress Bar -->\r\n @if (upload.status === 'uploading') {\r\n <div class=\"progress-container\">\r\n <div class=\"progress-bar\">\r\n <div class=\"progress-fill\" [style.width.%]=\"upload.progress\">\r\n </div>\r\n </div>\r\n <span class=\"progress-text\">{{ upload.progress }}%</span>\r\n </div>\r\n }\r\n\r\n <!-- Status Text -->\r\n @if (upload.status !== 'uploading') {\r\n <div class=\"status-text\">\r\n @switch (upload.status) {\r\n @case ('pending') {\r\n <span class=\"text-yellow-600\">Waiting...</span>\r\n }\r\n @case ('completed') {\r\n <span class=\"text-green-600\">Uploaded successfully</span>\r\n }\r\n @case ('error') {\r\n <span class=\"text-red-600\">{{ upload.error || 'Upload failed' }}</span>\r\n }\r\n @case ('cancelled') {\r\n <span class=\"text-gray-600\">Cancelled</span>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Actions -->\r\n <div class=\"upload-actions\">\r\n @switch (upload.status) {\r\n @case ('pending') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"cancelUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"cancel\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n @case ('uploading') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"cancelUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"cancel\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n @case ('completed') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"removeUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"close\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n @case ('error') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"retryUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"refresh\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"removeUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"close\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n @case ('cancelled') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"removeUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"close\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Queue Summary -->\r\n <div class=\"queue-summary\">\r\n <div class=\"summary-stats\">\r\n <span class=\"stat\">\r\n <cide-ele-icon name=\"schedule\" size=\"xs\"></cide-ele-icon>\r\n {{ pendingUploads().length }} pending\r\n </span>\r\n <span class=\"stat\">\r\n <cide-ele-icon name=\"cloud_upload\" size=\"xs\"></cide-ele-icon>\r\n {{ activeUploads().length }} uploading\r\n </span>\r\n <span class=\"stat\">\r\n <cide-ele-icon name=\"check_circle\" size=\"xs\"></cide-ele-icon>\r\n {{ completedUploads().length }} completed\r\n </span>\r\n @if (failedUploads().length > 0) {\r\n <span class=\"stat error\">\r\n <cide-ele-icon name=\"error\" size=\"xs\"></cide-ele-icon>\r\n {{ failedUploads().length }} failed\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n\r\n", styles: [".global-file-uploader{@apply tw-w-full tw-max-w-2xl tw-mx-auto tw-p-4;}.global-file-uploader .upload-zone{@apply tw-border-2 tw-border-dashed tw-border-gray-300 tw-rounded-lg tw-p-8 tw-text-center tw-cursor-pointer tw-transition-all tw-duration-200 tw-ease-in-out;@apply hover:tw-border-blue-400 hover:tw-bg-blue-50;}.global-file-uploader .upload-zone.drag-over{@apply tw-border-blue-500 tw-bg-blue-100 tw-scale-105;}.global-file-uploader .upload-zone .upload-zone-content{@apply tw-space-y-4;}.global-file-uploader .upload-zone .upload-zone-content .upload-icon{@apply tw-text-gray-400 tw-mx-auto;}.global-file-uploader .upload-zone .upload-zone-content .upload-title{@apply tw-text-xl tw-font-semibold tw-text-gray-700 tw-m-0;}.global-file-uploader .upload-zone .upload-zone-content .upload-subtitle{@apply tw-text-sm tw-text-gray-500 tw-m-0;}.global-file-uploader .upload-queue{@apply tw-mt-6 tw-bg-white tw-border tw-border-gray-200 tw-rounded-lg tw-shadow-sm;}.global-file-uploader .upload-queue .queue-header{@apply tw-flex tw-justify-between tw-items-center tw-p-4 tw-border-b tw-border-gray-200;}.global-file-uploader .upload-queue .queue-header .queue-title{@apply tw-text-lg tw-font-medium tw-text-gray-700 tw-m-0;}.global-file-uploader .upload-queue .queue-header .queue-actions{@apply tw-flex tw-space-x-2;}.global-file-uploader .upload-queue .queue-items{@apply tw-max-h-96 tw-overflow-y-auto;}.global-file-uploader .upload-queue .queue-items .upload-item{@apply tw-flex tw-items-center tw-justify-between tw-p-4 tw-border-b tw-border-gray-100 tw-transition-colors tw-duration-200;}.global-file-uploader .upload-queue .queue-items .upload-item:last-child{@apply tw-border-b-0;}.global-file-uploader .upload-queue .queue-items .upload-item.status-pending{@apply tw-bg-yellow-50;}.global-file-uploader .upload-queue .queue-items .upload-item.status-uploading{@apply tw-bg-blue-50;}.global-file-uploader .upload-queue .queue-items .upload-item.status-completed{@apply tw-bg-green-50;}.global-file-uploader .upload-queue .queue-items .upload-item.status-error{@apply tw-bg-red-50;}.global-file-uploader .upload-queue .queue-items .upload-item.status-cancelled{@apply tw-bg-gray-50;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info{@apply tw-flex tw-items-center tw-flex-1 tw-min-w-0;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .status-icon{@apply tw-mr-3 tw-flex-shrink-0;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details{@apply tw-min-w-0 tw-flex-1;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details .file-name{@apply tw-text-sm tw-font-medium tw-text-gray-700 tw-truncate;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details .file-size{@apply tw-text-xs tw-text-gray-500 tw-mt-1;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container{@apply tw-flex tw-items-center tw-space-x-3 tw-flex-1 tw-max-w-xs tw-mx-4;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-bar{@apply tw-flex-1 tw-h-2 tw-bg-gray-200 tw-rounded-full tw-overflow-hidden;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-bar .progress-fill{@apply tw-h-full tw-bg-blue-500 tw-transition-all tw-duration-300 tw-ease-out;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-text{@apply tw-text-xs tw-font-medium tw-text-gray-600 tw-min-w-12 tw-text-right;}.global-file-uploader .upload-queue .queue-items .upload-item .status-text{@apply tw-flex-1 tw-text-sm tw-text-center;}.global-file-uploader .upload-queue .queue-items .upload-item .status-text span{@apply tw-font-medium;}.global-file-uploader .upload-queue .queue-items .upload-item .upload-actions{@apply tw-flex tw-space-x-1 tw-ml-4;}.global-file-uploader .upload-queue .queue-summary{@apply tw-p-4 tw-bg-gray-50 tw-border-t tw-border-gray-200;}.global-file-uploader .upload-queue .queue-summary .summary-stats{@apply tw-flex tw-flex-wrap tw-gap-4 tw-text-sm tw-text-gray-600;}.global-file-uploader .upload-queue .queue-summary .summary-stats .stat{@apply tw-flex tw-items-center tw-space-x-1;}.global-file-uploader .upload-queue .queue-summary .summary-stats .stat.error{@apply tw-text-red-600;}@media (max-width: 640px){.global-file-uploader{@apply tw-p-2;}.global-file-uploader .upload-zone{@apply tw-p-6;}.global-file-uploader .upload-zone .upload-zone-content .upload-title{@apply tw-text-lg;}.global-file-uploader .upload-zone .upload-zone-content .upload-subtitle{@apply tw-text-xs;}.global-file-uploader .upload-queue .queue-header{@apply tw-p-3 tw-flex-col tw-space-y-2 tw-items-start;}.global-file-uploader .upload-queue .queue-header .queue-actions{@apply tw-w-full tw-justify-end;}.global-file-uploader .upload-queue .queue-items .upload-item{@apply tw-p-3 tw-flex-col tw-space-y-3 tw-items-start;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info{@apply tw-w-full;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container{@apply tw-w-full tw-mx-0;}.global-file-uploader .upload-queue .queue-items .upload-item .status-text{@apply tw-w-full tw-text-left;}.global-file-uploader .upload-queue .queue-items .upload-item .upload-actions{@apply tw-w-full tw-justify-end tw-ml-0;}}@media (prefers-color-scheme: dark){.global-file-uploader .upload-zone{@apply tw-border-gray-600 hover:tw-border-blue-400 hover:tw-bg-gray-800;}.global-file-uploader .upload-zone.drag-over{@apply tw-bg-blue-900;}.global-file-uploader .upload-zone .upload-zone-content .upload-icon{@apply tw-text-gray-500;}.global-file-uploader .upload-zone .upload-zone-content .upload-title{@apply tw-text-gray-200;}.global-file-uploader .upload-zone .upload-zone-content .upload-subtitle{@apply tw-text-gray-400;}.global-file-uploader .upload-queue{@apply tw-bg-gray-800 tw-border-gray-700;}.global-file-uploader .upload-queue .queue-header{@apply tw-border-gray-700;}.global-file-uploader .upload-queue .queue-header .queue-title{@apply tw-text-gray-200;}.global-file-uploader .upload-queue .queue-items .upload-item{@apply tw-border-gray-700;}.global-file-uploader .upload-queue .queue-items .upload-item.status-pending{@apply tw-bg-yellow-900;}.global-file-uploader .upload-queue .queue-items .upload-item.status-uploading{@apply tw-bg-blue-900;}.global-file-uploader .upload-queue .queue-items .upload-item.status-completed{@apply tw-bg-green-900;}.global-file-uploader .upload-queue .queue-items .upload-item.status-error{@apply tw-bg-red-900;}.global-file-uploader .upload-queue .queue-items .upload-item.status-cancelled{@apply tw-bg-gray-700;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details .file-name{@apply tw-text-gray-200;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details .file-size{@apply tw-text-gray-400;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-bar{@apply tw-bg-gray-600;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-text{@apply tw-text-gray-300;}.global-file-uploader .upload-queue .queue-summary{@apply tw-bg-gray-700 tw-border-gray-600;}.global-file-uploader .upload-queue .queue-summary .summary-stats{@apply tw-text-gray-300;}.global-file-uploader .upload-queue .queue-summary .summary-stats .stat.error{@apply tw-text-red-400;}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CideEleButtonComponent, selector: "button[cideEleButton], a[cideEleButton]", inputs: ["label", "variant", "size", "type", "shape", "elevation", "disabled", "id", "loading", "fullWidth", "leftIcon", "rightIcon", "customClass", "tooltip", "ariaLabel", "testId", "routerLink", "routerExtras", "preventDoubleClick", "animated"], outputs: ["btnClick", "doubleClick"] }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }] });
|
|
4852
4858
|
}
|
|
4853
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
4859
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleGlobalFileUploaderComponent, decorators: [{
|
|
4854
4860
|
type: Component,
|
|
4855
|
-
args: [{ selector: 'cide-ele-
|
|
4861
|
+
args: [{ selector: 'cide-ele-global-file-uploader', standalone: true, imports: [
|
|
4856
4862
|
CommonModule,
|
|
4863
|
+
CideEleButtonComponent,
|
|
4857
4864
|
CideIconComponent
|
|
4858
|
-
], template: "<!-- Floating File Uploader Container -->\r\n@if (isVisible()) {\r\n<div class=\"floating-uploader\" \r\n [class.minimized]=\"isMinimized()\" \r\n [class.animating]=\"isAnimating()\">\r\n\r\n <!-- Header -->\r\n <div class=\"uploader-header\">\r\n <div class=\"header-left\">\r\n <div class=\"upload-icon\">\r\n <cide-ele-icon size=\"sm\">cloud_upload</cide-ele-icon>\r\n </div>\r\n <div class=\"upload-info\">\r\n <div class=\"upload-title\">File Upload</div>\r\n <div class=\"upload-summary\">{{ getUploadSummary() }}</div>\r\n </div>\r\n </div>\r\n \r\n <div class=\"header-actions\">\r\n <button class=\"action-btn minimize-btn\" (click)=\"toggleMinimize()\" [title]=\"isMinimized() ? 'Expand' : 'Minimize'\">\r\n <cide-ele-icon size=\"xs\">{{ isMinimized() ? 'expand_more' : 'expand_less' }}</cide-ele-icon>\r\n </button>\r\n <button class=\"action-btn close-btn\" (click)=\"close()\" title=\"Close\">\r\n <cide-ele-icon size=\"xs\">close</cide-ele-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Content (hidden when minimized) -->\r\n @if (!isMinimized()) {\r\n <div class=\"uploader-content\">\r\n \r\n <!-- Overall Progress Bar -->\r\n @if (uploadQueue().length > 0) {\r\n <div class=\"overall-progress\">\r\n <div class=\"progress-bar\">\r\n <div class=\"progress-fill\" [style.width.%]=\"getOverallProgress()\"></div>\r\n </div>\r\n <div class=\"progress-text\">{{ getOverallProgress() }}%</div>\r\n </div>\r\n }\r\n\r\n <!-- Upload Queue -->\r\n @if (uploadQueue().length > 0) {\r\n <div class=\"upload-queue\">\r\n @for (upload of uploadQueue(); track upload.fileId) {\r\n <div class=\"upload-item\" [class]=\"getStatusClass(upload.status)\">\r\n \r\n <!-- File Info -->\r\n <div class=\"file-info\">\r\n <cide-ele-icon class=\"status-icon\" size=\"xs\">{{ getStatusIcon(upload.status) }}</cide-ele-icon>\r\n <div class=\"file-details\">\r\n <div class=\"file-name\">{{ upload.fileName }}</div>\r\n <div class=\"file-status\">\r\n @switch (upload.status) {\r\n @case ('pending') {\r\n <span class=\"text-yellow-600\">Waiting...</span>\r\n }\r\n @case ('uploading') {\r\n <span class=\"text-blue-600\">Uploading...</span>\r\n }\r\n @case ('completed') {\r\n <span class=\"text-green-600\">Completed</span>\r\n }\r\n @case ('error') {\r\n <span class=\"text-red-600\">{{ upload.error || 'Failed' }}</span>\r\n }\r\n @case ('cancelled') {\r\n <span class=\"text-gray-600\">Cancelled</span>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Progress Bar (for uploading files) -->\r\n @if (upload.status === 'uploading') {\r\n <div class=\"file-progress\">\r\n <div class=\"progress-bar\">\r\n <div class=\"progress-fill\" [style.width.%]=\"upload.progress\"></div>\r\n </div>\r\n <span class=\"progress-text\">{{ upload.progress }}%</span>\r\n </div>\r\n }\r\n\r\n <!-- Actions -->\r\n <div class=\"upload-actions\">\r\n @switch (upload.status) {\r\n @case ('pending') {\r\n <button class=\"action-btn cancel-btn\" (click)=\"cancelUpload(upload.fileId)\" title=\"Cancel\">\r\n <cide-ele-icon size=\"xs\">cancel</cide-ele-icon>\r\n </button>\r\n }\r\n @case ('uploading') {\r\n <button class=\"action-btn cancel-btn\" (click)=\"cancelUpload(upload.fileId)\" title=\"Cancel\">\r\n <cide-ele-icon size=\"xs\">cancel</cide-ele-icon>\r\n </button>\r\n }\r\n @case ('completed') {\r\n <button class=\"action-btn success-btn\" title=\"Completed\">\r\n <cide-ele-icon size=\"xs\">check_circle</cide-ele-icon>\r\n </button>\r\n }\r\n @case ('error') {\r\n <button class=\"action-btn retry-btn\" (click)=\"retryUpload(upload.fileId)\" title=\"Retry\">\r\n <cide-ele-icon size=\"xs\">refresh</cide-ele-icon>\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n</div>\r\n}\r\n", styles: [".floating-uploader{position:fixed;bottom:20px;right:20px;width:320px;max-height:500px;background:#fff;border-radius:12px;box-shadow:0 8px 32px #0000001f;border:1px solid rgba(0,0,0,.08);z-index:1000;overflow:hidden;transition:all .3s cubic-bezier(.4,0,.2,1);transform:translateY(0);opacity:1}.floating-uploader.animating{transition:all .3s cubic-bezier(.4,0,.2,1)}.floating-uploader.minimized .uploader-content{display:none}.floating-uploader.minimized .uploader-footer{border-top:none}.floating-uploader.uploading{border-color:#3b82f6;box-shadow:0 8px 32px #3b82f626}.floating-uploader .uploader-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#f8fafc;border-bottom:1px solid #e2e8f0}.floating-uploader .uploader-header .header-left{display:flex;align-items:center;gap:8px}.floating-uploader .uploader-header .header-left .upload-icon{display:flex;align-items:center;justify-content:center;width:24px;height:24px;background:#3b82f6;border-radius:6px;color:#fff}.floating-uploader .uploader-header .header-left .upload-info .upload-title{font-size:14px;font-weight:600;color:#1e293b;margin:0}.floating-uploader .uploader-header .header-left .upload-info .upload-summary{font-size:12px;color:#64748b;margin:0}.floating-uploader .uploader-header .header-actions{display:flex;gap:4px}.floating-uploader .uploader-header .header-actions .action-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;background:transparent;border-radius:4px;cursor:pointer;transition:background-color .2s;color:#64748b}.floating-uploader .uploader-header .header-actions .action-btn:hover{background:#e2e8f0;color:#1e293b}.floating-uploader .uploader-header .header-actions .action-btn.close-btn:hover{background:#fef2f2;color:#dc2626}.floating-uploader .uploader-content{max-height:400px;overflow-y:auto}.floating-uploader .uploader-content .overall-progress{padding:12px 16px;border-bottom:1px solid #e2e8f0}.floating-uploader .uploader-content .overall-progress .progress-bar{width:100%;height:4px;background:#e2e8f0;border-radius:2px;overflow:hidden;margin-bottom:4px}.floating-uploader .uploader-content .overall-progress .progress-bar .progress-fill{height:100%;background:#3b82f6;transition:width .3s ease}.floating-uploader .uploader-content .overall-progress .progress-text{font-size:12px;color:#64748b;text-align:center;display:block}.floating-uploader .uploader-content .upload-queue .upload-item{display:flex;align-items:center;padding:8px 16px;border-bottom:1px solid #f1f5f9;transition:background-color .2s}.floating-uploader .uploader-content .upload-queue .upload-item:last-child{border-bottom:none}.floating-uploader .uploader-content .upload-queue .upload-item.status-uploading{background:#f0f9ff}.floating-uploader .uploader-content .upload-queue .upload-item.status-completed{background:#f0fdf4}.floating-uploader .uploader-content .upload-queue .upload-item.status-error{background:#fef2f2}.floating-uploader .uploader-content .upload-queue .upload-item .file-info{display:flex;align-items:center;gap:8px;flex:1;min-width:0}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .status-icon{flex-shrink:0}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details{min-width:0;flex:1}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details .file-name{font-size:13px;font-weight:500;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details .file-status{font-size:11px;margin:0}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details .file-status span{font-weight:500}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress{display:flex;align-items:center;gap:8px;margin:0 8px;min-width:80px}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-bar{flex:1;height:3px;background:#e2e8f0;border-radius:2px;overflow:hidden}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-bar .progress-fill{height:100%;background:#3b82f6;transition:width .3s ease}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-text{font-size:10px;color:#64748b;min-width:24px;text-align:right}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions{display:flex;gap:4px}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;background:transparent;border-radius:4px;cursor:pointer;transition:all .2s;color:#64748b}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn:hover{background:#e2e8f0}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.cancel-btn:hover{background:#fef2f2;color:#dc2626}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.retry-btn:hover{background:#f0f9ff;color:#3b82f6}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.success-btn{color:#16a34a}.floating-uploader .uploader-content .hidden-uploader{display:none}.floating-uploader .uploader-footer{padding:8px 16px;background:#f8fafc;border-top:1px solid #e2e8f0}.floating-uploader .uploader-footer .footer-stats{display:flex;gap:12px;font-size:11px}.floating-uploader .uploader-footer .footer-stats .stat{display:flex;align-items:center;gap:4px;color:#64748b}.floating-uploader .uploader-footer .footer-stats .stat.uploading{color:#3b82f6}.floating-uploader .uploader-footer .footer-stats .stat.completed{color:#16a34a}.floating-uploader .uploader-footer .footer-stats .stat.failed{color:#dc2626}@media (max-width: 640px){.floating-uploader{bottom:10px;right:10px;left:10px;width:auto;max-width:none}}@media (prefers-color-scheme: dark){.floating-uploader{background:#1e293b;border-color:#334155;box-shadow:0 8px 32px #0000004d}.floating-uploader.uploading{border-color:#3b82f6;box-shadow:0 8px 32px #3b82f633}.floating-uploader .uploader-header{background:#334155;border-bottom-color:#475569}.floating-uploader .uploader-header .header-left .upload-icon{background:#3b82f6}.floating-uploader .uploader-header .header-left .upload-info .upload-title{color:#f1f5f9}.floating-uploader .uploader-header .header-left .upload-info .upload-summary,.floating-uploader .uploader-header .header-actions .action-btn{color:#94a3b8}.floating-uploader .uploader-header .header-actions .action-btn:hover{background:#475569;color:#f1f5f9}.floating-uploader .uploader-header .header-actions .action-btn.close-btn:hover{background:#7f1d1d;color:#fca5a5}.floating-uploader .uploader-content .overall-progress{border-bottom-color:#475569}.floating-uploader .uploader-content .overall-progress .progress-bar{background:#475569}.floating-uploader .uploader-content .overall-progress .progress-bar .progress-fill{background:#3b82f6}.floating-uploader .uploader-content .overall-progress .progress-text{color:#94a3b8}.floating-uploader .uploader-content .upload-queue .upload-item{border-bottom-color:#334155}.floating-uploader .uploader-content .upload-queue .upload-item.status-uploading{background:#1e3a8a}.floating-uploader .uploader-content .upload-queue .upload-item.status-completed{background:#14532d}.floating-uploader .uploader-content .upload-queue .upload-item.status-error{background:#7f1d1d}.floating-uploader .uploader-content .upload-queue .upload-item .file-info .file-details .file-name{color:#f1f5f9}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-bar{background:#475569}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-bar .progress-fill{background:#3b82f6}.floating-uploader .uploader-content .upload-queue .upload-item .file-progress .progress-text,.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn{color:#94a3b8}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn:hover{background:#475569}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.cancel-btn:hover{background:#7f1d1d;color:#fca5a5}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.retry-btn:hover{background:#1e3a8a;color:#60a5fa}.floating-uploader .uploader-content .upload-queue .upload-item .upload-actions .action-btn.success-btn{color:#4ade80}.floating-uploader .uploader-footer{background:#334155;border-top-color:#475569}.floating-uploader .uploader-footer .footer-stats .stat{color:#94a3b8}.floating-uploader .uploader-footer .footer-stats .stat.uploading{color:#60a5fa}.floating-uploader .uploader-footer .footer-stats .stat.completed{color:#4ade80}.floating-uploader .uploader-footer .footer-stats .stat.failed{color:#fca5a5}}@keyframes slideInUp{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes slideOutDown{0%{transform:translateY(0);opacity:1}to{transform:translateY(100%);opacity:0}}.floating-uploader.animating{animation:slideInUp .3s cubic-bezier(.4,0,.2,1)}.floating-uploader.animating.hiding{animation:slideOutDown .3s cubic-bezier(.4,0,.2,1)}\n"] }]
|
|
4859
|
-
}], ctorParameters: () => []
|
|
4865
|
+
], template: "<!-- Global File Uploader Component -->\r\n<div class=\"global-file-uploader\">\r\n\r\n <!-- Hidden file input -->\r\n <input id=\"global-file-input\" type=\"file\" [multiple]=\"multiple\" [accept]=\"accept\" \r\n (change)=\"onFileInputChange($event)\" style=\"display: none;\">\r\n\r\n <!-- Drag and Drop Zone -->\r\n <div class=\"upload-zone\" [class.drag-over]=\"isDragOver()\" (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\" (drop)=\"onDrop($event)\" (click)=\"triggerFileInput()\">\r\n\r\n <div class=\"upload-zone-content\">\r\n <cide-ele-icon name=\"cloud_upload\" class=\"upload-icon\" size=\"lg\">\r\n </cide-ele-icon>\r\n\r\n <h3 class=\"upload-title\">\r\n {{ isDragOver() ? 'Drop files here' : 'Upload Files' }}\r\n </h3>\r\n\r\n <p class=\"upload-subtitle\">\r\n {{ isDragOver() ? 'Release to upload' : 'Drag and drop files here or click to browse' }}\r\n </p>\r\n\r\n <button cideEleButton cideEleButton variant=\"outline\" size=\"sm\"\r\n (click)=\"triggerFileInput(); $event.stopPropagation()\">\r\n <cide-ele-icon name=\"add\" size=\"sm\"></cide-ele-icon>\r\n Choose Files\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Upload Queue -->\r\n @if (hasUploads()) {\r\n <div class=\"upload-queue\">\r\n <div class=\"queue-header\">\r\n <h4 class=\"queue-title\">Upload Queue</h4>\r\n <div class=\"queue-actions\">\r\n @if (completedUploads().length > 0) {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"clearCompleted()\">\r\n Clear Completed\r\n </button>\r\n }\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"clearAll()\">\r\n Clear All\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"queue-items\">\r\n @for (upload of uploadQueue(); track upload.fileId) {\r\n <div class=\"upload-item\" [class]=\"getStatusClass(upload.status)\">\r\n\r\n <!-- File Info -->\r\n <div class=\"file-info\">\r\n <cide-ele-icon class=\"status-icon\" size=\"sm\">\r\n {{getStatusIcon(upload.status)}}\r\n </cide-ele-icon>\r\n\r\n <div class=\"file-details\">\r\n <div class=\"file-name\">{{ upload.fileName }}</div>\r\n @if (upload.file) {\r\n <div class=\"file-size\">{{ getFileSizeDisplay(upload.file) }}</div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Progress Bar -->\r\n @if (upload.status === 'uploading') {\r\n <div class=\"progress-container\">\r\n <div class=\"progress-bar\">\r\n <div class=\"progress-fill\" [style.width.%]=\"upload.progress\">\r\n </div>\r\n </div>\r\n <span class=\"progress-text\">{{ upload.progress }}%</span>\r\n </div>\r\n }\r\n\r\n <!-- Status Text -->\r\n @if (upload.status !== 'uploading') {\r\n <div class=\"status-text\">\r\n @switch (upload.status) {\r\n @case ('pending') {\r\n <span class=\"text-yellow-600\">Waiting...</span>\r\n }\r\n @case ('completed') {\r\n <span class=\"text-green-600\">Uploaded successfully</span>\r\n }\r\n @case ('error') {\r\n <span class=\"text-red-600\">{{ upload.error || 'Upload failed' }}</span>\r\n }\r\n @case ('cancelled') {\r\n <span class=\"text-gray-600\">Cancelled</span>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Actions -->\r\n <div class=\"upload-actions\">\r\n @switch (upload.status) {\r\n @case ('pending') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"cancelUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"cancel\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n @case ('uploading') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"cancelUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"cancel\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n @case ('completed') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"removeUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"close\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n @case ('error') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"retryUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"refresh\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"removeUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"close\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n @case ('cancelled') {\r\n <button cideEleButton variant=\"text\" size=\"sm\" (click)=\"removeUpload(upload.fileId)\">\r\n <cide-ele-icon name=\"close\" size=\"xs\"></cide-ele-icon>\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Queue Summary -->\r\n <div class=\"queue-summary\">\r\n <div class=\"summary-stats\">\r\n <span class=\"stat\">\r\n <cide-ele-icon name=\"schedule\" size=\"xs\"></cide-ele-icon>\r\n {{ pendingUploads().length }} pending\r\n </span>\r\n <span class=\"stat\">\r\n <cide-ele-icon name=\"cloud_upload\" size=\"xs\"></cide-ele-icon>\r\n {{ activeUploads().length }} uploading\r\n </span>\r\n <span class=\"stat\">\r\n <cide-ele-icon name=\"check_circle\" size=\"xs\"></cide-ele-icon>\r\n {{ completedUploads().length }} completed\r\n </span>\r\n @if (failedUploads().length > 0) {\r\n <span class=\"stat error\">\r\n <cide-ele-icon name=\"error\" size=\"xs\"></cide-ele-icon>\r\n {{ failedUploads().length }} failed\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n\r\n", styles: [".global-file-uploader{@apply tw-w-full tw-max-w-2xl tw-mx-auto tw-p-4;}.global-file-uploader .upload-zone{@apply tw-border-2 tw-border-dashed tw-border-gray-300 tw-rounded-lg tw-p-8 tw-text-center tw-cursor-pointer tw-transition-all tw-duration-200 tw-ease-in-out;@apply hover:tw-border-blue-400 hover:tw-bg-blue-50;}.global-file-uploader .upload-zone.drag-over{@apply tw-border-blue-500 tw-bg-blue-100 tw-scale-105;}.global-file-uploader .upload-zone .upload-zone-content{@apply tw-space-y-4;}.global-file-uploader .upload-zone .upload-zone-content .upload-icon{@apply tw-text-gray-400 tw-mx-auto;}.global-file-uploader .upload-zone .upload-zone-content .upload-title{@apply tw-text-xl tw-font-semibold tw-text-gray-700 tw-m-0;}.global-file-uploader .upload-zone .upload-zone-content .upload-subtitle{@apply tw-text-sm tw-text-gray-500 tw-m-0;}.global-file-uploader .upload-queue{@apply tw-mt-6 tw-bg-white tw-border tw-border-gray-200 tw-rounded-lg tw-shadow-sm;}.global-file-uploader .upload-queue .queue-header{@apply tw-flex tw-justify-between tw-items-center tw-p-4 tw-border-b tw-border-gray-200;}.global-file-uploader .upload-queue .queue-header .queue-title{@apply tw-text-lg tw-font-medium tw-text-gray-700 tw-m-0;}.global-file-uploader .upload-queue .queue-header .queue-actions{@apply tw-flex tw-space-x-2;}.global-file-uploader .upload-queue .queue-items{@apply tw-max-h-96 tw-overflow-y-auto;}.global-file-uploader .upload-queue .queue-items .upload-item{@apply tw-flex tw-items-center tw-justify-between tw-p-4 tw-border-b tw-border-gray-100 tw-transition-colors tw-duration-200;}.global-file-uploader .upload-queue .queue-items .upload-item:last-child{@apply tw-border-b-0;}.global-file-uploader .upload-queue .queue-items .upload-item.status-pending{@apply tw-bg-yellow-50;}.global-file-uploader .upload-queue .queue-items .upload-item.status-uploading{@apply tw-bg-blue-50;}.global-file-uploader .upload-queue .queue-items .upload-item.status-completed{@apply tw-bg-green-50;}.global-file-uploader .upload-queue .queue-items .upload-item.status-error{@apply tw-bg-red-50;}.global-file-uploader .upload-queue .queue-items .upload-item.status-cancelled{@apply tw-bg-gray-50;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info{@apply tw-flex tw-items-center tw-flex-1 tw-min-w-0;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .status-icon{@apply tw-mr-3 tw-flex-shrink-0;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details{@apply tw-min-w-0 tw-flex-1;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details .file-name{@apply tw-text-sm tw-font-medium tw-text-gray-700 tw-truncate;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details .file-size{@apply tw-text-xs tw-text-gray-500 tw-mt-1;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container{@apply tw-flex tw-items-center tw-space-x-3 tw-flex-1 tw-max-w-xs tw-mx-4;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-bar{@apply tw-flex-1 tw-h-2 tw-bg-gray-200 tw-rounded-full tw-overflow-hidden;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-bar .progress-fill{@apply tw-h-full tw-bg-blue-500 tw-transition-all tw-duration-300 tw-ease-out;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-text{@apply tw-text-xs tw-font-medium tw-text-gray-600 tw-min-w-12 tw-text-right;}.global-file-uploader .upload-queue .queue-items .upload-item .status-text{@apply tw-flex-1 tw-text-sm tw-text-center;}.global-file-uploader .upload-queue .queue-items .upload-item .status-text span{@apply tw-font-medium;}.global-file-uploader .upload-queue .queue-items .upload-item .upload-actions{@apply tw-flex tw-space-x-1 tw-ml-4;}.global-file-uploader .upload-queue .queue-summary{@apply tw-p-4 tw-bg-gray-50 tw-border-t tw-border-gray-200;}.global-file-uploader .upload-queue .queue-summary .summary-stats{@apply tw-flex tw-flex-wrap tw-gap-4 tw-text-sm tw-text-gray-600;}.global-file-uploader .upload-queue .queue-summary .summary-stats .stat{@apply tw-flex tw-items-center tw-space-x-1;}.global-file-uploader .upload-queue .queue-summary .summary-stats .stat.error{@apply tw-text-red-600;}@media (max-width: 640px){.global-file-uploader{@apply tw-p-2;}.global-file-uploader .upload-zone{@apply tw-p-6;}.global-file-uploader .upload-zone .upload-zone-content .upload-title{@apply tw-text-lg;}.global-file-uploader .upload-zone .upload-zone-content .upload-subtitle{@apply tw-text-xs;}.global-file-uploader .upload-queue .queue-header{@apply tw-p-3 tw-flex-col tw-space-y-2 tw-items-start;}.global-file-uploader .upload-queue .queue-header .queue-actions{@apply tw-w-full tw-justify-end;}.global-file-uploader .upload-queue .queue-items .upload-item{@apply tw-p-3 tw-flex-col tw-space-y-3 tw-items-start;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info{@apply tw-w-full;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container{@apply tw-w-full tw-mx-0;}.global-file-uploader .upload-queue .queue-items .upload-item .status-text{@apply tw-w-full tw-text-left;}.global-file-uploader .upload-queue .queue-items .upload-item .upload-actions{@apply tw-w-full tw-justify-end tw-ml-0;}}@media (prefers-color-scheme: dark){.global-file-uploader .upload-zone{@apply tw-border-gray-600 hover:tw-border-blue-400 hover:tw-bg-gray-800;}.global-file-uploader .upload-zone.drag-over{@apply tw-bg-blue-900;}.global-file-uploader .upload-zone .upload-zone-content .upload-icon{@apply tw-text-gray-500;}.global-file-uploader .upload-zone .upload-zone-content .upload-title{@apply tw-text-gray-200;}.global-file-uploader .upload-zone .upload-zone-content .upload-subtitle{@apply tw-text-gray-400;}.global-file-uploader .upload-queue{@apply tw-bg-gray-800 tw-border-gray-700;}.global-file-uploader .upload-queue .queue-header{@apply tw-border-gray-700;}.global-file-uploader .upload-queue .queue-header .queue-title{@apply tw-text-gray-200;}.global-file-uploader .upload-queue .queue-items .upload-item{@apply tw-border-gray-700;}.global-file-uploader .upload-queue .queue-items .upload-item.status-pending{@apply tw-bg-yellow-900;}.global-file-uploader .upload-queue .queue-items .upload-item.status-uploading{@apply tw-bg-blue-900;}.global-file-uploader .upload-queue .queue-items .upload-item.status-completed{@apply tw-bg-green-900;}.global-file-uploader .upload-queue .queue-items .upload-item.status-error{@apply tw-bg-red-900;}.global-file-uploader .upload-queue .queue-items .upload-item.status-cancelled{@apply tw-bg-gray-700;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details .file-name{@apply tw-text-gray-200;}.global-file-uploader .upload-queue .queue-items .upload-item .file-info .file-details .file-size{@apply tw-text-gray-400;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-bar{@apply tw-bg-gray-600;}.global-file-uploader .upload-queue .queue-items .upload-item .progress-container .progress-text{@apply tw-text-gray-300;}.global-file-uploader .upload-queue .queue-summary{@apply tw-bg-gray-700 tw-border-gray-600;}.global-file-uploader .upload-queue .queue-summary .summary-stats{@apply tw-text-gray-300;}.global-file-uploader .upload-queue .queue-summary .summary-stats .stat.error{@apply tw-text-red-400;}}\n"] }]
|
|
4866
|
+
}], ctorParameters: () => [], propDecorators: { userId: [{
|
|
4867
|
+
type: Input
|
|
4868
|
+
}], multiple: [{
|
|
4869
|
+
type: Input
|
|
4870
|
+
}], accept: [{
|
|
4871
|
+
type: Input
|
|
4872
|
+
}], maxFileSize: [{
|
|
4873
|
+
type: Input
|
|
4874
|
+
}], allowedTypes: [{
|
|
4875
|
+
type: Input
|
|
4876
|
+
}], uploadComplete: [{
|
|
4877
|
+
type: Output
|
|
4878
|
+
}], uploadError: [{
|
|
4879
|
+
type: Output
|
|
4880
|
+
}], uploadCancelled: [{
|
|
4881
|
+
type: Output
|
|
4882
|
+
}], allUploadsComplete: [{
|
|
4883
|
+
type: Output
|
|
4884
|
+
}] } });
|
|
4885
|
+
|
|
4886
|
+
class CideFloatingUploadTriggerDirective {
|
|
4887
|
+
elementRef = inject(ElementRef);
|
|
4888
|
+
floatingUploader = inject(CideEleFloatingFileUploaderComponent);
|
|
4889
|
+
groupId;
|
|
4890
|
+
userId;
|
|
4891
|
+
showIcon = true;
|
|
4892
|
+
filesSelected = new EventEmitter();
|
|
4893
|
+
triggerIcon;
|
|
4894
|
+
ngOnInit() {
|
|
4895
|
+
this.setupTrigger();
|
|
4896
|
+
}
|
|
4897
|
+
ngOnDestroy() {
|
|
4898
|
+
this.removeTrigger();
|
|
4899
|
+
}
|
|
4900
|
+
setupTrigger() {
|
|
4901
|
+
const element = this.elementRef.nativeElement;
|
|
4902
|
+
if (element.type !== 'file') {
|
|
4903
|
+
console.warn('⚠️ [FloatingUploadTrigger] Directive should only be used on file input elements');
|
|
4904
|
+
return;
|
|
4905
|
+
}
|
|
4906
|
+
// Add change event listener
|
|
4907
|
+
element.addEventListener('change', this.onFileChange.bind(this));
|
|
4908
|
+
// Add floating uploader trigger icon if enabled
|
|
4909
|
+
if (this.showIcon) {
|
|
4910
|
+
this.addTriggerIcon();
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4913
|
+
removeTrigger() {
|
|
4914
|
+
const element = this.elementRef.nativeElement;
|
|
4915
|
+
element.removeEventListener('change', this.onFileChange.bind(this));
|
|
4916
|
+
if (this.triggerIcon) {
|
|
4917
|
+
this.triggerIcon.remove();
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
onFileChange(event) {
|
|
4921
|
+
const target = event.target;
|
|
4922
|
+
const files = target.files;
|
|
4923
|
+
if (files && files.length > 0) {
|
|
4924
|
+
console.log('📁 [FloatingUploadTrigger] Files selected:', files.length);
|
|
4925
|
+
// Show floating uploader
|
|
4926
|
+
this.floatingUploader.showUploader(this.groupId);
|
|
4927
|
+
// Handle files through floating uploader
|
|
4928
|
+
this.floatingUploader.handleExternalFiles(Array.from(files), this.userId, this.groupId);
|
|
4929
|
+
// Emit files selected event
|
|
4930
|
+
this.filesSelected.emit(Array.from(files));
|
|
4931
|
+
}
|
|
4932
|
+
}
|
|
4933
|
+
addTriggerIcon() {
|
|
4934
|
+
const element = this.elementRef.nativeElement;
|
|
4935
|
+
const container = element.parentElement;
|
|
4936
|
+
if (!container)
|
|
4937
|
+
return;
|
|
4938
|
+
// Create trigger icon
|
|
4939
|
+
this.triggerIcon = document.createElement('button');
|
|
4940
|
+
this.triggerIcon.type = 'button';
|
|
4941
|
+
this.triggerIcon.className = 'floating-upload-trigger-icon';
|
|
4942
|
+
this.triggerIcon.innerHTML = `
|
|
4943
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
4944
|
+
<path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z" />
|
|
4945
|
+
</svg>
|
|
4946
|
+
`;
|
|
4947
|
+
this.triggerIcon.title = 'View upload progress';
|
|
4948
|
+
this.triggerIcon.style.cssText = `
|
|
4949
|
+
position: absolute;
|
|
4950
|
+
right: 8px;
|
|
4951
|
+
top: 50%;
|
|
4952
|
+
transform: translateY(-50%);
|
|
4953
|
+
background: #3b82f6;
|
|
4954
|
+
color: white;
|
|
4955
|
+
border: none;
|
|
4956
|
+
border-radius: 4px;
|
|
4957
|
+
padding: 4px;
|
|
4958
|
+
cursor: pointer;
|
|
4959
|
+
display: flex;
|
|
4960
|
+
align-items: center;
|
|
4961
|
+
justify-content: center;
|
|
4962
|
+
z-index: 10;
|
|
4963
|
+
`;
|
|
4964
|
+
// Make container relative positioned
|
|
4965
|
+
container.style.position = 'relative';
|
|
4966
|
+
// Add click handler to show floating uploader
|
|
4967
|
+
this.triggerIcon.addEventListener('click', (e) => {
|
|
4968
|
+
e.preventDefault();
|
|
4969
|
+
e.stopPropagation();
|
|
4970
|
+
this.floatingUploader.showUploader(this.groupId);
|
|
4971
|
+
});
|
|
4972
|
+
// Insert icon after the input
|
|
4973
|
+
element.parentNode?.insertBefore(this.triggerIcon, element.nextSibling);
|
|
4974
|
+
}
|
|
4975
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideFloatingUploadTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
4976
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.1.7", type: CideFloatingUploadTriggerDirective, isStandalone: true, selector: "[cideFloatingUploadTrigger]", inputs: { groupId: "groupId", userId: "userId", showIcon: "showIcon" }, outputs: { filesSelected: "filesSelected" }, ngImport: i0 });
|
|
4977
|
+
}
|
|
4978
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideFloatingUploadTriggerDirective, decorators: [{
|
|
4979
|
+
type: Directive,
|
|
4980
|
+
args: [{
|
|
4981
|
+
selector: '[cideFloatingUploadTrigger]',
|
|
4982
|
+
standalone: true
|
|
4983
|
+
}]
|
|
4984
|
+
}], propDecorators: { groupId: [{
|
|
4985
|
+
type: Input
|
|
4986
|
+
}], userId: [{
|
|
4987
|
+
type: Input
|
|
4988
|
+
}], showIcon: [{
|
|
4989
|
+
type: Input
|
|
4990
|
+
}], filesSelected: [{
|
|
4991
|
+
type: Output
|
|
4992
|
+
}] } });
|
|
4860
4993
|
|
|
4861
4994
|
class CideTextareaComponent {
|
|
4862
4995
|
label = '';
|
|
@@ -9096,5 +9229,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
9096
9229
|
* Generated bundle index. Do not edit.
|
|
9097
9230
|
*/
|
|
9098
9231
|
|
|
9099
|
-
export { CideCoreFileManagerService, CideEleButtonComponent, CideEleConfirmationModalComponent, CideEleDataGridComponent, CideEleDropdownComponent, CideEleFileImageDirective, CideEleFileInputComponent, CideEleFileManagerService, CideEleFloatingFileUploaderComponent, CideEleGlobalFileUploaderComponent, CideEleGlobalNotificationsComponent, CideEleJsonEditorComponent, CideEleResizerDirective, CideEleSkeletonLoaderComponent, CideEleTabComponent, CideEleToastNotificationComponent, CideElementsService, CideIconComponent, CideInputComponent, CideSelectComponent, CideSelectOptionComponent, CideSpinnerComponent, CideTextareaComponent, ConfirmationService, CoreFileManagerInsertUpdatePayload, DEFAULT_GRID_CONFIG, DropdownManagerService, ICoreCyfmSave, MFileManager, NotificationService, TooltipDirective };
|
|
9232
|
+
export { CideCoreFileManagerService, CideEleButtonComponent, CideEleConfirmationModalComponent, CideEleDataGridComponent, CideEleDropdownComponent, CideEleFileImageDirective, CideEleFileInputComponent, CideEleFileManagerService, CideEleFloatingFileUploaderComponent, CideEleGlobalFileUploaderComponent, CideEleGlobalNotificationsComponent, CideEleJsonEditorComponent, CideEleResizerDirective, CideEleSkeletonLoaderComponent, CideEleTabComponent, CideEleToastNotificationComponent, CideElementsService, CideFloatingUploadTriggerDirective, CideIconComponent, CideInputComponent, CideSelectComponent, CideSelectOptionComponent, CideSpinnerComponent, CideTextareaComponent, ConfirmationService, CoreFileManagerInsertUpdatePayload, DEFAULT_GRID_CONFIG, DropdownManagerService, ICoreCyfmSave, MFileManager, NotificationService, TooltipDirective };
|
|
9100
9233
|
//# sourceMappingURL=cloud-ide-element.mjs.map
|