cloud-ide-element 1.0.81 → 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 +1633 -1467
- package/fesm2022/cloud-ide-element.mjs.map +1 -1
- package/index.d.ts +43 -5
- 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,1682 +3148,1848 @@ 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);
|
|
3155
|
-
destroyRef = inject(DestroyRef);
|
|
3156
|
-
// private readonly pendingTasks = inject(PendingTasks); // TODO: Fix PendingTasks API usage
|
|
3157
|
-
// Traditional @Input() decorators
|
|
3158
|
-
label = 'Choose file';
|
|
3159
|
-
accept = '';
|
|
3160
|
-
multiple = false;
|
|
3161
|
-
disabled = false;
|
|
3162
|
-
required = false;
|
|
3163
|
-
helperText = '';
|
|
3164
|
-
errorText = '';
|
|
3165
|
-
showPreview = false;
|
|
3166
|
-
previewWidth = '200px';
|
|
3167
|
-
previewHeight = '200px';
|
|
3168
|
-
previewBoxMode = false;
|
|
3169
|
-
showFileName = true;
|
|
3170
|
-
placeholderText = 'Click to select image';
|
|
3171
|
-
placeholderIcon = '📷';
|
|
3172
|
-
autoUpload = false;
|
|
3173
|
-
uploadData = {};
|
|
3174
|
-
// Traditional @Output() decorators
|
|
3175
|
-
fileChange = new EventEmitter();
|
|
3176
|
-
uploadSuccess = new EventEmitter();
|
|
3177
|
-
uploadError = new EventEmitter();
|
|
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" }] : []));
|
|
3235
|
-
constructor() {
|
|
3236
|
-
// Angular 20: afterRenderEffect for DOM operations
|
|
3237
|
-
afterRenderEffect(() => {
|
|
3238
|
-
// Update file input element when files change
|
|
3239
|
-
if (this.files()) {
|
|
3240
|
-
const fileInput = document.getElementById('cide-file-input-' + this.id());
|
|
3241
|
-
if (fileInput) {
|
|
3242
|
-
// Ensure the input reflects the current state
|
|
3243
|
-
fileInput.files = this.files();
|
|
3244
|
-
}
|
|
3245
|
-
}
|
|
3246
|
-
});
|
|
3247
|
-
// Angular 20: afterNextRender for one-time DOM operations
|
|
3248
|
-
afterNextRender(() => {
|
|
3249
|
-
console.log('🎯 [FileInput] Component rendered and DOM is ready');
|
|
3250
|
-
});
|
|
3251
|
-
}
|
|
3252
|
-
ngOnInit() {
|
|
3253
|
-
// Update signals with initial @Input() values
|
|
3254
|
-
this.labelSignal.set(this.label);
|
|
3255
|
-
this.acceptSignal.set(this.accept);
|
|
3256
|
-
this.multipleSignal.set(this.multiple);
|
|
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);
|
|
3270
|
-
}
|
|
3271
|
-
ngOnChanges(changes) {
|
|
3272
|
-
// Angular 20: Update signals when @Input() values change
|
|
3273
|
-
if (changes['label'])
|
|
3274
|
-
this.labelSignal.set(this.label);
|
|
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);
|
|
3305
|
-
}
|
|
3306
|
-
writeValue(value) {
|
|
3307
|
-
console.log('📝 [FileInput] writeValue called with:', value);
|
|
3308
|
-
if (typeof value === 'string') {
|
|
3309
|
-
// Value is an uploaded file ID - fetch file details and set preview
|
|
3310
|
-
console.log('📝 [FileInput] Value is uploaded file ID:', value);
|
|
3311
|
-
this.files.set(null);
|
|
3312
|
-
this.fileNames.set([]);
|
|
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
|
-
}
|
|
3331
|
-
}
|
|
3332
|
-
registerOnChange(fn) {
|
|
3333
|
-
this.onChange = fn;
|
|
3334
|
-
}
|
|
3335
|
-
registerOnTouched(fn) {
|
|
3336
|
-
this.onTouched = fn;
|
|
3337
|
-
}
|
|
3338
|
-
registerOnValidatorChange(fn) {
|
|
3339
|
-
this.onValidatorChange = fn;
|
|
3340
|
-
}
|
|
3341
|
-
setDisabledState(isDisabled) {
|
|
3342
|
-
// Note: With input signals, disabled state is controlled by the parent component
|
|
3343
|
-
// This method is kept for ControlValueAccessor compatibility but doesn't modify the signal
|
|
3344
|
-
console.log('🔧 [FileInput] setDisabledState called with:', isDisabled, '(controlled by parent component)');
|
|
3345
|
-
}
|
|
3346
|
-
onFileSelected(event) {
|
|
3347
|
-
console.log('🔍 [FileInput] onFileSelected called');
|
|
3348
|
-
const input = event.target;
|
|
3349
|
-
const selectedFiles = input.files;
|
|
3350
|
-
this.files.set(selectedFiles);
|
|
3351
|
-
this.fileNames.set(selectedFiles ? Array.from(selectedFiles).map(f => f.name) : []);
|
|
3352
|
-
console.log('📁 [FileInput] Files selected:', this.fileNames());
|
|
3353
|
-
this.generatePreviews();
|
|
3354
|
-
// Reset upload status when new file is selected
|
|
3355
|
-
this.uploadStatus.set('idle');
|
|
3356
|
-
console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus());
|
|
3357
|
-
this.onChange(selectedFiles);
|
|
3358
|
-
this.fileChange.emit(selectedFiles);
|
|
3359
|
-
this.onTouched();
|
|
3360
|
-
// Auto upload if enabled
|
|
3361
|
-
if (this.autoUploadSignal() && selectedFiles && selectedFiles.length > 0) {
|
|
3362
|
-
if (this.multipleSignal()) {
|
|
3363
|
-
console.log('🚀 [FileInput] Auto upload enabled for multiple files mode:', selectedFiles.length, 'files');
|
|
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
|
-
}
|
|
3558
|
-
}
|
|
3559
|
-
/**
|
|
3560
|
-
* Start uploading multiple files with the provided group ID
|
|
3561
|
-
* All files will be uploaded with the SAME group ID that was generated before this method
|
|
3562
|
-
*/
|
|
3563
|
-
startMulti(files, groupId) {
|
|
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) {
|
|
3804
|
-
event.preventDefault();
|
|
3805
|
-
event.stopPropagation();
|
|
3806
|
-
this.isDragOver.set(false);
|
|
3807
|
-
if (this.disabledSignal()) {
|
|
3808
|
-
console.log('⏸️ [FileInput] Drop ignored - component is disabled');
|
|
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
|
-
}
|
|
3873
|
-
}
|
|
3874
|
-
else {
|
|
3875
|
-
console.log('⏸️ [FileInput] Auto upload disabled or no files');
|
|
3876
|
-
}
|
|
3877
|
-
}
|
|
3878
|
-
isRequired() {
|
|
3879
|
-
return this.requiredSignal();
|
|
3880
|
-
}
|
|
3881
|
-
/**
|
|
3882
|
-
* Angular 20: Utility method to get upload data with proper typing
|
|
3883
|
-
* @returns Properly typed upload data
|
|
3884
|
-
*/
|
|
3885
|
-
getUploadData() {
|
|
3886
|
-
return this.uploadDataSignal();
|
|
3887
|
-
}
|
|
3888
|
-
/**
|
|
3889
|
-
* Angular 20: Utility method to update upload data with type safety
|
|
3890
|
-
* @param data Partial upload data to merge with existing data
|
|
3891
|
-
*/
|
|
3892
|
-
updateUploadData(data) {
|
|
3893
|
-
const currentData = this.uploadDataSignal();
|
|
3894
|
-
const updatedData = { ...currentData, ...data };
|
|
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);
|
|
3898
|
-
}
|
|
3899
|
-
getCurrentState() {
|
|
3900
|
-
return {
|
|
3901
|
-
id: this.id(),
|
|
3902
|
-
label: this.labelSignal(),
|
|
3903
|
-
required: this.requiredSignal(),
|
|
3904
|
-
disabled: this.disabledSignal(),
|
|
3905
|
-
accept: this.acceptSignal(),
|
|
3906
|
-
multiple: this.multipleSignal(),
|
|
3907
|
-
showPreview: this.showPreviewSignal(),
|
|
3908
|
-
autoUpload: this.autoUploadSignal(),
|
|
3909
|
-
uploadStatus: this.uploadStatus(),
|
|
3910
|
-
isUploading: this.isUploading(),
|
|
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
|
-
};
|
|
3925
|
-
}
|
|
3926
|
-
async getControlData() {
|
|
3927
|
-
console.log('🔍 [FileInput] getControlData called');
|
|
3928
|
-
const cide_element_data = await this.elementService?.getElementData({ sype_key: this.id() });
|
|
3929
|
-
if (cide_element_data) {
|
|
3930
|
-
console.log('📋 [FileInput] Element data loaded:', cide_element_data);
|
|
3931
|
-
// Note: Since we're using input signals, we can't directly set their values
|
|
3932
|
-
// This method would need to be refactored to work with the new signal-based approach
|
|
3933
|
-
// For now, we'll log the data and trigger validation
|
|
3934
|
-
console.log('✅ [FileInput] Control data received from element service');
|
|
3935
|
-
console.log('⚠️ [FileInput] Note: Input signals cannot be modified after component initialization');
|
|
3936
|
-
// Trigger validation update
|
|
3937
|
-
this.onValidatorChange();
|
|
3938
|
-
}
|
|
3939
|
-
else {
|
|
3940
|
-
console.log('⚠️ [FileInput] No element data found for key:', this.id());
|
|
3941
|
-
}
|
|
3942
|
-
}
|
|
3943
|
-
// Validator implementation
|
|
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...' } };
|
|
3950
|
-
}
|
|
3951
|
-
// If required and no file is selected and no control value (uploaded file ID), return validation error
|
|
3952
|
-
if (this.requiredSignal() && !this.files() && !control.value) {
|
|
3953
|
-
console.log('⚠️ [FileInput] Validation ERROR: File required');
|
|
3954
|
-
return { 'required': { message: 'Please select a file to upload.' } };
|
|
3955
|
-
}
|
|
3956
|
-
console.log('✅ [FileInput] Validation PASSED: No errors');
|
|
3957
|
-
return null; // No validation errors
|
|
3958
|
-
}
|
|
3959
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3960
|
-
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" }, outputs: { fileChange: "fileChange", uploadSuccess: "uploadSuccess", uploadError: "uploadError", uploadProgressChange: "uploadProgressChange" }, providers: [
|
|
3961
|
-
{
|
|
3962
|
-
provide: NG_VALUE_ACCESSOR,
|
|
3963
|
-
useExisting: CideEleFileInputComponent,
|
|
3964
|
-
multi: true
|
|
3965
|
-
},
|
|
3966
|
-
{
|
|
3967
|
-
provide: NG_VALIDATORS,
|
|
3968
|
-
useExisting: CideEleFileInputComponent,
|
|
3969
|
-
multi: true
|
|
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
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileInputComponent, decorators: [{
|
|
3974
|
-
type: Component,
|
|
3975
|
-
args: [{ selector: 'cide-ele-file-input', standalone: true, imports: [CommonModule, FormsModule, CideIconComponent], providers: [
|
|
3976
|
-
{
|
|
3977
|
-
provide: NG_VALUE_ACCESSOR,
|
|
3978
|
-
useExisting: CideEleFileInputComponent,
|
|
3979
|
-
multi: true
|
|
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
|
|
3151
|
+
class CideEleFloatingFileUploaderComponent {
|
|
4031
3152
|
destroyRef = inject(DestroyRef);
|
|
4032
|
-
|
|
4033
|
-
//
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
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" }] : []));
|
|
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" }] : []));
|
|
4048
3158
|
currentGroupId = signal(null, ...(ngDevMode ? [{ debugName: "currentGroupId" }] : []));
|
|
4049
|
-
//
|
|
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
|
|
4050
3163
|
hasUploads = computed(() => this.uploadQueue().length > 0, ...(ngDevMode ? [{ debugName: "hasUploads" }] : []));
|
|
4051
|
-
pendingUploads = computed(() => this.uploadQueue().
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
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" }] : []));
|
|
4055
3175
|
constructor() {
|
|
4056
|
-
console.log('🚀 [
|
|
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();
|
|
3194
|
+
}
|
|
3195
|
+
// Note: Removed auto-hide logic - floating uploader stays visible until manually closed
|
|
3196
|
+
});
|
|
3197
|
+
}
|
|
3198
|
+
ngOnInit() {
|
|
3199
|
+
// Set up drag and drop listeners
|
|
3200
|
+
this.setupDragAndDrop();
|
|
3201
|
+
// Set up file input change listeners
|
|
3202
|
+
this.setupFileInputListeners();
|
|
3203
|
+
}
|
|
3204
|
+
ngOnDestroy() {
|
|
3205
|
+
console.log('🧹 [FloatingFileUploader] Component destroyed');
|
|
3206
|
+
this.removeDragAndDropListeners();
|
|
3207
|
+
this.removeFileInputListeners();
|
|
3208
|
+
}
|
|
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));
|
|
3216
|
+
}
|
|
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));
|
|
3224
|
+
}
|
|
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));
|
|
3231
|
+
}
|
|
3232
|
+
/**
|
|
3233
|
+
* Remove file input listeners
|
|
3234
|
+
*/
|
|
3235
|
+
removeFileInputListeners() {
|
|
3236
|
+
document.removeEventListener('change', this.handleFileInputChange.bind(this));
|
|
3237
|
+
}
|
|
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
|
+
}
|
|
4057
3261
|
}
|
|
4058
3262
|
/**
|
|
4059
3263
|
* Handle drag over event
|
|
4060
3264
|
*/
|
|
4061
|
-
|
|
3265
|
+
handleDragOver(event) {
|
|
4062
3266
|
event.preventDefault();
|
|
4063
3267
|
event.stopPropagation();
|
|
4064
|
-
|
|
3268
|
+
// Show floating uploader when files are dragged over
|
|
3269
|
+
if (event.dataTransfer?.types.includes('Files')) {
|
|
3270
|
+
this.showWithAnimation();
|
|
3271
|
+
}
|
|
4065
3272
|
}
|
|
4066
3273
|
/**
|
|
4067
3274
|
* Handle drag leave event
|
|
4068
3275
|
*/
|
|
4069
|
-
|
|
3276
|
+
handleDragLeave(event) {
|
|
4070
3277
|
event.preventDefault();
|
|
4071
3278
|
event.stopPropagation();
|
|
4072
|
-
|
|
3279
|
+
// Only hide if leaving the entire document
|
|
3280
|
+
if (!event.relatedTarget || event.relatedTarget === document.body) {
|
|
3281
|
+
this.updateVisibility();
|
|
3282
|
+
}
|
|
4073
3283
|
}
|
|
4074
3284
|
/**
|
|
4075
3285
|
* Handle drop event
|
|
4076
3286
|
*/
|
|
4077
|
-
|
|
3287
|
+
handleDrop(event) {
|
|
4078
3288
|
event.preventDefault();
|
|
4079
3289
|
event.stopPropagation();
|
|
4080
|
-
this.isDragOver.set(false);
|
|
4081
3290
|
const files = event.dataTransfer?.files;
|
|
4082
3291
|
if (files && files.length > 0) {
|
|
4083
|
-
this.
|
|
3292
|
+
this.handleFiles(Array.from(files));
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
/**
|
|
3296
|
+
* Handle files from drag and drop or file input
|
|
3297
|
+
*/
|
|
3298
|
+
handleFiles(files) {
|
|
3299
|
+
console.log('📁 [FloatingFileUploader] Handling files:', files.length);
|
|
3300
|
+
// Use handleExternalFiles to process the files
|
|
3301
|
+
this.handleExternalFiles(files, this.currentUserId());
|
|
3302
|
+
}
|
|
3303
|
+
/**
|
|
3304
|
+
* Update visibility - simplified for notification only
|
|
3305
|
+
*/
|
|
3306
|
+
updateVisibility() {
|
|
3307
|
+
// This is just a notification component now
|
|
3308
|
+
// The actual uploads are handled by the global uploader
|
|
3309
|
+
}
|
|
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);
|
|
3322
|
+
}
|
|
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`;
|
|
3356
|
+
}
|
|
3357
|
+
else if (completed > 0 && failed === 0) {
|
|
3358
|
+
return `${completed} completed`;
|
|
3359
|
+
}
|
|
3360
|
+
else if (failed > 0) {
|
|
3361
|
+
return `${completed} completed, ${failed} failed`;
|
|
3362
|
+
}
|
|
3363
|
+
else if (queueLength > 0) {
|
|
3364
|
+
return `${queueLength} pending`;
|
|
3365
|
+
}
|
|
3366
|
+
return 'No uploads';
|
|
3367
|
+
}
|
|
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
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
/**
|
|
3391
|
+
* Get status class based on upload stage
|
|
3392
|
+
*/
|
|
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
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
/**
|
|
3403
|
+
* Cancel upload
|
|
3404
|
+
*/
|
|
3405
|
+
cancelUpload(fileId) {
|
|
3406
|
+
console.log('🚫 [FloatingFileUploader] Cancelling upload:', fileId);
|
|
3407
|
+
this.fileManagerService.cancelUpload(fileId);
|
|
3408
|
+
}
|
|
3409
|
+
/**
|
|
3410
|
+
* Get file name from file ID (extract from the ID format)
|
|
3411
|
+
*/
|
|
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('_');
|
|
3418
|
+
}
|
|
3419
|
+
return fileId;
|
|
3420
|
+
}
|
|
3421
|
+
/**
|
|
3422
|
+
* Set current user ID
|
|
3423
|
+
*/
|
|
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);
|
|
3437
|
+
}
|
|
3438
|
+
// Set group ID if provided
|
|
3439
|
+
if (groupId) {
|
|
3440
|
+
this.currentGroupId.set(groupId);
|
|
4084
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
|
+
});
|
|
4085
3462
|
}
|
|
4086
3463
|
/**
|
|
4087
|
-
*
|
|
3464
|
+
* Manually show the floating uploader
|
|
3465
|
+
* This can be called from file input components or other triggers
|
|
4088
3466
|
*/
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
this.addFilesToQueue(Array.from(files));
|
|
3467
|
+
showUploader(groupId) {
|
|
3468
|
+
console.log('👁️ [FloatingFileUploader] Manually showing uploader', groupId ? `for group: ${groupId}` : '');
|
|
3469
|
+
if (groupId) {
|
|
3470
|
+
this.currentGroupId.set(groupId);
|
|
4094
3471
|
}
|
|
4095
|
-
|
|
4096
|
-
target.value = '';
|
|
3472
|
+
this.showWithAnimation();
|
|
4097
3473
|
}
|
|
4098
3474
|
/**
|
|
4099
|
-
*
|
|
3475
|
+
* Check if there are any uploads for the current group
|
|
4100
3476
|
*/
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
return;
|
|
3477
|
+
hasUploadsForCurrentGroup() {
|
|
3478
|
+
const groupId = this.currentGroupId();
|
|
3479
|
+
if (!groupId) {
|
|
3480
|
+
// If no group filter, show all uploads
|
|
3481
|
+
return this.hasUploads();
|
|
4107
3482
|
}
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
progress: 0,
|
|
4112
|
-
status: 'pending',
|
|
4113
|
-
file: file
|
|
4114
|
-
}));
|
|
4115
|
-
this.uploadQueue.update(queue => [...queue, ...newUploads]);
|
|
4116
|
-
this.processUploadQueue();
|
|
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();
|
|
4117
3486
|
}
|
|
4118
|
-
|
|
4119
|
-
* Validate file
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
return false;
|
|
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();
|
|
4132
3675
|
}
|
|
4133
|
-
|
|
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();
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
registerOnChange(fn) {
|
|
3685
|
+
this.onChange = fn;
|
|
3686
|
+
}
|
|
3687
|
+
registerOnTouched(fn) {
|
|
3688
|
+
this.onTouched = fn;
|
|
3689
|
+
}
|
|
3690
|
+
registerOnValidatorChange(fn) {
|
|
3691
|
+
this.onValidatorChange = fn;
|
|
4134
3692
|
}
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
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');
|
|
4172
3835
|
}
|
|
3836
|
+
this.isUploading.set(false);
|
|
3837
|
+
console.log('🔄 [FileInput] isUploading set to false');
|
|
4173
3838
|
},
|
|
4174
3839
|
error: (error) => {
|
|
4175
|
-
console.error('
|
|
4176
|
-
|
|
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%');
|
|
4177
3860
|
}
|
|
4178
3861
|
});
|
|
4179
3862
|
}
|
|
4180
3863
|
/**
|
|
4181
|
-
* Upload files with group ID
|
|
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
|
|
4182
3866
|
*/
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
this.
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
uploadedFile: response
|
|
4226
|
-
});
|
|
4227
|
-
if (responseFile) {
|
|
4228
|
-
uploadedFiles.push(responseFile);
|
|
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);
|
|
4229
3909
|
}
|
|
4230
|
-
|
|
4231
|
-
|
|
3910
|
+
this.notificationService.error('❌ Failed to generate group ID for multiple file upload', { duration: 0 });
|
|
3911
|
+
this.uploadNotificationId.set(null);
|
|
3912
|
+
}
|
|
3913
|
+
});
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
/**
|
|
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
|
|
3919
|
+
*/
|
|
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({
|
|
3946
|
+
next: (response) => {
|
|
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
|
-
|
|
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
|
+
}
|
|
4298
4064
|
}
|
|
4299
4065
|
else {
|
|
4300
|
-
|
|
4301
|
-
this.updateUploadStatus(pendingUpload.fileId, 'completed', 100, undefined, response);
|
|
4066
|
+
console.warn('⚠️ [FileInput] No file data found for ID:', fileId);
|
|
4302
4067
|
}
|
|
4303
|
-
this.uploadComplete.emit({
|
|
4304
|
-
fileId: tempUniqueId || pendingUpload.fileId,
|
|
4305
|
-
fileName: pendingUpload.fileName,
|
|
4306
|
-
progress: 100,
|
|
4307
|
-
status: 'completed',
|
|
4308
|
-
uploadedFile: response
|
|
4309
|
-
});
|
|
4310
|
-
// Process next file
|
|
4311
|
-
setTimeout(() => this.uploadNextFile(), 500);
|
|
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
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
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" }] : []));
|
|
4422
|
+
uploadQueue = signal([], ...(ngDevMode ? [{ debugName: "uploadQueue" }] : []));
|
|
4423
|
+
currentGroupId = signal(null, ...(ngDevMode ? [{ debugName: "currentGroupId" }] : []));
|
|
4424
|
+
// Computed values
|
|
4522
4425
|
hasUploads = computed(() => this.uploadQueue().length > 0, ...(ngDevMode ? [{ debugName: "hasUploads" }] : []));
|
|
4523
|
-
pendingUploads = computed(() => this.uploadQueue().
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
}, ...(ngDevMode ? [{ debugName: "completedUploads" }] : []));
|
|
4528
|
-
failedUploads = computed(() => {
|
|
4529
|
-
const active = this.activeUploads();
|
|
4530
|
-
return Array.from(active.values()).filter(upload => upload.stage === 'error');
|
|
4531
|
-
}, ...(ngDevMode ? [{ debugName: "failedUploads" }] : []));
|
|
4532
|
-
// Animation states
|
|
4533
|
-
isAnimating = signal(false, ...(ngDevMode ? [{ debugName: "isAnimating" }] : []));
|
|
4426
|
+
pendingUploads = computed(() => this.uploadQueue().filter(upload => upload.status === 'pending'), ...(ngDevMode ? [{ debugName: "pendingUploads" }] : []));
|
|
4427
|
+
activeUploads = computed(() => this.uploadQueue().filter(upload => upload.status === 'uploading'), ...(ngDevMode ? [{ debugName: "activeUploads" }] : []));
|
|
4428
|
+
completedUploads = computed(() => this.uploadQueue().filter(upload => upload.status === 'completed'), ...(ngDevMode ? [{ debugName: "completedUploads" }] : []));
|
|
4429
|
+
failedUploads = computed(() => this.uploadQueue().filter(upload => upload.status === 'error'), ...(ngDevMode ? [{ debugName: "failedUploads" }] : []));
|
|
4534
4430
|
constructor() {
|
|
4535
|
-
console.log('🚀 [
|
|
4536
|
-
// Set up effect to monitor file manager service's upload queue
|
|
4537
|
-
effect(() => {
|
|
4538
|
-
const uploadQueue = this.fileManagerService.uploadQueue();
|
|
4539
|
-
const hasUploads = uploadQueue.length > 0;
|
|
4540
|
-
console.log('🔄 [FloatingFileUploader] File manager service queue changed:', {
|
|
4541
|
-
queueLength: uploadQueue.length,
|
|
4542
|
-
hasUploads,
|
|
4543
|
-
currentVisible: this.isVisible(),
|
|
4544
|
-
queueFiles: uploadQueue
|
|
4545
|
-
});
|
|
4546
|
-
// Show floating uploader when files are added to the file manager service queue
|
|
4547
|
-
if (hasUploads && !this.isVisible()) {
|
|
4548
|
-
console.log('👁️ [FloatingFileUploader] Showing floating uploader due to file manager queue');
|
|
4549
|
-
this.showWithAnimation();
|
|
4550
|
-
}
|
|
4551
|
-
else if (!hasUploads && this.isVisible()) {
|
|
4552
|
-
console.log('👁️ [FloatingFileUploader] Hiding floating uploader - no files in file manager queue');
|
|
4553
|
-
this.hideWithAnimation();
|
|
4554
|
-
}
|
|
4555
|
-
});
|
|
4431
|
+
console.log('🚀 [GlobalFileUploader] Component initialized');
|
|
4556
4432
|
}
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4433
|
+
/**
|
|
4434
|
+
* Handle drag over event
|
|
4435
|
+
*/
|
|
4436
|
+
onDragOver(event) {
|
|
4437
|
+
event.preventDefault();
|
|
4438
|
+
event.stopPropagation();
|
|
4439
|
+
this.isDragOver.set(true);
|
|
4562
4440
|
}
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4441
|
+
/**
|
|
4442
|
+
* Handle drag leave event
|
|
4443
|
+
*/
|
|
4444
|
+
onDragLeave(event) {
|
|
4445
|
+
event.preventDefault();
|
|
4446
|
+
event.stopPropagation();
|
|
4447
|
+
this.isDragOver.set(false);
|
|
4567
4448
|
}
|
|
4568
4449
|
/**
|
|
4569
|
-
*
|
|
4450
|
+
* Handle drop event
|
|
4570
4451
|
*/
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
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 = '';
|
|
4472
|
+
}
|
|
4473
|
+
/**
|
|
4474
|
+
* Add files to upload queue
|
|
4475
|
+
*/
|
|
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();
|
|
4575
4492
|
}
|
|
4576
4493
|
/**
|
|
4577
|
-
*
|
|
4494
|
+
* Validate file
|
|
4578
4495
|
*/
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
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;
|
|
4583
4509
|
}
|
|
4584
4510
|
/**
|
|
4585
|
-
*
|
|
4511
|
+
* Process upload queue
|
|
4586
4512
|
*/
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
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
|
+
}
|
|
4590
4526
|
}
|
|
4591
4527
|
/**
|
|
4592
|
-
*
|
|
4528
|
+
* Upload multiple files with group ID
|
|
4593
4529
|
*/
|
|
4594
|
-
|
|
4595
|
-
|
|
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
|
+
});
|
|
4596
4554
|
}
|
|
4597
4555
|
/**
|
|
4598
|
-
*
|
|
4556
|
+
* Upload files with group ID
|
|
4599
4557
|
*/
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
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
|
+
});
|
|
4606
4626
|
});
|
|
4607
|
-
// Check if this is a file input with files
|
|
4608
|
-
if (target.type === 'file' && target.files && target.files.length > 0) {
|
|
4609
|
-
console.log('📁 [FloatingFileUploader] File input change detected:', target.files.length, 'files');
|
|
4610
|
-
// Check if the input has a data-user-id attribute for user context
|
|
4611
|
-
const userId = target.getAttribute('data-user-id');
|
|
4612
|
-
if (userId && userId !== this.currentUserId()) {
|
|
4613
|
-
this.setCurrentUserId(userId);
|
|
4614
|
-
}
|
|
4615
|
-
// Handle the files
|
|
4616
|
-
this.handleFiles(Array.from(target.files));
|
|
4617
|
-
// Reset the input to allow selecting the same files again
|
|
4618
|
-
target.value = '';
|
|
4619
|
-
}
|
|
4620
4627
|
}
|
|
4621
4628
|
/**
|
|
4622
|
-
* Handle
|
|
4629
|
+
* Handle multiple upload completion
|
|
4623
4630
|
*/
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
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
|
+
});
|
|
4630
4639
|
}
|
|
4631
4640
|
}
|
|
4632
4641
|
/**
|
|
4633
|
-
*
|
|
4642
|
+
* Upload next file in queue (single file mode)
|
|
4634
4643
|
*/
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
this.updateVisibility();
|
|
4644
|
+
uploadNextFile() {
|
|
4645
|
+
const pendingUpload = this.uploadQueue().find(upload => upload.status === 'pending');
|
|
4646
|
+
if (!pendingUpload) {
|
|
4647
|
+
this.isUploading.set(false);
|
|
4648
|
+
return;
|
|
4641
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
|
+
});
|
|
4642
4699
|
}
|
|
4643
4700
|
/**
|
|
4644
|
-
*
|
|
4701
|
+
* Update upload progress
|
|
4645
4702
|
*/
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
if (files && files.length > 0) {
|
|
4651
|
-
this.handleFiles(Array.from(files));
|
|
4652
|
-
}
|
|
4703
|
+
updateUploadProgress(fileId, progress) {
|
|
4704
|
+
this.uploadQueue.update(queue => queue.map(upload => upload.fileId === fileId
|
|
4705
|
+
? { ...upload, progress }
|
|
4706
|
+
: upload));
|
|
4653
4707
|
}
|
|
4654
4708
|
/**
|
|
4655
|
-
*
|
|
4709
|
+
* Update upload status
|
|
4656
4710
|
*/
|
|
4657
|
-
|
|
4658
|
-
console.log('
|
|
4659
|
-
|
|
4660
|
-
|
|
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
|
+
});
|
|
4661
4724
|
}
|
|
4662
4725
|
/**
|
|
4663
|
-
* Update
|
|
4726
|
+
* Update upload status by cyfm_temp_unique_id
|
|
4664
4727
|
*/
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
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
|
+
});
|
|
4668
4742
|
}
|
|
4669
4743
|
/**
|
|
4670
|
-
*
|
|
4744
|
+
* Handle upload error
|
|
4671
4745
|
*/
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
this.isAnimating.set(false);
|
|
4679
|
-
console.log('🎬 [FloatingFileUploader] Animation completed, isVisible:', this.isVisible());
|
|
4680
|
-
}, 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);
|
|
4681
4752
|
}
|
|
4682
4753
|
/**
|
|
4683
|
-
*
|
|
4754
|
+
* Cancel upload
|
|
4684
4755
|
*/
|
|
4685
|
-
|
|
4686
|
-
this.
|
|
4687
|
-
|
|
4756
|
+
cancelUpload(fileId) {
|
|
4757
|
+
this.updateUploadStatus(fileId, 'cancelled', 0);
|
|
4758
|
+
this.uploadCancelled.emit(fileId);
|
|
4759
|
+
// Remove from queue after a delay
|
|
4688
4760
|
setTimeout(() => {
|
|
4689
|
-
this.
|
|
4690
|
-
|
|
4691
|
-
}, 300);
|
|
4761
|
+
this.uploadQueue.update(queue => queue.filter(upload => upload.fileId !== fileId));
|
|
4762
|
+
}, 1000);
|
|
4692
4763
|
}
|
|
4693
4764
|
/**
|
|
4694
|
-
*
|
|
4765
|
+
* Retry upload
|
|
4695
4766
|
*/
|
|
4696
|
-
|
|
4697
|
-
this.
|
|
4767
|
+
retryUpload(fileId) {
|
|
4768
|
+
this.updateUploadStatus(fileId, 'pending', 0);
|
|
4769
|
+
this.processUploadQueue();
|
|
4698
4770
|
}
|
|
4699
4771
|
/**
|
|
4700
|
-
*
|
|
4772
|
+
* Remove upload from queue
|
|
4701
4773
|
*/
|
|
4702
|
-
|
|
4703
|
-
this.
|
|
4774
|
+
removeUpload(fileId) {
|
|
4775
|
+
this.uploadQueue.update(queue => queue.filter(upload => upload.fileId !== fileId));
|
|
4704
4776
|
}
|
|
4705
4777
|
/**
|
|
4706
|
-
*
|
|
4778
|
+
* Clear completed uploads
|
|
4707
4779
|
*/
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
const activeCount = this.activeUploads().size;
|
|
4711
|
-
const completed = this.completedUploads().length;
|
|
4712
|
-
const failed = this.failedUploads().length;
|
|
4713
|
-
if (activeCount > 0) {
|
|
4714
|
-
return `${activeCount} uploading`;
|
|
4715
|
-
}
|
|
4716
|
-
else if (completed > 0 && failed === 0) {
|
|
4717
|
-
return `${completed} completed`;
|
|
4718
|
-
}
|
|
4719
|
-
else if (failed > 0) {
|
|
4720
|
-
return `${completed} completed, ${failed} failed`;
|
|
4721
|
-
}
|
|
4722
|
-
else if (queueLength > 0) {
|
|
4723
|
-
return `${queueLength} pending`;
|
|
4724
|
-
}
|
|
4725
|
-
return 'No uploads';
|
|
4780
|
+
clearCompleted() {
|
|
4781
|
+
this.uploadQueue.update(queue => queue.filter(upload => upload.status !== 'completed'));
|
|
4726
4782
|
}
|
|
4727
4783
|
/**
|
|
4728
|
-
*
|
|
4784
|
+
* Clear all uploads
|
|
4729
4785
|
*/
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
return 0;
|
|
4734
|
-
const totalProgress = Array.from(activeUploads.values()).reduce((sum, upload) => sum + upload.percentage, 0);
|
|
4735
|
-
return Math.round(totalProgress / activeUploads.size);
|
|
4786
|
+
clearAll() {
|
|
4787
|
+
this.uploadQueue.set([]);
|
|
4788
|
+
this.currentGroupId.set(null);
|
|
4736
4789
|
}
|
|
4737
4790
|
/**
|
|
4738
|
-
* Get status icon
|
|
4791
|
+
* Get status icon
|
|
4739
4792
|
*/
|
|
4740
|
-
getStatusIcon(
|
|
4741
|
-
switch (
|
|
4742
|
-
case '
|
|
4793
|
+
getStatusIcon(status) {
|
|
4794
|
+
switch (status) {
|
|
4795
|
+
case 'pending': return 'schedule';
|
|
4743
4796
|
case 'uploading': return 'cloud_upload';
|
|
4744
|
-
case '
|
|
4797
|
+
case 'completed': return 'check_circle';
|
|
4745
4798
|
case 'error': return 'error';
|
|
4799
|
+
case 'cancelled': return 'cancel';
|
|
4746
4800
|
default: return 'help';
|
|
4747
4801
|
}
|
|
4748
4802
|
}
|
|
4749
4803
|
/**
|
|
4750
|
-
* Get status class
|
|
4804
|
+
* Get status class
|
|
4751
4805
|
*/
|
|
4752
|
-
getStatusClass(
|
|
4753
|
-
switch (
|
|
4754
|
-
case '
|
|
4806
|
+
getStatusClass(status) {
|
|
4807
|
+
switch (status) {
|
|
4808
|
+
case 'pending': return 'status-pending';
|
|
4755
4809
|
case 'uploading': return 'status-uploading';
|
|
4756
|
-
case '
|
|
4810
|
+
case 'completed': return 'status-completed';
|
|
4757
4811
|
case 'error': return 'status-error';
|
|
4812
|
+
case 'cancelled': return 'status-cancelled';
|
|
4758
4813
|
default: return 'status-unknown';
|
|
4759
4814
|
}
|
|
4760
4815
|
}
|
|
4761
4816
|
/**
|
|
4762
|
-
*
|
|
4817
|
+
* Get file size display
|
|
4763
4818
|
*/
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
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];
|
|
4767
4827
|
}
|
|
4768
4828
|
/**
|
|
4769
|
-
*
|
|
4829
|
+
* Generate unique file ID - this should match the service's generateFileId method
|
|
4830
|
+
* The service uses: ${file.name}_${file.size}_${Date.now()}
|
|
4770
4831
|
*/
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
const parts = fileId.split('_');
|
|
4774
|
-
if (parts.length >= 3) {
|
|
4775
|
-
// Remove the last two parts (size and timestamp) to get the filename
|
|
4776
|
-
return parts.slice(0, -2).join('_');
|
|
4777
|
-
}
|
|
4778
|
-
return fileId;
|
|
4832
|
+
generateFileId(file) {
|
|
4833
|
+
return `${file.name}_${file.size}_${Date.now()}`;
|
|
4779
4834
|
}
|
|
4780
4835
|
/**
|
|
4781
|
-
*
|
|
4836
|
+
* Convert IFileUploadRequest to FileUploadOptions
|
|
4782
4837
|
*/
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
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
|
+
};
|
|
4786
4846
|
}
|
|
4787
4847
|
/**
|
|
4788
|
-
*
|
|
4789
|
-
* This can be called by other components to trigger the floating uploader
|
|
4848
|
+
* Trigger file input click
|
|
4790
4849
|
*/
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
this.setCurrentUserId(userId);
|
|
4850
|
+
triggerFileInput() {
|
|
4851
|
+
const fileInput = document.getElementById('global-file-input');
|
|
4852
|
+
if (fileInput) {
|
|
4853
|
+
fileInput.click();
|
|
4796
4854
|
}
|
|
4797
|
-
// Upload files using file manager service
|
|
4798
|
-
// The file manager service will handle adding to its queue and the effect will show the floating uploader
|
|
4799
|
-
files.forEach((file, index) => {
|
|
4800
|
-
console.log(`📁 [FloatingFileUploader] Starting upload for file ${index + 1}/${files.length}:`, file.name);
|
|
4801
|
-
this.fileManagerService.uploadFile(file, {
|
|
4802
|
-
userId: this.currentUserId(),
|
|
4803
|
-
permissions: ['read', 'write'],
|
|
4804
|
-
tags: []
|
|
4805
|
-
})
|
|
4806
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4807
|
-
.subscribe({
|
|
4808
|
-
next: (response) => {
|
|
4809
|
-
console.log('✅ [FloatingFileUploader] Upload completed:', response);
|
|
4810
|
-
},
|
|
4811
|
-
error: (error) => {
|
|
4812
|
-
console.error('❌ [FloatingFileUploader] Upload failed:', error);
|
|
4813
|
-
}
|
|
4814
|
-
});
|
|
4815
|
-
});
|
|
4816
4855
|
}
|
|
4817
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
4818
|
-
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 }\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"] }, { kind: "pipe", type: i1.KeyValuePipe, name: "keyvalue" }] });
|
|
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"] }] });
|
|
4819
4858
|
}
|
|
4820
|
-
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: [{
|
|
4821
4860
|
type: Component,
|
|
4822
|
-
args: [{ selector: 'cide-ele-
|
|
4861
|
+
args: [{ selector: 'cide-ele-global-file-uploader', standalone: true, imports: [
|
|
4823
4862
|
CommonModule,
|
|
4863
|
+
CideEleButtonComponent,
|
|
4824
4864
|
CideIconComponent
|
|
4825
|
-
], 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 }\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"] }]
|
|
4826
|
-
}], 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
|
+
}] } });
|
|
4827
4993
|
|
|
4828
4994
|
class CideTextareaComponent {
|
|
4829
4995
|
label = '';
|
|
@@ -9063,5 +9229,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
9063
9229
|
* Generated bundle index. Do not edit.
|
|
9064
9230
|
*/
|
|
9065
9231
|
|
|
9066
|
-
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 };
|
|
9067
9233
|
//# sourceMappingURL=cloud-ide-element.mjs.map
|