import { Component, Inject, OnInit, AfterViewInit, ViewChild, ViewEncapsulation } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule, NgForm, AbstractControl } from '@angular/forms'; import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { ErrorStateMatcher } from '@angular/material/core'; import { MatInputModule } from '@angular/material/input'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatToolbarModule } from '@angular/material/toolbar'; import { DialogCancelButtonComponent } from '../components/dialog-cancel-button.component'; import { I18nService } from '../services/i18n.service'; import { SettingsService } from '../services/settings.service'; import { RedisStateService } from '../services/redis-state.service'; import { CommonService } from '../services/common.service'; import { MainCommandService } from '../services/main-command.service'; import { SocketService } from '../services/socket.service'; import { TreeBuilderService } from '../services/tree-builder.service'; /** * Tree control settings dialog — Angular replacement for p3xrDialogTreecontrolSettings. * Edits pagination, sorting, search, display, and animation settings. */ @Component({ selector: 'p3xr-treecontrol-settings-dialog', standalone: true, imports: [ CommonModule, FormsModule, MatDialogModule, MatFormFieldModule, MatInputModule, MatSlideToggleModule, MatButtonModule, MatIconModule, MatToolbarModule, DialogCancelButtonComponent, ], template: `
{{ strings().form?.treeSettings?.label?.formName || 'Redis Settings' }}
@if (reducedFunctions) {
{{ strings().form?.treeSettings?.keyCount?.({ keyCount: keysRawLength }) }}
{{ strings().label?.tooManyKeys?.({ count: keysRawLength, maxLightKeysCount: settings.maxLightKeysCount }) }}
}
{{ strings().form?.treeSettings?.field?.treeSeparator || 'Tree separator' }}
{{ strings().label?.treeSeparatorEmpty }}
{{ strings().form?.treeSettings?.field?.page || 'Page size' }} @if (isFieldInvalid('pageCount', 10, 5000)) {
{{ strings().form?.treeSettings?.error?.page || 'The page count must be an integer between 10 - 5000' }}
}
{{ strings().form?.treeSettings?.field?.keyPageCount || 'Key page size' }} @if (isFieldInvalid('keyPageCount', 5, 100)) {
{{ strings().form?.treeSettings?.error?.keyPageCount }}
}
{{ strings().form?.treeSettings?.maxValueDisplay || 'Max value display' }} @if (isFieldInvalid('maxValueDisplay', -1, 32768)) {
{{ strings().form?.treeSettings?.error?.maxValueDisplay }}
} @else {
{{ strings().form?.treeSettings?.maxValueDisplayInfo }}
}
{{ strings().form?.treeSettings?.maxKeys || 'Max keys' }} @if (isFieldInvalid('maxKeys', 5, 100000)) {
{{ strings().form?.treeSettings?.error?.maxKeys }}
} @else {
{{ strings().form?.treeSettings?.maxKeysInfo }}
}
@if (!reducedFunctions) {
{{ model.keysSort ? strings().label?.keysSort?.on : strings().label?.keysSort?.off }}
{{ strings().label?.treeKeyStore }}
{{ model.searchClientSide ? strings().form?.treeSettings?.label?.searchModeClient : strings().form?.treeSettings?.label?.searchModeServer }}
{{ strings().page?.treeControls?.search?.info?.({ maxLightKeysCount: settings.maxLightKeysCount }) }} @if (dbsize > settings.maxLightKeysCount) {
{{ strings().page?.treeControls?.search?.largeSetInfo }}
}
}
{{ model.searchStartsWith ? strings().form?.treeSettings?.label?.searchModeStartsWith : strings().form?.treeSettings?.label?.searchModeIncludes }}
{{ model.jsonFormat ? strings().form?.treeSettings?.label?.jsonFormatTwoSpace : strings().form?.treeSettings?.label?.jsonFormatFourSpace }}
{{ model.animation ? strings().form?.treeSettings?.label?.animation : strings().form?.treeSettings?.label?.noAnimation }}
`, encapsulation: ViewEncapsulation.None, styles: [` .md-block { width: 100%; } .p3xr-field-error .mdc-line-ripple::before, .p3xr-field-error .mdc-line-ripple::after { border-bottom-color: #f44336 !important; } .p3xr-field-error .mdc-floating-label, .p3xr-field-error .mat-mdc-form-field-required-marker { color: #f44336 !important; } .p3xr-field-error-text { color: #f44336; font-size: 12px; margin-top: -16px; padding-left: 16px; } .p3xr-field-hint-text { color: var(--mat-app-text-color, rgba(0, 0, 0, 0.6)); opacity: 0.7; font-size: 12px; margin-top: -16px; padding-left: 16px; } `], }) export class TreecontrolSettingsDialogComponent implements OnInit, AfterViewInit { @ViewChild('settingsForm') private formRef?: NgForm; model: any = {}; reducedFunctions = false; keysRawLength = 0; dbsize = 0; strings; constructor( @Inject(MatDialogRef) private dialogRef: MatDialogRef, @Inject(I18nService) private i18n: I18nService, @Inject(SettingsService) public settings: SettingsService, @Inject(RedisStateService) private state: RedisStateService, @Inject(CommonService) private common: CommonService, @Inject(MainCommandService) private cmd: MainCommandService, @Inject(SocketService) private socket: SocketService, @Inject(TreeBuilderService) private treeBuilder: TreeBuilderService, ) { this.strings = this.i18n.strings; } ngOnInit(): void { // Read current settings into local model this.model = { treeSeparator: this.settings.redisTreeDivider(), pageCount: this.settings.pageCount(), keyPageCount: this.settings.keyPageCount(), keysSort: this.settings.keysSort(), searchClientSide: this.settings.searchClientSide(), searchStartsWith: this.settings.searchStartsWith(), maxValueDisplay: this.settings.maxValueDisplay(), maxKeys: this.settings.maxKeys(), jsonFormat: this.settings.jsonFormat() === 2, animation: this.settings.animation(), }; // Read state from signals (with fallback to global) this.reducedFunctions = this.state.reducedFunctions() ?? false; this.keysRawLength = this.state.keysRaw()?.length ?? 0; this.dbsize = this.state.dbsize() ?? 0; } ngAfterViewInit(): void { // Validate all fields after the form is ready so pre-filled invalid values show errors setTimeout(() => this.validateAllFields()); } isFieldInvalid(fieldName: string, min: number, max: number): boolean { const value = this.model[fieldName]; if (value === null || value === undefined || value === '') { return true; } const num = Number(value); return isNaN(num) || !Number.isInteger(num) || num < min || num > max; } validateField(fieldName: string, min: number, max: number): void { const control = this.formRef?.controls[fieldName]; if (!control) { return; } if (this.isFieldInvalid(fieldName, min, max)) { control.setErrors({ range: true }); control.markAsTouched(); } else { control.setErrors(null); } } onFieldChange(fieldName: string, min: number, max: number): void { setTimeout(() => this.validateField(fieldName, min, max)); } validateAllFields(): void { this.validateField('pageCount', 10, 5000); this.validateField('keyPageCount', 5, 100); this.validateField('maxValueDisplay', -1, 32768); this.validateField('maxKeys', 5, 100000); } showFieldError(controlName: string): boolean { const control = this.formRef?.controls[controlName]; return !!control && control.invalid && (control.touched || this.formRef?.submitted); } private markAllControlsTouched(): void { if (!this.formRef) { return; } Object.values(this.formRef.controls).forEach((control) => { control.markAsTouched(); control.updateValueAndValidity(); }); } private handleInvalidForm(): boolean { if (this.formRef?.invalid) { this.common.toast({ message: this.strings().form?.error?.invalid || 'Invalid form', }); return false; } return true; } submit(): void { this.markAllControlsTouched(); const hasRangeError = this.isFieldInvalid('pageCount', 10, 5000) || this.isFieldInvalid('keyPageCount', 5, 100) || this.isFieldInvalid('maxValueDisplay', -1, 32768) || this.isFieldInvalid('maxKeys', 5, 100000); if (hasRangeError || !this.handleInvalidForm()) { this.common.toast({ message: this.strings().form?.error?.invalid || 'Please fix the errors before saving', }); return; } // Save to Angular SettingsService signals this.settings.redisTreeDivider.set(this.model.treeSeparator); this.settings.pageCount.set(this.model.pageCount); this.settings.keyPageCount.set(this.model.keyPageCount); this.settings.keysSort.set(this.model.keysSort); this.settings.searchClientSide.set(this.model.searchClientSide); this.settings.searchStartsWith.set(this.model.searchStartsWith); this.settings.maxValueDisplay.set(this.model.maxValueDisplay); this.settings.maxKeys.set(this.model.maxKeys); this.settings.jsonFormat.set(this.model.jsonFormat ? 2 : 4); this.settings.animation.set(this.model.animation); this.state.page.set(1); this.state.redisChanged.set(true); // Always refresh from server — settings like sort, page size, max keys affect the data this.cmd.refresh().then(() => { this.socket.stateChanged$.next(); this.socket.tick(); }); this.dialogRef.close(); } cancel(): void { this.dialogRef.close(); } }