cloud-ide-element 1.1.94 → 1.1.96
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 +1064 -982
- package/fesm2022/cloud-ide-element.mjs.map +1 -1
- package/index.d.ts +36 -3
- package/package.json +1 -1
|
@@ -1242,8 +1242,9 @@ class CideInputComponent {
|
|
|
1242
1242
|
createDatePickerUsingPortal() {
|
|
1243
1243
|
// Reset to calendar view when opening
|
|
1244
1244
|
this.showMonthYearSelector = false;
|
|
1245
|
-
// Get input element
|
|
1246
|
-
const
|
|
1245
|
+
// Get input element - use idRandom as fallback if id is not provided
|
|
1246
|
+
const elementId = this.id || this.idRandom;
|
|
1247
|
+
const inputElement = document.getElementById(elementId);
|
|
1247
1248
|
if (!inputElement)
|
|
1248
1249
|
return;
|
|
1249
1250
|
// Create template portal configuration
|
|
@@ -3526,7 +3527,7 @@ class NotificationService {
|
|
|
3526
3527
|
return this.show({
|
|
3527
3528
|
message,
|
|
3528
3529
|
type: 'success',
|
|
3529
|
-
icon: '
|
|
3530
|
+
icon: 'done',
|
|
3530
3531
|
duration: 3000,
|
|
3531
3532
|
...options
|
|
3532
3533
|
});
|
|
@@ -3600,7 +3601,7 @@ class NotificationService {
|
|
|
3600
3601
|
return this.show({
|
|
3601
3602
|
message: isComplete ? `${taskName} completed successfully` : `${taskName} in progress...`,
|
|
3602
3603
|
type: isComplete ? 'success' : 'info',
|
|
3603
|
-
icon: isComplete ? '
|
|
3604
|
+
icon: isComplete ? 'done' : 'sync',
|
|
3604
3605
|
duration: isComplete ? 3000 : 0, // Don't auto-dismiss for in-progress tasks
|
|
3605
3606
|
...options
|
|
3606
3607
|
});
|
|
@@ -3708,7 +3709,7 @@ class NotificationService {
|
|
|
3708
3709
|
}
|
|
3709
3710
|
getDefaultIcon(type) {
|
|
3710
3711
|
const iconMap = {
|
|
3711
|
-
success: '
|
|
3712
|
+
success: 'done',
|
|
3712
3713
|
error: 'error',
|
|
3713
3714
|
warning: 'warning',
|
|
3714
3715
|
info: 'info'
|
|
@@ -4319,11 +4320,13 @@ class CideEleFileInputComponent {
|
|
|
4319
4320
|
groupId = signal(null, ...(ngDevMode ? [{ debugName: "groupId" }] : [])); // Group ID for multiple file uploads
|
|
4320
4321
|
isMultipleUploadMode = signal(false, ...(ngDevMode ? [{ debugName: "isMultipleUploadMode" }] : [])); // Flag to track if we're in multiple upload mode
|
|
4321
4322
|
hasEverUploaded = signal(false, ...(ngDevMode ? [{ debugName: "hasEverUploaded" }] : [])); // Track if this component has ever uploaded files
|
|
4323
|
+
failedFile = signal(null, ...(ngDevMode ? [{ debugName: "failedFile" }] : [])); // Store failed file for retry
|
|
4322
4324
|
// Computed signals for better relationships
|
|
4323
4325
|
hasFiles = computed(() => this.files() !== null && this.files().length > 0, ...(ngDevMode ? [{ debugName: "hasFiles" }] : []));
|
|
4324
4326
|
canUpload = computed(() => this.hasFiles() && !this.isUploading() && !this.disabledSignal(), ...(ngDevMode ? [{ debugName: "canUpload" }] : []));
|
|
4325
4327
|
isInErrorState = computed(() => this.uploadStatus() === 'error', ...(ngDevMode ? [{ debugName: "isInErrorState" }] : []));
|
|
4326
4328
|
isInSuccessState = computed(() => this.uploadStatus() === 'success', ...(ngDevMode ? [{ debugName: "isInSuccessState" }] : []));
|
|
4329
|
+
canRetry = computed(() => this.isInErrorState() && this.failedFile() !== null && !this.isUploading() && !this.disabledSignal(), ...(ngDevMode ? [{ debugName: "canRetry" }] : []));
|
|
4327
4330
|
// Optimized computed values - only calculate when needed
|
|
4328
4331
|
totalFileSize = computed(() => {
|
|
4329
4332
|
const files = this.files();
|
|
@@ -4465,10 +4468,27 @@ class CideEleFileInputComponent {
|
|
|
4465
4468
|
this.fileNames.set([]);
|
|
4466
4469
|
this.clearPreviews();
|
|
4467
4470
|
this.uploadStatus.set('idle');
|
|
4471
|
+
this.failedFile.set(null); // Clear failed file when clearing files
|
|
4468
4472
|
console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus());
|
|
4469
4473
|
this.onChange(null);
|
|
4470
4474
|
this.fileChange.emit(null);
|
|
4471
4475
|
}
|
|
4476
|
+
/**
|
|
4477
|
+
* Retry uploading the failed file
|
|
4478
|
+
*/
|
|
4479
|
+
retryUpload() {
|
|
4480
|
+
const failedFile = this.failedFile();
|
|
4481
|
+
if (!failedFile) {
|
|
4482
|
+
console.warn('⚠️ [FileInput] No failed file to retry');
|
|
4483
|
+
return;
|
|
4484
|
+
}
|
|
4485
|
+
console.log('🔄 [FileInput] Retrying upload for file:', failedFile.name);
|
|
4486
|
+
// Clear the failed file reference (will be set again if retry fails)
|
|
4487
|
+
this.failedFile.set(null);
|
|
4488
|
+
// Reset upload status and retry
|
|
4489
|
+
this.uploadStatus.set('idle');
|
|
4490
|
+
this.uploadFile(failedFile);
|
|
4491
|
+
}
|
|
4472
4492
|
uploadFile(file) {
|
|
4473
4493
|
console.log('🚨 [FileInput] uploadFile called for:', file.name, '- SINGLE UPLOAD INITIATED');
|
|
4474
4494
|
// Angular 20: Use PendingTasks for better loading state management
|
|
@@ -4547,6 +4567,8 @@ class CideEleFileInputComponent {
|
|
|
4547
4567
|
const uploadedId = response?.data?.core_file_manager?.[0]?.cyfm_id;
|
|
4548
4568
|
if (uploadedId) {
|
|
4549
4569
|
console.log('✅ [FileInput] File uploaded successfully with ID:', uploadedId);
|
|
4570
|
+
// Clear failed file on successful upload
|
|
4571
|
+
this.failedFile.set(null);
|
|
4550
4572
|
// Set the uploaded ID as the form control value
|
|
4551
4573
|
this.onChange(uploadedId);
|
|
4552
4574
|
console.log('📝 [FileInput] Form control value set to uploaded ID:', uploadedId);
|
|
@@ -4571,6 +4593,9 @@ class CideEleFileInputComponent {
|
|
|
4571
4593
|
// Angular 20: Complete the pending task even on error
|
|
4572
4594
|
// this.pendingTasks.complete(uploadTask); // TODO: Fix PendingTasks API usage
|
|
4573
4595
|
// console.log('❌ [FileInput] Pending task completed for failed upload');
|
|
4596
|
+
// Store the failed file for retry
|
|
4597
|
+
this.failedFile.set(file);
|
|
4598
|
+
console.log('💾 [FileInput] Failed file stored for retry:', file.name);
|
|
4574
4599
|
// Set upload status to 'error' and remove upload validation error
|
|
4575
4600
|
this.uploadStatus.set('error');
|
|
4576
4601
|
console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
|
|
@@ -5293,7 +5318,7 @@ class CideEleFileInputComponent {
|
|
|
5293
5318
|
useExisting: CideEleFileInputComponent,
|
|
5294
5319
|
multi: true
|
|
5295
5320
|
}
|
|
5296
|
-
], usesOnChanges: true, ngImport: i0, template: "<div class=\"tw-flex tw-flex-col tw-gap-2\">\r\n <!-- Label (shown when not in preview box mode or when preview box mode but no label override) -->\r\n @if (labelSignal() && !isPreviewBoxMode()) {\r\n <label class=\"tw-block tw-text-sm tw-font-medium tw-text-gray-700 dark:tw-text-gray-200 tw-mb-1.5 tw-leading-5\" [attr.for]=\"'cide-file-input-' + id()\">\r\n {{ labelSignal() }}@if (requiredSignal()) {<span class=\"tw-text-red-500 dark:tw-text-red-400\"> *</span>}\r\n </label>\r\n }\r\n \r\n <!-- Preview Box Mode -->\r\n @if (isPreviewBoxMode()) {\r\n <div class=\"tw-relative\">\r\n <!-- Hidden file input -->\r\n <input\r\n type=\"file\"\r\n [attr.id]=\"'cide-file-input-' + id()\"\r\n [attr.accept]=\"acceptSignal()\"\r\n [attr.multiple]=\"multipleSignal() ? true : null\"\r\n [disabled]=\"disabledSignal()\"\r\n (change)=\"onFileSelected($event)\"\r\n class=\"tw-hidden\"\r\n />\r\n \r\n <!-- Preview Box -->\r\n <div \r\n class=\"tw-border-2 tw-border-dashed tw-border-gray-300 dark:tw-border-gray-600 tw-rounded-lg tw-bg-gray-50 dark:tw-bg-gray-800 tw-cursor-pointer tw-transition-all tw-duration-200 tw-relative tw-overflow-hidden hover:tw-border-blue-500 hover:tw-bg-blue-50 dark:hover:tw-bg-blue-900/20\"\r\n [class]=\"getPreviewBoxClasses()\"\r\n [style.width]=\"previewWidthSignal()\"\r\n [style.height]=\"previewHeightSignal()\"\r\n (click)=\"triggerFileSelect()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragenter)=\"onDragEnter($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\"\r\n [attr.title]=\"disabledSignal() ? 'File selection disabled' : placeholderTextSignal()\">\r\n \r\n <!-- No Image State -->\r\n @if (!hasImages()) {\r\n <div class=\"tw-flex tw-flex-col tw-items-center tw-justify-center tw-h-full tw-p-4\">\r\n <div class=\"tw-mb-2\">\r\n <cide-ele-icon class=\"tw-text-gray-400 dark:tw-text-gray-500\" size=\"lg\">{{ isDragOver() ? '\uD83D\uDCC1' : placeholderIconSignal() }}</cide-ele-icon>\r\n </div>\r\n <div class=\"tw-text-sm tw-text-gray-600 dark:tw-text-gray-400 tw-text-center\">\r\n {{ isDragOver() ? 'Drop files here...' : placeholderTextSignal() }}\r\n </div>\r\n </div>\r\n }\r\n \r\n <!-- Image Preview State -->\r\n @if (hasImages()) {\r\n <div class=\"tw-relative tw-w-full tw-h-full\">\r\n <img \r\n [src]=\"previewUrls()[0]\" \r\n [alt]=\"fileNames()[0] || 'Preview image'\"\r\n class=\"tw-w-full tw-h-full tw-object-cover tw-rounded-lg\">\r\n <div class=\"tw-absolute tw-inset-0 tw-bg-black tw-bg-opacity-0 hover:tw-bg-opacity-30 tw-transition-all tw-duration-200 tw-flex tw-items-center tw-justify-center tw-rounded-lg\">\r\n <div class=\"tw-text-white tw-text-sm tw-opacity-0 hover:tw-opacity-100 tw-transition-opacity tw-duration-200\">Click to change</div>\r\n </div>\r\n @if (!disabledSignal()) {\r\n <button \r\n type=\"button\" \r\n class=\"tw-absolute tw-top-2 tw-right-2 tw-w-6 tw-h-6 tw-bg-red-500 hover:tw-bg-red-600 tw-text-white tw-rounded-full tw-flex tw-items-center tw-justify-center tw-text-sm tw-font-bold tw-transition-colors tw-duration-200 tw-border-none tw-cursor-pointer\"\r\n (click)=\"clearFiles(); $event.stopPropagation()\"\r\n title=\"Remove image\">\r\n \u00D7\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n \r\n <!-- File name display for preview box mode -->\r\n @if (hasImages() && fileNames().length && showFileNameSignal()) {\r\n <div class=\"tw-mt-2 tw-text-sm tw-text-gray-600 dark:tw-text-gray-400 tw-text-center tw-truncate\">\r\n {{ fileNames()[0] }}\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Standard Mode -->\r\n @if (!isPreviewBoxMode()) {\r\n <!-- Hidden file input -->\r\n <input\r\n type=\"file\"\r\n [attr.id]=\"'cide-file-input-' + id()\"\r\n [attr.accept]=\"acceptSignal()\"\r\n [attr.multiple]=\"multipleSignal() ? true : null\"\r\n [disabled]=\"disabledSignal()\"\r\n (change)=\"onFileSelected($event)\"\r\n class=\"tw-hidden\"\r\n />\r\n \r\n <!-- Modern Drag and Drop Zone -->\r\n <div \r\n class=\"tw-border-2 tw-border-dashed tw-border-gray-300 dark:tw-border-gray-600 tw-rounded-lg tw-bg-gray-50 dark:tw-bg-gray-800 tw-cursor-pointer tw-transition-all tw-duration-200 tw-min-h-[60px] hover:tw-border-blue-500 hover:tw-bg-blue-50 dark:hover:tw-bg-blue-900/20\"\r\n [class]=\"getDragDropZoneClasses()\"\r\n (click)=\"triggerFileSelect()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragenter)=\"onDragEnter($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\">\r\n \r\n <div class=\"tw-flex tw-items-center tw-justify-between tw-p-3 tw-gap-3\">\r\n <!-- Icon and Text -->\r\n <div class=\"tw-flex tw-items-center tw-gap-2.5 tw-flex-1 tw-min-w-0\">\r\n <cide-ele-icon class=\"tw-flex-shrink-0 tw-transition-colors tw-duration-200\" \r\n [class]=\"getIconClasses()\" \r\n size=\"sm\">\r\n {{ isDragOver() ? 'file_download' : (hasFiles() ? 'check_circle' : 'cloud_upload') }}\r\n </cide-ele-icon>\r\n \r\n <div class=\"tw-flex tw-flex-col tw-gap-0.5 tw-min-w-0\">\r\n @if (isDragOver()) {\r\n <span class=\"tw-text-sm tw-font-medium tw-text-blue-700 dark:tw-text-blue-300 tw-whitespace-nowrap tw-overflow-hidden tw-text-ellipsis\">Drop files here</span>\r\n } @else if (hasFiles()) {\r\n <span class=\"tw-text-sm tw-font-medium tw-text-emerald-700 dark:tw-text-emerald-300 tw-whitespace-nowrap tw-overflow-hidden tw-text-ellipsis\">\r\n @if (multipleSignal() && fileNames().length > 1) {\r\n {{ fileNames().length }} files selected\r\n } @else {\r\n {{ fileNames()[0] }}\r\n }\r\n </span>\r\n @if (totalFileSize() > 0) {\r\n <span class=\"tw-text-xs tw-text-emerald-600 dark:tw-text-emerald-400\">{{ fileSizeInMB() }} MB</span>\r\n }\r\n } @else {\r\n <span class=\"tw-text-sm tw-font-medium tw-text-gray-700 dark:tw-text-gray-300 tw-whitespace-nowrap tw-overflow-hidden tw-text-ellipsis\">\r\n {{ multipleSignal() ? 'Choose files or drag here' : 'Choose file or drag here' }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n \r\n <!-- Action Buttons -->\r\n <div class=\"tw-flex tw-gap-1 tw-flex-shrink-0\">\r\n @if (hasFiles()) {\r\n <button type=\"button\" \r\n class=\"tw-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-border-none tw-rounded tw-bg-transparent tw-cursor-pointer tw-transition-all tw-duration-200 tw-text-red-600 hover:tw-bg-red-50 dark:hover:tw-bg-red-900/20 hover:tw-text-red-700\" \r\n (click)=\"clearFiles(); $event.stopPropagation()\"\r\n title=\"Clear files\">\r\n <cide-ele-icon size=\"xs\">close</cide-ele-icon>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n \r\n <!-- Image Preview Section (only for standard mode) -->\r\n @if (isImagePreviewAvailable() && !isPreviewBoxMode()) {\r\n <div class=\"tw-mt-3\">\r\n <div class=\"tw-text-sm tw-font-medium tw-text-gray-700 dark:tw-text-gray-200 tw-mb-2\">Preview:</div>\r\n <div class=\"tw-flex tw-flex-wrap tw-gap-3\">\r\n @for (previewUrl of previewUrls(); track previewUrl; let i = $index) {\r\n <div \r\n class=\"tw-relative tw-border tw-border-gray-200 dark:tw-border-gray-600 tw-rounded-lg tw-overflow-hidden tw-bg-white dark:tw-bg-gray-800\"\r\n [style.width]=\"previewWidthSignal()\"\r\n [style.height]=\"previewHeightSignal()\">\r\n <button \r\n type=\"button\" \r\n class=\"tw-absolute tw-top-1 tw-right-1 tw-w-5 tw-h-5 tw-bg-red-500 hover:tw-bg-red-600 tw-text-white tw-rounded-full tw-flex tw-items-center tw-justify-center tw-text-xs tw-font-bold tw-transition-colors tw-duration-200 tw-border-none tw-cursor-pointer tw-z-10\"\r\n (click)=\"removePreview(i)\"\r\n title=\"Remove image\">\r\n \u00D7\r\n </button>\r\n <img \r\n [src]=\"previewUrl\" \r\n [alt]=\"fileNames()[i] || 'Preview image'\"\r\n class=\"tw-w-full tw-h-full tw-object-cover\"\r\n loading=\"lazy\">\r\n <div class=\"tw-absolute tw-bottom-0 tw-left-0 tw-right-0 tw-bg-black tw-bg-opacity-75 tw-text-white tw-text-xs tw-p-1 tw-truncate\">{{ fileNames()[i] }}</div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n \r\n <!-- Upload Status and Show Files Button (only for multiple file inputs) -->\r\n @if (multiple && showFloatingUploaderSignal() && (getUploadCount() > 0 || hasActiveUploads() || hasEverUploaded())) {\r\n <div class=\"tw-flex tw-items-center tw-justify-between tw-py-1.5 tw-gap-2\">\r\n <div class=\"tw-flex tw-items-center tw-gap-2\">\r\n <cide-ele-icon class=\"tw-text-blue-600 dark:tw-text-blue-400\" size=\"sm\">cloud_upload</cide-ele-icon>\r\n <span class=\"tw-text-sm tw-text-gray-700 dark:tw-text-gray-300\">\r\n @if (hasActiveUploads()) {\r\n {{ getActiveUploadCount() }} uploading\r\n } @else if (getUploadCount() > 0) {\r\n {{ getUploadCount() }} completed\r\n } @else if (hasEverUploaded()) {\r\n View uploads\r\n }\r\n </span>\r\n </div>\r\n <button \r\n type=\"button\" \r\n class=\"tw-flex tw-items-center tw-justify-center tw-w-8 tw-h-8 tw-rounded-md tw-bg-gray-100 dark:tw-bg-gray-700 hover:tw-bg-gray-200 dark:hover:tw-bg-gray-600 tw-text-gray-600 dark:tw-text-gray-300 tw-transition-colors tw-duration-200 tw-border-none tw-cursor-pointer\"\r\n (click)=\"showFloatingUploaderDialog()\"\r\n title=\"View upload progress and history\">\r\n <cide-ele-icon size=\"sm\">visibility</cide-ele-icon>\r\n </button>\r\n </div>\r\n }\r\n \r\n @if (errorTextSignal()) {\r\n <div class=\"tw-text-sm tw-text-red-600 dark:tw-text-red-400 tw-mt-1\">{{ errorTextSignal() }}</div>\r\n }\r\n @if (helperTextSignal() && !errorTextSignal()) {\r\n <div class=\"tw-text-sm tw-text-gray-500 dark:tw-text-gray-400 tw-mt-1\">{{ helperTextSignal() }}</div>\r\n }\r\n</div> ", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }] });
|
|
5321
|
+
], usesOnChanges: true, ngImport: i0, template: "<div class=\"tw-flex tw-flex-col tw-gap-2\">\r\n <!-- Label (shown when not in preview box mode or when preview box mode but no label override) -->\r\n @if (labelSignal() && !isPreviewBoxMode()) {\r\n <label class=\"tw-block tw-text-sm tw-font-medium tw-text-gray-700 dark:tw-text-gray-200 tw-mb-1.5 tw-leading-5\" [attr.for]=\"'cide-file-input-' + id()\">\r\n {{ labelSignal() }}@if (requiredSignal()) {<span class=\"tw-text-red-500 dark:tw-text-red-400\"> *</span>}\r\n </label>\r\n }\r\n \r\n <!-- Preview Box Mode -->\r\n @if (isPreviewBoxMode()) {\r\n <div class=\"tw-relative\">\r\n <!-- Hidden file input -->\r\n <input\r\n type=\"file\"\r\n [attr.id]=\"'cide-file-input-' + id()\"\r\n [attr.accept]=\"acceptSignal()\"\r\n [attr.multiple]=\"multipleSignal() ? true : null\"\r\n [disabled]=\"disabledSignal()\"\r\n (change)=\"onFileSelected($event)\"\r\n class=\"tw-hidden\"\r\n />\r\n \r\n <!-- Preview Box -->\r\n <div \r\n class=\"tw-border-2 tw-border-dashed tw-border-gray-300 dark:tw-border-gray-600 tw-rounded-lg tw-bg-gray-50 dark:tw-bg-gray-800 tw-cursor-pointer tw-transition-all tw-duration-200 tw-relative tw-overflow-hidden hover:tw-border-blue-500 hover:tw-bg-blue-50 dark:hover:tw-bg-blue-900/20\"\r\n [class]=\"getPreviewBoxClasses()\"\r\n [style.width]=\"previewWidthSignal()\"\r\n [style.height]=\"previewHeightSignal()\"\r\n (click)=\"triggerFileSelect()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragenter)=\"onDragEnter($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\"\r\n [attr.title]=\"disabledSignal() ? 'File selection disabled' : placeholderTextSignal()\">\r\n \r\n <!-- No Image State -->\r\n @if (!hasImages()) {\r\n <div class=\"tw-flex tw-flex-col tw-items-center tw-justify-center tw-h-full tw-p-4\">\r\n <div class=\"tw-mb-2\">\r\n <cide-ele-icon class=\"tw-text-gray-400 dark:tw-text-gray-500\" size=\"lg\">{{ isDragOver() ? '\uD83D\uDCC1' : placeholderIconSignal() }}</cide-ele-icon>\r\n </div>\r\n <div class=\"tw-text-sm tw-text-gray-600 dark:tw-text-gray-400 tw-text-center\">\r\n {{ isDragOver() ? 'Drop files here...' : placeholderTextSignal() }}\r\n </div>\r\n </div>\r\n }\r\n \r\n <!-- Image Preview State -->\r\n @if (hasImages()) {\r\n <div class=\"tw-relative tw-w-full tw-h-full\">\r\n <img \r\n [src]=\"previewUrls()[0]\" \r\n [alt]=\"fileNames()[0] || 'Preview image'\"\r\n class=\"tw-w-full tw-h-full tw-object-cover tw-rounded-lg\">\r\n <div class=\"tw-absolute tw-inset-0 tw-bg-black tw-bg-opacity-0 hover:tw-bg-opacity-30 tw-transition-all tw-duration-200 tw-flex tw-items-center tw-justify-center tw-rounded-lg\">\r\n <div class=\"tw-text-white tw-text-sm tw-opacity-0 hover:tw-opacity-100 tw-transition-opacity tw-duration-200\">Click to change</div>\r\n </div>\r\n @if (!disabledSignal()) {\r\n <button \r\n type=\"button\" \r\n class=\"tw-absolute tw-top-2 tw-right-2 tw-w-6 tw-h-6 tw-bg-red-500 hover:tw-bg-red-600 tw-text-white tw-rounded-full tw-flex tw-items-center tw-justify-center tw-text-sm tw-font-bold tw-transition-colors tw-duration-200 tw-border-none tw-cursor-pointer\"\r\n (click)=\"clearFiles(); $event.stopPropagation()\"\r\n title=\"Remove image\">\r\n \u00D7\r\n </button>\r\n }\r\n @if (isInErrorState() && failedFile()) {\r\n <button \r\n type=\"button\" \r\n class=\"tw-absolute tw-bottom-2 tw-left-1/2 tw-transform tw--translate-x-1/2 tw-px-3 tw-py-1.5 tw-text-xs tw-font-medium tw-border-none tw-rounded tw-bg-blue-600 hover:tw-bg-blue-700 tw-text-white tw-cursor-pointer tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed tw-shadow-lg tw-z-10\"\r\n (click)=\"retryUpload(); $event.stopPropagation()\"\r\n [disabled]=\"isUploading() || disabledSignal()\"\r\n title=\"Retry upload\">\r\n <cide-ele-icon size=\"xs\" class=\"tw-mr-1\">refresh</cide-ele-icon>\r\n Retry Upload\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n \r\n <!-- File name display for preview box mode -->\r\n @if (hasImages() && fileNames().length && showFileNameSignal()) {\r\n <div class=\"tw-mt-2 tw-text-sm tw-text-gray-600 dark:tw-text-gray-400 tw-text-center tw-truncate\">\r\n {{ fileNames()[0] }}\r\n </div>\r\n }\r\n <!-- Error message and retry button for preview box mode -->\r\n @if (isInErrorState() && failedFile() && !hasImages()) {\r\n <div class=\"tw-mt-2 tw-flex tw-flex-col tw-items-center tw-gap-2\">\r\n <span class=\"tw-text-sm tw-text-red-600 dark:tw-text-red-400\">Upload failed. Please retry.</span>\r\n <button type=\"button\" \r\n class=\"tw-flex tw-items-center tw-gap-1 tw-px-3 tw-py-1.5 tw-text-xs tw-font-medium tw-border-none tw-rounded tw-bg-blue-600 hover:tw-bg-blue-700 tw-text-white tw-cursor-pointer tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\" \r\n (click)=\"retryUpload()\"\r\n [disabled]=\"isUploading() || disabledSignal()\">\r\n <cide-ele-icon size=\"xs\">refresh</cide-ele-icon>\r\n Retry Upload\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Standard Mode -->\r\n @if (!isPreviewBoxMode()) {\r\n <!-- Hidden file input -->\r\n <input\r\n type=\"file\"\r\n [attr.id]=\"'cide-file-input-' + id()\"\r\n [attr.accept]=\"acceptSignal()\"\r\n [attr.multiple]=\"multipleSignal() ? true : null\"\r\n [disabled]=\"disabledSignal()\"\r\n (change)=\"onFileSelected($event)\"\r\n class=\"tw-hidden\"\r\n />\r\n \r\n <!-- Modern Drag and Drop Zone -->\r\n <div \r\n class=\"tw-border-2 tw-border-dashed tw-border-gray-300 dark:tw-border-gray-600 tw-rounded-lg tw-bg-gray-50 dark:tw-bg-gray-800 tw-cursor-pointer tw-transition-all tw-duration-200 tw-min-h-[60px] hover:tw-border-blue-500 hover:tw-bg-blue-50 dark:hover:tw-bg-blue-900/20\"\r\n [class]=\"getDragDropZoneClasses()\"\r\n (click)=\"triggerFileSelect()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragenter)=\"onDragEnter($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\">\r\n \r\n <div class=\"tw-flex tw-items-center tw-justify-between tw-p-3 tw-gap-3\">\r\n <!-- Icon and Text -->\r\n <div class=\"tw-flex tw-items-center tw-gap-2.5 tw-flex-1 tw-min-w-0\">\r\n <cide-ele-icon class=\"tw-flex-shrink-0 tw-transition-colors tw-duration-200\" \r\n [class]=\"getIconClasses()\" \r\n size=\"sm\">\r\n {{ isDragOver() ? 'file_download' : (hasFiles() ? 'check_circle' : 'cloud_upload') }}\r\n </cide-ele-icon>\r\n \r\n <div class=\"tw-flex tw-flex-col tw-gap-0.5 tw-min-w-0\">\r\n @if (isDragOver()) {\r\n <span class=\"tw-text-sm tw-font-medium tw-text-blue-700 dark:tw-text-blue-300 tw-whitespace-nowrap tw-overflow-hidden tw-text-ellipsis\">Drop files here</span>\r\n } @else if (hasFiles()) {\r\n <span class=\"tw-text-sm tw-font-medium tw-text-emerald-700 dark:tw-text-emerald-300 tw-whitespace-nowrap tw-overflow-hidden tw-text-ellipsis\">\r\n @if (multipleSignal() && fileNames().length > 1) {\r\n {{ fileNames().length }} files selected\r\n } @else {\r\n {{ fileNames()[0] }}\r\n }\r\n </span>\r\n @if (totalFileSize() > 0) {\r\n <span class=\"tw-text-xs tw-text-emerald-600 dark:tw-text-emerald-400\">{{ fileSizeInMB() }} MB</span>\r\n }\r\n } @else {\r\n <span class=\"tw-text-sm tw-font-medium tw-text-gray-700 dark:tw-text-gray-300 tw-whitespace-nowrap tw-overflow-hidden tw-text-ellipsis\">\r\n {{ multipleSignal() ? 'Choose files or drag here' : 'Choose file or drag here' }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n \r\n <!-- Action Buttons -->\r\n <div class=\"tw-flex tw-gap-1 tw-flex-shrink-0\">\r\n @if (isInErrorState() && failedFile()) {\r\n <button type=\"button\" \r\n class=\"tw-flex tw-items-center tw-justify-center tw-px-2 tw-py-1 tw-text-xs tw-font-medium tw-border-none tw-rounded tw-bg-blue-600 hover:tw-bg-blue-700 tw-text-white tw-cursor-pointer tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\" \r\n (click)=\"retryUpload(); $event.stopPropagation()\"\r\n [disabled]=\"isUploading() || disabledSignal()\"\r\n title=\"Retry upload\">\r\n <cide-ele-icon size=\"xs\" class=\"tw-mr-1\">refresh</cide-ele-icon>\r\n Retry\r\n </button>\r\n }\r\n @if (hasFiles()) {\r\n <button type=\"button\" \r\n class=\"tw-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-border-none tw-rounded tw-bg-transparent tw-cursor-pointer tw-transition-all tw-duration-200 tw-text-red-600 hover:tw-bg-red-50 dark:hover:tw-bg-red-900/20 hover:tw-text-red-700\" \r\n (click)=\"clearFiles(); $event.stopPropagation()\"\r\n title=\"Clear files\">\r\n <cide-ele-icon size=\"xs\">close</cide-ele-icon>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n \r\n <!-- Image Preview Section (only for standard mode) -->\r\n @if (isImagePreviewAvailable() && !isPreviewBoxMode()) {\r\n <div class=\"tw-mt-3\">\r\n <div class=\"tw-text-sm tw-font-medium tw-text-gray-700 dark:tw-text-gray-200 tw-mb-2\">Preview:</div>\r\n <div class=\"tw-flex tw-flex-wrap tw-gap-3\">\r\n @for (previewUrl of previewUrls(); track previewUrl; let i = $index) {\r\n <div \r\n class=\"tw-relative tw-border tw-border-gray-200 dark:tw-border-gray-600 tw-rounded-lg tw-overflow-hidden tw-bg-white dark:tw-bg-gray-800\"\r\n [style.width]=\"previewWidthSignal()\"\r\n [style.height]=\"previewHeightSignal()\">\r\n <button \r\n type=\"button\" \r\n class=\"tw-absolute tw-top-1 tw-right-1 tw-w-5 tw-h-5 tw-bg-red-500 hover:tw-bg-red-600 tw-text-white tw-rounded-full tw-flex tw-items-center tw-justify-center tw-text-xs tw-font-bold tw-transition-colors tw-duration-200 tw-border-none tw-cursor-pointer tw-z-10\"\r\n (click)=\"removePreview(i)\"\r\n title=\"Remove image\">\r\n \u00D7\r\n </button>\r\n <img \r\n [src]=\"previewUrl\" \r\n [alt]=\"fileNames()[i] || 'Preview image'\"\r\n class=\"tw-w-full tw-h-full tw-object-cover\"\r\n loading=\"lazy\">\r\n <div class=\"tw-absolute tw-bottom-0 tw-left-0 tw-right-0 tw-bg-black tw-bg-opacity-75 tw-text-white tw-text-xs tw-p-1 tw-truncate\">{{ fileNames()[i] }}</div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n \r\n <!-- Upload Status and Show Files Button (only for multiple file inputs) -->\r\n @if (multiple && showFloatingUploaderSignal() && (getUploadCount() > 0 || hasActiveUploads() || hasEverUploaded())) {\r\n <div class=\"tw-flex tw-items-center tw-justify-between tw-py-1.5 tw-gap-2\">\r\n <div class=\"tw-flex tw-items-center tw-gap-2\">\r\n <cide-ele-icon class=\"tw-text-blue-600 dark:tw-text-blue-400\" size=\"sm\">cloud_upload</cide-ele-icon>\r\n <span class=\"tw-text-sm tw-text-gray-700 dark:tw-text-gray-300\">\r\n @if (hasActiveUploads()) {\r\n {{ getActiveUploadCount() }} uploading\r\n } @else if (getUploadCount() > 0) {\r\n {{ getUploadCount() }} completed\r\n } @else if (hasEverUploaded()) {\r\n View uploads\r\n }\r\n </span>\r\n </div>\r\n <button \r\n type=\"button\" \r\n class=\"tw-flex tw-items-center tw-justify-center tw-w-8 tw-h-8 tw-rounded-md tw-bg-gray-100 dark:tw-bg-gray-700 hover:tw-bg-gray-200 dark:hover:tw-bg-gray-600 tw-text-gray-600 dark:tw-text-gray-300 tw-transition-colors tw-duration-200 tw-border-none tw-cursor-pointer\"\r\n (click)=\"showFloatingUploaderDialog()\"\r\n title=\"View upload progress and history\">\r\n <cide-ele-icon size=\"sm\">visibility</cide-ele-icon>\r\n </button>\r\n </div>\r\n }\r\n \r\n @if (errorTextSignal()) {\r\n <div class=\"tw-text-sm tw-text-red-600 dark:tw-text-red-400 tw-mt-1\">{{ errorTextSignal() }}</div>\r\n }\r\n @if (isInErrorState() && failedFile() && !errorTextSignal()) {\r\n <div class=\"tw-flex tw-items-center tw-gap-2 tw-mt-1\">\r\n <span class=\"tw-text-sm tw-text-red-600 dark:tw-text-red-400\">Upload failed. Please retry.</span>\r\n <button type=\"button\" \r\n class=\"tw-flex tw-items-center tw-gap-1 tw-px-2 tw-py-1 tw-text-xs tw-font-medium tw-border-none tw-rounded tw-bg-blue-600 hover:tw-bg-blue-700 tw-text-white tw-cursor-pointer tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\" \r\n (click)=\"retryUpload()\"\r\n [disabled]=\"isUploading() || disabledSignal()\">\r\n <cide-ele-icon size=\"xs\">refresh</cide-ele-icon>\r\n Retry Upload\r\n </button>\r\n </div>\r\n }\r\n @if (helperTextSignal() && !errorTextSignal() && !isInErrorState()) {\r\n <div class=\"tw-text-sm tw-text-gray-500 dark:tw-text-gray-400 tw-mt-1\">{{ helperTextSignal() }}</div>\r\n }\r\n</div> ", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }] });
|
|
5297
5322
|
}
|
|
5298
5323
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileInputComponent, decorators: [{
|
|
5299
5324
|
type: Component,
|
|
@@ -5308,7 +5333,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
5308
5333
|
useExisting: CideEleFileInputComponent,
|
|
5309
5334
|
multi: true
|
|
5310
5335
|
}
|
|
5311
|
-
], template: "<div class=\"tw-flex tw-flex-col tw-gap-2\">\r\n <!-- Label (shown when not in preview box mode or when preview box mode but no label override) -->\r\n @if (labelSignal() && !isPreviewBoxMode()) {\r\n <label class=\"tw-block tw-text-sm tw-font-medium tw-text-gray-700 dark:tw-text-gray-200 tw-mb-1.5 tw-leading-5\" [attr.for]=\"'cide-file-input-' + id()\">\r\n {{ labelSignal() }}@if (requiredSignal()) {<span class=\"tw-text-red-500 dark:tw-text-red-400\"> *</span>}\r\n </label>\r\n }\r\n \r\n <!-- Preview Box Mode -->\r\n @if (isPreviewBoxMode()) {\r\n <div class=\"tw-relative\">\r\n <!-- Hidden file input -->\r\n <input\r\n type=\"file\"\r\n [attr.id]=\"'cide-file-input-' + id()\"\r\n [attr.accept]=\"acceptSignal()\"\r\n [attr.multiple]=\"multipleSignal() ? true : null\"\r\n [disabled]=\"disabledSignal()\"\r\n (change)=\"onFileSelected($event)\"\r\n class=\"tw-hidden\"\r\n />\r\n \r\n <!-- Preview Box -->\r\n <div \r\n class=\"tw-border-2 tw-border-dashed tw-border-gray-300 dark:tw-border-gray-600 tw-rounded-lg tw-bg-gray-50 dark:tw-bg-gray-800 tw-cursor-pointer tw-transition-all tw-duration-200 tw-relative tw-overflow-hidden hover:tw-border-blue-500 hover:tw-bg-blue-50 dark:hover:tw-bg-blue-900/20\"\r\n [class]=\"getPreviewBoxClasses()\"\r\n [style.width]=\"previewWidthSignal()\"\r\n [style.height]=\"previewHeightSignal()\"\r\n (click)=\"triggerFileSelect()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragenter)=\"onDragEnter($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\"\r\n [attr.title]=\"disabledSignal() ? 'File selection disabled' : placeholderTextSignal()\">\r\n \r\n <!-- No Image State -->\r\n @if (!hasImages()) {\r\n <div class=\"tw-flex tw-flex-col tw-items-center tw-justify-center tw-h-full tw-p-4\">\r\n <div class=\"tw-mb-2\">\r\n <cide-ele-icon class=\"tw-text-gray-400 dark:tw-text-gray-500\" size=\"lg\">{{ isDragOver() ? '\uD83D\uDCC1' : placeholderIconSignal() }}</cide-ele-icon>\r\n </div>\r\n <div class=\"tw-text-sm tw-text-gray-600 dark:tw-text-gray-400 tw-text-center\">\r\n {{ isDragOver() ? 'Drop files here...' : placeholderTextSignal() }}\r\n </div>\r\n </div>\r\n }\r\n \r\n <!-- Image Preview State -->\r\n @if (hasImages()) {\r\n <div class=\"tw-relative tw-w-full tw-h-full\">\r\n <img \r\n [src]=\"previewUrls()[0]\" \r\n [alt]=\"fileNames()[0] || 'Preview image'\"\r\n class=\"tw-w-full tw-h-full tw-object-cover tw-rounded-lg\">\r\n <div class=\"tw-absolute tw-inset-0 tw-bg-black tw-bg-opacity-0 hover:tw-bg-opacity-30 tw-transition-all tw-duration-200 tw-flex tw-items-center tw-justify-center tw-rounded-lg\">\r\n <div class=\"tw-text-white tw-text-sm tw-opacity-0 hover:tw-opacity-100 tw-transition-opacity tw-duration-200\">Click to change</div>\r\n </div>\r\n @if (!disabledSignal()) {\r\n <button \r\n type=\"button\" \r\n class=\"tw-absolute tw-top-2 tw-right-2 tw-w-6 tw-h-6 tw-bg-red-500 hover:tw-bg-red-600 tw-text-white tw-rounded-full tw-flex tw-items-center tw-justify-center tw-text-sm tw-font-bold tw-transition-colors tw-duration-200 tw-border-none tw-cursor-pointer\"\r\n (click)=\"clearFiles(); $event.stopPropagation()\"\r\n title=\"Remove image\">\r\n \u00D7\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n \r\n <!-- File name display for preview box mode -->\r\n @if (hasImages() && fileNames().length && showFileNameSignal()) {\r\n <div class=\"tw-mt-2 tw-text-sm tw-text-gray-600 dark:tw-text-gray-400 tw-text-center tw-truncate\">\r\n {{ fileNames()[0] }}\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Standard Mode -->\r\n @if (!isPreviewBoxMode()) {\r\n <!-- Hidden file input -->\r\n <input\r\n type=\"file\"\r\n [attr.id]=\"'cide-file-input-' + id()\"\r\n [attr.accept]=\"acceptSignal()\"\r\n [attr.multiple]=\"multipleSignal() ? true : null\"\r\n [disabled]=\"disabledSignal()\"\r\n (change)=\"onFileSelected($event)\"\r\n class=\"tw-hidden\"\r\n />\r\n \r\n <!-- Modern Drag and Drop Zone -->\r\n <div \r\n class=\"tw-border-2 tw-border-dashed tw-border-gray-300 dark:tw-border-gray-600 tw-rounded-lg tw-bg-gray-50 dark:tw-bg-gray-800 tw-cursor-pointer tw-transition-all tw-duration-200 tw-min-h-[60px] hover:tw-border-blue-500 hover:tw-bg-blue-50 dark:hover:tw-bg-blue-900/20\"\r\n [class]=\"getDragDropZoneClasses()\"\r\n (click)=\"triggerFileSelect()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragenter)=\"onDragEnter($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\">\r\n \r\n <div class=\"tw-flex tw-items-center tw-justify-between tw-p-3 tw-gap-3\">\r\n <!-- Icon and Text -->\r\n <div class=\"tw-flex tw-items-center tw-gap-2.5 tw-flex-1 tw-min-w-0\">\r\n <cide-ele-icon class=\"tw-flex-shrink-0 tw-transition-colors tw-duration-200\" \r\n [class]=\"getIconClasses()\" \r\n size=\"sm\">\r\n {{ isDragOver() ? 'file_download' : (hasFiles() ? 'check_circle' : 'cloud_upload') }}\r\n </cide-ele-icon>\r\n \r\n <div class=\"tw-flex tw-flex-col tw-gap-0.5 tw-min-w-0\">\r\n @if (isDragOver()) {\r\n <span class=\"tw-text-sm tw-font-medium tw-text-blue-700 dark:tw-text-blue-300 tw-whitespace-nowrap tw-overflow-hidden tw-text-ellipsis\">Drop files here</span>\r\n } @else if (hasFiles()) {\r\n <span class=\"tw-text-sm tw-font-medium tw-text-emerald-700 dark:tw-text-emerald-300 tw-whitespace-nowrap tw-overflow-hidden tw-text-ellipsis\">\r\n @if (multipleSignal() && fileNames().length > 1) {\r\n {{ fileNames().length }} files selected\r\n } @else {\r\n {{ fileNames()[0] }}\r\n }\r\n </span>\r\n @if (totalFileSize() > 0) {\r\n <span class=\"tw-text-xs tw-text-emerald-600 dark:tw-text-emerald-400\">{{ fileSizeInMB() }} MB</span>\r\n }\r\n } @else {\r\n <span class=\"tw-text-sm tw-font-medium tw-text-gray-700 dark:tw-text-gray-300 tw-whitespace-nowrap tw-overflow-hidden tw-text-ellipsis\">\r\n {{ multipleSignal() ? 'Choose files or drag here' : 'Choose file or drag here' }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n \r\n <!-- Action Buttons -->\r\n <div class=\"tw-flex tw-gap-1 tw-flex-shrink-0\">\r\n @if (hasFiles()) {\r\n <button type=\"button\" \r\n class=\"tw-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-border-none tw-rounded tw-bg-transparent tw-cursor-pointer tw-transition-all tw-duration-200 tw-text-red-600 hover:tw-bg-red-50 dark:hover:tw-bg-red-900/20 hover:tw-text-red-700\" \r\n (click)=\"clearFiles(); $event.stopPropagation()\"\r\n title=\"Clear files\">\r\n <cide-ele-icon size=\"xs\">close</cide-ele-icon>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n \r\n <!-- Image Preview Section (only for standard mode) -->\r\n @if (isImagePreviewAvailable() && !isPreviewBoxMode()) {\r\n <div class=\"tw-mt-3\">\r\n <div class=\"tw-text-sm tw-font-medium tw-text-gray-700 dark:tw-text-gray-200 tw-mb-2\">Preview:</div>\r\n <div class=\"tw-flex tw-flex-wrap tw-gap-3\">\r\n @for (previewUrl of previewUrls(); track previewUrl; let i = $index) {\r\n <div \r\n class=\"tw-relative tw-border tw-border-gray-200 dark:tw-border-gray-600 tw-rounded-lg tw-overflow-hidden tw-bg-white dark:tw-bg-gray-800\"\r\n [style.width]=\"previewWidthSignal()\"\r\n [style.height]=\"previewHeightSignal()\">\r\n <button \r\n type=\"button\" \r\n class=\"tw-absolute tw-top-1 tw-right-1 tw-w-5 tw-h-5 tw-bg-red-500 hover:tw-bg-red-600 tw-text-white tw-rounded-full tw-flex tw-items-center tw-justify-center tw-text-xs tw-font-bold tw-transition-colors tw-duration-200 tw-border-none tw-cursor-pointer tw-z-10\"\r\n (click)=\"removePreview(i)\"\r\n title=\"Remove image\">\r\n \u00D7\r\n </button>\r\n <img \r\n [src]=\"previewUrl\" \r\n [alt]=\"fileNames()[i] || 'Preview image'\"\r\n class=\"tw-w-full tw-h-full tw-object-cover\"\r\n loading=\"lazy\">\r\n <div class=\"tw-absolute tw-bottom-0 tw-left-0 tw-right-0 tw-bg-black tw-bg-opacity-75 tw-text-white tw-text-xs tw-p-1 tw-truncate\">{{ fileNames()[i] }}</div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n \r\n <!-- Upload Status and Show Files Button (only for multiple file inputs) -->\r\n @if (multiple && showFloatingUploaderSignal() && (getUploadCount() > 0 || hasActiveUploads() || hasEverUploaded())) {\r\n <div class=\"tw-flex tw-items-center tw-justify-between tw-py-1.5 tw-gap-2\">\r\n <div class=\"tw-flex tw-items-center tw-gap-2\">\r\n <cide-ele-icon class=\"tw-text-blue-600 dark:tw-text-blue-400\" size=\"sm\">cloud_upload</cide-ele-icon>\r\n <span class=\"tw-text-sm tw-text-gray-700 dark:tw-text-gray-300\">\r\n @if (hasActiveUploads()) {\r\n {{ getActiveUploadCount() }} uploading\r\n } @else if (getUploadCount() > 0) {\r\n {{ getUploadCount() }} completed\r\n } @else if (hasEverUploaded()) {\r\n View uploads\r\n }\r\n </span>\r\n </div>\r\n <button \r\n type=\"button\" \r\n class=\"tw-flex tw-items-center tw-justify-center tw-w-8 tw-h-8 tw-rounded-md tw-bg-gray-100 dark:tw-bg-gray-700 hover:tw-bg-gray-200 dark:hover:tw-bg-gray-600 tw-text-gray-600 dark:tw-text-gray-300 tw-transition-colors tw-duration-200 tw-border-none tw-cursor-pointer\"\r\n (click)=\"showFloatingUploaderDialog()\"\r\n title=\"View upload progress and history\">\r\n <cide-ele-icon size=\"sm\">visibility</cide-ele-icon>\r\n </button>\r\n </div>\r\n }\r\n \r\n @if (errorTextSignal()) {\r\n <div class=\"tw-text-sm tw-text-red-600 dark:tw-text-red-400 tw-mt-1\">{{ errorTextSignal() }}</div>\r\n }\r\n @if (helperTextSignal() && !errorTextSignal()) {\r\n <div class=\"tw-text-sm tw-text-gray-500 dark:tw-text-gray-400 tw-mt-1\">{{ helperTextSignal() }}</div>\r\n }\r\n</div> " }]
|
|
5336
|
+
], template: "<div class=\"tw-flex tw-flex-col tw-gap-2\">\r\n <!-- Label (shown when not in preview box mode or when preview box mode but no label override) -->\r\n @if (labelSignal() && !isPreviewBoxMode()) {\r\n <label class=\"tw-block tw-text-sm tw-font-medium tw-text-gray-700 dark:tw-text-gray-200 tw-mb-1.5 tw-leading-5\" [attr.for]=\"'cide-file-input-' + id()\">\r\n {{ labelSignal() }}@if (requiredSignal()) {<span class=\"tw-text-red-500 dark:tw-text-red-400\"> *</span>}\r\n </label>\r\n }\r\n \r\n <!-- Preview Box Mode -->\r\n @if (isPreviewBoxMode()) {\r\n <div class=\"tw-relative\">\r\n <!-- Hidden file input -->\r\n <input\r\n type=\"file\"\r\n [attr.id]=\"'cide-file-input-' + id()\"\r\n [attr.accept]=\"acceptSignal()\"\r\n [attr.multiple]=\"multipleSignal() ? true : null\"\r\n [disabled]=\"disabledSignal()\"\r\n (change)=\"onFileSelected($event)\"\r\n class=\"tw-hidden\"\r\n />\r\n \r\n <!-- Preview Box -->\r\n <div \r\n class=\"tw-border-2 tw-border-dashed tw-border-gray-300 dark:tw-border-gray-600 tw-rounded-lg tw-bg-gray-50 dark:tw-bg-gray-800 tw-cursor-pointer tw-transition-all tw-duration-200 tw-relative tw-overflow-hidden hover:tw-border-blue-500 hover:tw-bg-blue-50 dark:hover:tw-bg-blue-900/20\"\r\n [class]=\"getPreviewBoxClasses()\"\r\n [style.width]=\"previewWidthSignal()\"\r\n [style.height]=\"previewHeightSignal()\"\r\n (click)=\"triggerFileSelect()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragenter)=\"onDragEnter($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\"\r\n [attr.title]=\"disabledSignal() ? 'File selection disabled' : placeholderTextSignal()\">\r\n \r\n <!-- No Image State -->\r\n @if (!hasImages()) {\r\n <div class=\"tw-flex tw-flex-col tw-items-center tw-justify-center tw-h-full tw-p-4\">\r\n <div class=\"tw-mb-2\">\r\n <cide-ele-icon class=\"tw-text-gray-400 dark:tw-text-gray-500\" size=\"lg\">{{ isDragOver() ? '\uD83D\uDCC1' : placeholderIconSignal() }}</cide-ele-icon>\r\n </div>\r\n <div class=\"tw-text-sm tw-text-gray-600 dark:tw-text-gray-400 tw-text-center\">\r\n {{ isDragOver() ? 'Drop files here...' : placeholderTextSignal() }}\r\n </div>\r\n </div>\r\n }\r\n \r\n <!-- Image Preview State -->\r\n @if (hasImages()) {\r\n <div class=\"tw-relative tw-w-full tw-h-full\">\r\n <img \r\n [src]=\"previewUrls()[0]\" \r\n [alt]=\"fileNames()[0] || 'Preview image'\"\r\n class=\"tw-w-full tw-h-full tw-object-cover tw-rounded-lg\">\r\n <div class=\"tw-absolute tw-inset-0 tw-bg-black tw-bg-opacity-0 hover:tw-bg-opacity-30 tw-transition-all tw-duration-200 tw-flex tw-items-center tw-justify-center tw-rounded-lg\">\r\n <div class=\"tw-text-white tw-text-sm tw-opacity-0 hover:tw-opacity-100 tw-transition-opacity tw-duration-200\">Click to change</div>\r\n </div>\r\n @if (!disabledSignal()) {\r\n <button \r\n type=\"button\" \r\n class=\"tw-absolute tw-top-2 tw-right-2 tw-w-6 tw-h-6 tw-bg-red-500 hover:tw-bg-red-600 tw-text-white tw-rounded-full tw-flex tw-items-center tw-justify-center tw-text-sm tw-font-bold tw-transition-colors tw-duration-200 tw-border-none tw-cursor-pointer\"\r\n (click)=\"clearFiles(); $event.stopPropagation()\"\r\n title=\"Remove image\">\r\n \u00D7\r\n </button>\r\n }\r\n @if (isInErrorState() && failedFile()) {\r\n <button \r\n type=\"button\" \r\n class=\"tw-absolute tw-bottom-2 tw-left-1/2 tw-transform tw--translate-x-1/2 tw-px-3 tw-py-1.5 tw-text-xs tw-font-medium tw-border-none tw-rounded tw-bg-blue-600 hover:tw-bg-blue-700 tw-text-white tw-cursor-pointer tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed tw-shadow-lg tw-z-10\"\r\n (click)=\"retryUpload(); $event.stopPropagation()\"\r\n [disabled]=\"isUploading() || disabledSignal()\"\r\n title=\"Retry upload\">\r\n <cide-ele-icon size=\"xs\" class=\"tw-mr-1\">refresh</cide-ele-icon>\r\n Retry Upload\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n \r\n <!-- File name display for preview box mode -->\r\n @if (hasImages() && fileNames().length && showFileNameSignal()) {\r\n <div class=\"tw-mt-2 tw-text-sm tw-text-gray-600 dark:tw-text-gray-400 tw-text-center tw-truncate\">\r\n {{ fileNames()[0] }}\r\n </div>\r\n }\r\n <!-- Error message and retry button for preview box mode -->\r\n @if (isInErrorState() && failedFile() && !hasImages()) {\r\n <div class=\"tw-mt-2 tw-flex tw-flex-col tw-items-center tw-gap-2\">\r\n <span class=\"tw-text-sm tw-text-red-600 dark:tw-text-red-400\">Upload failed. Please retry.</span>\r\n <button type=\"button\" \r\n class=\"tw-flex tw-items-center tw-gap-1 tw-px-3 tw-py-1.5 tw-text-xs tw-font-medium tw-border-none tw-rounded tw-bg-blue-600 hover:tw-bg-blue-700 tw-text-white tw-cursor-pointer tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\" \r\n (click)=\"retryUpload()\"\r\n [disabled]=\"isUploading() || disabledSignal()\">\r\n <cide-ele-icon size=\"xs\">refresh</cide-ele-icon>\r\n Retry Upload\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Standard Mode -->\r\n @if (!isPreviewBoxMode()) {\r\n <!-- Hidden file input -->\r\n <input\r\n type=\"file\"\r\n [attr.id]=\"'cide-file-input-' + id()\"\r\n [attr.accept]=\"acceptSignal()\"\r\n [attr.multiple]=\"multipleSignal() ? true : null\"\r\n [disabled]=\"disabledSignal()\"\r\n (change)=\"onFileSelected($event)\"\r\n class=\"tw-hidden\"\r\n />\r\n \r\n <!-- Modern Drag and Drop Zone -->\r\n <div \r\n class=\"tw-border-2 tw-border-dashed tw-border-gray-300 dark:tw-border-gray-600 tw-rounded-lg tw-bg-gray-50 dark:tw-bg-gray-800 tw-cursor-pointer tw-transition-all tw-duration-200 tw-min-h-[60px] hover:tw-border-blue-500 hover:tw-bg-blue-50 dark:hover:tw-bg-blue-900/20\"\r\n [class]=\"getDragDropZoneClasses()\"\r\n (click)=\"triggerFileSelect()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragenter)=\"onDragEnter($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\">\r\n \r\n <div class=\"tw-flex tw-items-center tw-justify-between tw-p-3 tw-gap-3\">\r\n <!-- Icon and Text -->\r\n <div class=\"tw-flex tw-items-center tw-gap-2.5 tw-flex-1 tw-min-w-0\">\r\n <cide-ele-icon class=\"tw-flex-shrink-0 tw-transition-colors tw-duration-200\" \r\n [class]=\"getIconClasses()\" \r\n size=\"sm\">\r\n {{ isDragOver() ? 'file_download' : (hasFiles() ? 'check_circle' : 'cloud_upload') }}\r\n </cide-ele-icon>\r\n \r\n <div class=\"tw-flex tw-flex-col tw-gap-0.5 tw-min-w-0\">\r\n @if (isDragOver()) {\r\n <span class=\"tw-text-sm tw-font-medium tw-text-blue-700 dark:tw-text-blue-300 tw-whitespace-nowrap tw-overflow-hidden tw-text-ellipsis\">Drop files here</span>\r\n } @else if (hasFiles()) {\r\n <span class=\"tw-text-sm tw-font-medium tw-text-emerald-700 dark:tw-text-emerald-300 tw-whitespace-nowrap tw-overflow-hidden tw-text-ellipsis\">\r\n @if (multipleSignal() && fileNames().length > 1) {\r\n {{ fileNames().length }} files selected\r\n } @else {\r\n {{ fileNames()[0] }}\r\n }\r\n </span>\r\n @if (totalFileSize() > 0) {\r\n <span class=\"tw-text-xs tw-text-emerald-600 dark:tw-text-emerald-400\">{{ fileSizeInMB() }} MB</span>\r\n }\r\n } @else {\r\n <span class=\"tw-text-sm tw-font-medium tw-text-gray-700 dark:tw-text-gray-300 tw-whitespace-nowrap tw-overflow-hidden tw-text-ellipsis\">\r\n {{ multipleSignal() ? 'Choose files or drag here' : 'Choose file or drag here' }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n \r\n <!-- Action Buttons -->\r\n <div class=\"tw-flex tw-gap-1 tw-flex-shrink-0\">\r\n @if (isInErrorState() && failedFile()) {\r\n <button type=\"button\" \r\n class=\"tw-flex tw-items-center tw-justify-center tw-px-2 tw-py-1 tw-text-xs tw-font-medium tw-border-none tw-rounded tw-bg-blue-600 hover:tw-bg-blue-700 tw-text-white tw-cursor-pointer tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\" \r\n (click)=\"retryUpload(); $event.stopPropagation()\"\r\n [disabled]=\"isUploading() || disabledSignal()\"\r\n title=\"Retry upload\">\r\n <cide-ele-icon size=\"xs\" class=\"tw-mr-1\">refresh</cide-ele-icon>\r\n Retry\r\n </button>\r\n }\r\n @if (hasFiles()) {\r\n <button type=\"button\" \r\n class=\"tw-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-border-none tw-rounded tw-bg-transparent tw-cursor-pointer tw-transition-all tw-duration-200 tw-text-red-600 hover:tw-bg-red-50 dark:hover:tw-bg-red-900/20 hover:tw-text-red-700\" \r\n (click)=\"clearFiles(); $event.stopPropagation()\"\r\n title=\"Clear files\">\r\n <cide-ele-icon size=\"xs\">close</cide-ele-icon>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n \r\n <!-- Image Preview Section (only for standard mode) -->\r\n @if (isImagePreviewAvailable() && !isPreviewBoxMode()) {\r\n <div class=\"tw-mt-3\">\r\n <div class=\"tw-text-sm tw-font-medium tw-text-gray-700 dark:tw-text-gray-200 tw-mb-2\">Preview:</div>\r\n <div class=\"tw-flex tw-flex-wrap tw-gap-3\">\r\n @for (previewUrl of previewUrls(); track previewUrl; let i = $index) {\r\n <div \r\n class=\"tw-relative tw-border tw-border-gray-200 dark:tw-border-gray-600 tw-rounded-lg tw-overflow-hidden tw-bg-white dark:tw-bg-gray-800\"\r\n [style.width]=\"previewWidthSignal()\"\r\n [style.height]=\"previewHeightSignal()\">\r\n <button \r\n type=\"button\" \r\n class=\"tw-absolute tw-top-1 tw-right-1 tw-w-5 tw-h-5 tw-bg-red-500 hover:tw-bg-red-600 tw-text-white tw-rounded-full tw-flex tw-items-center tw-justify-center tw-text-xs tw-font-bold tw-transition-colors tw-duration-200 tw-border-none tw-cursor-pointer tw-z-10\"\r\n (click)=\"removePreview(i)\"\r\n title=\"Remove image\">\r\n \u00D7\r\n </button>\r\n <img \r\n [src]=\"previewUrl\" \r\n [alt]=\"fileNames()[i] || 'Preview image'\"\r\n class=\"tw-w-full tw-h-full tw-object-cover\"\r\n loading=\"lazy\">\r\n <div class=\"tw-absolute tw-bottom-0 tw-left-0 tw-right-0 tw-bg-black tw-bg-opacity-75 tw-text-white tw-text-xs tw-p-1 tw-truncate\">{{ fileNames()[i] }}</div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n \r\n <!-- Upload Status and Show Files Button (only for multiple file inputs) -->\r\n @if (multiple && showFloatingUploaderSignal() && (getUploadCount() > 0 || hasActiveUploads() || hasEverUploaded())) {\r\n <div class=\"tw-flex tw-items-center tw-justify-between tw-py-1.5 tw-gap-2\">\r\n <div class=\"tw-flex tw-items-center tw-gap-2\">\r\n <cide-ele-icon class=\"tw-text-blue-600 dark:tw-text-blue-400\" size=\"sm\">cloud_upload</cide-ele-icon>\r\n <span class=\"tw-text-sm tw-text-gray-700 dark:tw-text-gray-300\">\r\n @if (hasActiveUploads()) {\r\n {{ getActiveUploadCount() }} uploading\r\n } @else if (getUploadCount() > 0) {\r\n {{ getUploadCount() }} completed\r\n } @else if (hasEverUploaded()) {\r\n View uploads\r\n }\r\n </span>\r\n </div>\r\n <button \r\n type=\"button\" \r\n class=\"tw-flex tw-items-center tw-justify-center tw-w-8 tw-h-8 tw-rounded-md tw-bg-gray-100 dark:tw-bg-gray-700 hover:tw-bg-gray-200 dark:hover:tw-bg-gray-600 tw-text-gray-600 dark:tw-text-gray-300 tw-transition-colors tw-duration-200 tw-border-none tw-cursor-pointer\"\r\n (click)=\"showFloatingUploaderDialog()\"\r\n title=\"View upload progress and history\">\r\n <cide-ele-icon size=\"sm\">visibility</cide-ele-icon>\r\n </button>\r\n </div>\r\n }\r\n \r\n @if (errorTextSignal()) {\r\n <div class=\"tw-text-sm tw-text-red-600 dark:tw-text-red-400 tw-mt-1\">{{ errorTextSignal() }}</div>\r\n }\r\n @if (isInErrorState() && failedFile() && !errorTextSignal()) {\r\n <div class=\"tw-flex tw-items-center tw-gap-2 tw-mt-1\">\r\n <span class=\"tw-text-sm tw-text-red-600 dark:tw-text-red-400\">Upload failed. Please retry.</span>\r\n <button type=\"button\" \r\n class=\"tw-flex tw-items-center tw-gap-1 tw-px-2 tw-py-1 tw-text-xs tw-font-medium tw-border-none tw-rounded tw-bg-blue-600 hover:tw-bg-blue-700 tw-text-white tw-cursor-pointer tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\" \r\n (click)=\"retryUpload()\"\r\n [disabled]=\"isUploading() || disabledSignal()\">\r\n <cide-ele-icon size=\"xs\">refresh</cide-ele-icon>\r\n Retry Upload\r\n </button>\r\n </div>\r\n }\r\n @if (helperTextSignal() && !errorTextSignal() && !isInErrorState()) {\r\n <div class=\"tw-text-sm tw-text-gray-500 dark:tw-text-gray-400 tw-mt-1\">{{ helperTextSignal() }}</div>\r\n }\r\n</div> " }]
|
|
5312
5337
|
}], ctorParameters: () => [], propDecorators: { label: [{
|
|
5313
5338
|
type: Input
|
|
5314
5339
|
}], accept: [{
|
|
@@ -7380,1230 +7405,1287 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
7380
7405
|
}] } });
|
|
7381
7406
|
|
|
7382
7407
|
/**
|
|
7383
|
-
*
|
|
7384
|
-
*
|
|
7408
|
+
* CIDE Element Library - Theme Service
|
|
7409
|
+
* Independent theme management for the element library
|
|
7410
|
+
* Can be used standalone without parent application theme dependencies
|
|
7385
7411
|
*/
|
|
7386
|
-
class
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7412
|
+
class CideThemeService {
|
|
7413
|
+
platformId = inject(PLATFORM_ID);
|
|
7414
|
+
isBrowser;
|
|
7415
|
+
currentTheme$ = new BehaviorSubject('light');
|
|
7416
|
+
effectiveTheme$ = new BehaviorSubject('light');
|
|
7417
|
+
config = {
|
|
7418
|
+
theme: 'light',
|
|
7419
|
+
targetElement: null,
|
|
7420
|
+
persistPreference: true,
|
|
7421
|
+
storageKey: 'cide-theme',
|
|
7422
|
+
useDataAttribute: true,
|
|
7423
|
+
useClassName: true
|
|
7424
|
+
};
|
|
7425
|
+
constructor() {
|
|
7426
|
+
this.isBrowser = isPlatformBrowser(this.platformId);
|
|
7427
|
+
if (this.isBrowser) {
|
|
7428
|
+
this.config.targetElement = document.documentElement;
|
|
7429
|
+
}
|
|
7430
|
+
}
|
|
7392
7431
|
/**
|
|
7393
|
-
*
|
|
7432
|
+
* Initialize the theme service with configuration
|
|
7433
|
+
* This should be called once in the application, typically in main.ts or app config
|
|
7394
7434
|
*/
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7435
|
+
initialize(config) {
|
|
7436
|
+
if (!this.isBrowser) {
|
|
7437
|
+
return;
|
|
7438
|
+
}
|
|
7439
|
+
// Merge configuration
|
|
7440
|
+
if (config) {
|
|
7441
|
+
this.config = {
|
|
7442
|
+
...this.config,
|
|
7443
|
+
...config,
|
|
7444
|
+
targetElement: config.targetElement || document.documentElement
|
|
7445
|
+
};
|
|
7446
|
+
}
|
|
7447
|
+
// Load saved theme preference
|
|
7448
|
+
const savedTheme = this.loadThemePreference();
|
|
7449
|
+
const initialTheme = savedTheme || this.config.theme;
|
|
7450
|
+
// Apply initial theme
|
|
7451
|
+
this.setTheme(initialTheme);
|
|
7452
|
+
// Listen for system theme changes if theme is set to 'auto'
|
|
7453
|
+
if (initialTheme === 'auto') {
|
|
7454
|
+
this.listenToSystemTheme();
|
|
7404
7455
|
}
|
|
7405
|
-
// Debug: Log the URL being used
|
|
7406
|
-
console.log('[NotificationApi] Request URL:', this.apiUrl);
|
|
7407
|
-
console.log('[NotificationApi] Full URL with params:', `${this.apiUrl}?${httpParams.toString()}`);
|
|
7408
|
-
return this.http.get(this.apiUrl, { params: httpParams });
|
|
7409
7456
|
}
|
|
7410
7457
|
/**
|
|
7411
|
-
*
|
|
7458
|
+
* Set the current theme
|
|
7412
7459
|
*/
|
|
7413
|
-
|
|
7414
|
-
|
|
7460
|
+
setTheme(theme) {
|
|
7461
|
+
if (!this.isBrowser) {
|
|
7462
|
+
return;
|
|
7463
|
+
}
|
|
7464
|
+
this.currentTheme$.next(theme);
|
|
7465
|
+
// Save preference if persistence is enabled
|
|
7466
|
+
if (this.config.persistPreference) {
|
|
7467
|
+
this.saveThemePreference(theme);
|
|
7468
|
+
}
|
|
7469
|
+
// Determine effective theme
|
|
7470
|
+
let effectiveTheme;
|
|
7471
|
+
if (theme === 'auto') {
|
|
7472
|
+
effectiveTheme = this.getSystemTheme();
|
|
7473
|
+
this.listenToSystemTheme();
|
|
7474
|
+
}
|
|
7475
|
+
else {
|
|
7476
|
+
effectiveTheme = theme;
|
|
7477
|
+
}
|
|
7478
|
+
this.effectiveTheme$.next(effectiveTheme);
|
|
7479
|
+
this.applyTheme(effectiveTheme);
|
|
7415
7480
|
}
|
|
7416
7481
|
/**
|
|
7417
|
-
* Get
|
|
7482
|
+
* Get the current theme preference
|
|
7418
7483
|
*/
|
|
7419
|
-
|
|
7420
|
-
return this.
|
|
7484
|
+
getCurrentTheme() {
|
|
7485
|
+
return this.currentTheme$.value;
|
|
7421
7486
|
}
|
|
7422
7487
|
/**
|
|
7423
|
-
*
|
|
7488
|
+
* Get the effective theme (actual theme being displayed)
|
|
7424
7489
|
*/
|
|
7425
|
-
|
|
7426
|
-
return this.
|
|
7490
|
+
getEffectiveTheme() {
|
|
7491
|
+
return this.effectiveTheme$.value;
|
|
7427
7492
|
}
|
|
7428
7493
|
/**
|
|
7429
|
-
*
|
|
7494
|
+
* Observable for current theme changes
|
|
7430
7495
|
*/
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
console.log('[NotificationApi] Mark as read URL:', url);
|
|
7434
|
-
return this.http.put(url, {});
|
|
7496
|
+
getCurrentTheme$() {
|
|
7497
|
+
return this.currentTheme$.asObservable();
|
|
7435
7498
|
}
|
|
7436
7499
|
/**
|
|
7437
|
-
*
|
|
7500
|
+
* Observable for effective theme changes
|
|
7438
7501
|
*/
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
console.log('[NotificationApi] Mark all as read URL:', url);
|
|
7442
|
-
return this.http.put(url, {});
|
|
7502
|
+
getEffectiveTheme$() {
|
|
7503
|
+
return this.effectiveTheme$.asObservable();
|
|
7443
7504
|
}
|
|
7444
7505
|
/**
|
|
7445
|
-
*
|
|
7506
|
+
* Toggle between light and dark themes
|
|
7446
7507
|
*/
|
|
7447
|
-
|
|
7448
|
-
|
|
7508
|
+
toggleTheme() {
|
|
7509
|
+
const currentEffective = this.effectiveTheme$.value;
|
|
7510
|
+
const newTheme = currentEffective === 'light' ? 'dark' : 'light';
|
|
7511
|
+
this.setTheme(newTheme);
|
|
7449
7512
|
}
|
|
7450
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: NotificationApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
7451
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: NotificationApiService, providedIn: 'root' });
|
|
7452
|
-
}
|
|
7453
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: NotificationApiService, decorators: [{
|
|
7454
|
-
type: Injectable,
|
|
7455
|
-
args: [{
|
|
7456
|
-
providedIn: 'root'
|
|
7457
|
-
}]
|
|
7458
|
-
}] });
|
|
7459
|
-
|
|
7460
|
-
/**
|
|
7461
|
-
* WebSocket Notification Service
|
|
7462
|
-
* Socket.IO client for real-time notifications
|
|
7463
|
-
*/
|
|
7464
|
-
class WebSocketNotificationService {
|
|
7465
|
-
socket = null;
|
|
7466
|
-
notificationSubject = new Subject();
|
|
7467
|
-
connectionStatusSubject = new BehaviorSubject({
|
|
7468
|
-
connected: false
|
|
7469
|
-
});
|
|
7470
|
-
// Signals for reactive state
|
|
7471
|
-
notifications = signal([], ...(ngDevMode ? [{ debugName: "notifications" }] : []));
|
|
7472
|
-
unreadNotifications = signal([], ...(ngDevMode ? [{ debugName: "unreadNotifications" }] : []));
|
|
7473
|
-
// Public observables
|
|
7474
|
-
notification$ = this.notificationSubject.asObservable();
|
|
7475
|
-
connectionStatus$ = this.connectionStatusSubject.asObservable();
|
|
7476
|
-
// Computed signals
|
|
7477
|
-
allNotifications = computed(() => this.notifications(), ...(ngDevMode ? [{ debugName: "allNotifications" }] : []));
|
|
7478
|
-
unreadNotificationsCount = computed(() => this.unreadNotifications().length, ...(ngDevMode ? [{ debugName: "unreadNotificationsCount" }] : []));
|
|
7479
|
-
isConnected = computed(() => this.connectionStatusSubject.value.connected, ...(ngDevMode ? [{ debugName: "isConnected" }] : []));
|
|
7480
7513
|
/**
|
|
7481
|
-
*
|
|
7514
|
+
* Check if dark mode is currently active
|
|
7482
7515
|
*/
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
console.log('Socket already connected');
|
|
7486
|
-
return;
|
|
7487
|
-
}
|
|
7488
|
-
// Resolve actual API URL from placeholder
|
|
7489
|
-
// The placeholder is replaced by host interceptor for HTTP, but WebSocket needs direct resolution
|
|
7490
|
-
// Based on host-manager.json: "__cloud_ide_suite_layout__" -> "http://localhost:3001"
|
|
7491
|
-
const placeholder = hostManagerRoutesUrl.cideSuiteHost;
|
|
7492
|
-
let apiUrl = 'http://localhost:3001'; // Default from host-manager.json
|
|
7493
|
-
// Try to resolve from host-manager.json via fetch (same as HTTP interceptor)
|
|
7494
|
-
if (typeof window !== 'undefined') {
|
|
7495
|
-
// Try to fetch host-manager.json to get the actual URL
|
|
7496
|
-
fetch('/routes/host-manager.json')
|
|
7497
|
-
.then(res => res.json())
|
|
7498
|
-
.then((hostManagerRoutes) => {
|
|
7499
|
-
const hostManagerRoutesEntry = hostManagerRoutes?.hosts?.find((hostRoute) => placeholder?.includes(hostRoute?.url));
|
|
7500
|
-
if (hostManagerRoutesEntry?.replace) {
|
|
7501
|
-
apiUrl = hostManagerRoutesEntry.replace;
|
|
7502
|
-
}
|
|
7503
|
-
// Remove /api suffix if present (Socket.IO connects to base server)
|
|
7504
|
-
apiUrl = apiUrl.replace(/\/api\/?$/, '');
|
|
7505
|
-
this.connectWithUrl(apiUrl, token, userId);
|
|
7506
|
-
})
|
|
7507
|
-
.catch(() => {
|
|
7508
|
-
// Fallback if fetch fails - use default or try localStorage
|
|
7509
|
-
this.resolveUrlFallback(token, userId);
|
|
7510
|
-
});
|
|
7511
|
-
}
|
|
7512
|
-
else {
|
|
7513
|
-
// Server-side or fallback
|
|
7514
|
-
this.resolveUrlFallback(token, userId);
|
|
7515
|
-
}
|
|
7516
|
+
isDarkMode() {
|
|
7517
|
+
return this.effectiveTheme$.value === 'dark';
|
|
7516
7518
|
}
|
|
7517
7519
|
/**
|
|
7518
|
-
*
|
|
7520
|
+
* Apply theme to target element
|
|
7519
7521
|
*/
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7522
|
+
applyTheme(theme) {
|
|
7523
|
+
if (!this.isBrowser || !this.config.targetElement) {
|
|
7524
|
+
return;
|
|
7525
|
+
}
|
|
7526
|
+
const element = this.config.targetElement;
|
|
7527
|
+
// Apply data-theme attribute
|
|
7528
|
+
if (this.config.useDataAttribute) {
|
|
7529
|
+
element.setAttribute('data-theme', theme);
|
|
7530
|
+
}
|
|
7531
|
+
// Apply class name
|
|
7532
|
+
if (this.config.useClassName) {
|
|
7533
|
+
if (theme === 'dark') {
|
|
7534
|
+
element.classList.add('dark-mode');
|
|
7535
|
+
element.classList.remove('light-mode');
|
|
7529
7536
|
}
|
|
7530
7537
|
else {
|
|
7531
|
-
|
|
7532
|
-
|
|
7538
|
+
element.classList.add('light-mode');
|
|
7539
|
+
element.classList.remove('dark-mode');
|
|
7533
7540
|
}
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7541
|
+
}
|
|
7542
|
+
// Also apply to body for better compatibility
|
|
7543
|
+
if (element === document.documentElement && document.body) {
|
|
7544
|
+
if (this.config.useDataAttribute) {
|
|
7545
|
+
document.body.setAttribute('data-theme', theme);
|
|
7546
|
+
}
|
|
7547
|
+
if (this.config.useClassName) {
|
|
7548
|
+
if (theme === 'dark') {
|
|
7549
|
+
document.body.classList.add('dark-mode');
|
|
7550
|
+
document.body.classList.remove('light-mode');
|
|
7551
|
+
}
|
|
7552
|
+
else {
|
|
7553
|
+
document.body.classList.add('light-mode');
|
|
7554
|
+
document.body.classList.remove('dark-mode');
|
|
7555
|
+
}
|
|
7538
7556
|
}
|
|
7539
7557
|
}
|
|
7540
|
-
this.connectWithUrl(apiUrl, token, userId);
|
|
7541
7558
|
}
|
|
7542
7559
|
/**
|
|
7543
|
-
*
|
|
7560
|
+
* Get system theme preference
|
|
7544
7561
|
*/
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
reconnection: true,
|
|
7554
|
-
reconnectionDelay: 1000,
|
|
7555
|
-
reconnectionAttempts: 5,
|
|
7556
|
-
reconnectionDelayMax: 5000
|
|
7557
|
-
});
|
|
7558
|
-
this.setupEventHandlers(userId);
|
|
7562
|
+
getSystemTheme() {
|
|
7563
|
+
if (!this.isBrowser) {
|
|
7564
|
+
return 'light';
|
|
7565
|
+
}
|
|
7566
|
+
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
7567
|
+
return 'dark';
|
|
7568
|
+
}
|
|
7569
|
+
return 'light';
|
|
7559
7570
|
}
|
|
7560
7571
|
/**
|
|
7561
|
-
*
|
|
7572
|
+
* Listen to system theme changes
|
|
7562
7573
|
*/
|
|
7563
|
-
|
|
7564
|
-
if (!this.
|
|
7574
|
+
listenToSystemTheme() {
|
|
7575
|
+
if (!this.isBrowser || !window.matchMedia) {
|
|
7565
7576
|
return;
|
|
7566
|
-
// Connection established
|
|
7567
|
-
this.socket.on('connect', () => {
|
|
7568
|
-
console.log('Socket.IO connected:', this.socket?.id);
|
|
7569
|
-
this.connectionStatusSubject.next({
|
|
7570
|
-
connected: true,
|
|
7571
|
-
socketId: this.socket?.id,
|
|
7572
|
-
userId: userId,
|
|
7573
|
-
timestamp: new Date()
|
|
7574
|
-
});
|
|
7575
|
-
});
|
|
7576
|
-
// Connection confirmation
|
|
7577
|
-
this.socket.on('connected', (data) => {
|
|
7578
|
-
console.log('Notification service connected:', data);
|
|
7579
|
-
this.connectionStatusSubject.next({
|
|
7580
|
-
connected: true,
|
|
7581
|
-
socketId: data.socketId,
|
|
7582
|
-
userId: data.userId,
|
|
7583
|
-
timestamp: data.timestamp
|
|
7584
|
-
});
|
|
7585
|
-
});
|
|
7586
|
-
// Receive notification
|
|
7587
|
-
this.socket.on('notification', (notification) => {
|
|
7588
|
-
console.log('Received notification:', notification);
|
|
7589
|
-
// Add to notifications list
|
|
7590
|
-
this.notifications.update(notifications => [notification, ...notifications]);
|
|
7591
|
-
// Add to unread list
|
|
7592
|
-
this.unreadNotifications.update(unread => [notification, ...unread]);
|
|
7593
|
-
// Emit to subscribers
|
|
7594
|
-
this.notificationSubject.next(notification);
|
|
7595
|
-
// Acknowledge receipt
|
|
7596
|
-
this.acknowledgeNotification(notification.id);
|
|
7597
|
-
});
|
|
7598
|
-
// Disconnect
|
|
7599
|
-
this.socket.on('disconnect', (reason) => {
|
|
7600
|
-
console.log('Socket.IO disconnected:', reason);
|
|
7601
|
-
this.connectionStatusSubject.next({
|
|
7602
|
-
connected: false,
|
|
7603
|
-
timestamp: new Date()
|
|
7604
|
-
});
|
|
7605
|
-
});
|
|
7606
|
-
// Connection error
|
|
7607
|
-
this.socket.on('connect_error', (error) => {
|
|
7608
|
-
console.error('Socket.IO connection error:', error);
|
|
7609
|
-
this.connectionStatusSubject.next({
|
|
7610
|
-
connected: false,
|
|
7611
|
-
timestamp: new Date()
|
|
7612
|
-
});
|
|
7613
|
-
});
|
|
7614
|
-
}
|
|
7615
|
-
/**
|
|
7616
|
-
* Acknowledge notification receipt
|
|
7617
|
-
*/
|
|
7618
|
-
acknowledgeNotification(notificationId) {
|
|
7619
|
-
if (this.socket?.connected) {
|
|
7620
|
-
this.socket.emit('notification:ack', { notificationId });
|
|
7621
7577
|
}
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7578
|
+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
7579
|
+
const handler = (e) => {
|
|
7580
|
+
if (this.currentTheme$.value === 'auto') {
|
|
7581
|
+
const effectiveTheme = e.matches ? 'dark' : 'light';
|
|
7582
|
+
this.effectiveTheme$.next(effectiveTheme);
|
|
7583
|
+
this.applyTheme(effectiveTheme);
|
|
7584
|
+
}
|
|
7585
|
+
};
|
|
7586
|
+
// Modern browsers
|
|
7587
|
+
if (mediaQuery.addEventListener) {
|
|
7588
|
+
mediaQuery.addEventListener('change', handler);
|
|
7589
|
+
}
|
|
7590
|
+
else if (mediaQuery.addListener) {
|
|
7591
|
+
// Legacy browsers
|
|
7592
|
+
mediaQuery.addListener(handler);
|
|
7631
7593
|
}
|
|
7632
7594
|
}
|
|
7633
7595
|
/**
|
|
7634
|
-
*
|
|
7635
|
-
*/
|
|
7636
|
-
markAllAsRead() {
|
|
7637
|
-
const unreadIds = this.unreadNotifications().map(n => n.id);
|
|
7638
|
-
unreadIds.forEach(id => this.markAsRead(id));
|
|
7639
|
-
}
|
|
7640
|
-
/**
|
|
7641
|
-
* Disconnect from server
|
|
7596
|
+
* Save theme preference to localStorage
|
|
7642
7597
|
*/
|
|
7643
|
-
|
|
7644
|
-
if (this.
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7598
|
+
saveThemePreference(theme) {
|
|
7599
|
+
if (!this.isBrowser) {
|
|
7600
|
+
return;
|
|
7601
|
+
}
|
|
7602
|
+
try {
|
|
7603
|
+
// Save in new format: 'light', 'dark', or 'auto'
|
|
7604
|
+
localStorage.setItem(this.config.storageKey, theme);
|
|
7605
|
+
// Also update old format for backward compatibility
|
|
7606
|
+
// This allows old code that reads 'true'/'false' to still work
|
|
7607
|
+
if (theme === 'dark') {
|
|
7608
|
+
localStorage.setItem(this.config.storageKey, 'dark');
|
|
7609
|
+
}
|
|
7610
|
+
else if (theme === 'light') {
|
|
7611
|
+
localStorage.setItem(this.config.storageKey, 'light');
|
|
7612
|
+
}
|
|
7613
|
+
}
|
|
7614
|
+
catch (error) {
|
|
7615
|
+
console.warn('Failed to save theme preference to localStorage:', error);
|
|
7651
7616
|
}
|
|
7652
7617
|
}
|
|
7653
7618
|
/**
|
|
7654
|
-
*
|
|
7619
|
+
* Load theme preference from localStorage
|
|
7655
7620
|
*/
|
|
7656
|
-
|
|
7657
|
-
this.
|
|
7658
|
-
|
|
7621
|
+
loadThemePreference() {
|
|
7622
|
+
if (!this.isBrowser) {
|
|
7623
|
+
return null;
|
|
7624
|
+
}
|
|
7625
|
+
try {
|
|
7626
|
+
const saved = localStorage.getItem(this.config.storageKey);
|
|
7627
|
+
// Handle new format: 'light', 'dark', 'auto'
|
|
7628
|
+
if (saved && ['light', 'dark', 'auto'].includes(saved)) {
|
|
7629
|
+
return saved;
|
|
7630
|
+
}
|
|
7631
|
+
// Handle legacy format: 'true' (dark mode) or 'false' (light mode)
|
|
7632
|
+
if (saved === 'true') {
|
|
7633
|
+
// Migrate to new format
|
|
7634
|
+
localStorage.setItem(this.config.storageKey, 'dark');
|
|
7635
|
+
return 'dark';
|
|
7636
|
+
}
|
|
7637
|
+
else if (saved === 'false') {
|
|
7638
|
+
// Migrate to new format
|
|
7639
|
+
localStorage.setItem(this.config.storageKey, 'light');
|
|
7640
|
+
return 'light';
|
|
7641
|
+
}
|
|
7642
|
+
}
|
|
7643
|
+
catch (error) {
|
|
7644
|
+
console.warn('Failed to load theme preference from localStorage:', error);
|
|
7645
|
+
}
|
|
7646
|
+
return null;
|
|
7659
7647
|
}
|
|
7660
7648
|
/**
|
|
7661
|
-
*
|
|
7649
|
+
* Clear saved theme preference
|
|
7662
7650
|
*/
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
7651
|
+
clearThemePreference() {
|
|
7652
|
+
if (!this.isBrowser) {
|
|
7653
|
+
return;
|
|
7654
|
+
}
|
|
7655
|
+
try {
|
|
7656
|
+
localStorage.removeItem(this.config.storageKey);
|
|
7657
|
+
}
|
|
7658
|
+
catch (error) {
|
|
7659
|
+
console.warn('Failed to clear theme preference from localStorage:', error);
|
|
7660
|
+
}
|
|
7671
7661
|
}
|
|
7672
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
7673
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
7662
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
7663
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideThemeService, providedIn: 'root' });
|
|
7674
7664
|
}
|
|
7675
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
7665
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideThemeService, decorators: [{
|
|
7676
7666
|
type: Injectable,
|
|
7677
7667
|
args: [{
|
|
7678
7668
|
providedIn: 'root'
|
|
7679
7669
|
}]
|
|
7680
|
-
}] });
|
|
7670
|
+
}], ctorParameters: () => [] });
|
|
7681
7671
|
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7672
|
+
/**
|
|
7673
|
+
* Apple-inspired dark/light mode toggle component
|
|
7674
|
+
*
|
|
7675
|
+
* Features:
|
|
7676
|
+
* - Smooth animated toggle switch
|
|
7677
|
+
* - Apple-style design with rounded corners and shadows
|
|
7678
|
+
* - Works on pages without header/sidebar (dynamic website, login pages, auth project)
|
|
7679
|
+
* - Automatically syncs with system theme preference
|
|
7680
|
+
* - Persists user preference to localStorage
|
|
7681
|
+
*/
|
|
7682
|
+
class CideEleThemeToggleComponent {
|
|
7683
|
+
themeService = inject(CideThemeService);
|
|
7684
|
+
destroyRef = inject(DestroyRef);
|
|
7685
|
+
// Current theme state
|
|
7686
|
+
isDarkMode = signal(false, ...(ngDevMode ? [{ debugName: "isDarkMode" }] : []));
|
|
7687
|
+
isAnimating = signal(false, ...(ngDevMode ? [{ debugName: "isAnimating" }] : []));
|
|
7688
|
+
// Computed values
|
|
7689
|
+
togglePosition = computed(() => this.isDarkMode() ? 'translate-x-5' : 'translate-x-0', ...(ngDevMode ? [{ debugName: "togglePosition" }] : []));
|
|
7690
|
+
toggleBgColor = computed(() => this.isDarkMode()
|
|
7691
|
+
? 'tw-bg-gradient-to-r tw-from-blue-500 tw-to-indigo-600'
|
|
7692
|
+
: 'tw-bg-gray-200 dark:tw-bg-gray-700', ...(ngDevMode ? [{ debugName: "toggleBgColor" }] : []));
|
|
7693
|
+
ngOnInit() {
|
|
7694
|
+
// Subscribe to theme changes
|
|
7695
|
+
this.themeService.getEffectiveTheme$()
|
|
7696
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
7697
|
+
.subscribe(theme => {
|
|
7698
|
+
this.isDarkMode.set(theme === 'dark');
|
|
7699
|
+
});
|
|
7700
|
+
// Set initial state
|
|
7701
|
+
this.isDarkMode.set(this.themeService.isDarkMode());
|
|
7690
7702
|
}
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
*/
|
|
7694
|
-
getFileList(body) {
|
|
7695
|
-
const query = generateStringFromObject(body);
|
|
7696
|
-
return this.http?.get(cidePath$1?.join([hostManagerRoutesUrl$1?.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.fileManager, query]))
|
|
7697
|
-
.pipe(tap((response) => {
|
|
7698
|
-
if (response?.success) {
|
|
7699
|
-
this.fileListSubject.next(response?.data || []);
|
|
7700
|
-
}
|
|
7701
|
-
}), catchError$1(error => {
|
|
7702
|
-
console.error('CideCoreFileManagerService API error:', error);
|
|
7703
|
-
return this.handleError(error);
|
|
7704
|
-
}));
|
|
7703
|
+
ngOnDestroy() {
|
|
7704
|
+
// Cleanup handled by takeUntilDestroyed
|
|
7705
7705
|
}
|
|
7706
7706
|
/**
|
|
7707
|
-
*
|
|
7707
|
+
* Toggle between light and dark mode
|
|
7708
7708
|
*/
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7709
|
+
toggleTheme() {
|
|
7710
|
+
if (this.isAnimating()) {
|
|
7711
|
+
return; // Prevent rapid clicking
|
|
7712
|
+
}
|
|
7713
|
+
this.isAnimating.set(true);
|
|
7714
|
+
// Toggle theme using the service
|
|
7715
|
+
this.themeService.toggleTheme();
|
|
7716
|
+
// Reset animation flag after transition
|
|
7717
|
+
setTimeout(() => {
|
|
7718
|
+
this.isAnimating.set(false);
|
|
7719
|
+
}, 300);
|
|
7712
7720
|
}
|
|
7721
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleThemeToggleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7722
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: CideEleThemeToggleComponent, isStandalone: true, selector: "cide-ele-theme-toggle", ngImport: i0, template: "<div class=\"cide-theme-toggle-wrapper\">\r\n <button\r\n type=\"button\"\r\n class=\"cide-theme-toggle-button\"\r\n [class.is-dark]=\"isDarkMode()\"\r\n [class.is-animating]=\"isAnimating()\"\r\n (click)=\"toggleTheme()\"\r\n [attr.aria-label]=\"isDarkMode() ? 'Switch to light mode' : 'Switch to dark mode'\"\r\n [attr.aria-pressed]=\"isDarkMode()\">\r\n \r\n <!-- Toggle Track -->\r\n <span class=\"cide-theme-toggle-track\" [class]=\"toggleBgColor()\">\r\n <!-- Sun Icon (Light Mode) -->\r\n <span class=\"cide-theme-toggle-icon sun-icon\" [class.hidden]=\"isDarkMode()\">\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <circle cx=\"12\" cy=\"12\" r=\"4\" stroke=\"currentColor\" stroke-width=\"2\" fill=\"currentColor\"/>\r\n <path d=\"M12 2V4M12 20V22M4 12H2M22 12H20M19.07 4.93L17.66 6.34M6.34 17.66L4.93 19.07M19.07 19.07L17.66 17.66M6.34 6.34L4.93 4.93\" \r\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\r\n </svg>\r\n </span>\r\n \r\n <!-- Moon Icon (Dark Mode) -->\r\n <span class=\"cide-theme-toggle-icon moon-icon\" [class.hidden]=\"!isDarkMode()\">\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\" \r\n stroke=\"currentColor\" stroke-width=\"2\" fill=\"currentColor\"/>\r\n </svg>\r\n </span>\r\n </span>\r\n \r\n <!-- Toggle Thumb (Slider) -->\r\n <span class=\"cide-theme-toggle-thumb\" [style.transform]=\"togglePosition()\">\r\n </span>\r\n </button>\r\n</div>\r\n\r\n", styles: [".cide-theme-toggle-wrapper{display:inline-flex;align-items:center;justify-content:center}.cide-theme-toggle-button{position:relative;width:52px;height:32px;padding:0;border:none;background:transparent;cursor:pointer;outline:none;transition:all .3s cubic-bezier(.4,0,.2,1);user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.cide-theme-toggle-button:focus-visible{outline:2px solid rgba(59,130,246,.5);outline-offset:2px;border-radius:20px}.cide-theme-toggle-track{position:absolute;top:0;left:0;width:100%;height:100%;border-radius:16px;transition:all .3s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;justify-content:space-between;padding:0 4px;box-shadow:inset 0 2px 4px #0000001a,0 1px 2px #0000000d}.is-dark .cide-theme-toggle-track{box-shadow:inset 0 2px 4px #0003,0 1px 2px #0000001a}.cide-theme-toggle-icon{display:flex;align-items:center;justify-content:center;width:20px;height:20px;color:#fff;transition:all .3s cubic-bezier(.4,0,.2,1);opacity:1}.cide-theme-toggle-icon.hidden{opacity:0;width:0;transform:scale(0)}.cide-theme-toggle-icon svg{width:16px;height:16px;filter:drop-shadow(0 1px 2px rgba(0,0,0,.2))}.cide-theme-toggle-icon.sun-icon{color:#fbbf24}.cide-theme-toggle-icon.moon-icon{color:#e0e7ff}.cide-theme-toggle-thumb{position:absolute;top:2px;left:2px;width:28px;height:28px;background:#fff;border-radius:50%;box-shadow:0 2px 4px #0003,0 1px 2px #0000001a;transition:transform .3s cubic-bezier(.4,0,.2,1);z-index:1;background:linear-gradient(180deg,#fff,#f9fafb)}.is-dark .cide-theme-toggle-thumb{background:linear-gradient(180deg,#f3f4f6,#e5e7eb);box-shadow:0 2px 4px #0000004d,0 1px 2px #00000026}.is-animating .cide-theme-toggle-thumb{transition:transform .3s cubic-bezier(.4,0,.2,1)}.cide-theme-toggle-button:hover .cide-theme-toggle-thumb{box-shadow:0 3px 6px #00000040,0 2px 4px #00000026}.is-dark .cide-theme-toggle-button:hover .cide-theme-toggle-thumb{box-shadow:0 3px 6px #0006,0 2px 4px #0003}.cide-theme-toggle-button:hover .cide-theme-toggle-track{box-shadow:inset 0 2px 4px #00000026,0 2px 4px #0000001a}.is-dark .cide-theme-toggle-button:hover .cide-theme-toggle-track{box-shadow:inset 0 2px 4px #00000040,0 2px 4px #00000026}.cide-theme-toggle-button:active .cide-theme-toggle-thumb{transform:scale(.95)}.cide-theme-toggle-button:disabled{opacity:.5;cursor:not-allowed}.cide-theme-toggle-button:disabled:hover .cide-theme-toggle-thumb,.cide-theme-toggle-button:disabled:hover .cide-theme-toggle-track{box-shadow:none}@media (prefers-color-scheme: dark){.cide-theme-toggle-thumb{background:linear-gradient(180deg,#f3f4f6,#e5e7eb)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
7723
|
+
}
|
|
7724
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleThemeToggleComponent, decorators: [{
|
|
7725
|
+
type: Component,
|
|
7726
|
+
args: [{ selector: 'cide-ele-theme-toggle', standalone: true, imports: [CommonModule], template: "<div class=\"cide-theme-toggle-wrapper\">\r\n <button\r\n type=\"button\"\r\n class=\"cide-theme-toggle-button\"\r\n [class.is-dark]=\"isDarkMode()\"\r\n [class.is-animating]=\"isAnimating()\"\r\n (click)=\"toggleTheme()\"\r\n [attr.aria-label]=\"isDarkMode() ? 'Switch to light mode' : 'Switch to dark mode'\"\r\n [attr.aria-pressed]=\"isDarkMode()\">\r\n \r\n <!-- Toggle Track -->\r\n <span class=\"cide-theme-toggle-track\" [class]=\"toggleBgColor()\">\r\n <!-- Sun Icon (Light Mode) -->\r\n <span class=\"cide-theme-toggle-icon sun-icon\" [class.hidden]=\"isDarkMode()\">\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <circle cx=\"12\" cy=\"12\" r=\"4\" stroke=\"currentColor\" stroke-width=\"2\" fill=\"currentColor\"/>\r\n <path d=\"M12 2V4M12 20V22M4 12H2M22 12H20M19.07 4.93L17.66 6.34M6.34 17.66L4.93 19.07M19.07 19.07L17.66 17.66M6.34 6.34L4.93 4.93\" \r\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\r\n </svg>\r\n </span>\r\n \r\n <!-- Moon Icon (Dark Mode) -->\r\n <span class=\"cide-theme-toggle-icon moon-icon\" [class.hidden]=\"!isDarkMode()\">\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\" \r\n stroke=\"currentColor\" stroke-width=\"2\" fill=\"currentColor\"/>\r\n </svg>\r\n </span>\r\n </span>\r\n \r\n <!-- Toggle Thumb (Slider) -->\r\n <span class=\"cide-theme-toggle-thumb\" [style.transform]=\"togglePosition()\">\r\n </span>\r\n </button>\r\n</div>\r\n\r\n", styles: [".cide-theme-toggle-wrapper{display:inline-flex;align-items:center;justify-content:center}.cide-theme-toggle-button{position:relative;width:52px;height:32px;padding:0;border:none;background:transparent;cursor:pointer;outline:none;transition:all .3s cubic-bezier(.4,0,.2,1);user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.cide-theme-toggle-button:focus-visible{outline:2px solid rgba(59,130,246,.5);outline-offset:2px;border-radius:20px}.cide-theme-toggle-track{position:absolute;top:0;left:0;width:100%;height:100%;border-radius:16px;transition:all .3s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;justify-content:space-between;padding:0 4px;box-shadow:inset 0 2px 4px #0000001a,0 1px 2px #0000000d}.is-dark .cide-theme-toggle-track{box-shadow:inset 0 2px 4px #0003,0 1px 2px #0000001a}.cide-theme-toggle-icon{display:flex;align-items:center;justify-content:center;width:20px;height:20px;color:#fff;transition:all .3s cubic-bezier(.4,0,.2,1);opacity:1}.cide-theme-toggle-icon.hidden{opacity:0;width:0;transform:scale(0)}.cide-theme-toggle-icon svg{width:16px;height:16px;filter:drop-shadow(0 1px 2px rgba(0,0,0,.2))}.cide-theme-toggle-icon.sun-icon{color:#fbbf24}.cide-theme-toggle-icon.moon-icon{color:#e0e7ff}.cide-theme-toggle-thumb{position:absolute;top:2px;left:2px;width:28px;height:28px;background:#fff;border-radius:50%;box-shadow:0 2px 4px #0003,0 1px 2px #0000001a;transition:transform .3s cubic-bezier(.4,0,.2,1);z-index:1;background:linear-gradient(180deg,#fff,#f9fafb)}.is-dark .cide-theme-toggle-thumb{background:linear-gradient(180deg,#f3f4f6,#e5e7eb);box-shadow:0 2px 4px #0000004d,0 1px 2px #00000026}.is-animating .cide-theme-toggle-thumb{transition:transform .3s cubic-bezier(.4,0,.2,1)}.cide-theme-toggle-button:hover .cide-theme-toggle-thumb{box-shadow:0 3px 6px #00000040,0 2px 4px #00000026}.is-dark .cide-theme-toggle-button:hover .cide-theme-toggle-thumb{box-shadow:0 3px 6px #0006,0 2px 4px #0003}.cide-theme-toggle-button:hover .cide-theme-toggle-track{box-shadow:inset 0 2px 4px #00000026,0 2px 4px #0000001a}.is-dark .cide-theme-toggle-button:hover .cide-theme-toggle-track{box-shadow:inset 0 2px 4px #00000040,0 2px 4px #00000026}.cide-theme-toggle-button:active .cide-theme-toggle-thumb{transform:scale(.95)}.cide-theme-toggle-button:disabled{opacity:.5;cursor:not-allowed}.cide-theme-toggle-button:disabled:hover .cide-theme-toggle-thumb,.cide-theme-toggle-button:disabled:hover .cide-theme-toggle-track{box-shadow:none}@media (prefers-color-scheme: dark){.cide-theme-toggle-thumb{background:linear-gradient(180deg,#f3f4f6,#e5e7eb)}}\n"] }]
|
|
7727
|
+
}] });
|
|
7728
|
+
|
|
7729
|
+
/**
|
|
7730
|
+
* Notification API Service
|
|
7731
|
+
* HTTP client for notification API endpoints
|
|
7732
|
+
*/
|
|
7733
|
+
class NotificationApiService {
|
|
7734
|
+
http = inject(HttpClient);
|
|
7735
|
+
apiUrl = cidePath.join([
|
|
7736
|
+
hostManagerRoutesUrl.cideSuiteHost,
|
|
7737
|
+
notificationRoutesUrl.module
|
|
7738
|
+
]);
|
|
7713
7739
|
/**
|
|
7714
|
-
* Get
|
|
7740
|
+
* Get user notifications
|
|
7715
7741
|
*/
|
|
7716
|
-
|
|
7717
|
-
|
|
7742
|
+
getNotifications(params) {
|
|
7743
|
+
let httpParams = new HttpParams();
|
|
7744
|
+
if (params) {
|
|
7745
|
+
Object.keys(params).forEach(key => {
|
|
7746
|
+
const value = params[key];
|
|
7747
|
+
if (value !== undefined && value !== null) {
|
|
7748
|
+
httpParams = httpParams.set(key, value.toString());
|
|
7749
|
+
}
|
|
7750
|
+
});
|
|
7751
|
+
}
|
|
7752
|
+
// Debug: Log the URL being used
|
|
7753
|
+
console.log('[NotificationApi] Request URL:', this.apiUrl);
|
|
7754
|
+
console.log('[NotificationApi] Full URL with params:', `${this.apiUrl}?${httpParams.toString()}`);
|
|
7755
|
+
return this.http.get(this.apiUrl, { params: httpParams });
|
|
7718
7756
|
}
|
|
7719
7757
|
/**
|
|
7720
|
-
*
|
|
7758
|
+
* Get notification by ID
|
|
7721
7759
|
*/
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
formData.append('file', request.file);
|
|
7725
|
-
formData.append('altText', request.altText || '');
|
|
7726
|
-
formData.append('tags', JSON.stringify(request.tags || []));
|
|
7727
|
-
formData.append('permissions', JSON.stringify(request.permissions || []));
|
|
7728
|
-
formData.append('userId', request.userId || '');
|
|
7729
|
-
const url = cidePath$1?.join([hostManagerRoutesUrl$1?.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.fileManager, 'upload']);
|
|
7730
|
-
const req = new HttpRequest('POST', url, formData, {
|
|
7731
|
-
reportProgress: true
|
|
7732
|
-
});
|
|
7733
|
-
return this.http.request(req).pipe(catchError$1(this.handleError));
|
|
7760
|
+
getNotificationById(id) {
|
|
7761
|
+
return this.http.get(`${this.apiUrl}/${id}`);
|
|
7734
7762
|
}
|
|
7735
7763
|
/**
|
|
7736
|
-
*
|
|
7764
|
+
* Get unread count
|
|
7737
7765
|
*/
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
return this.uploadFile(request);
|
|
7766
|
+
getUnreadCount() {
|
|
7767
|
+
return this.http.get(`${this.apiUrl}/${notificationRoutesUrl.unreadCount}`);
|
|
7741
7768
|
}
|
|
7742
7769
|
/**
|
|
7743
|
-
*
|
|
7770
|
+
* Create notification
|
|
7744
7771
|
*/
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
const url = cidePath$1?.join([hostManagerRoutesUrl$1?.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.fileManager]);
|
|
7748
|
-
return this.http.post(url, request)
|
|
7749
|
-
.pipe(tap((response) => {
|
|
7750
|
-
if (response.success) {
|
|
7751
|
-
this.refreshFileList();
|
|
7752
|
-
}
|
|
7753
|
-
}), catchError$1(this.handleError));
|
|
7772
|
+
createNotification(payload) {
|
|
7773
|
+
return this.http.post(this.apiUrl, payload);
|
|
7754
7774
|
}
|
|
7755
7775
|
/**
|
|
7756
|
-
*
|
|
7776
|
+
* Mark notification as read
|
|
7757
7777
|
*/
|
|
7758
|
-
|
|
7759
|
-
const
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
return this.http.delete(url)
|
|
7763
|
-
.pipe(tap((response) => {
|
|
7764
|
-
if (response.success) {
|
|
7765
|
-
this.refreshFileList();
|
|
7766
|
-
}
|
|
7767
|
-
}), catchError$1(this.handleError));
|
|
7778
|
+
markAsRead(id) {
|
|
7779
|
+
const url = `${this.apiUrl}/${id}/read`;
|
|
7780
|
+
console.log('[NotificationApi] Mark as read URL:', url);
|
|
7781
|
+
return this.http.put(url, {});
|
|
7768
7782
|
}
|
|
7769
7783
|
/**
|
|
7770
|
-
*
|
|
7784
|
+
* Mark all notifications as read
|
|
7771
7785
|
*/
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
const url = cidePath$1?.join([hostManagerRoutesUrl$1?.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.fileManager, query]);
|
|
7777
|
-
return this.http.delete(url)
|
|
7778
|
-
.pipe(tap((response) => {
|
|
7779
|
-
if (response.success) {
|
|
7780
|
-
this.refreshFileList();
|
|
7781
|
-
}
|
|
7782
|
-
}), catchError$1(this.handleError));
|
|
7786
|
+
markAllAsRead() {
|
|
7787
|
+
const url = `${this.apiUrl}/read-all`;
|
|
7788
|
+
console.log('[NotificationApi] Mark all as read URL:', url);
|
|
7789
|
+
return this.http.put(url, {});
|
|
7783
7790
|
}
|
|
7784
7791
|
/**
|
|
7785
|
-
*
|
|
7792
|
+
* Archive notification
|
|
7786
7793
|
*/
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
const payload = { id };
|
|
7790
|
-
const query = generateStringFromObject(payload);
|
|
7791
|
-
const url = cidePath$1?.join([hostManagerRoutesUrl$1?.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.fileManager, query]);
|
|
7792
|
-
return this.http.put(url, {})
|
|
7793
|
-
.pipe(tap((response) => {
|
|
7794
|
-
if (response.success) {
|
|
7795
|
-
this.refreshFileList();
|
|
7796
|
-
}
|
|
7797
|
-
}), catchError$1(this.handleError));
|
|
7794
|
+
archiveNotification(id) {
|
|
7795
|
+
return this.http.put(`${this.apiUrl}/${id}/archive`, {});
|
|
7798
7796
|
}
|
|
7797
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: NotificationApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
7798
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: NotificationApiService, providedIn: 'root' });
|
|
7799
|
+
}
|
|
7800
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: NotificationApiService, decorators: [{
|
|
7801
|
+
type: Injectable,
|
|
7802
|
+
args: [{
|
|
7803
|
+
providedIn: 'root'
|
|
7804
|
+
}]
|
|
7805
|
+
}] });
|
|
7806
|
+
|
|
7807
|
+
/**
|
|
7808
|
+
* WebSocket Notification Service
|
|
7809
|
+
* Socket.IO client for real-time notifications
|
|
7810
|
+
*/
|
|
7811
|
+
class WebSocketNotificationService {
|
|
7812
|
+
socket = null;
|
|
7813
|
+
notificationSubject = new Subject();
|
|
7814
|
+
connectionStatusSubject = new BehaviorSubject({
|
|
7815
|
+
connected: false
|
|
7816
|
+
});
|
|
7817
|
+
// Signals for reactive state
|
|
7818
|
+
notifications = signal([], ...(ngDevMode ? [{ debugName: "notifications" }] : []));
|
|
7819
|
+
unreadNotifications = signal([], ...(ngDevMode ? [{ debugName: "unreadNotifications" }] : []));
|
|
7820
|
+
// Public observables
|
|
7821
|
+
notification$ = this.notificationSubject.asObservable();
|
|
7822
|
+
connectionStatus$ = this.connectionStatusSubject.asObservable();
|
|
7823
|
+
// Computed signals
|
|
7824
|
+
allNotifications = computed(() => this.notifications(), ...(ngDevMode ? [{ debugName: "allNotifications" }] : []));
|
|
7825
|
+
unreadNotificationsCount = computed(() => this.unreadNotifications().length, ...(ngDevMode ? [{ debugName: "unreadNotificationsCount" }] : []));
|
|
7826
|
+
isConnected = computed(() => this.connectionStatusSubject.value.connected, ...(ngDevMode ? [{ debugName: "isConnected" }] : []));
|
|
7799
7827
|
/**
|
|
7800
|
-
*
|
|
7828
|
+
* Connect to Socket.IO server
|
|
7801
7829
|
*/
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7830
|
+
connect(token, userId) {
|
|
7831
|
+
if (this.socket?.connected) {
|
|
7832
|
+
console.log('Socket already connected');
|
|
7833
|
+
return;
|
|
7834
|
+
}
|
|
7835
|
+
// Resolve actual API URL from placeholder
|
|
7836
|
+
// The placeholder is replaced by host interceptor for HTTP, but WebSocket needs direct resolution
|
|
7837
|
+
// Based on host-manager.json: "__cloud_ide_suite_layout__" -> "http://localhost:3001"
|
|
7838
|
+
const placeholder = hostManagerRoutesUrl.cideSuiteHost;
|
|
7839
|
+
let apiUrl = 'http://localhost:3001'; // Default from host-manager.json
|
|
7840
|
+
// Try to resolve from host-manager.json via fetch (same as HTTP interceptor)
|
|
7841
|
+
if (typeof window !== 'undefined') {
|
|
7842
|
+
// Try to fetch host-manager.json to get the actual URL
|
|
7843
|
+
fetch('/routes/host-manager.json')
|
|
7844
|
+
.then(res => res.json())
|
|
7845
|
+
.then((hostManagerRoutes) => {
|
|
7846
|
+
const hostManagerRoutesEntry = hostManagerRoutes?.hosts?.find((hostRoute) => placeholder?.includes(hostRoute?.url));
|
|
7847
|
+
if (hostManagerRoutesEntry?.replace) {
|
|
7848
|
+
apiUrl = hostManagerRoutesEntry.replace;
|
|
7849
|
+
}
|
|
7850
|
+
// Remove /api suffix if present (Socket.IO connects to base server)
|
|
7851
|
+
apiUrl = apiUrl.replace(/\/api\/?$/, '');
|
|
7852
|
+
this.connectWithUrl(apiUrl, token, userId);
|
|
7853
|
+
})
|
|
7854
|
+
.catch(() => {
|
|
7855
|
+
// Fallback if fetch fails - use default or try localStorage
|
|
7856
|
+
this.resolveUrlFallback(token, userId);
|
|
7857
|
+
});
|
|
7858
|
+
}
|
|
7859
|
+
else {
|
|
7860
|
+
// Server-side or fallback
|
|
7861
|
+
this.resolveUrlFallback(token, userId);
|
|
7862
|
+
}
|
|
7809
7863
|
}
|
|
7810
7864
|
/**
|
|
7811
|
-
*
|
|
7865
|
+
* Fallback URL resolution
|
|
7812
7866
|
*/
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7867
|
+
resolveUrlFallback(token, userId) {
|
|
7868
|
+
let apiUrl = 'http://localhost:3001'; // Default from host-manager.json
|
|
7869
|
+
// Try to get from current window location
|
|
7870
|
+
if (typeof window !== 'undefined') {
|
|
7871
|
+
const currentOrigin = window.location.origin;
|
|
7872
|
+
// Check if we're on localhost (development)
|
|
7873
|
+
if (currentOrigin.includes('localhost') || currentOrigin.includes('127.0.0.1')) {
|
|
7874
|
+
// Use localhost:3001 for development (from host-manager.json)
|
|
7875
|
+
apiUrl = 'http://localhost:3001';
|
|
7876
|
+
}
|
|
7877
|
+
else {
|
|
7878
|
+
// In production, use the same origin (assuming API is on same domain)
|
|
7879
|
+
apiUrl = currentOrigin || 'http://localhost:3001';
|
|
7880
|
+
}
|
|
7881
|
+
// Try to get from localStorage (if set by app initialization)
|
|
7882
|
+
const storedApiUrl = localStorage.getItem('api_base_url');
|
|
7883
|
+
if (storedApiUrl) {
|
|
7884
|
+
apiUrl = storedApiUrl.replace(/\/api\/?$/, '');
|
|
7817
7885
|
}
|
|
7818
7886
|
}
|
|
7819
|
-
|
|
7820
|
-
}
|
|
7821
|
-
/**
|
|
7822
|
-
* Get file size in human readable format
|
|
7823
|
-
*/
|
|
7824
|
-
getFileSizeDisplay(bytes) {
|
|
7825
|
-
if (bytes === 0)
|
|
7826
|
-
return '0 Bytes';
|
|
7827
|
-
const k = 1024;
|
|
7828
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
7829
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
7830
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
7887
|
+
this.connectWithUrl(apiUrl, token, userId);
|
|
7831
7888
|
}
|
|
7832
7889
|
/**
|
|
7833
|
-
*
|
|
7890
|
+
* Connect with resolved URL
|
|
7834
7891
|
*/
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
'
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
'application/msword': 'description',
|
|
7850
|
-
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'description',
|
|
7851
|
-
'application/vnd.ms-excel': 'table_chart',
|
|
7852
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'table_chart',
|
|
7853
|
-
'application/vnd.ms-powerpoint': 'slideshow',
|
|
7854
|
-
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'slideshow'
|
|
7855
|
-
};
|
|
7856
|
-
return typeMap[fileType] || 'insert_drive_file';
|
|
7892
|
+
connectWithUrl(apiUrl, token, userId) {
|
|
7893
|
+
console.log('[WebSocket] Connecting to:', apiUrl);
|
|
7894
|
+
this.socket = io(apiUrl, {
|
|
7895
|
+
path: '/notifications-socket',
|
|
7896
|
+
transports: ['websocket', 'polling'],
|
|
7897
|
+
auth: {
|
|
7898
|
+
token: token
|
|
7899
|
+
},
|
|
7900
|
+
reconnection: true,
|
|
7901
|
+
reconnectionDelay: 1000,
|
|
7902
|
+
reconnectionAttempts: 5,
|
|
7903
|
+
reconnectionDelayMax: 5000
|
|
7904
|
+
});
|
|
7905
|
+
this.setupEventHandlers(userId);
|
|
7857
7906
|
}
|
|
7858
7907
|
/**
|
|
7859
|
-
*
|
|
7908
|
+
* Setup Socket.IO event handlers
|
|
7860
7909
|
*/
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
next
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
}
|
|
7910
|
+
setupEventHandlers(userId) {
|
|
7911
|
+
if (!this.socket)
|
|
7912
|
+
return;
|
|
7913
|
+
// Connection established
|
|
7914
|
+
this.socket.on('connect', () => {
|
|
7915
|
+
console.log('Socket.IO connected:', this.socket?.id);
|
|
7916
|
+
this.connectionStatusSubject.next({
|
|
7917
|
+
connected: true,
|
|
7918
|
+
socketId: this.socket?.id,
|
|
7919
|
+
userId: userId,
|
|
7920
|
+
timestamp: new Date()
|
|
7921
|
+
});
|
|
7922
|
+
});
|
|
7923
|
+
// Connection confirmation
|
|
7924
|
+
this.socket.on('connected', (data) => {
|
|
7925
|
+
console.log('Notification service connected:', data);
|
|
7926
|
+
this.connectionStatusSubject.next({
|
|
7927
|
+
connected: true,
|
|
7928
|
+
socketId: data.socketId,
|
|
7929
|
+
userId: data.userId,
|
|
7930
|
+
timestamp: data.timestamp
|
|
7931
|
+
});
|
|
7932
|
+
});
|
|
7933
|
+
// Receive notification
|
|
7934
|
+
this.socket.on('notification', (notification) => {
|
|
7935
|
+
console.log('Received notification:', notification);
|
|
7936
|
+
// Add to notifications list
|
|
7937
|
+
this.notifications.update(notifications => [notification, ...notifications]);
|
|
7938
|
+
// Add to unread list
|
|
7939
|
+
this.unreadNotifications.update(unread => [notification, ...unread]);
|
|
7940
|
+
// Emit to subscribers
|
|
7941
|
+
this.notificationSubject.next(notification);
|
|
7942
|
+
// Acknowledge receipt
|
|
7943
|
+
this.acknowledgeNotification(notification.id);
|
|
7944
|
+
});
|
|
7945
|
+
// Disconnect
|
|
7946
|
+
this.socket.on('disconnect', (reason) => {
|
|
7947
|
+
console.log('Socket.IO disconnected:', reason);
|
|
7948
|
+
this.connectionStatusSubject.next({
|
|
7949
|
+
connected: false,
|
|
7950
|
+
timestamp: new Date()
|
|
7951
|
+
});
|
|
7952
|
+
});
|
|
7953
|
+
// Connection error
|
|
7954
|
+
this.socket.on('connect_error', (error) => {
|
|
7955
|
+
console.error('Socket.IO connection error:', error);
|
|
7956
|
+
this.connectionStatusSubject.next({
|
|
7957
|
+
connected: false,
|
|
7958
|
+
timestamp: new Date()
|
|
7959
|
+
});
|
|
7873
7960
|
});
|
|
7874
7961
|
}
|
|
7875
7962
|
/**
|
|
7876
|
-
*
|
|
7963
|
+
* Acknowledge notification receipt
|
|
7877
7964
|
*/
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
errorMessage = error.message;
|
|
7882
|
-
}
|
|
7883
|
-
else if (typeof error === 'string') {
|
|
7884
|
-
errorMessage = error;
|
|
7965
|
+
acknowledgeNotification(notificationId) {
|
|
7966
|
+
if (this.socket?.connected) {
|
|
7967
|
+
this.socket.emit('notification:ack', { notificationId });
|
|
7885
7968
|
}
|
|
7886
|
-
console.error('CideCoreFileManagerService Error:', errorMessage);
|
|
7887
|
-
return throwError(() => new Error(errorMessage));
|
|
7888
|
-
}
|
|
7889
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideCoreFileManagerService, deps: [{ token: i1$1.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
7890
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideCoreFileManagerService, providedIn: 'root' });
|
|
7891
|
-
}
|
|
7892
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideCoreFileManagerService, decorators: [{
|
|
7893
|
-
type: Injectable,
|
|
7894
|
-
args: [{
|
|
7895
|
-
providedIn: 'root'
|
|
7896
|
-
}]
|
|
7897
|
-
}], ctorParameters: () => [{ type: i1$1.HttpClient }] });
|
|
7898
|
-
|
|
7899
|
-
class DropdownManagerService {
|
|
7900
|
-
activeDropdowns = signal(new Map(), ...(ngDevMode ? [{ debugName: "activeDropdowns" }] : []));
|
|
7901
|
-
documentClickListener;
|
|
7902
|
-
escapeKeyListener;
|
|
7903
|
-
constructor() {
|
|
7904
|
-
this.initializeGlobalListeners();
|
|
7905
|
-
}
|
|
7906
|
-
initializeGlobalListeners() {
|
|
7907
|
-
// Global document click listener
|
|
7908
|
-
this.documentClickListener = (event) => {
|
|
7909
|
-
this.handleDocumentClick(event);
|
|
7910
|
-
};
|
|
7911
|
-
// Global escape key listener
|
|
7912
|
-
this.escapeKeyListener = (event) => {
|
|
7913
|
-
if (event.key === 'Escape') {
|
|
7914
|
-
this.closeAllDropdowns();
|
|
7915
|
-
}
|
|
7916
|
-
};
|
|
7917
|
-
// Add listeners
|
|
7918
|
-
document.addEventListener('click', this.documentClickListener, true);
|
|
7919
|
-
document.addEventListener('keydown', this.escapeKeyListener, true);
|
|
7920
|
-
}
|
|
7921
|
-
registerDropdown(dropdown) {
|
|
7922
|
-
// Close any existing dropdowns before opening a new one
|
|
7923
|
-
this.closeAllDropdowns();
|
|
7924
|
-
this.activeDropdowns.update(map => {
|
|
7925
|
-
const newMap = new Map(map);
|
|
7926
|
-
newMap.set(dropdown.id, dropdown);
|
|
7927
|
-
return newMap;
|
|
7928
|
-
});
|
|
7929
7969
|
}
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7970
|
+
/**
|
|
7971
|
+
* Mark notification as read
|
|
7972
|
+
*/
|
|
7973
|
+
markAsRead(notificationId) {
|
|
7974
|
+
if (this.socket?.connected) {
|
|
7975
|
+
this.socket.emit('notification:read', { notificationId });
|
|
7976
|
+
// Remove from unread list
|
|
7977
|
+
this.unreadNotifications.update(unread => unread.filter(n => n.id !== notificationId));
|
|
7978
|
+
}
|
|
7936
7979
|
}
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7980
|
+
/**
|
|
7981
|
+
* Mark all as read
|
|
7982
|
+
*/
|
|
7983
|
+
markAllAsRead() {
|
|
7984
|
+
const unreadIds = this.unreadNotifications().map(n => n.id);
|
|
7985
|
+
unreadIds.forEach(id => this.markAsRead(id));
|
|
7943
7986
|
}
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
this.
|
|
7987
|
+
/**
|
|
7988
|
+
* Disconnect from server
|
|
7989
|
+
*/
|
|
7990
|
+
disconnect() {
|
|
7991
|
+
if (this.socket) {
|
|
7992
|
+
this.socket.disconnect();
|
|
7993
|
+
this.socket = null;
|
|
7994
|
+
this.connectionStatusSubject.next({
|
|
7995
|
+
connected: false,
|
|
7996
|
+
timestamp: new Date()
|
|
7997
|
+
});
|
|
7950
7998
|
}
|
|
7951
7999
|
}
|
|
7952
|
-
|
|
7953
|
-
|
|
8000
|
+
/**
|
|
8001
|
+
* Clear all notifications
|
|
8002
|
+
*/
|
|
8003
|
+
clearNotifications() {
|
|
8004
|
+
this.notifications.set([]);
|
|
8005
|
+
this.unreadNotifications.set([]);
|
|
7954
8006
|
}
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
const target = event.target;
|
|
7962
|
-
let isClickInsideDropdown = false;
|
|
7963
|
-
dropdowns.forEach(dropdown => {
|
|
7964
|
-
if (dropdown.element && dropdown.element.contains(target)) {
|
|
7965
|
-
isClickInsideDropdown = true;
|
|
7966
|
-
}
|
|
7967
|
-
});
|
|
7968
|
-
// If click is outside all dropdowns, close them
|
|
7969
|
-
if (!isClickInsideDropdown) {
|
|
7970
|
-
this.closeAllDropdowns();
|
|
7971
|
-
}
|
|
8007
|
+
/**
|
|
8008
|
+
* Remove notification from list
|
|
8009
|
+
*/
|
|
8010
|
+
removeNotification(notificationId) {
|
|
8011
|
+
this.notifications.update(notifications => notifications.filter(n => n.id !== notificationId));
|
|
8012
|
+
this.unreadNotifications.update(unread => unread.filter(n => n.id !== notificationId));
|
|
7972
8013
|
}
|
|
7973
8014
|
ngOnDestroy() {
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
}
|
|
7978
|
-
if (this.escapeKeyListener) {
|
|
7979
|
-
document.removeEventListener('keydown', this.escapeKeyListener, true);
|
|
7980
|
-
}
|
|
8015
|
+
this.disconnect();
|
|
8016
|
+
this.notificationSubject.complete();
|
|
8017
|
+
this.connectionStatusSubject.complete();
|
|
7981
8018
|
}
|
|
7982
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
7983
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
8019
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: WebSocketNotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
8020
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: WebSocketNotificationService, providedIn: 'root' });
|
|
7984
8021
|
}
|
|
7985
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
8022
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: WebSocketNotificationService, decorators: [{
|
|
7986
8023
|
type: Injectable,
|
|
7987
8024
|
args: [{
|
|
7988
8025
|
providedIn: 'root'
|
|
7989
8026
|
}]
|
|
7990
|
-
}]
|
|
8027
|
+
}] });
|
|
7991
8028
|
|
|
7992
|
-
class
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
this.removeGlobalListener();
|
|
8029
|
+
class CideCoreFileManagerService {
|
|
8030
|
+
http;
|
|
8031
|
+
apiUrl = `${coreRoutesUrl?.fileManager}`;
|
|
8032
|
+
fileListSubject = new BehaviorSubject([]);
|
|
8033
|
+
fileList$ = this.fileListSubject.asObservable();
|
|
8034
|
+
constructor(http) {
|
|
8035
|
+
this.http = http;
|
|
8036
|
+
console.log('CideCoreFileManagerService initialized - using real API');
|
|
8001
8037
|
}
|
|
8002
8038
|
/**
|
|
8003
|
-
*
|
|
8039
|
+
* Get file list from API
|
|
8004
8040
|
*/
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8041
|
+
getFileList(body) {
|
|
8042
|
+
const query = generateStringFromObject(body);
|
|
8043
|
+
return this.http?.get(cidePath$1?.join([hostManagerRoutesUrl$1?.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.fileManager, query]))
|
|
8044
|
+
.pipe(tap((response) => {
|
|
8045
|
+
if (response?.success) {
|
|
8046
|
+
this.fileListSubject.next(response?.data || []);
|
|
8047
|
+
}
|
|
8048
|
+
}), catchError$1(error => {
|
|
8049
|
+
console.error('CideCoreFileManagerService API error:', error);
|
|
8050
|
+
return this.handleError(error);
|
|
8051
|
+
}));
|
|
8008
8052
|
}
|
|
8009
8053
|
/**
|
|
8010
|
-
*
|
|
8054
|
+
* Get file list using mock data (deprecated - use getFileList instead)
|
|
8011
8055
|
*/
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
console.warn(`⚠️ [KeyboardShortcut] Cannot override shortcut '${shortcutId}' - not found`);
|
|
8016
|
-
return;
|
|
8017
|
-
}
|
|
8018
|
-
const override = {
|
|
8019
|
-
shortcutId,
|
|
8020
|
-
newKey,
|
|
8021
|
-
newCtrlKey: options?.ctrlKey,
|
|
8022
|
-
newAltKey: options?.altKey,
|
|
8023
|
-
newShiftKey: options?.shiftKey,
|
|
8024
|
-
newMetaKey: options?.metaKey
|
|
8025
|
-
};
|
|
8026
|
-
this.overrides.set(shortcutId, override);
|
|
8027
|
-
console.log(`🔄 [KeyboardShortcut] Override registered for '${shortcutId}': ${this.getKeyDescription({ ...originalShortcut, ...options, key: newKey })}`);
|
|
8056
|
+
getFileListWithMockData(_body) {
|
|
8057
|
+
console.warn('getFileListWithMockData is deprecated. Use getFileList instead.');
|
|
8058
|
+
return this.getFileList(_body);
|
|
8028
8059
|
}
|
|
8029
8060
|
/**
|
|
8030
|
-
*
|
|
8031
|
-
*/
|
|
8032
|
-
unregister(shortcutId) {
|
|
8033
|
-
this.shortcuts.delete(shortcutId);
|
|
8034
|
-
this.overrides.delete(shortcutId);
|
|
8035
|
-
console.log(`🗑️ [KeyboardShortcut] Removed shortcut: ${shortcutId}`);
|
|
8036
|
-
}
|
|
8037
|
-
/**
|
|
8038
|
-
* Remove override for a shortcut (restore original key combination)
|
|
8061
|
+
* Get file list from cache (if available)
|
|
8039
8062
|
*/
|
|
8040
|
-
|
|
8041
|
-
this.
|
|
8042
|
-
console.log(`🔄 [KeyboardShortcut] Override removed for: ${shortcutId}`);
|
|
8063
|
+
getFileListFromCache() {
|
|
8064
|
+
return this.fileListSubject.value;
|
|
8043
8065
|
}
|
|
8044
8066
|
/**
|
|
8045
|
-
*
|
|
8067
|
+
* Upload file with progress tracking
|
|
8046
8068
|
*/
|
|
8047
|
-
|
|
8048
|
-
|
|
8069
|
+
uploadFile(request) {
|
|
8070
|
+
const formData = new FormData();
|
|
8071
|
+
formData.append('file', request.file);
|
|
8072
|
+
formData.append('altText', request.altText || '');
|
|
8073
|
+
formData.append('tags', JSON.stringify(request.tags || []));
|
|
8074
|
+
formData.append('permissions', JSON.stringify(request.permissions || []));
|
|
8075
|
+
formData.append('userId', request.userId || '');
|
|
8076
|
+
const url = cidePath$1?.join([hostManagerRoutesUrl$1?.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.fileManager, 'upload']);
|
|
8077
|
+
const req = new HttpRequest('POST', url, formData, {
|
|
8078
|
+
reportProgress: true
|
|
8079
|
+
});
|
|
8080
|
+
return this.http.request(req).pipe(catchError$1(this.handleError));
|
|
8049
8081
|
}
|
|
8050
8082
|
/**
|
|
8051
|
-
*
|
|
8083
|
+
* Upload file with progress tracking (mock version - deprecated)
|
|
8052
8084
|
*/
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
return
|
|
8085
|
+
uploadFileWithMockData(request) {
|
|
8086
|
+
console.warn('uploadFileWithMockData is deprecated. Use uploadFile instead.');
|
|
8087
|
+
return this.uploadFile(request);
|
|
8056
8088
|
}
|
|
8057
8089
|
/**
|
|
8058
|
-
*
|
|
8090
|
+
* Update file metadata
|
|
8059
8091
|
*/
|
|
8060
|
-
|
|
8061
|
-
|
|
8092
|
+
updateFile(request) {
|
|
8093
|
+
console.log('Updating file:', request);
|
|
8094
|
+
const url = cidePath$1?.join([hostManagerRoutesUrl$1?.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.fileManager]);
|
|
8095
|
+
return this.http.post(url, request)
|
|
8096
|
+
.pipe(tap((response) => {
|
|
8097
|
+
if (response.success) {
|
|
8098
|
+
this.refreshFileList();
|
|
8099
|
+
}
|
|
8100
|
+
}), catchError$1(this.handleError));
|
|
8062
8101
|
}
|
|
8063
8102
|
/**
|
|
8064
|
-
*
|
|
8103
|
+
* Delete file
|
|
8065
8104
|
*/
|
|
8066
|
-
|
|
8067
|
-
|
|
8105
|
+
deleteFile(id) {
|
|
8106
|
+
const payload = { cyfm_id: id };
|
|
8107
|
+
const query = generateStringFromObject(payload);
|
|
8108
|
+
const url = cidePath$1?.join([hostManagerRoutesUrl$1?.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.fileManager, query]);
|
|
8109
|
+
return this.http.delete(url)
|
|
8110
|
+
.pipe(tap((response) => {
|
|
8111
|
+
if (response.success) {
|
|
8112
|
+
this.refreshFileList();
|
|
8113
|
+
}
|
|
8114
|
+
}), catchError$1(this.handleError));
|
|
8068
8115
|
}
|
|
8069
8116
|
/**
|
|
8070
|
-
*
|
|
8117
|
+
* Delete multiple files
|
|
8071
8118
|
*/
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8119
|
+
deleteMultipleFiles(ids) {
|
|
8120
|
+
console.log('Deleting multiple files:', ids);
|
|
8121
|
+
const payload = { ids };
|
|
8122
|
+
const query = generateStringFromObject(payload);
|
|
8123
|
+
const url = cidePath$1?.join([hostManagerRoutesUrl$1?.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.fileManager, query]);
|
|
8124
|
+
return this.http.delete(url)
|
|
8125
|
+
.pipe(tap((response) => {
|
|
8126
|
+
if (response.success) {
|
|
8127
|
+
this.refreshFileList();
|
|
8128
|
+
}
|
|
8129
|
+
}), catchError$1(this.handleError));
|
|
8076
8130
|
}
|
|
8077
8131
|
/**
|
|
8078
|
-
*
|
|
8132
|
+
* Toggle file active status
|
|
8079
8133
|
*/
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8134
|
+
toggleFileStatus(id) {
|
|
8135
|
+
console.log('Toggling file status:', id);
|
|
8136
|
+
const payload = { id };
|
|
8137
|
+
const query = generateStringFromObject(payload);
|
|
8138
|
+
const url = cidePath$1?.join([hostManagerRoutesUrl$1?.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.fileManager, query]);
|
|
8139
|
+
return this.http.put(url, {})
|
|
8140
|
+
.pipe(tap((response) => {
|
|
8141
|
+
if (response.success) {
|
|
8142
|
+
this.refreshFileList();
|
|
8143
|
+
}
|
|
8144
|
+
}), catchError$1(this.handleError));
|
|
8086
8145
|
}
|
|
8087
8146
|
/**
|
|
8088
|
-
*
|
|
8147
|
+
* Get file by ID
|
|
8089
8148
|
*/
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8149
|
+
getFileById(id) {
|
|
8150
|
+
console.log('Getting file by ID:', id);
|
|
8151
|
+
const payload = { cyfm_id: id };
|
|
8152
|
+
const query = generateStringFromObject(payload);
|
|
8153
|
+
const url = cidePath$1?.join([hostManagerRoutesUrl$1?.cideSuiteHost, coreRoutesUrl?.module, coreRoutesUrl?.fileManager, 'byId', query]);
|
|
8154
|
+
return this.http.get(url)
|
|
8155
|
+
.pipe(catchError$1(this.handleError));
|
|
8096
8156
|
}
|
|
8097
8157
|
/**
|
|
8098
|
-
*
|
|
8158
|
+
* Find file by ID
|
|
8099
8159
|
*/
|
|
8100
|
-
|
|
8101
|
-
for (const
|
|
8102
|
-
if (
|
|
8103
|
-
|
|
8104
|
-
const override = this.overrides.get(shortcutId);
|
|
8105
|
-
if (override && !this.matchesOverride(event, override)) {
|
|
8106
|
-
continue; // Skip if override doesn't match
|
|
8107
|
-
}
|
|
8108
|
-
if (shortcut.preventDefault !== false) {
|
|
8109
|
-
event.preventDefault();
|
|
8110
|
-
}
|
|
8111
|
-
if (shortcut.stopPropagation) {
|
|
8112
|
-
event.stopPropagation();
|
|
8113
|
-
}
|
|
8114
|
-
console.log(`⌨️ [KeyboardShortcut] Executing shortcut: ${shortcutId}`);
|
|
8115
|
-
shortcut.action();
|
|
8116
|
-
break; // Only execute one shortcut per keydown
|
|
8160
|
+
findFileById(id, items = this.fileListSubject.value) {
|
|
8161
|
+
for (const item of items) {
|
|
8162
|
+
if (item._id === id) {
|
|
8163
|
+
return item;
|
|
8117
8164
|
}
|
|
8118
8165
|
}
|
|
8166
|
+
return null;
|
|
8119
8167
|
}
|
|
8120
8168
|
/**
|
|
8121
|
-
*
|
|
8169
|
+
* Get file size in human readable format
|
|
8122
8170
|
*/
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8171
|
+
getFileSizeDisplay(bytes) {
|
|
8172
|
+
if (bytes === 0)
|
|
8173
|
+
return '0 Bytes';
|
|
8174
|
+
const k = 1024;
|
|
8175
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
8176
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
8177
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
8129
8178
|
}
|
|
8130
8179
|
/**
|
|
8131
|
-
*
|
|
8180
|
+
* Get file type icon
|
|
8132
8181
|
*/
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8182
|
+
getFileTypeIcon(fileType) {
|
|
8183
|
+
const typeMap = {
|
|
8184
|
+
'image/jpeg': 'image',
|
|
8185
|
+
'image/png': 'image',
|
|
8186
|
+
'image/gif': 'image',
|
|
8187
|
+
'image/svg+xml': 'image',
|
|
8188
|
+
'application/pdf': 'picture_as_pdf',
|
|
8189
|
+
'text/plain': 'description',
|
|
8190
|
+
'text/html': 'code',
|
|
8191
|
+
'application/json': 'code',
|
|
8192
|
+
'application/javascript': 'code',
|
|
8193
|
+
'text/css': 'code',
|
|
8194
|
+
'application/zip': 'folder_zip',
|
|
8195
|
+
'application/x-zip-compressed': 'folder_zip',
|
|
8196
|
+
'application/msword': 'description',
|
|
8197
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'description',
|
|
8198
|
+
'application/vnd.ms-excel': 'table_chart',
|
|
8199
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'table_chart',
|
|
8200
|
+
'application/vnd.ms-powerpoint': 'slideshow',
|
|
8201
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'slideshow'
|
|
8202
|
+
};
|
|
8203
|
+
return typeMap[fileType] || 'insert_drive_file';
|
|
8139
8204
|
}
|
|
8140
8205
|
/**
|
|
8141
|
-
*
|
|
8206
|
+
* Refresh file list from server
|
|
8142
8207
|
*/
|
|
8143
|
-
|
|
8144
|
-
const
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8208
|
+
refreshFileList() {
|
|
8209
|
+
const defaultBody = {
|
|
8210
|
+
pageIndex: 1,
|
|
8211
|
+
pageSize: 10
|
|
8212
|
+
};
|
|
8213
|
+
this.getFileList(defaultBody).subscribe({
|
|
8214
|
+
next: () => {
|
|
8215
|
+
console.log('File list refreshed successfully');
|
|
8216
|
+
},
|
|
8217
|
+
error: (error) => {
|
|
8218
|
+
console.error('Error refreshing file list:', error);
|
|
8219
|
+
}
|
|
8220
|
+
});
|
|
8154
8221
|
}
|
|
8155
8222
|
/**
|
|
8156
|
-
*
|
|
8223
|
+
* Handle errors
|
|
8157
8224
|
*/
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
if (
|
|
8161
|
-
|
|
8162
|
-
const override = this.overrides.get(shortcutId);
|
|
8163
|
-
if (override) {
|
|
8164
|
-
return this.getKeyDescription({
|
|
8165
|
-
key: override.newKey,
|
|
8166
|
-
ctrlKey: override.newCtrlKey,
|
|
8167
|
-
altKey: override.newAltKey,
|
|
8168
|
-
shiftKey: override.newShiftKey,
|
|
8169
|
-
metaKey: override.newMetaKey
|
|
8170
|
-
});
|
|
8225
|
+
handleError(error) {
|
|
8226
|
+
let errorMessage = 'An error occurred';
|
|
8227
|
+
if (error instanceof Error) {
|
|
8228
|
+
errorMessage = error.message;
|
|
8171
8229
|
}
|
|
8172
|
-
|
|
8230
|
+
else if (typeof error === 'string') {
|
|
8231
|
+
errorMessage = error;
|
|
8232
|
+
}
|
|
8233
|
+
console.error('CideCoreFileManagerService Error:', errorMessage);
|
|
8234
|
+
return throwError(() => new Error(errorMessage));
|
|
8173
8235
|
}
|
|
8174
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
8175
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
8236
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideCoreFileManagerService, deps: [{ token: i1$1.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
8237
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideCoreFileManagerService, providedIn: 'root' });
|
|
8176
8238
|
}
|
|
8177
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
8239
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideCoreFileManagerService, decorators: [{
|
|
8178
8240
|
type: Injectable,
|
|
8179
8241
|
args: [{
|
|
8180
8242
|
providedIn: 'root'
|
|
8181
8243
|
}]
|
|
8182
|
-
}], ctorParameters: () => [] });
|
|
8244
|
+
}], ctorParameters: () => [{ type: i1$1.HttpClient }] });
|
|
8183
8245
|
|
|
8184
|
-
class
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
// Modern computed signals with proper typing
|
|
8189
|
-
hasActiveConfirmation = computed(() => this.currentRequest() !== null, ...(ngDevMode ? [{ debugName: "hasActiveConfirmation" }] : []));
|
|
8190
|
-
queueLength = computed(() => this.confirmationQueue().length, ...(ngDevMode ? [{ debugName: "queueLength" }] : []));
|
|
8246
|
+
class DropdownManagerService {
|
|
8247
|
+
activeDropdowns = signal(new Map(), ...(ngDevMode ? [{ debugName: "activeDropdowns" }] : []));
|
|
8248
|
+
documentClickListener;
|
|
8249
|
+
escapeKeyListener;
|
|
8191
8250
|
constructor() {
|
|
8192
|
-
|
|
8193
|
-
}
|
|
8194
|
-
ngOnDestroy() {
|
|
8195
|
-
// Cleanup if needed
|
|
8196
|
-
}
|
|
8197
|
-
/**
|
|
8198
|
-
* Ask for confirmation with a simple yes/no dialog
|
|
8199
|
-
*/
|
|
8200
|
-
ask(options) {
|
|
8201
|
-
console.log('🔔 ConfirmationService.ask called with options:', options);
|
|
8202
|
-
return new Promise((resolve, reject) => {
|
|
8203
|
-
const request = {
|
|
8204
|
-
id: this.generateId(),
|
|
8205
|
-
title: options.title,
|
|
8206
|
-
message: options.message,
|
|
8207
|
-
type: options.type || 'warning',
|
|
8208
|
-
icon: options.icon || this.getDefaultIcon(options.type || 'warning'),
|
|
8209
|
-
confirmText: options.confirmText || 'Confirm',
|
|
8210
|
-
cancelText: options.cancelText || 'Cancel',
|
|
8211
|
-
resolve: (value) => resolve(value),
|
|
8212
|
-
reject
|
|
8213
|
-
};
|
|
8214
|
-
console.log('🔽 Ask for confirmation with a simple yes/no dialog', request);
|
|
8215
|
-
this.addToQueue(request);
|
|
8216
|
-
});
|
|
8251
|
+
this.initializeGlobalListeners();
|
|
8217
8252
|
}
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
customData: options.customData,
|
|
8233
|
-
resolve: (value) => resolve(value),
|
|
8234
|
-
reject
|
|
8235
|
-
};
|
|
8236
|
-
// Cast to unknown for queue compatibility
|
|
8237
|
-
this.addToQueue(request);
|
|
8238
|
-
});
|
|
8253
|
+
initializeGlobalListeners() {
|
|
8254
|
+
// Global document click listener
|
|
8255
|
+
this.documentClickListener = (event) => {
|
|
8256
|
+
this.handleDocumentClick(event);
|
|
8257
|
+
};
|
|
8258
|
+
// Global escape key listener
|
|
8259
|
+
this.escapeKeyListener = (event) => {
|
|
8260
|
+
if (event.key === 'Escape') {
|
|
8261
|
+
this.closeAllDropdowns();
|
|
8262
|
+
}
|
|
8263
|
+
};
|
|
8264
|
+
// Add listeners
|
|
8265
|
+
document.addEventListener('click', this.documentClickListener, true);
|
|
8266
|
+
document.addEventListener('keydown', this.escapeKeyListener, true);
|
|
8239
8267
|
}
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
type: 'danger',
|
|
8248
|
-
icon: 'delete_forever',
|
|
8249
|
-
confirmText: 'Delete',
|
|
8250
|
-
cancelText: 'Cancel'
|
|
8268
|
+
registerDropdown(dropdown) {
|
|
8269
|
+
// Close any existing dropdowns before opening a new one
|
|
8270
|
+
this.closeAllDropdowns();
|
|
8271
|
+
this.activeDropdowns.update(map => {
|
|
8272
|
+
const newMap = new Map(map);
|
|
8273
|
+
newMap.set(dropdown.id, dropdown);
|
|
8274
|
+
return newMap;
|
|
8251
8275
|
});
|
|
8252
8276
|
}
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
title: `Confirm ${action}`,
|
|
8259
|
-
message: itemName ? `Are you sure you want to ${action.toLowerCase()} "${itemName}"? This action cannot be undone.` : `Are you sure you want to ${action.toLowerCase()}? This action cannot be undone.`,
|
|
8260
|
-
type: 'danger',
|
|
8261
|
-
icon: 'warning',
|
|
8262
|
-
confirmText: action,
|
|
8263
|
-
cancelText: 'Cancel'
|
|
8277
|
+
unregisterDropdown(id) {
|
|
8278
|
+
this.activeDropdowns.update(map => {
|
|
8279
|
+
const newMap = new Map(map);
|
|
8280
|
+
newMap.delete(id);
|
|
8281
|
+
return newMap;
|
|
8264
8282
|
});
|
|
8265
8283
|
}
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
return this.ask({
|
|
8271
|
-
title: `Confirm ${action}`,
|
|
8272
|
-
message: itemName ? `Are you sure you want to ${action.toLowerCase()} "${itemName}"?` : `Are you sure you want to ${action.toLowerCase()}?`,
|
|
8273
|
-
type: 'info',
|
|
8274
|
-
icon: 'info',
|
|
8275
|
-
confirmText: action,
|
|
8276
|
-
cancelText: 'Cancel'
|
|
8284
|
+
closeAllDropdowns() {
|
|
8285
|
+
const dropdowns = this.activeDropdowns();
|
|
8286
|
+
dropdowns.forEach(dropdown => {
|
|
8287
|
+
dropdown.close();
|
|
8277
8288
|
});
|
|
8289
|
+
this.activeDropdowns.set(new Map());
|
|
8278
8290
|
}
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
processQueue() {
|
|
8286
|
-
console.log('🔔 Processing queue. Current request:', this.currentRequest(), 'Queue length:', this.confirmationQueue().length);
|
|
8287
|
-
if (this.currentRequest() || this.confirmationQueue().length === 0) {
|
|
8288
|
-
return;
|
|
8291
|
+
closeDropdown(id) {
|
|
8292
|
+
const dropdowns = this.activeDropdowns();
|
|
8293
|
+
const dropdown = dropdowns.get(id);
|
|
8294
|
+
if (dropdown) {
|
|
8295
|
+
dropdown.close();
|
|
8296
|
+
this.unregisterDropdown(id);
|
|
8289
8297
|
}
|
|
8290
|
-
const nextRequest = this.confirmationQueue()[0];
|
|
8291
|
-
console.log('🔔 Setting current request:', nextRequest);
|
|
8292
|
-
this.currentRequest.set(nextRequest);
|
|
8293
8298
|
}
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
const request = this.currentRequest();
|
|
8297
|
-
if (!request)
|
|
8298
|
-
return;
|
|
8299
|
-
// Always resolve with true for confirmation, unless custom data is explicitly provided
|
|
8300
|
-
const resolvedValue = data !== undefined ? data : true;
|
|
8301
|
-
console.log('🔔 Confirming request with value:', resolvedValue);
|
|
8302
|
-
request.resolve(resolvedValue);
|
|
8303
|
-
this.removeCurrentRequest();
|
|
8299
|
+
isDropdownOpen(id) {
|
|
8300
|
+
return this.activeDropdowns().has(id);
|
|
8304
8301
|
}
|
|
8305
|
-
|
|
8306
|
-
const
|
|
8307
|
-
if (
|
|
8302
|
+
handleDocumentClick(event) {
|
|
8303
|
+
const dropdowns = this.activeDropdowns();
|
|
8304
|
+
if (dropdowns.size === 0) {
|
|
8308
8305
|
return;
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
getDefaultIcon(type) {
|
|
8323
|
-
const iconMap = {
|
|
8324
|
-
danger: 'error',
|
|
8325
|
-
warning: 'warning',
|
|
8326
|
-
info: 'info',
|
|
8327
|
-
success: 'check_circle'
|
|
8328
|
-
};
|
|
8329
|
-
return iconMap[type];
|
|
8330
|
-
}
|
|
8331
|
-
// Modern public getters with proper typing
|
|
8332
|
-
getCurrentRequest() {
|
|
8333
|
-
return this.currentRequest;
|
|
8306
|
+
}
|
|
8307
|
+
// Check if click is inside any open dropdown
|
|
8308
|
+
const target = event.target;
|
|
8309
|
+
let isClickInsideDropdown = false;
|
|
8310
|
+
dropdowns.forEach(dropdown => {
|
|
8311
|
+
if (dropdown.element && dropdown.element.contains(target)) {
|
|
8312
|
+
isClickInsideDropdown = true;
|
|
8313
|
+
}
|
|
8314
|
+
});
|
|
8315
|
+
// If click is outside all dropdowns, close them
|
|
8316
|
+
if (!isClickInsideDropdown) {
|
|
8317
|
+
this.closeAllDropdowns();
|
|
8318
|
+
}
|
|
8334
8319
|
}
|
|
8335
|
-
|
|
8336
|
-
|
|
8320
|
+
ngOnDestroy() {
|
|
8321
|
+
// Clean up global listeners
|
|
8322
|
+
if (this.documentClickListener) {
|
|
8323
|
+
document.removeEventListener('click', this.documentClickListener, true);
|
|
8324
|
+
}
|
|
8325
|
+
if (this.escapeKeyListener) {
|
|
8326
|
+
document.removeEventListener('keydown', this.escapeKeyListener, true);
|
|
8327
|
+
}
|
|
8337
8328
|
}
|
|
8338
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
8339
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
8329
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: DropdownManagerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
8330
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: DropdownManagerService, providedIn: 'root' });
|
|
8340
8331
|
}
|
|
8341
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
8332
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: DropdownManagerService, decorators: [{
|
|
8342
8333
|
type: Injectable,
|
|
8343
8334
|
args: [{
|
|
8344
8335
|
providedIn: 'root'
|
|
8345
8336
|
}]
|
|
8346
8337
|
}], ctorParameters: () => [] });
|
|
8347
8338
|
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
*/
|
|
8353
|
-
class CideThemeService {
|
|
8354
|
-
platformId = inject(PLATFORM_ID);
|
|
8355
|
-
isBrowser;
|
|
8356
|
-
currentTheme$ = new BehaviorSubject('light');
|
|
8357
|
-
effectiveTheme$ = new BehaviorSubject('light');
|
|
8358
|
-
config = {
|
|
8359
|
-
theme: 'light',
|
|
8360
|
-
targetElement: null,
|
|
8361
|
-
persistPreference: true,
|
|
8362
|
-
storageKey: 'cide-theme',
|
|
8363
|
-
useDataAttribute: true,
|
|
8364
|
-
useClassName: true
|
|
8365
|
-
};
|
|
8339
|
+
class KeyboardShortcutService {
|
|
8340
|
+
shortcuts = new Map();
|
|
8341
|
+
overrides = new Map();
|
|
8342
|
+
keydownListener;
|
|
8366
8343
|
constructor() {
|
|
8367
|
-
this.
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8344
|
+
this.setupGlobalListener();
|
|
8345
|
+
}
|
|
8346
|
+
ngOnDestroy() {
|
|
8347
|
+
this.removeGlobalListener();
|
|
8371
8348
|
}
|
|
8372
8349
|
/**
|
|
8373
|
-
*
|
|
8374
|
-
* This should be called once in the application, typically in main.ts or app config
|
|
8350
|
+
* Register a new keyboard shortcut
|
|
8375
8351
|
*/
|
|
8376
|
-
|
|
8377
|
-
|
|
8352
|
+
register(shortcut) {
|
|
8353
|
+
this.shortcuts.set(shortcut.id, shortcut);
|
|
8354
|
+
console.log(`⌨️ [KeyboardShortcut] Registered shortcut: ${shortcut.id} (${this.getKeyDescription(shortcut)})`);
|
|
8355
|
+
}
|
|
8356
|
+
/**
|
|
8357
|
+
* Override an existing shortcut with new key combination
|
|
8358
|
+
*/
|
|
8359
|
+
override(shortcutId, newKey, options) {
|
|
8360
|
+
const originalShortcut = this.shortcuts.get(shortcutId);
|
|
8361
|
+
if (!originalShortcut) {
|
|
8362
|
+
console.warn(`⚠️ [KeyboardShortcut] Cannot override shortcut '${shortcutId}' - not found`);
|
|
8378
8363
|
return;
|
|
8379
8364
|
}
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
}
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
const initialTheme = savedTheme || this.config.theme;
|
|
8391
|
-
// Apply initial theme
|
|
8392
|
-
this.setTheme(initialTheme);
|
|
8393
|
-
// Listen for system theme changes if theme is set to 'auto'
|
|
8394
|
-
if (initialTheme === 'auto') {
|
|
8395
|
-
this.listenToSystemTheme();
|
|
8396
|
-
}
|
|
8365
|
+
const override = {
|
|
8366
|
+
shortcutId,
|
|
8367
|
+
newKey,
|
|
8368
|
+
newCtrlKey: options?.ctrlKey,
|
|
8369
|
+
newAltKey: options?.altKey,
|
|
8370
|
+
newShiftKey: options?.shiftKey,
|
|
8371
|
+
newMetaKey: options?.metaKey
|
|
8372
|
+
};
|
|
8373
|
+
this.overrides.set(shortcutId, override);
|
|
8374
|
+
console.log(`🔄 [KeyboardShortcut] Override registered for '${shortcutId}': ${this.getKeyDescription({ ...originalShortcut, ...options, key: newKey })}`);
|
|
8397
8375
|
}
|
|
8398
8376
|
/**
|
|
8399
|
-
*
|
|
8377
|
+
* Remove a shortcut
|
|
8400
8378
|
*/
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
}
|
|
8405
|
-
this.currentTheme$.next(theme);
|
|
8406
|
-
// Save preference if persistence is enabled
|
|
8407
|
-
if (this.config.persistPreference) {
|
|
8408
|
-
this.saveThemePreference(theme);
|
|
8409
|
-
}
|
|
8410
|
-
// Determine effective theme
|
|
8411
|
-
let effectiveTheme;
|
|
8412
|
-
if (theme === 'auto') {
|
|
8413
|
-
effectiveTheme = this.getSystemTheme();
|
|
8414
|
-
this.listenToSystemTheme();
|
|
8415
|
-
}
|
|
8416
|
-
else {
|
|
8417
|
-
effectiveTheme = theme;
|
|
8418
|
-
}
|
|
8419
|
-
this.effectiveTheme$.next(effectiveTheme);
|
|
8420
|
-
this.applyTheme(effectiveTheme);
|
|
8379
|
+
unregister(shortcutId) {
|
|
8380
|
+
this.shortcuts.delete(shortcutId);
|
|
8381
|
+
this.overrides.delete(shortcutId);
|
|
8382
|
+
console.log(`🗑️ [KeyboardShortcut] Removed shortcut: ${shortcutId}`);
|
|
8421
8383
|
}
|
|
8422
8384
|
/**
|
|
8423
|
-
*
|
|
8385
|
+
* Remove override for a shortcut (restore original key combination)
|
|
8424
8386
|
*/
|
|
8425
|
-
|
|
8426
|
-
|
|
8387
|
+
removeOverride(shortcutId) {
|
|
8388
|
+
this.overrides.delete(shortcutId);
|
|
8389
|
+
console.log(`🔄 [KeyboardShortcut] Override removed for: ${shortcutId}`);
|
|
8427
8390
|
}
|
|
8428
8391
|
/**
|
|
8429
|
-
* Get
|
|
8392
|
+
* Get all registered shortcuts
|
|
8430
8393
|
*/
|
|
8431
|
-
|
|
8432
|
-
return this.
|
|
8394
|
+
getAllShortcuts() {
|
|
8395
|
+
return Array.from(this.shortcuts.values());
|
|
8433
8396
|
}
|
|
8434
8397
|
/**
|
|
8435
|
-
*
|
|
8398
|
+
* Get shortcuts by category or filter
|
|
8436
8399
|
*/
|
|
8437
|
-
|
|
8438
|
-
|
|
8400
|
+
getShortcuts(filter) {
|
|
8401
|
+
const shortcuts = this.getAllShortcuts();
|
|
8402
|
+
return filter ? shortcuts.filter(filter) : shortcuts;
|
|
8439
8403
|
}
|
|
8440
8404
|
/**
|
|
8441
|
-
*
|
|
8405
|
+
* Check if a shortcut is registered
|
|
8442
8406
|
*/
|
|
8443
|
-
|
|
8444
|
-
return this.
|
|
8407
|
+
hasShortcut(shortcutId) {
|
|
8408
|
+
return this.shortcuts.has(shortcutId);
|
|
8445
8409
|
}
|
|
8446
8410
|
/**
|
|
8447
|
-
*
|
|
8411
|
+
* Get shortcut information
|
|
8448
8412
|
*/
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
const newTheme = currentEffective === 'light' ? 'dark' : 'light';
|
|
8452
|
-
this.setTheme(newTheme);
|
|
8413
|
+
getShortcut(shortcutId) {
|
|
8414
|
+
return this.shortcuts.get(shortcutId);
|
|
8453
8415
|
}
|
|
8454
8416
|
/**
|
|
8455
|
-
*
|
|
8417
|
+
* Clear all shortcuts
|
|
8456
8418
|
*/
|
|
8457
|
-
|
|
8458
|
-
|
|
8419
|
+
clearAll() {
|
|
8420
|
+
this.shortcuts.clear();
|
|
8421
|
+
this.overrides.clear();
|
|
8422
|
+
console.log(`🧹 [KeyboardShortcut] All shortcuts cleared`);
|
|
8459
8423
|
}
|
|
8460
8424
|
/**
|
|
8461
|
-
*
|
|
8425
|
+
* Set up global keyboard listener
|
|
8462
8426
|
*/
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
}
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
else {
|
|
8479
|
-
element.classList.add('light-mode');
|
|
8480
|
-
element.classList.remove('dark-mode');
|
|
8481
|
-
}
|
|
8427
|
+
setupGlobalListener() {
|
|
8428
|
+
this.keydownListener = (event) => {
|
|
8429
|
+
this.handleKeydown(event);
|
|
8430
|
+
};
|
|
8431
|
+
document.addEventListener('keydown', this.keydownListener);
|
|
8432
|
+
console.log(`🎧 [KeyboardShortcut] Global keyboard listener attached`);
|
|
8433
|
+
}
|
|
8434
|
+
/**
|
|
8435
|
+
* Remove global keyboard listener
|
|
8436
|
+
*/
|
|
8437
|
+
removeGlobalListener() {
|
|
8438
|
+
if (this.keydownListener) {
|
|
8439
|
+
document.removeEventListener('keydown', this.keydownListener);
|
|
8440
|
+
this.keydownListener = undefined;
|
|
8441
|
+
console.log(`🎧 [KeyboardShortcut] Global keyboard listener removed`);
|
|
8482
8442
|
}
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8443
|
+
}
|
|
8444
|
+
/**
|
|
8445
|
+
* Handle keydown events
|
|
8446
|
+
*/
|
|
8447
|
+
handleKeydown(event) {
|
|
8448
|
+
for (const [shortcutId, shortcut] of this.shortcuts) {
|
|
8449
|
+
if (this.matchesShortcut(event, shortcut)) {
|
|
8450
|
+
// Check for override
|
|
8451
|
+
const override = this.overrides.get(shortcutId);
|
|
8452
|
+
if (override && !this.matchesOverride(event, override)) {
|
|
8453
|
+
continue; // Skip if override doesn't match
|
|
8492
8454
|
}
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
document.body.classList.remove('dark-mode');
|
|
8455
|
+
if (shortcut.preventDefault !== false) {
|
|
8456
|
+
event.preventDefault();
|
|
8496
8457
|
}
|
|
8458
|
+
if (shortcut.stopPropagation) {
|
|
8459
|
+
event.stopPropagation();
|
|
8460
|
+
}
|
|
8461
|
+
console.log(`⌨️ [KeyboardShortcut] Executing shortcut: ${shortcutId}`);
|
|
8462
|
+
shortcut.action();
|
|
8463
|
+
break; // Only execute one shortcut per keydown
|
|
8497
8464
|
}
|
|
8498
8465
|
}
|
|
8499
8466
|
}
|
|
8500
8467
|
/**
|
|
8501
|
-
*
|
|
8468
|
+
* Check if event matches shortcut
|
|
8502
8469
|
*/
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
}
|
|
8510
|
-
return 'light';
|
|
8470
|
+
matchesShortcut(event, shortcut) {
|
|
8471
|
+
return event.key === shortcut.key &&
|
|
8472
|
+
(shortcut.ctrlKey === undefined || event.ctrlKey === shortcut.ctrlKey) &&
|
|
8473
|
+
(shortcut.altKey === undefined || event.altKey === shortcut.altKey) &&
|
|
8474
|
+
(shortcut.shiftKey === undefined || event.shiftKey === shortcut.shiftKey) &&
|
|
8475
|
+
(shortcut.metaKey === undefined || event.metaKey === shortcut.metaKey);
|
|
8511
8476
|
}
|
|
8512
8477
|
/**
|
|
8513
|
-
*
|
|
8478
|
+
* Check if event matches override
|
|
8514
8479
|
*/
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
if (this.currentTheme$.value === 'auto') {
|
|
8522
|
-
const effectiveTheme = e.matches ? 'dark' : 'light';
|
|
8523
|
-
this.effectiveTheme$.next(effectiveTheme);
|
|
8524
|
-
this.applyTheme(effectiveTheme);
|
|
8525
|
-
}
|
|
8526
|
-
};
|
|
8527
|
-
// Modern browsers
|
|
8528
|
-
if (mediaQuery.addEventListener) {
|
|
8529
|
-
mediaQuery.addEventListener('change', handler);
|
|
8530
|
-
}
|
|
8531
|
-
else if (mediaQuery.addListener) {
|
|
8532
|
-
// Legacy browsers
|
|
8533
|
-
mediaQuery.addListener(handler);
|
|
8534
|
-
}
|
|
8480
|
+
matchesOverride(event, override) {
|
|
8481
|
+
return event.key === override.newKey &&
|
|
8482
|
+
(override.newCtrlKey === undefined || event.ctrlKey === override.newCtrlKey) &&
|
|
8483
|
+
(override.newAltKey === undefined || event.altKey === override.newAltKey) &&
|
|
8484
|
+
(override.newShiftKey === undefined || event.shiftKey === override.newShiftKey) &&
|
|
8485
|
+
(override.newMetaKey === undefined || event.metaKey === override.newMetaKey);
|
|
8535
8486
|
}
|
|
8536
8487
|
/**
|
|
8537
|
-
*
|
|
8488
|
+
* Get human-readable key description
|
|
8538
8489
|
*/
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
}
|
|
8551
|
-
else if (theme === 'light') {
|
|
8552
|
-
localStorage.setItem(this.config.storageKey, 'light');
|
|
8553
|
-
}
|
|
8554
|
-
}
|
|
8555
|
-
catch (error) {
|
|
8556
|
-
console.warn('Failed to save theme preference to localStorage:', error);
|
|
8557
|
-
}
|
|
8490
|
+
getKeyDescription(shortcut) {
|
|
8491
|
+
const modifiers = [];
|
|
8492
|
+
if (shortcut.ctrlKey)
|
|
8493
|
+
modifiers.push('Ctrl');
|
|
8494
|
+
if (shortcut.altKey)
|
|
8495
|
+
modifiers.push('Alt');
|
|
8496
|
+
if (shortcut.shiftKey)
|
|
8497
|
+
modifiers.push('Shift');
|
|
8498
|
+
if (shortcut.metaKey)
|
|
8499
|
+
modifiers.push('Meta');
|
|
8500
|
+
return [...modifiers, shortcut.key].join(' + ');
|
|
8558
8501
|
}
|
|
8559
8502
|
/**
|
|
8560
|
-
*
|
|
8503
|
+
* Get key description for a shortcut ID
|
|
8561
8504
|
*/
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
localStorage.setItem(this.config.storageKey, 'dark');
|
|
8576
|
-
return 'dark';
|
|
8577
|
-
}
|
|
8578
|
-
else if (saved === 'false') {
|
|
8579
|
-
// Migrate to new format
|
|
8580
|
-
localStorage.setItem(this.config.storageKey, 'light');
|
|
8581
|
-
return 'light';
|
|
8582
|
-
}
|
|
8583
|
-
}
|
|
8584
|
-
catch (error) {
|
|
8585
|
-
console.warn('Failed to load theme preference from localStorage:', error);
|
|
8505
|
+
getKeyDescriptionForShortcut(shortcutId) {
|
|
8506
|
+
const shortcut = this.shortcuts.get(shortcutId);
|
|
8507
|
+
if (!shortcut)
|
|
8508
|
+
return 'Not found';
|
|
8509
|
+
const override = this.overrides.get(shortcutId);
|
|
8510
|
+
if (override) {
|
|
8511
|
+
return this.getKeyDescription({
|
|
8512
|
+
key: override.newKey,
|
|
8513
|
+
ctrlKey: override.newCtrlKey,
|
|
8514
|
+
altKey: override.newAltKey,
|
|
8515
|
+
shiftKey: override.newShiftKey,
|
|
8516
|
+
metaKey: override.newMetaKey
|
|
8517
|
+
});
|
|
8586
8518
|
}
|
|
8587
|
-
return
|
|
8519
|
+
return this.getKeyDescription(shortcut);
|
|
8520
|
+
}
|
|
8521
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: KeyboardShortcutService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
8522
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: KeyboardShortcutService, providedIn: 'root' });
|
|
8523
|
+
}
|
|
8524
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: KeyboardShortcutService, decorators: [{
|
|
8525
|
+
type: Injectable,
|
|
8526
|
+
args: [{
|
|
8527
|
+
providedIn: 'root'
|
|
8528
|
+
}]
|
|
8529
|
+
}], ctorParameters: () => [] });
|
|
8530
|
+
|
|
8531
|
+
class ConfirmationService {
|
|
8532
|
+
// Modern signal-based state management
|
|
8533
|
+
confirmationQueue = signal([], ...(ngDevMode ? [{ debugName: "confirmationQueue" }] : []));
|
|
8534
|
+
currentRequest = signal(null, ...(ngDevMode ? [{ debugName: "currentRequest" }] : []));
|
|
8535
|
+
// Modern computed signals with proper typing
|
|
8536
|
+
hasActiveConfirmation = computed(() => this.currentRequest() !== null, ...(ngDevMode ? [{ debugName: "hasActiveConfirmation" }] : []));
|
|
8537
|
+
queueLength = computed(() => this.confirmationQueue().length, ...(ngDevMode ? [{ debugName: "queueLength" }] : []));
|
|
8538
|
+
constructor() {
|
|
8539
|
+
// Modern service initialization
|
|
8540
|
+
}
|
|
8541
|
+
ngOnDestroy() {
|
|
8542
|
+
// Cleanup if needed
|
|
8588
8543
|
}
|
|
8589
8544
|
/**
|
|
8590
|
-
*
|
|
8545
|
+
* Ask for confirmation with a simple yes/no dialog
|
|
8591
8546
|
*/
|
|
8592
|
-
|
|
8593
|
-
|
|
8547
|
+
ask(options) {
|
|
8548
|
+
console.log('🔔 ConfirmationService.ask called with options:', options);
|
|
8549
|
+
return new Promise((resolve, reject) => {
|
|
8550
|
+
const request = {
|
|
8551
|
+
id: this.generateId(),
|
|
8552
|
+
title: options.title,
|
|
8553
|
+
message: options.message,
|
|
8554
|
+
type: options.type || 'warning',
|
|
8555
|
+
icon: options.icon || this.getDefaultIcon(options.type || 'warning'),
|
|
8556
|
+
confirmText: options.confirmText || 'Confirm',
|
|
8557
|
+
cancelText: options.cancelText || 'Cancel',
|
|
8558
|
+
resolve: (value) => resolve(value),
|
|
8559
|
+
reject
|
|
8560
|
+
};
|
|
8561
|
+
console.log('🔽 Ask for confirmation with a simple yes/no dialog', request);
|
|
8562
|
+
this.addToQueue(request);
|
|
8563
|
+
});
|
|
8564
|
+
}
|
|
8565
|
+
/**
|
|
8566
|
+
* Ask for confirmation with custom template and return custom data
|
|
8567
|
+
*/
|
|
8568
|
+
askWithTemplate(options) {
|
|
8569
|
+
return new Promise((resolve, reject) => {
|
|
8570
|
+
const request = {
|
|
8571
|
+
id: this.generateId(),
|
|
8572
|
+
title: options.title,
|
|
8573
|
+
message: options.message,
|
|
8574
|
+
type: options.type || 'info',
|
|
8575
|
+
icon: options.icon || this.getDefaultIcon(options.type || 'info'),
|
|
8576
|
+
confirmText: options.confirmText || 'Confirm',
|
|
8577
|
+
cancelText: options.cancelText || 'Cancel',
|
|
8578
|
+
customTemplate: options.customTemplate,
|
|
8579
|
+
customData: options.customData,
|
|
8580
|
+
resolve: (value) => resolve(value),
|
|
8581
|
+
reject
|
|
8582
|
+
};
|
|
8583
|
+
// Cast to unknown for queue compatibility
|
|
8584
|
+
this.addToQueue(request);
|
|
8585
|
+
});
|
|
8586
|
+
}
|
|
8587
|
+
/**
|
|
8588
|
+
* Quick confirmation for dangerous actions
|
|
8589
|
+
*/
|
|
8590
|
+
confirmDelete(itemName) {
|
|
8591
|
+
return this.ask({
|
|
8592
|
+
title: 'Confirm Delete',
|
|
8593
|
+
message: itemName ? `Are you sure you want to delete "${itemName}"? This action cannot be undone.` : 'Are you sure you want to delete this item? This action cannot be undone.',
|
|
8594
|
+
type: 'danger',
|
|
8595
|
+
icon: 'delete_forever',
|
|
8596
|
+
confirmText: 'Delete',
|
|
8597
|
+
cancelText: 'Cancel'
|
|
8598
|
+
});
|
|
8599
|
+
}
|
|
8600
|
+
/**
|
|
8601
|
+
* Quick confirmation for permanent actions
|
|
8602
|
+
*/
|
|
8603
|
+
confirmPermanentAction(action, itemName) {
|
|
8604
|
+
return this.ask({
|
|
8605
|
+
title: `Confirm ${action}`,
|
|
8606
|
+
message: itemName ? `Are you sure you want to ${action.toLowerCase()} "${itemName}"? This action cannot be undone.` : `Are you sure you want to ${action.toLowerCase()}? This action cannot be undone.`,
|
|
8607
|
+
type: 'danger',
|
|
8608
|
+
icon: 'warning',
|
|
8609
|
+
confirmText: action,
|
|
8610
|
+
cancelText: 'Cancel'
|
|
8611
|
+
});
|
|
8612
|
+
}
|
|
8613
|
+
/**
|
|
8614
|
+
* Quick confirmation for safe actions
|
|
8615
|
+
*/
|
|
8616
|
+
confirmSafeAction(action, itemName) {
|
|
8617
|
+
return this.ask({
|
|
8618
|
+
title: `Confirm ${action}`,
|
|
8619
|
+
message: itemName ? `Are you sure you want to ${action.toLowerCase()} "${itemName}"?` : `Are you sure you want to ${action.toLowerCase()}?`,
|
|
8620
|
+
type: 'info',
|
|
8621
|
+
icon: 'info',
|
|
8622
|
+
confirmText: action,
|
|
8623
|
+
cancelText: 'Cancel'
|
|
8624
|
+
});
|
|
8625
|
+
}
|
|
8626
|
+
// Modern private methods with proper typing
|
|
8627
|
+
addToQueue(request) {
|
|
8628
|
+
this.confirmationQueue.update(queue => [...queue, request]);
|
|
8629
|
+
console.log('🔔 Added request to queue:', request);
|
|
8630
|
+
this.processQueue();
|
|
8631
|
+
}
|
|
8632
|
+
processQueue() {
|
|
8633
|
+
console.log('🔔 Processing queue. Current request:', this.currentRequest(), 'Queue length:', this.confirmationQueue().length);
|
|
8634
|
+
if (this.currentRequest() || this.confirmationQueue().length === 0) {
|
|
8594
8635
|
return;
|
|
8595
8636
|
}
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
catch (error) {
|
|
8600
|
-
console.warn('Failed to clear theme preference from localStorage:', error);
|
|
8601
|
-
}
|
|
8637
|
+
const nextRequest = this.confirmationQueue()[0];
|
|
8638
|
+
console.log('🔔 Setting current request:', nextRequest);
|
|
8639
|
+
this.currentRequest.set(nextRequest);
|
|
8602
8640
|
}
|
|
8603
|
-
|
|
8604
|
-
|
|
8641
|
+
// Modern public methods with proper typing
|
|
8642
|
+
confirmCurrentRequest(data) {
|
|
8643
|
+
const request = this.currentRequest();
|
|
8644
|
+
if (!request)
|
|
8645
|
+
return;
|
|
8646
|
+
// Always resolve with true for confirmation, unless custom data is explicitly provided
|
|
8647
|
+
const resolvedValue = data !== undefined ? data : true;
|
|
8648
|
+
console.log('🔔 Confirming request with value:', resolvedValue);
|
|
8649
|
+
request.resolve(resolvedValue);
|
|
8650
|
+
this.removeCurrentRequest();
|
|
8651
|
+
}
|
|
8652
|
+
cancelCurrentRequest() {
|
|
8653
|
+
const request = this.currentRequest();
|
|
8654
|
+
if (!request)
|
|
8655
|
+
return;
|
|
8656
|
+
request.reject(new Error('User cancelled'));
|
|
8657
|
+
this.removeCurrentRequest();
|
|
8658
|
+
}
|
|
8659
|
+
removeCurrentRequest() {
|
|
8660
|
+
this.confirmationQueue.update(queue => queue.slice(1));
|
|
8661
|
+
this.currentRequest.set(null);
|
|
8662
|
+
// Process next request if any
|
|
8663
|
+
setTimeout(() => this.processQueue(), 100);
|
|
8664
|
+
}
|
|
8665
|
+
generateId() {
|
|
8666
|
+
return `confirmation-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
8667
|
+
}
|
|
8668
|
+
// Modern private utility method with proper typing
|
|
8669
|
+
getDefaultIcon(type) {
|
|
8670
|
+
const iconMap = {
|
|
8671
|
+
danger: 'error',
|
|
8672
|
+
warning: 'warning',
|
|
8673
|
+
info: 'info',
|
|
8674
|
+
success: 'done'
|
|
8675
|
+
};
|
|
8676
|
+
return iconMap[type];
|
|
8677
|
+
}
|
|
8678
|
+
// Modern public getters with proper typing
|
|
8679
|
+
getCurrentRequest() {
|
|
8680
|
+
return this.currentRequest;
|
|
8681
|
+
}
|
|
8682
|
+
getHasActiveConfirmation() {
|
|
8683
|
+
return this.hasActiveConfirmation;
|
|
8684
|
+
}
|
|
8685
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: ConfirmationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
8686
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: ConfirmationService, providedIn: 'root' });
|
|
8605
8687
|
}
|
|
8606
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type:
|
|
8688
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: ConfirmationService, decorators: [{
|
|
8607
8689
|
type: Injectable,
|
|
8608
8690
|
args: [{
|
|
8609
8691
|
providedIn: 'root'
|
|
@@ -14249,5 +14331,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
14249
14331
|
* Generated bundle index. Do not edit.
|
|
14250
14332
|
*/
|
|
14251
14333
|
|
|
14252
|
-
export { CapitalizePipe, CideCoreFileManagerService, CideEleBreadcrumbComponent, CideEleButtonComponent, CideEleConfirmationModalComponent, CideEleDataGridComponent, CideEleDropdownComponent, CideEleFileImageDirective, CideEleFileInputComponent, CideEleFileManagerService, CideEleFloatingContainerComponent, CideEleFloatingContainerDynamicDirective, CideEleFloatingContainerManagerComponent, CideEleFloatingContainerService, CideEleFloatingFeaturesService, CideEleFloatingFileUploaderComponent, CideEleFloatingFileUploaderService, CideEleGlobalNotificationsComponent, CideEleJsonEditorComponent, CideEleResizerDirective, CideEleSkeletonLoaderComponent, CideEleTabComponent, CideEleToastNotificationComponent, CideElementsService, CideFormFieldErrorComponent, CideIconComponent, CideInputComponent, CideSelectComponent, CideSelectOptionComponent, CideSpinnerComponent, CideTextareaComponent, CideThemeService, ConfirmationService, CoreFileManagerInsertUpdatePayload, DEFAULT_GRID_CONFIG, DropdownManagerService, ExportService, ICoreCyfmSave, KeyboardShortcutService, MFileManager, NotificationApiService, NotificationService, PortalService, TooltipDirective, WebSocketNotificationService, cidePath, hostManagerRoutesUrl, notificationRoutesUrl };
|
|
14334
|
+
export { CapitalizePipe, CideCoreFileManagerService, CideEleBreadcrumbComponent, CideEleButtonComponent, CideEleConfirmationModalComponent, CideEleDataGridComponent, CideEleDropdownComponent, CideEleFileImageDirective, CideEleFileInputComponent, CideEleFileManagerService, CideEleFloatingContainerComponent, CideEleFloatingContainerDynamicDirective, CideEleFloatingContainerManagerComponent, CideEleFloatingContainerService, CideEleFloatingFeaturesService, CideEleFloatingFileUploaderComponent, CideEleFloatingFileUploaderService, CideEleGlobalNotificationsComponent, CideEleJsonEditorComponent, CideEleResizerDirective, CideEleSkeletonLoaderComponent, CideEleTabComponent, CideEleThemeToggleComponent, CideEleToastNotificationComponent, CideElementsService, CideFormFieldErrorComponent, CideIconComponent, CideInputComponent, CideSelectComponent, CideSelectOptionComponent, CideSpinnerComponent, CideTextareaComponent, CideThemeService, ConfirmationService, CoreFileManagerInsertUpdatePayload, DEFAULT_GRID_CONFIG, DropdownManagerService, ExportService, ICoreCyfmSave, KeyboardShortcutService, MFileManager, NotificationApiService, NotificationService, PortalService, TooltipDirective, WebSocketNotificationService, cidePath, hostManagerRoutesUrl, notificationRoutesUrl };
|
|
14253
14335
|
//# sourceMappingURL=cloud-ide-element.mjs.map
|