cloud-ide-element 1.0.56 → 1.0.57
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 +440 -64
- package/fesm2022/cloud-ide-element.mjs.map +1 -1
- package/index.d.ts +142 -27
- package/package.json +1 -1
|
@@ -1,12 +1,12 @@
|
|
|
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,
|
|
4
|
+
import { Pipe, Injectable, inject, EventEmitter, ViewContainerRef, forwardRef, ViewChild, Output, Input, Component, HostListener, ContentChildren, signal, DestroyRef, computed, input, output, linkedSignal, afterRenderEffect, afterNextRender, Directive, viewChild, effect } from '@angular/core';
|
|
5
5
|
import * as i2 from '@angular/forms';
|
|
6
6
|
import { FormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
|
|
7
|
-
import { BehaviorSubject, Subject, debounceTime, takeUntil, distinctUntilChanged, Observable } from 'rxjs';
|
|
7
|
+
import { BehaviorSubject, Subject, debounceTime, takeUntil, distinctUntilChanged, Observable, retry, catchError, finalize, throwError } from 'rxjs';
|
|
8
8
|
import * as i2$1 from '@angular/router';
|
|
9
|
-
import
|
|
9
|
+
import { HttpClient, HttpEventType } from '@angular/common/http';
|
|
10
10
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
11
11
|
|
|
12
12
|
class CapitalizePipe {
|
|
@@ -2564,88 +2564,307 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
2564
2564
|
type: Output
|
|
2565
2565
|
}] } });
|
|
2566
2566
|
|
|
2567
|
+
class ICoreCyfm {
|
|
2568
|
+
_id;
|
|
2569
|
+
cyfm_name;
|
|
2570
|
+
cyfm_alt_text;
|
|
2571
|
+
cyfm_path;
|
|
2572
|
+
cyfm_size_in_byte;
|
|
2573
|
+
cyfm_type;
|
|
2574
|
+
cyfm_creation_dt;
|
|
2575
|
+
cyfm_id_user;
|
|
2576
|
+
cyfm_permissions;
|
|
2577
|
+
cyfm_tags;
|
|
2578
|
+
cyfm_version;
|
|
2579
|
+
cyfm_file_status_sygmt;
|
|
2580
|
+
cyfm_isactive;
|
|
2581
|
+
}
|
|
2582
|
+
/* INTERFACE END */
|
|
2583
|
+
/* MODEL START */
|
|
2584
|
+
class MFileManager {
|
|
2585
|
+
cyfm_id = "";
|
|
2586
|
+
constructor(init) {
|
|
2587
|
+
Object.assign(this, init);
|
|
2588
|
+
}
|
|
2589
|
+
Validate() {
|
|
2590
|
+
let errorLogger = {};
|
|
2591
|
+
// Log errors here
|
|
2592
|
+
if (!this.cyfm_id) {
|
|
2593
|
+
errorLogger.cyfm_id = "File ID is Required!";
|
|
2594
|
+
}
|
|
2595
|
+
return errorLogger;
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
class ICoreCyfmSave extends ICoreCyfm {
|
|
2599
|
+
cyfm_file_base64 = "";
|
|
2600
|
+
cyfm_temp_unique_id = "";
|
|
2601
|
+
constructor(init) {
|
|
2602
|
+
super();
|
|
2603
|
+
Object.assign(this, init);
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
class CoreFileManagerInsertUpdatePayload {
|
|
2607
|
+
core_file_manager_new = [];
|
|
2608
|
+
core_file_manager_update = [];
|
|
2609
|
+
core_file_manager_delete = [];
|
|
2610
|
+
constructor(init) {
|
|
2611
|
+
Object.assign(this, init);
|
|
2612
|
+
}
|
|
2613
|
+
Validate() {
|
|
2614
|
+
let errorLogger = {};
|
|
2615
|
+
// Log errors here
|
|
2616
|
+
return errorLogger;
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2567
2620
|
class CideEleFileManagerService {
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2621
|
+
// Angular 20: Modern dependency injection
|
|
2622
|
+
http = inject(HttpClient);
|
|
2623
|
+
destroyRef = inject(DestroyRef);
|
|
2624
|
+
// Angular 20: Signal-based state management
|
|
2625
|
+
_baseUrl = signal('/api', ...(ngDevMode ? [{ debugName: "_baseUrl" }] : []));
|
|
2626
|
+
_isUploading = signal(false, ...(ngDevMode ? [{ debugName: "_isUploading" }] : []));
|
|
2627
|
+
_uploadQueue = signal([], ...(ngDevMode ? [{ debugName: "_uploadQueue" }] : []));
|
|
2628
|
+
_activeUploads = signal(new Map(), ...(ngDevMode ? [{ debugName: "_activeUploads" }] : []));
|
|
2629
|
+
_error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : []));
|
|
2630
|
+
// Angular 20: Computed values
|
|
2631
|
+
baseUrl = this._baseUrl.asReadonly();
|
|
2632
|
+
isUploading = this._isUploading.asReadonly();
|
|
2633
|
+
uploadQueue = this._uploadQueue.asReadonly();
|
|
2634
|
+
activeUploads = this._activeUploads.asReadonly();
|
|
2635
|
+
error = this._error.asReadonly();
|
|
2636
|
+
hasActiveUploads = computed(() => this._activeUploads().size > 0, ...(ngDevMode ? [{ debugName: "hasActiveUploads" }] : []));
|
|
2637
|
+
queueLength = computed(() => this._uploadQueue().length, ...(ngDevMode ? [{ debugName: "queueLength" }] : []));
|
|
2638
|
+
serviceState = computed(() => ({
|
|
2639
|
+
isUploading: this._isUploading(),
|
|
2640
|
+
uploadQueue: this._uploadQueue(),
|
|
2641
|
+
activeUploads: this._activeUploads(),
|
|
2642
|
+
error: this._error()
|
|
2643
|
+
}), ...(ngDevMode ? [{ debugName: "serviceState" }] : []));
|
|
2644
|
+
constructor() {
|
|
2645
|
+
console.log('🚀 [FileManagerService] Angular 20 service initialized');
|
|
2572
2646
|
}
|
|
2573
2647
|
/**
|
|
2574
2648
|
* Upload a file with base64 encoding and progress tracking
|
|
2649
|
+
* Angular 20: Enhanced with better error handling, retry logic, and signal-based state
|
|
2575
2650
|
* @param file The file to upload
|
|
2576
|
-
* @param
|
|
2651
|
+
* @param options Upload options and additional data
|
|
2577
2652
|
* @param progressCallback Optional callback for progress updates
|
|
2578
2653
|
* @returns Observable with the upload response
|
|
2579
2654
|
*/
|
|
2580
|
-
uploadFile(file,
|
|
2581
|
-
|
|
2655
|
+
uploadFile(file, options, progressCallback) {
|
|
2656
|
+
const fileId = this.generateFileId(file);
|
|
2657
|
+
const uploadOptions = { retryAttempts: 3, timeout: 30000, ...options };
|
|
2658
|
+
// Angular 20: Update service state
|
|
2659
|
+
this._isUploading.set(true);
|
|
2660
|
+
this._error.set(null);
|
|
2661
|
+
this.addToUploadQueue(fileId);
|
|
2662
|
+
console.log('📤 [FileManagerService] Starting upload for:', file.name, 'Size:', file.size, 'bytes');
|
|
2582
2663
|
return new Observable(observer => {
|
|
2583
2664
|
const reader = new FileReader();
|
|
2584
|
-
//
|
|
2665
|
+
// Angular 20: Enhanced progress tracking with signals
|
|
2585
2666
|
reader.onprogress = (event) => {
|
|
2586
2667
|
if (event.lengthComputable && progressCallback) {
|
|
2587
2668
|
const progress = (event.loaded / event.total) * 50; // File reading is 50% of total progress
|
|
2588
2669
|
progressCallback(progress);
|
|
2670
|
+
this.updateUploadProgress(fileId, {
|
|
2671
|
+
loaded: event.loaded,
|
|
2672
|
+
total: event.total,
|
|
2673
|
+
percentage: progress,
|
|
2674
|
+
stage: 'reading'
|
|
2675
|
+
});
|
|
2589
2676
|
}
|
|
2590
2677
|
};
|
|
2591
2678
|
reader.onload = () => {
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
progressCallback
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2679
|
+
try {
|
|
2680
|
+
const base64String = reader.result;
|
|
2681
|
+
const base64Data = base64String.split(',')[1]; // Remove data:image/jpeg;base64, prefix
|
|
2682
|
+
// Update progress to 50% (file reading complete)
|
|
2683
|
+
if (progressCallback) {
|
|
2684
|
+
progressCallback(50);
|
|
2685
|
+
}
|
|
2686
|
+
this.updateUploadProgress(fileId, {
|
|
2687
|
+
loaded: 50,
|
|
2688
|
+
total: 100,
|
|
2689
|
+
percentage: 50,
|
|
2690
|
+
stage: 'uploading'
|
|
2691
|
+
});
|
|
2692
|
+
// Create the file data object with proper typing
|
|
2693
|
+
const fileData = {
|
|
2694
|
+
cyfm_name: file.name,
|
|
2695
|
+
cyfm_alt_text: uploadOptions?.altText || file.name,
|
|
2696
|
+
cyfm_path: '', // Will be set by backend
|
|
2697
|
+
cyfm_size_in_byte: file.size,
|
|
2698
|
+
cyfm_type: file.type,
|
|
2699
|
+
cyfm_creation_dt: new Date().toISOString(),
|
|
2700
|
+
cyfm_id_user: uploadOptions?.userId || '',
|
|
2701
|
+
cyfm_permissions: [], // Match the interface type
|
|
2702
|
+
cyfm_tags: uploadOptions?.tags || [],
|
|
2703
|
+
cyfm_version: 1,
|
|
2704
|
+
cyfm_file_status_sygmt: uploadOptions?.fileStatus || 'file_manager_file_status_active',
|
|
2705
|
+
cyfm_isactive: true,
|
|
2706
|
+
cyfm_file_base64: base64Data,
|
|
2707
|
+
cyfm_temp_unique_id: fileId
|
|
2708
|
+
};
|
|
2709
|
+
// Create the payload with proper typing
|
|
2710
|
+
const payload = new CoreFileManagerInsertUpdatePayload({
|
|
2711
|
+
core_file_manager_new: [fileData],
|
|
2712
|
+
core_file_manager_update: [],
|
|
2713
|
+
core_file_manager_delete: []
|
|
2714
|
+
});
|
|
2715
|
+
// Angular 20: Enhanced HTTP request with retry logic and better error handling
|
|
2716
|
+
// Use proper API endpoint from cloud-ide-lms-model
|
|
2717
|
+
this.http.post(`${this._baseUrl()}`, payload, {
|
|
2718
|
+
reportProgress: true,
|
|
2719
|
+
observe: 'events'
|
|
2720
|
+
}).pipe(retry(uploadOptions.retryAttempts || 3), catchError(this.handleError.bind(this)), finalize(() => {
|
|
2721
|
+
this.removeFromUploadQueue(fileId);
|
|
2722
|
+
this.removeActiveUpload(fileId);
|
|
2723
|
+
if (this._uploadQueue().length === 0) {
|
|
2724
|
+
this._isUploading.set(false);
|
|
2624
2725
|
}
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2726
|
+
}), takeUntilDestroyed(this.destroyRef)).subscribe({
|
|
2727
|
+
next: (event) => {
|
|
2728
|
+
if (event.type === HttpEventType.UploadProgress && progressCallback) {
|
|
2729
|
+
const uploadProgress = (event.loaded / event.total) * 50; // Upload is remaining 50%
|
|
2730
|
+
const totalProgress = 50 + uploadProgress; // Total progress (50% file reading + upload progress)
|
|
2731
|
+
progressCallback(totalProgress);
|
|
2732
|
+
this.updateUploadProgress(fileId, {
|
|
2733
|
+
loaded: event.loaded,
|
|
2734
|
+
total: event.total,
|
|
2735
|
+
percentage: totalProgress,
|
|
2736
|
+
stage: 'uploading'
|
|
2737
|
+
});
|
|
2738
|
+
}
|
|
2739
|
+
else if (event.type === HttpEventType.Response) {
|
|
2740
|
+
if (progressCallback) {
|
|
2741
|
+
progressCallback(100);
|
|
2742
|
+
}
|
|
2743
|
+
this.updateUploadProgress(fileId, {
|
|
2744
|
+
loaded: 100,
|
|
2745
|
+
total: 100,
|
|
2746
|
+
percentage: 100,
|
|
2747
|
+
stage: 'complete'
|
|
2748
|
+
});
|
|
2749
|
+
console.log('✅ [FileManagerService] Upload successful:', event.body);
|
|
2750
|
+
// Handle the CoreFileManagerInsertUpdateResponse
|
|
2751
|
+
const response = event.body;
|
|
2752
|
+
if (response.success) {
|
|
2753
|
+
observer.next(response);
|
|
2754
|
+
observer.complete();
|
|
2755
|
+
}
|
|
2756
|
+
else {
|
|
2757
|
+
observer.error(new Error(response.message || 'Upload failed'));
|
|
2758
|
+
}
|
|
2628
2759
|
}
|
|
2629
|
-
|
|
2760
|
+
},
|
|
2761
|
+
error: (error) => {
|
|
2762
|
+
console.error('❌ [FileManagerService] Upload failed:', error);
|
|
2763
|
+
this._error.set(error.message || 'Upload failed');
|
|
2764
|
+
this.updateUploadProgress(fileId, {
|
|
2765
|
+
loaded: 0,
|
|
2766
|
+
total: 100,
|
|
2767
|
+
percentage: 0,
|
|
2768
|
+
stage: 'error'
|
|
2769
|
+
});
|
|
2770
|
+
observer.error(error);
|
|
2630
2771
|
}
|
|
2631
|
-
}
|
|
2632
|
-
|
|
2633
|
-
|
|
2772
|
+
});
|
|
2773
|
+
}
|
|
2774
|
+
catch (error) {
|
|
2775
|
+
console.error('❌ [FileManagerService] File processing error:', error);
|
|
2776
|
+
this._error.set('Failed to process file');
|
|
2777
|
+
observer.error(error);
|
|
2778
|
+
}
|
|
2779
|
+
};
|
|
2780
|
+
reader.onerror = () => {
|
|
2781
|
+
console.error('❌ [FileManagerService] File reading failed');
|
|
2782
|
+
this._error.set('Failed to read file');
|
|
2783
|
+
observer.error(new Error('Failed to read file'));
|
|
2634
2784
|
};
|
|
2635
|
-
reader.onerror = () => observer.error('Failed to read file');
|
|
2636
2785
|
reader.readAsDataURL(file);
|
|
2637
2786
|
});
|
|
2638
2787
|
}
|
|
2639
2788
|
/**
|
|
2640
2789
|
* Set the base URL for API calls
|
|
2641
|
-
*
|
|
2790
|
+
* Angular 20: Using signal-based state management
|
|
2642
2791
|
* @param url The base URL for the API
|
|
2643
2792
|
*/
|
|
2644
2793
|
setBaseUrl(url) {
|
|
2645
2794
|
console.log('🔍 [FileManagerService] Setting base URL:', url);
|
|
2646
|
-
this.
|
|
2795
|
+
this._baseUrl.set(url);
|
|
2647
2796
|
}
|
|
2648
|
-
|
|
2797
|
+
/**
|
|
2798
|
+
* Angular 20: Helper methods for state management
|
|
2799
|
+
*/
|
|
2800
|
+
generateFileId(file) {
|
|
2801
|
+
return `${file.name}_${file.size}_${Date.now()}`;
|
|
2802
|
+
}
|
|
2803
|
+
addToUploadQueue(fileId) {
|
|
2804
|
+
const currentQueue = this._uploadQueue();
|
|
2805
|
+
this._uploadQueue.set([...currentQueue, fileId]);
|
|
2806
|
+
}
|
|
2807
|
+
removeFromUploadQueue(fileId) {
|
|
2808
|
+
const currentQueue = this._uploadQueue();
|
|
2809
|
+
this._uploadQueue.set(currentQueue.filter(id => id !== fileId));
|
|
2810
|
+
}
|
|
2811
|
+
updateUploadProgress(fileId, progress) {
|
|
2812
|
+
const currentUploads = new Map(this._activeUploads());
|
|
2813
|
+
currentUploads.set(fileId, progress);
|
|
2814
|
+
this._activeUploads.set(currentUploads);
|
|
2815
|
+
}
|
|
2816
|
+
removeActiveUpload(fileId) {
|
|
2817
|
+
const currentUploads = new Map(this._activeUploads());
|
|
2818
|
+
currentUploads.delete(fileId);
|
|
2819
|
+
this._activeUploads.set(currentUploads);
|
|
2820
|
+
}
|
|
2821
|
+
/**
|
|
2822
|
+
* Angular 20: Enhanced error handling
|
|
2823
|
+
*/
|
|
2824
|
+
handleError(error) {
|
|
2825
|
+
let errorMessage = 'An unknown error occurred';
|
|
2826
|
+
if (error.error instanceof ErrorEvent) {
|
|
2827
|
+
// Client-side error
|
|
2828
|
+
errorMessage = `Client Error: ${error.error.message}`;
|
|
2829
|
+
}
|
|
2830
|
+
else {
|
|
2831
|
+
// Server-side error
|
|
2832
|
+
errorMessage = `Server Error: ${error.status} - ${error.message}`;
|
|
2833
|
+
}
|
|
2834
|
+
console.error('❌ [FileManagerService] HTTP Error:', errorMessage);
|
|
2835
|
+
this._error.set(errorMessage);
|
|
2836
|
+
return throwError(() => new Error(errorMessage));
|
|
2837
|
+
}
|
|
2838
|
+
/**
|
|
2839
|
+
* Angular 20: Service utility methods
|
|
2840
|
+
*/
|
|
2841
|
+
clearError() {
|
|
2842
|
+
this._error.set(null);
|
|
2843
|
+
}
|
|
2844
|
+
getUploadProgress(fileId) {
|
|
2845
|
+
return this._activeUploads().get(fileId);
|
|
2846
|
+
}
|
|
2847
|
+
cancelUpload(fileId) {
|
|
2848
|
+
this.removeFromUploadQueue(fileId);
|
|
2849
|
+
this.removeActiveUpload(fileId);
|
|
2850
|
+
console.log('🚫 [FileManagerService] Upload cancelled for:', fileId);
|
|
2851
|
+
}
|
|
2852
|
+
/**
|
|
2853
|
+
* Angular 20: File validation utility
|
|
2854
|
+
*/
|
|
2855
|
+
validateFile(file, maxSizeMB = 10, allowedTypes = []) {
|
|
2856
|
+
// Check file size
|
|
2857
|
+
const maxSizeBytes = maxSizeMB * 1024 * 1024; // Convert MB to bytes
|
|
2858
|
+
if (file.size > maxSizeBytes) {
|
|
2859
|
+
return { valid: false, error: `File size exceeds ${maxSizeMB}MB limit` };
|
|
2860
|
+
}
|
|
2861
|
+
// Check file type
|
|
2862
|
+
if (allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
|
|
2863
|
+
return { valid: false, error: `File type ${file.type} is not allowed` };
|
|
2864
|
+
}
|
|
2865
|
+
return { valid: true };
|
|
2866
|
+
}
|
|
2867
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileManagerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2649
2868
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileManagerService, providedIn: 'root' });
|
|
2650
2869
|
}
|
|
2651
2870
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileManagerService, decorators: [{
|
|
@@ -2653,7 +2872,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
2653
2872
|
args: [{
|
|
2654
2873
|
providedIn: 'root'
|
|
2655
2874
|
}]
|
|
2656
|
-
}], ctorParameters: () => [
|
|
2875
|
+
}], ctorParameters: () => [] });
|
|
2657
2876
|
|
|
2658
2877
|
class NotificationService {
|
|
2659
2878
|
notifications = signal([], ...(ngDevMode ? [{ debugName: "notifications" }] : []));
|
|
@@ -2879,6 +3098,7 @@ class CideEleFileInputComponent {
|
|
|
2879
3098
|
notificationService = inject(NotificationService);
|
|
2880
3099
|
elementService = inject(CideElementsService);
|
|
2881
3100
|
destroyRef = inject(DestroyRef);
|
|
3101
|
+
// private readonly pendingTasks = inject(PendingTasks); // TODO: Fix PendingTasks API usage
|
|
2882
3102
|
// Modern input signals
|
|
2883
3103
|
label = input('Choose file', ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
2884
3104
|
accept = input('', ...(ngDevMode ? [{ debugName: "accept" }] : []));
|
|
@@ -2910,6 +3130,26 @@ class CideEleFileInputComponent {
|
|
|
2910
3130
|
fileNames = signal([], ...(ngDevMode ? [{ debugName: "fileNames" }] : []));
|
|
2911
3131
|
previewUrls = signal([], ...(ngDevMode ? [{ debugName: "previewUrls" }] : []));
|
|
2912
3132
|
uploadNotificationId = signal(null, ...(ngDevMode ? [{ debugName: "uploadNotificationId" }] : []));
|
|
3133
|
+
isDragOver = signal(false, ...(ngDevMode ? [{ debugName: "isDragOver" }] : []));
|
|
3134
|
+
// Angular 20: Linked signals for better relationships
|
|
3135
|
+
hasFiles = linkedSignal(() => this.files() !== null && this.files().length > 0);
|
|
3136
|
+
canUpload = linkedSignal(() => this.hasFiles() && !this.isUploading() && !this.disabled());
|
|
3137
|
+
isInErrorState = linkedSignal(() => this.uploadStatus() === 'error');
|
|
3138
|
+
isInSuccessState = linkedSignal(() => this.uploadStatus() === 'success');
|
|
3139
|
+
// Angular 20: Computed values using new features
|
|
3140
|
+
totalFileSize = computed(() => {
|
|
3141
|
+
if (!this.files())
|
|
3142
|
+
return 0;
|
|
3143
|
+
return Array.from(this.files()).reduce((total, file) => total + file.size, 0);
|
|
3144
|
+
}, ...(ngDevMode ? [{ debugName: "totalFileSize" }] : []));
|
|
3145
|
+
fileSizeInMB = computed(() => {
|
|
3146
|
+
// Angular 20: Using ** operator for exponentiation
|
|
3147
|
+
return (this.totalFileSize() / (1024 ** 2)).toFixed(2);
|
|
3148
|
+
}, ...(ngDevMode ? [{ debugName: "fileSizeInMB" }] : []));
|
|
3149
|
+
uploadProgressPercentage = computed(() => {
|
|
3150
|
+
// Angular 20: Using ** operator for exponentiation
|
|
3151
|
+
return Math.round(this.uploadProgress() ** 1); // Simple power operation
|
|
3152
|
+
}, ...(ngDevMode ? [{ debugName: "uploadProgressPercentage" }] : []));
|
|
2913
3153
|
// ControlValueAccessor callbacks
|
|
2914
3154
|
onChange = (files) => { };
|
|
2915
3155
|
onTouched = () => { };
|
|
@@ -2918,6 +3158,23 @@ class CideEleFileInputComponent {
|
|
|
2918
3158
|
hasImages = computed(() => this.previewUrls().length > 0, ...(ngDevMode ? [{ debugName: "hasImages" }] : []));
|
|
2919
3159
|
isPreviewBoxMode = computed(() => this.previewBoxMode() && this.showPreview(), ...(ngDevMode ? [{ debugName: "isPreviewBoxMode" }] : []));
|
|
2920
3160
|
isImagePreviewAvailable = computed(() => this.showPreview() && this.previewUrls().length > 0, ...(ngDevMode ? [{ debugName: "isImagePreviewAvailable" }] : []));
|
|
3161
|
+
constructor() {
|
|
3162
|
+
// Angular 20: afterRenderEffect for DOM operations
|
|
3163
|
+
afterRenderEffect(() => {
|
|
3164
|
+
// Update file input element when files change
|
|
3165
|
+
if (this.files()) {
|
|
3166
|
+
const fileInput = document.getElementById('cide-file-input-' + this.id());
|
|
3167
|
+
if (fileInput) {
|
|
3168
|
+
// Ensure the input reflects the current state
|
|
3169
|
+
fileInput.files = this.files();
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
});
|
|
3173
|
+
// Angular 20: afterNextRender for one-time DOM operations
|
|
3174
|
+
afterNextRender(() => {
|
|
3175
|
+
console.log('🎯 [FileInput] Component rendered and DOM is ready');
|
|
3176
|
+
});
|
|
3177
|
+
}
|
|
2921
3178
|
writeValue(files) {
|
|
2922
3179
|
console.log('📝 [FileInput] writeValue called with:', files ? Array.from(files).map(f => f.name) : 'null');
|
|
2923
3180
|
this.files.set(files);
|
|
@@ -2975,6 +3232,9 @@ class CideEleFileInputComponent {
|
|
|
2975
3232
|
}
|
|
2976
3233
|
uploadFile(file) {
|
|
2977
3234
|
console.log('📤 [FileInput] uploadFile called for:', file.name, 'Size:', file.size, 'bytes');
|
|
3235
|
+
// Angular 20: Use PendingTasks for better loading state management
|
|
3236
|
+
// const uploadTask = this.pendingTasks.add(); // TODO: Fix PendingTasks API usage
|
|
3237
|
+
// console.log('⏳ [FileInput] Pending task added for upload tracking');
|
|
2978
3238
|
// Set upload status to 'start' before starting upload
|
|
2979
3239
|
this.uploadStatus.set('start');
|
|
2980
3240
|
console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
|
|
@@ -3031,6 +3291,9 @@ class CideEleFileInputComponent {
|
|
|
3031
3291
|
}).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
|
3032
3292
|
next: (response) => {
|
|
3033
3293
|
console.log('🎉 [FileInput] Upload SUCCESS - Response received:', response);
|
|
3294
|
+
// Angular 20: Complete the pending task
|
|
3295
|
+
// this.pendingTasks.complete(uploadTask); // TODO: Fix PendingTasks API usage
|
|
3296
|
+
// console.log('✅ [FileInput] Pending task completed for successful upload');
|
|
3034
3297
|
// Set upload status to 'success'
|
|
3035
3298
|
this.uploadStatus.set('success');
|
|
3036
3299
|
console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
|
|
@@ -3046,14 +3309,13 @@ class CideEleFileInputComponent {
|
|
|
3046
3309
|
}
|
|
3047
3310
|
this.notificationService.success('✅ File uploaded successfully!', { duration: 0 });
|
|
3048
3311
|
this.uploadNotificationId.set(null);
|
|
3049
|
-
// Extract ID from
|
|
3050
|
-
const uploadedId = response?.
|
|
3312
|
+
// Extract ID from CoreFileManagerInsertUpdateResponse
|
|
3313
|
+
const uploadedId = response?.data?.core_file_manager?.[0]?.cyfm_id;
|
|
3051
3314
|
if (uploadedId) {
|
|
3052
3315
|
console.log('✅ [FileInput] File uploaded successfully with ID:', uploadedId);
|
|
3053
3316
|
this.uploadSuccess.emit(uploadedId);
|
|
3054
|
-
//
|
|
3055
|
-
|
|
3056
|
-
console.log('📝 [FileInput] Form control value updated with uploaded file ID');
|
|
3317
|
+
// Note: Form control value remains as FileList - uploaded ID is emitted separately
|
|
3318
|
+
console.log('📝 [FileInput] Upload success event emitted with file ID:', uploadedId);
|
|
3057
3319
|
}
|
|
3058
3320
|
else {
|
|
3059
3321
|
console.error('❌ [FileInput] Upload successful but no ID returned:', response);
|
|
@@ -3067,6 +3329,9 @@ class CideEleFileInputComponent {
|
|
|
3067
3329
|
},
|
|
3068
3330
|
error: (error) => {
|
|
3069
3331
|
console.error('💥 [FileInput] Upload FAILED:', error);
|
|
3332
|
+
// Angular 20: Complete the pending task even on error
|
|
3333
|
+
// this.pendingTasks.complete(uploadTask); // TODO: Fix PendingTasks API usage
|
|
3334
|
+
// console.log('❌ [FileInput] Pending task completed for failed upload');
|
|
3070
3335
|
// Set upload status to 'error' and remove upload validation error
|
|
3071
3336
|
this.uploadStatus.set('error');
|
|
3072
3337
|
console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
|
|
@@ -3158,9 +3423,120 @@ class CideEleFileInputComponent {
|
|
|
3158
3423
|
fileInput.click();
|
|
3159
3424
|
}
|
|
3160
3425
|
}
|
|
3426
|
+
// Drag and Drop Event Handlers
|
|
3427
|
+
onDragOver(event) {
|
|
3428
|
+
event.preventDefault();
|
|
3429
|
+
event.stopPropagation();
|
|
3430
|
+
if (!this.disabled()) {
|
|
3431
|
+
this.isDragOver.set(true);
|
|
3432
|
+
console.log('🔄 [FileInput] Drag over detected');
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
onDragLeave(event) {
|
|
3436
|
+
event.preventDefault();
|
|
3437
|
+
event.stopPropagation();
|
|
3438
|
+
this.isDragOver.set(false);
|
|
3439
|
+
console.log('🔄 [FileInput] Drag leave detected');
|
|
3440
|
+
}
|
|
3441
|
+
onDragEnter(event) {
|
|
3442
|
+
event.preventDefault();
|
|
3443
|
+
event.stopPropagation();
|
|
3444
|
+
if (!this.disabled()) {
|
|
3445
|
+
this.isDragOver.set(true);
|
|
3446
|
+
console.log('🔄 [FileInput] Drag enter detected');
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
onDrop(event) {
|
|
3450
|
+
event.preventDefault();
|
|
3451
|
+
event.stopPropagation();
|
|
3452
|
+
this.isDragOver.set(false);
|
|
3453
|
+
if (this.disabled()) {
|
|
3454
|
+
console.log('⏸️ [FileInput] Drop ignored - component is disabled');
|
|
3455
|
+
return;
|
|
3456
|
+
}
|
|
3457
|
+
const files = event.dataTransfer?.files;
|
|
3458
|
+
if (files && files.length > 0) {
|
|
3459
|
+
console.log('📁 [FileInput] Files dropped:', Array.from(files).map(f => f.name));
|
|
3460
|
+
// Validate file types if accept is specified
|
|
3461
|
+
if (this.accept() && !this.validateFileTypes(files)) {
|
|
3462
|
+
console.log('❌ [FileInput] Invalid file types dropped');
|
|
3463
|
+
this.notificationService.error('❌ Invalid file type. Please select files of the correct type.', { duration: 0 });
|
|
3464
|
+
return;
|
|
3465
|
+
}
|
|
3466
|
+
// Handle single vs multiple files
|
|
3467
|
+
if (!this.multiple() && files.length > 1) {
|
|
3468
|
+
console.log('⚠️ [FileInput] Multiple files dropped but multiple is disabled');
|
|
3469
|
+
this.notificationService.warning('⚠️ Only one file is allowed. Using the first file.', { duration: 0 });
|
|
3470
|
+
// Create a new FileList with only the first file
|
|
3471
|
+
const dt = new DataTransfer();
|
|
3472
|
+
dt.items.add(files[0]);
|
|
3473
|
+
this.handleFileSelection(dt.files);
|
|
3474
|
+
}
|
|
3475
|
+
else {
|
|
3476
|
+
this.handleFileSelection(files);
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
validateFileTypes(files) {
|
|
3481
|
+
const acceptTypes = this.accept().split(',').map(type => type.trim());
|
|
3482
|
+
if (acceptTypes.length === 0 || acceptTypes[0] === '')
|
|
3483
|
+
return true;
|
|
3484
|
+
return Array.from(files).every(file => {
|
|
3485
|
+
return acceptTypes.some(acceptType => {
|
|
3486
|
+
if (acceptType.startsWith('.')) {
|
|
3487
|
+
// Extension-based validation
|
|
3488
|
+
return file.name.toLowerCase().endsWith(acceptType.toLowerCase());
|
|
3489
|
+
}
|
|
3490
|
+
else if (acceptType.includes('/')) {
|
|
3491
|
+
// MIME type validation
|
|
3492
|
+
return file.type === acceptType || file.type.startsWith(acceptType.replace('*', ''));
|
|
3493
|
+
}
|
|
3494
|
+
return false;
|
|
3495
|
+
});
|
|
3496
|
+
});
|
|
3497
|
+
}
|
|
3498
|
+
handleFileSelection(files) {
|
|
3499
|
+
this.files.set(files);
|
|
3500
|
+
this.fileNames.set(Array.from(files).map(f => f.name));
|
|
3501
|
+
console.log('📁 [FileInput] Files selected via drag & drop:', this.fileNames());
|
|
3502
|
+
this.generatePreviews();
|
|
3503
|
+
// Reset upload status when new file is selected
|
|
3504
|
+
this.uploadStatus.set('idle');
|
|
3505
|
+
console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus());
|
|
3506
|
+
this.onChange(files);
|
|
3507
|
+
this.fileChange.emit(files);
|
|
3508
|
+
this.onTouched();
|
|
3509
|
+
this.onValidatorChange();
|
|
3510
|
+
// Auto upload if enabled
|
|
3511
|
+
if (this.autoUpload() && files.length > 0) {
|
|
3512
|
+
console.log('🚀 [FileInput] Auto upload enabled, starting upload for:', files[0].name);
|
|
3513
|
+
this.uploadFile(files[0]);
|
|
3514
|
+
}
|
|
3515
|
+
else {
|
|
3516
|
+
console.log('⏸️ [FileInput] Auto upload disabled or no files');
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3161
3519
|
isRequired() {
|
|
3162
3520
|
return this.required();
|
|
3163
3521
|
}
|
|
3522
|
+
/**
|
|
3523
|
+
* Angular 20: Utility method to get upload data with proper typing
|
|
3524
|
+
* @returns Properly typed upload data
|
|
3525
|
+
*/
|
|
3526
|
+
getUploadData() {
|
|
3527
|
+
return this.uploadData();
|
|
3528
|
+
}
|
|
3529
|
+
/**
|
|
3530
|
+
* Angular 20: Utility method to update upload data with type safety
|
|
3531
|
+
* @param data Partial upload data to merge with existing data
|
|
3532
|
+
*/
|
|
3533
|
+
updateUploadData(data) {
|
|
3534
|
+
const currentData = this.uploadData();
|
|
3535
|
+
const updatedData = { ...currentData, ...data };
|
|
3536
|
+
// Note: This would require the uploadData to be a writable signal
|
|
3537
|
+
// For now, this method serves as a type-safe way to work with upload data
|
|
3538
|
+
console.log('📝 [FileInput] Upload data updated:', updatedData);
|
|
3539
|
+
}
|
|
3164
3540
|
getCurrentState() {
|
|
3165
3541
|
return {
|
|
3166
3542
|
id: this.id(),
|
|
@@ -3222,7 +3598,7 @@ class CideEleFileInputComponent {
|
|
|
3222
3598
|
return null; // No validation errors
|
|
3223
3599
|
}
|
|
3224
3600
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3225
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
3601
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: CideEleFileInputComponent, isStandalone: true, selector: "cide-ele-file-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, errorText: { classPropertyName: "errorText", publicName: "errorText", isSignal: true, isRequired: false, transformFunction: null }, showPreview: { classPropertyName: "showPreview", publicName: "showPreview", isSignal: true, isRequired: false, transformFunction: null }, previewWidth: { classPropertyName: "previewWidth", publicName: "previewWidth", isSignal: true, isRequired: false, transformFunction: null }, previewHeight: { classPropertyName: "previewHeight", publicName: "previewHeight", isSignal: true, isRequired: false, transformFunction: null }, previewBoxMode: { classPropertyName: "previewBoxMode", publicName: "previewBoxMode", isSignal: true, isRequired: false, transformFunction: null }, showFileName: { classPropertyName: "showFileName", publicName: "showFileName", isSignal: true, isRequired: false, transformFunction: null }, placeholderText: { classPropertyName: "placeholderText", publicName: "placeholderText", isSignal: true, isRequired: false, transformFunction: null }, placeholderIcon: { classPropertyName: "placeholderIcon", publicName: "placeholderIcon", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null }, uploadData: { classPropertyName: "uploadData", publicName: "uploadData", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileChange: "fileChange", uploadSuccess: "uploadSuccess", uploadError: "uploadError", uploadProgressChange: "uploadProgressChange" }, providers: [
|
|
3226
3602
|
{
|
|
3227
3603
|
provide: NG_VALUE_ACCESSOR,
|
|
3228
3604
|
useExisting: CideEleFileInputComponent,
|
|
@@ -3233,7 +3609,7 @@ class CideEleFileInputComponent {
|
|
|
3233
3609
|
useExisting: CideEleFileInputComponent,
|
|
3234
3610
|
multi: true
|
|
3235
3611
|
}
|
|
3236
|
-
], 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 <label *ngIf=\"label() && !isPreviewBoxMode()\" class=\"cide-file-input-label\" [attr.for]=\"'cide-file-input-' + id()\">\n {{ label() }}<span *ngIf=\"required()\" class=\"cide-file-input-required\"> *</span>\n </label>\n \n <!-- Preview Box Mode -->\n <div *ngIf=\"isPreviewBoxMode()\" 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]=\"accept()\"\n [attr.multiple]=\"multiple() ? true : null\"\n [disabled]=\"disabled()\"\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]=\"disabled()\"\n [class.cide-file-input-preview-box-has-image]=\"hasImages()\"\n [style.width]=\"previewWidth()\"\n [style.height]=\"previewHeight()\"\n (click)=\"triggerFileSelect()\"\n [attr.title]=\"disabled() ? 'File selection disabled' : placeholderText()\">\n \n <!-- No Image State -->\n <div *ngIf=\"!hasImages()\" class=\"cide-file-input-preview-box-placeholder\">\n <div class=\"cide-file-input-preview-box-icon\">\n <cide-ele-icon>{{ placeholderIcon() }}</cide-ele-icon>\n </div>\n <div class=\"cide-file-input-preview-box-text\">{{ placeholderText() }}</div>\n </div>\n \n <!-- Image Preview State -->\n <div *ngIf=\"hasImages()\" 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 <button \n *ngIf=\"!disabled()\"\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 </div>\n </div>\n \n <!-- File name display for preview box mode -->\n <div *ngIf=\"hasImages() && fileNames().length && showFileName()\" class=\"cide-file-input-preview-box-filename\">\n {{ fileNames()[0] }}\n </div>\n </div>\n\n <!-- Standard Mode -->\n <div *ngIf=\"!isPreviewBoxMode()\" class=\"cide-file-input-wrapper\">\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id()\"\n [attr.accept]=\"accept()\"\n [attr.multiple]=\"multiple() ? true : null\"\n [disabled]=\"disabled()\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-element\"\n />\n <button *ngIf=\"fileNames().length\" type=\"button\" class=\"cide-file-input-clear\" (click)=\"clearFiles()\">\n Clear\n </button>\n </div>\n <div *ngIf=\"fileNames().length && !isPreviewBoxMode()\" class=\"cide-file-input-files\">\n <span *ngFor=\"let name of fileNames()\">{{ name }}</span>\n </div>\n \n <!-- Image Preview Section (only for standard mode) -->\n <div *ngIf=\"isImagePreviewAvailable() && !isPreviewBoxMode()\" class=\"cide-file-input-preview\">\n <div class=\"cide-file-input-preview-label\">Preview:</div>\n <div class=\"cide-file-input-preview-container\">\n <div \n *ngFor=\"let previewUrl of previewUrls(); let i = index\" \n class=\"cide-file-input-preview-item\"\n [style.width]=\"previewWidth()\"\n [style.height]=\"previewHeight()\">\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 </div>\n </div>\n \n <div *ngIf=\"errorText()\" class=\"cide-file-input-error\">{{ errorText() }}</div>\n <div *ngIf=\"helperText() && !errorText()\" class=\"cide-file-input-helper\">{{ helperText() }}</div>\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}.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-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-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: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }] });
|
|
3612
|
+
], 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 (label() && !isPreviewBoxMode()) {\n <label class=\"cide-file-input-label\" [attr.for]=\"'cide-file-input-' + id()\">\n {{ label() }}@if (required()) {<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]=\"accept()\"\n [attr.multiple]=\"multiple() ? true : null\"\n [disabled]=\"disabled()\"\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]=\"disabled()\"\n [class.cide-file-input-preview-box-has-image]=\"hasImages()\"\n [class.cide-file-input-preview-box-drag-over]=\"isDragOver()\"\n [style.width]=\"previewWidth()\"\n [style.height]=\"previewHeight()\"\n (click)=\"triggerFileSelect()\"\n (dragover)=\"onDragOver($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [attr.title]=\"disabled() ? 'File selection disabled' : placeholderText()\">\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' : placeholderIcon() }}</cide-ele-icon>\n </div>\n <div class=\"cide-file-input-preview-box-text\">\n {{ isDragOver() ? 'Drop files here...' : placeholderText() }}\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 (!disabled()) {\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 && showFileName()) {\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]=\"accept()\"\n [attr.multiple]=\"multiple() ? true : null\"\n [disabled]=\"disabled()\"\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]=\"previewWidth()\"\n [style.height]=\"previewHeight()\">\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 (errorText()) {\n <div class=\"cide-file-input-error\">{{ errorText() }}</div>\n }\n @if (helperText() && !errorText()) {\n <div class=\"cide-file-input-helper\">{{ helperText() }}</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"] }] });
|
|
3237
3613
|
}
|
|
3238
3614
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileInputComponent, decorators: [{
|
|
3239
3615
|
type: Component,
|
|
@@ -3248,8 +3624,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
3248
3624
|
useExisting: CideEleFileInputComponent,
|
|
3249
3625
|
multi: true
|
|
3250
3626
|
}
|
|
3251
|
-
], 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 <label *ngIf=\"label() && !isPreviewBoxMode()\" class=\"cide-file-input-label\" [attr.for]=\"'cide-file-input-' + id()\">\n {{ label() }}<span *ngIf=\"required()\" class=\"cide-file-input-required\"> *</span>\n </label>\n \n <!-- Preview Box Mode -->\n <div *ngIf=\"isPreviewBoxMode()\" 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]=\"accept()\"\n [attr.multiple]=\"multiple() ? true : null\"\n [disabled]=\"disabled()\"\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]=\"disabled()\"\n [class.cide-file-input-preview-box-has-image]=\"hasImages()\"\n [style.width]=\"previewWidth()\"\n [style.height]=\"previewHeight()\"\n (click)=\"triggerFileSelect()\"\n [attr.title]=\"disabled() ? 'File selection disabled' : placeholderText()\">\n \n <!-- No Image State -->\n <div *ngIf=\"!hasImages()\" class=\"cide-file-input-preview-box-placeholder\">\n <div class=\"cide-file-input-preview-box-icon\">\n <cide-ele-icon>{{ placeholderIcon() }}</cide-ele-icon>\n </div>\n <div class=\"cide-file-input-preview-box-text\">{{ placeholderText() }}</div>\n </div>\n \n <!-- Image Preview State -->\n <div *ngIf=\"hasImages()\" 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 <button \n *ngIf=\"!disabled()\"\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 </div>\n </div>\n \n <!-- File name display for preview box mode -->\n <div *ngIf=\"hasImages() && fileNames().length && showFileName()\" class=\"cide-file-input-preview-box-filename\">\n {{ fileNames()[0] }}\n </div>\n </div>\n\n <!-- Standard Mode -->\n <div *ngIf=\"!isPreviewBoxMode()\" class=\"cide-file-input-wrapper\">\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id()\"\n [attr.accept]=\"accept()\"\n [attr.multiple]=\"multiple() ? true : null\"\n [disabled]=\"disabled()\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-element\"\n />\n <button *ngIf=\"fileNames().length\" type=\"button\" class=\"cide-file-input-clear\" (click)=\"clearFiles()\">\n Clear\n </button>\n </div>\n <div *ngIf=\"fileNames().length && !isPreviewBoxMode()\" class=\"cide-file-input-files\">\n <span *ngFor=\"let name of fileNames()\">{{ name }}</span>\n </div>\n \n <!-- Image Preview Section (only for standard mode) -->\n <div *ngIf=\"isImagePreviewAvailable() && !isPreviewBoxMode()\" class=\"cide-file-input-preview\">\n <div class=\"cide-file-input-preview-label\">Preview:</div>\n <div class=\"cide-file-input-preview-container\">\n <div \n *ngFor=\"let previewUrl of previewUrls(); let i = index\" \n class=\"cide-file-input-preview-item\"\n [style.width]=\"previewWidth()\"\n [style.height]=\"previewHeight()\">\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 </div>\n </div>\n \n <div *ngIf=\"errorText()\" class=\"cide-file-input-error\">{{ errorText() }}</div>\n <div *ngIf=\"helperText() && !errorText()\" class=\"cide-file-input-helper\">{{ helperText() }}</div>\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}.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-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-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"] }]
|
|
3252
|
-
}] });
|
|
3627
|
+
], 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 (label() && !isPreviewBoxMode()) {\n <label class=\"cide-file-input-label\" [attr.for]=\"'cide-file-input-' + id()\">\n {{ label() }}@if (required()) {<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]=\"accept()\"\n [attr.multiple]=\"multiple() ? true : null\"\n [disabled]=\"disabled()\"\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]=\"disabled()\"\n [class.cide-file-input-preview-box-has-image]=\"hasImages()\"\n [class.cide-file-input-preview-box-drag-over]=\"isDragOver()\"\n [style.width]=\"previewWidth()\"\n [style.height]=\"previewHeight()\"\n (click)=\"triggerFileSelect()\"\n (dragover)=\"onDragOver($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [attr.title]=\"disabled() ? 'File selection disabled' : placeholderText()\">\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' : placeholderIcon() }}</cide-ele-icon>\n </div>\n <div class=\"cide-file-input-preview-box-text\">\n {{ isDragOver() ? 'Drop files here...' : placeholderText() }}\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 (!disabled()) {\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 && showFileName()) {\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]=\"accept()\"\n [attr.multiple]=\"multiple() ? true : null\"\n [disabled]=\"disabled()\"\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]=\"previewWidth()\"\n [style.height]=\"previewHeight()\">\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 (errorText()) {\n <div class=\"cide-file-input-error\">{{ errorText() }}</div>\n }\n @if (helperText() && !errorText()) {\n <div class=\"cide-file-input-helper\">{{ helperText() }}</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"] }]
|
|
3628
|
+
}], ctorParameters: () => [] });
|
|
3253
3629
|
|
|
3254
3630
|
class CideTextareaComponent {
|
|
3255
3631
|
label = '';
|