Compare commits
3
Commits
0801ed3c70
...
4605fd7ee4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4605fd7ee4 | ||
|
|
be48e8ef89 | ||
|
|
02f936b345 |
No files matched your search
@@ -1,12 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
afterNextRender,
|
afterNextRender, ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
effect,
|
effect,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
inject,
|
inject,
|
||||||
NgZone,
|
NgZone,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
ViewChild,
|
viewChild,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { SettingsService } from '../../services/settings.service';
|
import { SettingsService } from '../../services/settings.service';
|
||||||
@@ -17,9 +17,10 @@ import { SettingsService } from '../../services/settings.service';
|
|||||||
imports: [CommonModule],
|
imports: [CommonModule],
|
||||||
templateUrl: './brand-accent.html',
|
templateUrl: './brand-accent.html',
|
||||||
styleUrl: './brand-accent.scss',
|
styleUrl: './brand-accent.scss',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class BrandAccent implements OnDestroy {
|
export class BrandAccent implements OnDestroy {
|
||||||
@ViewChild('canvasElement') canvasRef!: ElementRef<HTMLCanvasElement>;
|
canvasRef = viewChild<ElementRef<HTMLCanvasElement>>('canvasElement');
|
||||||
private ctx!: CanvasRenderingContext2D;
|
private ctx!: CanvasRenderingContext2D;
|
||||||
private settingsService = inject(SettingsService);
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
@@ -27,8 +28,8 @@ export class BrandAccent implements OnDestroy {
|
|||||||
private bgGradient!: CanvasGradient;
|
private bgGradient!: CanvasGradient;
|
||||||
private gridGradient!: CanvasGradient;
|
private gridGradient!: CanvasGradient;
|
||||||
|
|
||||||
private offscreenCanvas!: OffscreenCanvas;
|
private readonly offscreenCanvas!: OffscreenCanvas;
|
||||||
private offscreenCtx!: OffscreenCanvasRenderingContext2D;
|
private readonly offscreenCtx!: OffscreenCanvasRenderingContext2D;
|
||||||
|
|
||||||
private resizeObserver?: ResizeObserver;
|
private resizeObserver?: ResizeObserver;
|
||||||
private intersectionObserver?: IntersectionObserver;
|
private intersectionObserver?: IntersectionObserver;
|
||||||
@@ -68,7 +69,10 @@ export class BrandAccent implements OnDestroy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterNextRender(() => {
|
afterNextRender(() => {
|
||||||
this.ctx = this.canvasRef.nativeElement.getContext('2d', { colorSpace: 'srgb' })!;
|
const nativeElement = this.canvasRef()?.nativeElement;
|
||||||
|
|
||||||
|
if (nativeElement) {
|
||||||
|
this.ctx = nativeElement.getContext('2d', { colorSpace: 'srgb' })!;
|
||||||
|
|
||||||
this.ngZone.runOutsideAngular(() => {
|
this.ngZone.runOutsideAngular(() => {
|
||||||
this.resizeObserver = new ResizeObserver((entries) => {
|
this.resizeObserver = new ResizeObserver((entries) => {
|
||||||
@@ -77,7 +81,7 @@ export class BrandAccent implements OnDestroy {
|
|||||||
this.resize(width, height);
|
this.resize(width, height);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.resizeObserver.observe(this.canvasRef.nativeElement);
|
this.resizeObserver.observe(nativeElement);
|
||||||
|
|
||||||
this.intersectionObserver = new IntersectionObserver(
|
this.intersectionObserver = new IntersectionObserver(
|
||||||
(entries) => {
|
(entries) => {
|
||||||
@@ -90,22 +94,23 @@ export class BrandAccent implements OnDestroy {
|
|||||||
},
|
},
|
||||||
{ rootMargin: '200px' },
|
{ rootMargin: '200px' },
|
||||||
);
|
);
|
||||||
this.intersectionObserver.observe(this.canvasRef.nativeElement);
|
this.intersectionObserver.observe(nativeElement);
|
||||||
|
|
||||||
// Safe Initialization
|
// Safe Initialization
|
||||||
const rect = this.canvasRef.nativeElement.getBoundingClientRect();
|
const rect = nativeElement.getBoundingClientRect();
|
||||||
this.resize(rect.width, rect.height);
|
this.resize(rect.width, rect.height);
|
||||||
|
|
||||||
this.animation.startTime = performance.now();
|
this.animation.startTime = performance.now();
|
||||||
|
|
||||||
this.draw();
|
this.draw();
|
||||||
// this.animate(performance.now());
|
// this.animate(performance.now());
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private resize(width: number, height: number) {
|
private resize(width: number, height: number) {
|
||||||
const canvas = this.canvasRef.nativeElement;
|
const canvas = this.canvasRef()?.nativeElement;
|
||||||
|
if (canvas) {
|
||||||
const dpr = window.devicePixelRatio || 1;
|
const dpr = window.devicePixelRatio || 1;
|
||||||
|
|
||||||
this.rendererScale = dpr;
|
this.rendererScale = dpr;
|
||||||
@@ -118,19 +123,15 @@ export class BrandAccent implements OnDestroy {
|
|||||||
this.offscreenCanvas.height = height;
|
this.offscreenCanvas.height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.canvasNativeSize = [width, height];
|
||||||
|
|
||||||
this.ctx.scale(dpr, dpr);
|
this.ctx.scale(dpr, dpr);
|
||||||
this.updateAnimationInfo();
|
this.updateAnimationInfo();
|
||||||
|
this.animate(performance.now());
|
||||||
this.update(performance.now());
|
}
|
||||||
this.draw();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateAnimationInfo() {
|
private updateAnimationInfo() {
|
||||||
this.canvasNativeSize = [
|
|
||||||
this.canvasRef.nativeElement.clientWidth,
|
|
||||||
this.canvasRef.nativeElement.clientHeight,
|
|
||||||
];
|
|
||||||
|
|
||||||
this.animation.gridDimension = [
|
this.animation.gridDimension = [
|
||||||
Math.trunc(this.canvasNativeSize[0] / this.animation.gridSquareSize) + 3,
|
Math.trunc(this.canvasNativeSize[0] / this.animation.gridSquareSize) + 3,
|
||||||
Math.trunc(this.canvasNativeSize[1] / this.animation.gridSquareSize) + 3,
|
Math.trunc(this.canvasNativeSize[1] / this.animation.gridSquareSize) + 3,
|
||||||
@@ -209,14 +210,6 @@ export class BrandAccent implements OnDestroy {
|
|||||||
private animate = (currentTime: number) => {
|
private animate = (currentTime: number) => {
|
||||||
this.animation.id = requestAnimationFrame(this.animate);
|
this.animation.id = requestAnimationFrame(this.animate);
|
||||||
|
|
||||||
const canvas = this.canvasRef.nativeElement;
|
|
||||||
if (
|
|
||||||
canvas.clientWidth !== this.canvasNativeSize[0] ||
|
|
||||||
canvas.clientHeight !== this.canvasNativeSize[1]
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.update(currentTime);
|
this.update(currentTime);
|
||||||
this.draw();
|
this.draw();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<div class="menu-left">
|
<div class="menu-left">
|
||||||
<a class="brand" [routerLink]="['/']">
|
<a class="brand" [routerLink]="['/']">
|
||||||
<img [src]="settings.darkMode() ? '/logo.svg' : '/logo_inverted.svg'" alt="logo" height="20" width="59.52" />
|
<img [src]="settings.darkMode() ? '/logo.svg' : '/logo_inverted.svg'" alt="logo" height="20" width="59.52" />
|
||||||
<span><strong>ADOFAI Editor Forum</strong></span>
|
<span #brandText [class.hidden]="!hasExtraSpace()">
|
||||||
|
<strong>ADOFAI Editor Forum</strong>
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<span class="menu-item dummy-link">{{ translation.menu.levels }}</span>
|
<span class="menu-item dummy-link">{{ translation.menu.levels }}</span>
|
||||||
@@ -10,7 +12,7 @@
|
|||||||
<span class="menu-item dummy-link">{{ translation.menu.about }}</span>
|
<span class="menu-item dummy-link">{{ translation.menu.about }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="menu-right">
|
<div class="menu-right" #menuRight>
|
||||||
<a class="menu-item nav-link" [routerLink]="['/known-issues']">{{ translation.menu.knownIssues }}</a>
|
<a class="menu-item nav-link" [routerLink]="['/known-issues']">{{ translation.menu.knownIssues }}</a>
|
||||||
<span class="menu-item dummy-link"><strong>{{ translation.menu.login }}</strong></span>
|
<span class="menu-item dummy-link"><strong>{{ translation.menu.login }}</strong></span>
|
||||||
<span class="menu-item dummy-link">{{ translation.menu.signup }}</span>
|
<span class="menu-item dummy-link">{{ translation.menu.signup }}</span>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
gap: 30px;
|
gap: 0;
|
||||||
|
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
@@ -112,8 +112,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
/*text-box-trim: trim-both;
|
display: inline-block;
|
||||||
text-box-edge: cap alphabetic;*/
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,14 @@
|
|||||||
import { Component, inject } from '@angular/core';
|
import {
|
||||||
|
afterNextRender,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
inject,
|
||||||
|
NgZone,
|
||||||
|
OnDestroy,
|
||||||
|
signal,
|
||||||
|
viewChild,
|
||||||
|
} from '@angular/core';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
import { PopupService } from '../../services/popup.service';
|
import { PopupService } from '../../services/popup.service';
|
||||||
import { SettingsPopup } from '../settings/settings';
|
import { SettingsPopup } from '../settings/settings';
|
||||||
@@ -10,12 +20,104 @@ import { SettingsService } from '../../services/settings.service';
|
|||||||
imports: [RouterLink],
|
imports: [RouterLink],
|
||||||
templateUrl: './header-menu.html',
|
templateUrl: './header-menu.html',
|
||||||
styleUrl: './header-menu.scss',
|
styleUrl: './header-menu.scss',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class HeaderMenu {
|
export class HeaderMenu implements OnDestroy {
|
||||||
private popupService = inject(PopupService);
|
private popupService = inject(PopupService);
|
||||||
protected translation = inject(TranslationService).translation;
|
protected translation = inject(TranslationService).translation;
|
||||||
protected settings = inject(SettingsService);
|
protected settings = inject(SettingsService);
|
||||||
|
|
||||||
|
menuRight = viewChild<ElementRef<HTMLElement>>('menuRight');
|
||||||
|
brandText = viewChild<ElementRef<HTMLElement>>('brandText');
|
||||||
|
|
||||||
|
hasExtraSpace = signal(true);
|
||||||
|
|
||||||
|
private menuResizeObserver: ResizeObserver | null = null;
|
||||||
|
private textResizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
private childWidths = new Map<Element, number>();
|
||||||
|
private cachedTextWidth = 0;
|
||||||
|
private cachedRightMenuContentWidth = 0;
|
||||||
|
|
||||||
|
constructor(private ngZone: NgZone) {
|
||||||
|
afterNextRender(() => {
|
||||||
|
const rightElement = this.menuRight()?.nativeElement;
|
||||||
|
const textElement = this.brandText()?.nativeElement;
|
||||||
|
if (!rightElement || !textElement) return;
|
||||||
|
|
||||||
|
this.ngZone.runOutsideAngular(() => {
|
||||||
|
// 1. Right Menu & Children Observer
|
||||||
|
this.menuResizeObserver = new ResizeObserver((entries) => {
|
||||||
|
let containerWidth = -1;
|
||||||
|
let contentChanged = false;
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.target === rightElement) {
|
||||||
|
containerWidth = entry.borderBoxSize[0].inlineSize;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = entry.borderBoxSize[0].inlineSize;
|
||||||
|
if (this.childWidths.get(entry.target) !== width) {
|
||||||
|
this.childWidths.set(entry.target, width);
|
||||||
|
contentChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentChanged) this.updateCachedRightMenuContentWidth();
|
||||||
|
if (containerWidth !== -1) this.resize(containerWidth);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.menuResizeObserver.observe(rightElement);
|
||||||
|
Array.from(rightElement.children).forEach((child) => {
|
||||||
|
this.childWidths.set(child, (child as HTMLElement).offsetWidth);
|
||||||
|
this.menuResizeObserver?.observe(child);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Brand Text Observer
|
||||||
|
this.textResizeObserver = new ResizeObserver((entries) => {
|
||||||
|
const width = entries[0].borderBoxSize[0].inlineSize;
|
||||||
|
if (width > 0) this.cachedTextWidth = width;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.textResizeObserver.observe(textElement);
|
||||||
|
|
||||||
|
// Initial calculation
|
||||||
|
this.cachedTextWidth = textElement.offsetWidth;
|
||||||
|
this.updateCachedRightMenuContentWidth();
|
||||||
|
this.resize(rightElement.offsetWidth);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.menuResizeObserver?.disconnect();
|
||||||
|
this.textResizeObserver?.disconnect();
|
||||||
|
this.childWidths.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateCachedRightMenuContentWidth() {
|
||||||
|
const gap = 48;
|
||||||
|
const widths = Array.from(this.childWidths.values());
|
||||||
|
if (widths.length === 0) {
|
||||||
|
this.cachedRightMenuContentWidth = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cachedRightMenuContentWidth =
|
||||||
|
widths.reduce((acc, w) => acc + w, 0) + (widths.length - 1) * gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private resize(availableWidth: number) {
|
||||||
|
const hadSpace = this.hasExtraSpace();
|
||||||
|
|
||||||
|
const buffer = 20 * 2 + 24; // Internal Padding + Logical Gap
|
||||||
|
const threshold = hadSpace ? 0 : this.cachedTextWidth;
|
||||||
|
const hasSpace = availableWidth > this.cachedRightMenuContentWidth + threshold + buffer;
|
||||||
|
|
||||||
|
if (hadSpace !== hasSpace) this.ngZone.run(() => this.hasExtraSpace.set(hasSpace));
|
||||||
|
}
|
||||||
|
|
||||||
onSettingsClick() {
|
onSettingsClick() {
|
||||||
this.popupService.open(SettingsPopup, { position: { top: '84px', right: '20px' } });
|
this.popupService.open(SettingsPopup, { position: { top: '84px', right: '20px' } });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user