From 2be79c180a6b52b802877808c86ba7bcc2cfa2c8 Mon Sep 17 00:00:00 2001 From: akastijn Date: Wed, 29 Oct 2025 21:39:39 +0100 Subject: [PATCH] Refactor Nickname Generator component with Angular Material, update logic for fields and commands, and improve styling. --- frontend/src/app/app.routes.ts | 2 +- .../nick-generator.component.html | 108 +++++++++ .../nick-generator.component.scss | 77 ++++++ .../nickgenerator/nick-generator.component.ts | 226 ++++++++++++++++++ .../nickgenerator.component.html | 53 ---- .../nickgenerator.component.scss | 0 .../nickgenerator.component.spec.ts | 23 -- .../nickgenerator/nickgenerator.component.ts | 14 -- 8 files changed, 412 insertions(+), 91 deletions(-) create mode 100644 frontend/src/app/pages/reference/nickgenerator/nick-generator.component.html create mode 100644 frontend/src/app/pages/reference/nickgenerator/nick-generator.component.scss create mode 100644 frontend/src/app/pages/reference/nickgenerator/nick-generator.component.ts delete mode 100644 frontend/src/app/pages/reference/nickgenerator/nickgenerator.component.html delete mode 100644 frontend/src/app/pages/reference/nickgenerator/nickgenerator.component.scss delete mode 100644 frontend/src/app/pages/reference/nickgenerator/nickgenerator.component.spec.ts delete mode 100644 frontend/src/app/pages/reference/nickgenerator/nickgenerator.component.ts diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 450f505..8fd59f0 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -154,6 +154,6 @@ export const routes: Routes = [ }, { path: 'nickgenerator', - loadComponent: () => import('./pages/reference/nickgenerator/nickgenerator.component').then(m => m.NickgeneratorComponent) + loadComponent: () => import('@pages/reference/nickgenerator/nick-generator.component').then(m => m.NickGeneratorComponent) }, ]; diff --git a/frontend/src/app/pages/reference/nickgenerator/nick-generator.component.html b/frontend/src/app/pages/reference/nickgenerator/nick-generator.component.html new file mode 100644 index 0000000..87a1b25 --- /dev/null +++ b/frontend/src/app/pages/reference/nickgenerator/nick-generator.component.html @@ -0,0 +1,108 @@ + + +
+

Nickname Generator

+

Customize your in-game nickname

+

Made by TheParm

+ +

NOTICE: This page is in the process of being updated to work on the new + site.
This version is functional, but only barely. Expect updates in the coming days

+
+
+ +
+
+
+ + @for (part of parts; track $index; let i = $index) { +
+
+ + Text + + {{ part.text.length }} / 16 + + + Gradient + + + + Color A + + + + + Color B + + + + Continuation + +
+ + @if (part.invalid) { +
(min 1 – max 16 chars{{ part.gradient ? '' : ' for non-empty text' }})
+ } + +
+ } + +
+ + +
+ + @if (showCommands) { +
+
+
{{ tryCmd }}
+ +
+
+
{{ requestCmd }}
+ +
+
+ } + + @if (showPreview) { +
+ } +
+
+
+
diff --git a/frontend/src/app/pages/reference/nickgenerator/nick-generator.component.scss b/frontend/src/app/pages/reference/nickgenerator/nick-generator.component.scss new file mode 100644 index 0000000..bf3c716 --- /dev/null +++ b/frontend/src/app/pages/reference/nickgenerator/nick-generator.component.scss @@ -0,0 +1,77 @@ +/* nick-generator.component.css */ +.containerNick { + background-color: #292828; + padding: 40px 5%; + max-width: 1220px; + margin: 0 auto; +} + +.controls { + width: 100%; +} + +.part { + padding: 8px 0 16px 0; +} + +.row { + display: flex; + flex-wrap: wrap; + gap: 12px; + align-items: center; +} + +.textField { + flex: 1 1 260px; + min-width: 220px; +} + +.colorField { + width: 110px; +} + +.checkbox { + padding: 0 6px; +} + +.invalid { + color: #dd0000; + font-size: 12px; + margin-top: 6px; +} + +.buttons { + display: flex; + gap: 12px; + margin: 20px 0 32px 0; +} + +.commands { + display: grid; + gap: 10px; + margin-bottom: 16px; +} + +.commandRow { + display: flex; + gap: 12px; + align-items: center; +} + +.command { + background: #1e1e1e; + color: #ffffff; + padding: 10px 12px; + border-radius: 6px; + font-family: monospace; + overflow-x: auto; +} + +.preview { + background: #1e1e1e; + padding: 14px 12px; + border-radius: 6px; + color: #ffffff; + font-family: 'minecraft-text', monospace; + white-space: pre-wrap; +} diff --git a/frontend/src/app/pages/reference/nickgenerator/nick-generator.component.ts b/frontend/src/app/pages/reference/nickgenerator/nick-generator.component.ts new file mode 100644 index 0000000..55ee893 --- /dev/null +++ b/frontend/src/app/pages/reference/nickgenerator/nick-generator.component.ts @@ -0,0 +1,226 @@ +import {Component} 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 { + parts: Part[] = [ + {text: '', gradient: false, colorA: '#ffffff', colorB: '#ffffff', continuation: false} + ]; + + tryCmd = ''; + requestCmd = ''; + previewHtml: SafeHtml = ''; + showPreview = false; + showCommands = false; + + constructor(private sanitizer: DomSanitizer) { + } + + 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, '>'); + } +} diff --git a/frontend/src/app/pages/reference/nickgenerator/nickgenerator.component.html b/frontend/src/app/pages/reference/nickgenerator/nickgenerator.component.html deleted file mode 100644 index a389278..0000000 --- a/frontend/src/app/pages/reference/nickgenerator/nickgenerator.component.html +++ /dev/null @@ -1,53 +0,0 @@ - - -
-

Nickname Generator

-

Customize your in-game nickname

-

Made by TheParm

-
-
- -
- -
-
diff --git a/frontend/src/app/pages/reference/nickgenerator/nickgenerator.component.scss b/frontend/src/app/pages/reference/nickgenerator/nickgenerator.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/app/pages/reference/nickgenerator/nickgenerator.component.spec.ts b/frontend/src/app/pages/reference/nickgenerator/nickgenerator.component.spec.ts deleted file mode 100644 index 96a3e06..0000000 --- a/frontend/src/app/pages/reference/nickgenerator/nickgenerator.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { NickgeneratorComponent } from './nickgenerator.component'; - -describe('NickgeneratorComponent', () => { - let component: NickgeneratorComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [NickgeneratorComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(NickgeneratorComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/frontend/src/app/pages/reference/nickgenerator/nickgenerator.component.ts b/frontend/src/app/pages/reference/nickgenerator/nickgenerator.component.ts deleted file mode 100644 index 0eedf36..0000000 --- a/frontend/src/app/pages/reference/nickgenerator/nickgenerator.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {Component} from '@angular/core'; -import {HeaderComponent} from "@header/header.component"; - -@Component({ - selector: 'app-nickgenerator', - imports: [ - HeaderComponent - ], - templateUrl: './nickgenerator.component.html', - styleUrl: './nickgenerator.component.scss' -}) -export class NickgeneratorComponent { - -}