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