Refactor Nickname Generator component with Angular Material, update logic for fields and commands, and improve styling.
This commit is contained in:
parent
423d5e4a4c
commit
2be79c180a
|
|
@ -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)
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
<ng-container>
|
||||
<app-header [current_page]="'nickgenerator'" height="460px" background_image="/public/img/backgrounds/trees.jpg"
|
||||
[overlay_gradient]="0.5">
|
||||
<div class="title" header-content>
|
||||
<h1>Nickname Generator</h1>
|
||||
<h2>Customize your in-game nickname</h2>
|
||||
<h3 style="font-family: 'minecraft-text', sans-serif; font-size: 0.8rem; margin-top: 10px;">Made by TheParm</h3>
|
||||
<!--TODO remove this message when everything works-->
|
||||
<p style="font-weight: bolder; color: red">NOTICE: This page is in the process of being updated to work on the new
|
||||
site.<br> This version is functional, but only barely. Expect updates in the coming days</p>
|
||||
</div>
|
||||
</app-header>
|
||||
|
||||
<main>
|
||||
<section class="containerNick">
|
||||
<div class="controls">
|
||||
|
||||
@for (part of parts; track $index; let i = $index) {
|
||||
<div class="part">
|
||||
<div class="row">
|
||||
<mat-form-field class="textField" appearance="outline">
|
||||
<mat-label>Text</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[value]="part.text"
|
||||
(input)="part.text = ($any($event.target).value || ''); onInputChanged()"
|
||||
maxlength="16"
|
||||
/>
|
||||
<mat-hint align="end">{{ part.text.length }} / 16</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-checkbox
|
||||
class="checkbox"
|
||||
[(ngModel)]="part.gradient"
|
||||
(change)="onGradientToggle(i)"
|
||||
>Gradient
|
||||
</mat-checkbox
|
||||
>
|
||||
|
||||
<mat-form-field
|
||||
class="colorField"
|
||||
appearance="outline"
|
||||
[style.visibility]="(part.continuation && i>0 && parts[i-1]?.gradient && part.gradient) ? 'hidden' : 'visible'">
|
||||
<mat-label>Color A</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="color"
|
||||
[value]="part.colorA"
|
||||
(input)="part.colorA = $any($event.target).value; onInputChanged()"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field
|
||||
class="colorField"
|
||||
appearance="outline"
|
||||
[style.visibility]="part.gradient ? 'visible' : 'hidden'">
|
||||
<mat-label>Color B</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="color"
|
||||
[value]="part.colorB"
|
||||
(input)="part.colorB = $any($event.target).value; onInputChanged()"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-checkbox
|
||||
class="checkbox"
|
||||
[(ngModel)]="part.continuation"
|
||||
(change)="onContinuationToggle(i)"
|
||||
[disabled]="i===0 || !part.gradient || !parts[i-1]?.gradient"
|
||||
>Continuation
|
||||
</mat-checkbox
|
||||
>
|
||||
</div>
|
||||
|
||||
@if (part.invalid) {
|
||||
<div class="invalid">(min 1 – max 16 chars{{ part.gradient ? '' : ' for non-empty text' }})</div>
|
||||
}
|
||||
<mat-divider></mat-divider>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="buttons">
|
||||
<button mat-raised-button (click)="addPart()">Add Part</button>
|
||||
<button mat-raised-button (click)="deletePart()">Remove Part</button>
|
||||
</div>
|
||||
|
||||
@if (showCommands) {
|
||||
<div class="commands">
|
||||
<div class="commandRow">
|
||||
<div class="command">{{ tryCmd }}</div>
|
||||
<button mat-stroked-button (click)="copy(tryCmd, 'try')">{{ tryCommandButtonContent }}</button>
|
||||
</div>
|
||||
<div class="commandRow">
|
||||
<div class="command">{{ requestCmd }}</div>
|
||||
<button mat-stroked-button (click)="copy(requestCmd, 'request')">{{ requestCommandButtonContent }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (showPreview) {
|
||||
<div class="preview" [innerHTML]="previewHtml"></div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</ng-container>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 `<span style="color:${color}">${this.escape(text)}</span>`;
|
||||
}
|
||||
|
||||
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, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
<ng-container>
|
||||
<app-header [current_page]="'nickgenerator'" height="460px" background_image="/public/img/backgrounds/trees.jpg"
|
||||
[overlay_gradient]="0.5">
|
||||
<div class="title" header-content>
|
||||
<h1>Nickname Generator</h1>
|
||||
<h2>Customize your in-game nickname</h2>
|
||||
<h3 style="font-family: 'minecraft-text', sans-serif; font-size: 0.8rem; margin-top: 10px;">Made by TheParm</h3>
|
||||
</div>
|
||||
</app-header>
|
||||
|
||||
<main>
|
||||
<!-- <section class="darkmodeSection">
|
||||
<div class="container containerNick">
|
||||
<div style="padding: 0 5% 0 5%;">
|
||||
<div id="parts" class="previewNickDiv">
|
||||
</div>
|
||||
<div class="previewNickDiv">
|
||||
<input type="button" class="button" value="Add Part" onclick="addPart()"/>
|
||||
<input type="button" class="button" value="Remove Part" onclick="deletePart()"/>
|
||||
</div>
|
||||
<br><br><br><br>
|
||||
<div id="commandTry" class="previewNickDiv">
|
||||
<div id="try" class="command darkBg"></div>
|
||||
<input type="button" class="button copy" value="Copy" onclick="copy(this)"/>
|
||||
</div>
|
||||
<div id="commandRequest" class="previewNickDiv">
|
||||
<div id="request" class="command darkBg"></div>
|
||||
<input type="button" class="button copy" value="Copy" onclick="copy(this)"/>
|
||||
</div>
|
||||
<div id="preview" class="preview darkBg previewNickDiv">
|
||||
</div>
|
||||
<div id="template" class='part' style="display: none">
|
||||
<p style="font-family: 'minecraft-text', sans-serif">
|
||||
Text: <input type="text" id="text" class="textPart" size=18 oninput="inputChanged()"/>
|
||||
Gradient: <input type="checkbox" id="grad" class="gradPart" oninput="onGradient(this)"/>
|
||||
<input id="colorA" type="text" class="coloris colorAPart color" value="#ffffff" oninput="inputChanged()"/>
|
||||
<input id="colorB" type="text" class="coloris colorBPart color" value="#ffffff" oninput="inputChanged()"/>
|
||||
Continuation: <input type="checkbox" id="cont" class="contPart" disabled oninput="onContinuation(this)"/>
|
||||
<span id="invalid" class="invalidPart" style="display: none">(min 1 - max 16 chars)</span>
|
||||
</p>
|
||||
</div>
|
||||
<div style="margin-top: 20px; text-align: center;">
|
||||
<p style="font-family: 'minecraft-text', sans-serif">
|
||||
Usage: Add as many parts as you wish, then apply the color and/or gradient, and copy/paste the command
|
||||
into the minecraft chat. The total length of the nickname should be between 3 and 16 characters. Use the
|
||||
continuation checkbox to continue the gradient from the last gradient color.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section> -->
|
||||
</main>
|
||||
</ng-container>
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NickgeneratorComponent } from './nickgenerator.component';
|
||||
|
||||
describe('NickgeneratorComponent', () => {
|
||||
let component: NickgeneratorComponent;
|
||||
let fixture: ComponentFixture<NickgeneratorComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NickgeneratorComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(NickgeneratorComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user