Implement dynamic container height adjustment in NickGeneratorComponent based on header and footer dimensions. Refactor HTML structure for improved dark mode styling and accessibility. Optimize component lifecycle by adding AfterViewInit and OnDestroy handling with a ResizeObserver.

This commit is contained in:
akastijn 2025-11-08 22:09:35 +01:00
parent 72b9109ece
commit e415ecc415
3 changed files with 141 additions and 90 deletions

View File

@ -12,96 +12,97 @@
</app-header> </app-header>
<main> <main>
<section class="containerNick"> <section class="darkmodeSection full-width">
<div class="controls"> <div class="containerNick">
<div class="controls">
@for (part of parts; track $index; let i = $index) { @for (part of parts; track $index; let i = $index) {
<div class="part"> <div class="part">
<div class="row"> <div class="row">
<mat-form-field class="textField" appearance="outline"> <mat-form-field class="textField" appearance="outline">
<mat-label>Text</mat-label> <mat-label>Text</mat-label>
<input <input
matInput matInput
[value]="part.text" [value]="part.text"
(input)="part.text = ($any($event.target).value || ''); onInputChanged()" (input)="part.text = ($any($event.target).value || ''); onInputChanged()"
maxlength="16" maxlength="16"
/> />
<mat-hint align="end">{{ part.text.length }} / 16</mat-hint> <mat-hint align="end">{{ part.text.length }} / 16</mat-hint>
</mat-form-field> </mat-form-field>
<mat-checkbox <mat-checkbox
class="checkbox" class="checkbox"
[(ngModel)]="part.gradient" [(ngModel)]="part.gradient"
(change)="onGradientToggle(i)" (change)="onGradientToggle(i)"
>Gradient >Gradient
</mat-checkbox </mat-checkbox>
>
<mat-form-field <mat-form-field
class="colorField" class="colorField"
appearance="outline" appearance="outline"
[style.visibility]="(part.continuation && i>0 && parts[i-1]?.gradient && part.gradient) ? 'hidden' : 'visible'"> [style.visibility]="(part.continuation && i>0 && parts[i-1]?.gradient && part.gradient) ? 'hidden' : 'visible'">
<mat-label>Color A</mat-label> <mat-label>Color A</mat-label>
<input <input
matInput matInput
type="color" type="color"
[value]="part.colorA" [value]="part.colorA"
(input)="part.colorA = $any($event.target).value; onInputChanged()" (input)="part.colorA = $any($event.target).value; onInputChanged()"
/> />
</mat-form-field> </mat-form-field>
<mat-form-field <mat-form-field
class="colorField" class="colorField"
appearance="outline" appearance="outline"
[style.visibility]="part.gradient ? 'visible' : 'hidden'"> [style.visibility]="part.gradient ? 'visible' : 'hidden'">
<mat-label>Color B</mat-label> <mat-label>Color B</mat-label>
<input <input
matInput matInput
type="color" type="color"
[value]="part.colorB" [value]="part.colorB"
(input)="part.colorB = $any($event.target).value; onInputChanged()" (input)="part.colorB = $any($event.target).value; onInputChanged()"
/> />
</mat-form-field> </mat-form-field>
<mat-checkbox <mat-checkbox
class="checkbox" class="checkbox"
[(ngModel)]="part.continuation" [(ngModel)]="part.continuation"
(change)="onContinuationToggle(i)" (change)="onContinuationToggle(i)"
[disabled]="i===0 || !part.gradient || !parts[i-1]?.gradient" [disabled]="i===0 || !part.gradient || !parts[i-1]?.gradient"
>Continuation >Continuation
</mat-checkbox </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>
}
@if (part.invalid) { <div class="buttons">
<div class="invalid">(min 1 max 16 chars{{ part.gradient ? '' : ' for non-empty text' }})</div> <button mat-raised-button (click)="addPart()">Add Part</button>
} <button mat-raised-button (click)="deletePart()">Remove Part</button>
<mat-divider></mat-divider>
</div> </div>
}
<div class="buttons"> @if (showCommands) {
<button mat-raised-button (click)="addPart()">Add Part</button> <div class="commands">
<button mat-raised-button (click)="deletePart()">Remove Part</button> <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> </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> </div>
</section> </section>
</main> </main>

View File

@ -1,9 +1,7 @@
/* nick-generator.component.css */
.containerNick { .containerNick {
background-color: #292828;
padding: 40px 5%;
max-width: 1220px; max-width: 1220px;
margin: 0 auto; margin: 0 auto;
height: 100%;
} }
.controls { .controls {
@ -59,8 +57,6 @@
} }
.command { .command {
background: #1e1e1e;
color: #ffffff;
padding: 10px 12px; padding: 10px 12px;
border-radius: 6px; border-radius: 6px;
font-family: monospace; font-family: monospace;
@ -68,10 +64,8 @@
} }
.preview { .preview {
background: #1e1e1e;
padding: 14px 12px; padding: 14px 12px;
border-radius: 6px; border-radius: 6px;
color: #ffffff;
font-family: 'minecraft-text', monospace; font-family: 'minecraft-text', monospace;
white-space: pre-wrap; white-space: pre-wrap;
} }

View File

@ -1,4 +1,4 @@
import {Component} from '@angular/core'; import {AfterViewInit, Component, ElementRef, OnDestroy, Renderer2} from '@angular/core';
import {DomSanitizer, SafeHtml} from '@angular/platform-browser'; import {DomSanitizer, SafeHtml} from '@angular/platform-browser';
import {MatFormFieldModule} from '@angular/material/form-field'; import {MatFormFieldModule} from '@angular/material/form-field';
import {MatInputModule} from '@angular/material/input'; import {MatInputModule} from '@angular/material/input';
@ -31,7 +31,7 @@ interface Part {
MatButtonModule, MatButtonModule,
] ]
}) })
export class NickGeneratorComponent { export class NickGeneratorComponent implements AfterViewInit, OnDestroy {
parts: Part[] = [ parts: Part[] = [
{text: '', gradient: false, colorA: '#ffffff', colorB: '#ffffff', continuation: false} {text: '', gradient: false, colorA: '#ffffff', colorB: '#ffffff', continuation: false}
]; ];
@ -41,8 +41,33 @@ export class NickGeneratorComponent {
previewHtml: SafeHtml = ''; previewHtml: SafeHtml = '';
showPreview = false; showPreview = false;
showCommands = false; showCommands = false;
private handleResize: any;
private boundHandleResize: any;
private resizeObserver: ResizeObserver | null = null;
constructor(private sanitizer: DomSanitizer) {
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 { addPart(): void {
@ -223,4 +248,35 @@ export class NickGeneratorComponent {
.replace(/</g, '&lt;') .replace(/</g, '&lt;')
.replace(/>/g, '&gt;'); .replace(/>/g, '&gt;');
} }
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);
}
}
} }