import {AfterViewInit, Component, ElementRef, OnDestroy, Renderer2} from '@angular/core'; import {DomSanitizer, SafeHtml} from '@angular/platform-browser'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatInputModule} from '@angular/material/input'; import {HeaderComponent} from '@header/header.component'; import {MatCheckboxModule} from '@angular/material/checkbox'; import {FormsModule} from '@angular/forms'; import {MatDividerModule} from '@angular/material/divider'; import {MatButtonModule} from '@angular/material/button'; interface Part { text: string; gradient: boolean; colorA: string; colorB: string; continuation: boolean; invalid?: boolean; } @Component({ selector: 'app-nick-generator', templateUrl: './nick-generator.component.html', styleUrls: ['./nick-generator.component.scss'], imports: [ MatFormFieldModule, MatInputModule, HeaderComponent, MatCheckboxModule, FormsModule, MatDividerModule, MatButtonModule, ] }) export class NickGeneratorComponent implements AfterViewInit, OnDestroy { parts: Part[] = [ {text: '', gradient: false, colorA: '#ffffff', colorB: '#ffffff', continuation: false} ]; tryCmd = ''; requestCmd = ''; previewHtml: SafeHtml = ''; showPreview = false; showCommands = false; private handleResize: any; private boundHandleResize: any; private resizeObserver: ResizeObserver | null = null; constructor(private sanitizer: DomSanitizer, private elementRef: ElementRef, private renderer: Renderer2 ) { } ngOnDestroy() { if (this.resizeObserver) { this.resizeObserver.disconnect(); this.resizeObserver = null; } if (this.boundHandleResize) { window.removeEventListener('resize', this.boundHandleResize); } } ngAfterViewInit(): void { this.setupResizeObserver(); window.addEventListener('resize', this.boundHandleResize); this.boundHandleResize = this.handleResize.bind(this); setTimeout(() => this.updateContainerHeight(), 0); } addPart(): void { this.parts.push({text: '', gradient: false, colorA: '#ffffff', colorB: '#ffffff', continuation: false}); this.onInputChanged(); } deletePart(): void { if (this.parts.length > 1) { this.parts.pop(); // If last part was a gradient, unset continuation on new last part if (this.parts.length > 0) this.parts[this.parts.length - 1].continuation = false; this.onInputChanged(); } } onGradientToggle(i: number): void { // Toggling gradient affects availability of continuation for this & next part if (!this.parts[i].gradient) { // If gradient turned off, force continuation off for this index (not visible anymore) this.parts[i].continuation = false; } if (i + 1 < this.parts.length && !this.parts[i + 1].gradient) { this.parts[i + 1].continuation = false; } this.onInputChanged(); } onContinuationToggle(_: number): void { this.onInputChanged(); } onInputChanged(): void { let result = ''; let preview = ''; let valid = true; let nickLen = 0; let prevColorB = '#ffffff'; for (let i = 0; i < this.parts.length; i++) { const p = this.parts[i]; const len = p.text.length; nickLen += len; const partValid = (p.gradient && len >= 1 && len <= 16) || (!p.gradient && len > 0); p.invalid = !partValid; if (!partValid) { valid = false; continue; } if (p.gradient) { // Continuation allowed only if previous & current are gradient const contAllowed = i > 0 && this.parts[i - 1].gradient; const cont = p.continuation && contAllowed; if (cont) { result += p.text; preview += this.generateGradient(p.text, prevColorB, p.colorB); } else { result += `{${p.colorA}>}${p.text}`; preview += this.generateGradient(p.text, p.colorA, p.colorB); } // Add closing/continuation marker const nextContinuation = (i + 1 < this.parts.length) && this.parts[i + 1].continuation; if (i < this.parts.length - 1) { result += `{${p.colorB}<>}`; } else { result += `{${p.colorB}<}`; } prevColorB = p.colorB; } else { // Solid result += `{${p.colorA}}${p.text}`; preview += this.generateSolidColor(p.text, p.colorA); } } this.tryCmd = ''; this.requestCmd = ''; this.showPreview = false; this.showCommands = false; if (valid && result.length > 0 && nickLen >= 3 && nickLen <= 16) { this.tryCmd = `/nick try ${result}`; this.requestCmd = `/nick request ${result}`; this.previewHtml = this.sanitizer.bypassSecurityTrustHtml( this.generateSolidColor('Nickname preview: ', '#ffffff') + preview ); this.showPreview = true; this.showCommands = true; } else { if (!valid && (this.parts.length > 1 || nickLen > 0)) { this.previewHtml = this.sanitizer.bypassSecurityTrustHtml( this.generateSolidColor('Invalid part(s) length', '#dd0000') ); } else if (valid && (nickLen < 3 || nickLen > 16)) { this.previewHtml = this.sanitizer.bypassSecurityTrustHtml( this.generateSolidColor('Nickname needs to be 3–16 chars', '#dd0000') ); } else { this.previewHtml = this.sanitizer.bypassSecurityTrustHtml(''); } this.showPreview = nickLen > 0; } } tryCommandButtonContent = 'Copy'; requestCommandButtonContent = 'Copy'; copy(text: string, button: 'try' | 'request'): void { navigator.clipboard.writeText(text); if (button === 'try') { this.tryCommandButtonContent = 'Copied!'; } else if (button === 'request') { this.requestCommandButtonContent = 'Copied!'; } setTimeout(() => { if (button === 'try') { this.tryCommandButtonContent = 'Copy'; } else if (button === 'request') { this.requestCommandButtonContent = 'Copy'; } }, 1000); } generateSolidColor(text: string, color: string): string { return `${this.escape(text)}`; } generateGradient(text: string, colorA: string, colorB: string): string { const len = text.length; if (len === 0) return ''; const a = this.hexToRgb(colorA); const b = this.hexToRgb(colorB); if (!a || !b) return this.generateSolidColor(text, colorA); const stepR = len > 1 ? (b.r - a.r) / (len - 1) : 0; const stepG = len > 1 ? (b.g - a.g) / (len - 1) : 0; const stepB = len > 1 ? (b.b - a.b) / (len - 1) : 0; let res = ''; for (let i = 0; i < len; i++) { const r = a.r + stepR * i; const g = a.g + stepG * i; const bl = a.b + stepB * i; res += this.generateSolidColor(text[i], this.rgbToHex(r, g, bl)); } return res; } hexToRgb(hex: string): { r: number; g: number; b: number } | null { const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return m ? {r: parseInt(m[1], 16), g: parseInt(m[2], 16), b: parseInt(m[3], 16)} : null; } componentToHex(c: number): string { const x = Math.round(c); const h = x.toString(16); return h.length === 1 ? '0' + h : h; } rgbToHex(r: number, g: number, b: number): string { return ( '#' + this.componentToHex(r) + this.componentToHex(g) + this.componentToHex(b) ); } escape(s: string): string { return s .replace(/&/g, '&') .replace(//g, '>'); } private updateContainerHeight() { const headerElement = document.querySelector('app-header'); const footerElement = document.querySelector('footer'); const container = this.elementRef.nativeElement.querySelector('.containerNick'); if (headerElement && footerElement && container) { const headerHeight = headerElement.getBoundingClientRect().height; const footerHeight = footerElement.getBoundingClientRect().height; const calculatedHeight = `calc(100vh - ${headerHeight}px - ${footerHeight}px)`; this.renderer.setStyle(container, 'min-height', calculatedHeight); } } private setupResizeObserver() { this.resizeObserver = new ResizeObserver(() => { this.updateContainerHeight(); }); const headerElement = document.querySelector('app-header'); if (headerElement) { this.resizeObserver.observe(headerElement); } const footerElement = document.querySelector('footer'); if (footerElement) { this.resizeObserver.observe(footerElement); } } }