diff --git a/eslint.config.js b/eslint.config.js index 2d0de58..7c67f09 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -18,8 +18,6 @@ module.exports = defineConfig([ "@typescript-eslint/no-inferrable-types": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/prefer-for-of": "off", - // ng update set ChangeDetectionStrategy.Eager everywhere; OnPush is a separate refactor. - "@angular-eslint/prefer-on-push-component-change-detection": "off", "@angular-eslint/directive-selector": [ "error", { diff --git a/src/app/layout/app/app.component.ts b/src/app/layout/app/app.component.ts index 03d4fb5..b95bee8 100644 --- a/src/app/layout/app/app.component.ts +++ b/src/app/layout/app/app.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, ChangeDetectionStrategy } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { RouterLink, RouterOutlet } from '@angular/router'; import {TopbarComponent} from '../topbar/topbar.component'; import {TranslatePipe} from '@ngx-translate/core'; @@ -10,7 +10,6 @@ import {SeoService} from '../../service/seo.service'; selector: 'app-root', imports: [RouterLink, RouterOutlet, TopbarComponent, TranslatePipe, ParticleBackgroundComponent], templateUrl: './app.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './app.component.scss' }) export class AppComponent { diff --git a/src/app/layout/imageDialog/image.component.ts b/src/app/layout/imageDialog/image.component.ts deleted file mode 100644 index 9e2151f..0000000 --- a/src/app/layout/imageDialog/image.component.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Component, inject, ChangeDetectionStrategy } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; -import { MatButtonModule } from '@angular/material/button'; -import { MatIconModule } from '@angular/material/icon'; - -@Component({ - imports: [MatDialogModule, MatButtonModule, MatIconModule], - template: ` -
-
-
{{ data.title }}
- -
- -
- -
-
- `, - changeDetection: ChangeDetectionStrategy.Eager, - styles: [` - .dialog { - display: flex; - flex-direction: column; - height: 100%; - } - - .topbar { - flex: 0 0 auto; - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; - - padding: 14px 16px; - } - - .title { - font-weight: 600; - line-height: 1.2; - max-width: calc(100% - 56px); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .stage { - flex: 1 1 auto; - padding: 0 16px 16px; - min-height: 0; - display: grid; - place-items: center; - } - - .full { - width: 100%; - height: 100%; - object-fit: contain; - border-radius: 12px; - } - `], -}) -export class ImageDialogComponent { - data = inject<{ - title: string; - src: string; -}>(MAT_DIALOG_DATA); - - -} diff --git a/src/app/layout/topbar/topbar.component.ts b/src/app/layout/topbar/topbar.component.ts index c1ee1dd..fa2e65b 100644 --- a/src/app/layout/topbar/topbar.component.ts +++ b/src/app/layout/topbar/topbar.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, inject, ChangeDetectionStrategy } from '@angular/core'; +import { Component, computed, inject } from '@angular/core'; import { RouterLink, RouterLinkActive } from '@angular/router'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatIconModule } from '@angular/material/icon'; @@ -21,7 +21,6 @@ import {RouterConstants} from '../../constants/RouterConstants'; TranslatePipe, MatDivider, NgOptimizedImage ], templateUrl: './topbar.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './topbar.component.scss' }) export class TopbarComponent { diff --git a/src/app/pages/about/about.component.ts b/src/app/pages/about/about.component.ts index b9ba5f8..2b1c004 100644 --- a/src/app/pages/about/about.component.ts +++ b/src/app/pages/about/about.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, signal, ChangeDetectionStrategy } from '@angular/core'; +import { Component, computed, signal } from '@angular/core'; import { NgOptimizedImage } from '@angular/common'; import { MatCardModule } from '@angular/material/card'; import { MatChipsModule } from '@angular/material/chips'; @@ -37,7 +37,6 @@ interface SkillGroup { RouterLink ], templateUrl: './about.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './about.component.scss' }) export class AboutComponent { diff --git a/src/app/pages/algorithms/algorithms.component.ts b/src/app/pages/algorithms/algorithms.component.ts index 8895c49..7ebb2a4 100644 --- a/src/app/pages/algorithms/algorithms.component.ts +++ b/src/app/pages/algorithms/algorithms.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, ChangeDetectionStrategy } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { AlgorithmsService } from './algorithms.service'; import { AlgorithmCategory } from './algorithm-category'; import { RouterLink } from '@angular/router'; @@ -10,7 +10,6 @@ import {TranslatePipe} from '@ngx-translate/core'; selector: 'app-algorithms', templateUrl: './algorithms.component.html', styleUrl: './algorithms.component.scss', - changeDetection: ChangeDetectionStrategy.Eager, imports: [RouterLink, MatCardModule, MatIconModule, TranslatePipe], }) export class AlgorithmsComponent { diff --git a/src/app/pages/algorithms/cloth/cloth.component.html b/src/app/pages/algorithms/cloth/cloth.component.html index 026005a..cbc11a7 100644 --- a/src/app/pages/algorithms/cloth/cloth.component.html +++ b/src/app/pages/algorithms/cloth/cloth.component.html @@ -7,17 +7,17 @@
- {{ 'CLOTH.ELONGATION' | translate }}: {{ elongation }} + {{ 'CLOTH.ELONGATION' | translate }}: {{ elongation() }} diff --git a/src/app/pages/algorithms/cloth/cloth.component.ts b/src/app/pages/algorithms/cloth/cloth.component.ts index 9f15e77..a248981 100644 --- a/src/app/pages/algorithms/cloth/cloth.component.ts +++ b/src/app/pages/algorithms/cloth/cloth.component.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; import {MatSliderModule} from '@angular/material/slider'; @@ -28,7 +28,6 @@ import {ClothCpuStrategy} from './strategies/cloth-cpu.strategy'; Information ], templateUrl: './cloth.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './cloth.component.scss', }) export class ClothComponent { @@ -36,10 +35,10 @@ export class ClothComponent { private simulationTime: number = 0; private strategy: ClothSimulationStrategy | null = null; - public isWindActive: boolean = false; - public isOutlineActive: boolean = false; + public readonly isWindActive = signal(false); + public readonly isOutlineActive = signal(false); public stiffness: number = 80; - public elongation: number = 1.0; + public readonly elongation = signal(1.0); public renderConfig: RenderConfig = { mode: '3D', @@ -85,16 +84,16 @@ export class ClothComponent { } public toggleWind(): void { - this.isWindActive = !this.isWindActive; + this.isWindActive.update(v => !v); } public toggleMesh(): void { - this.isOutlineActive = !this.isOutlineActive; + this.isOutlineActive.update(v => !v); const mesh = this.strategy?.getMesh(); if (!mesh?.material) { return; } - mesh.material.wireframe = this.isOutlineActive; + mesh.material.wireframe = this.isOutlineActive(); } public restartSimulation(): void { @@ -130,8 +129,8 @@ export class ClothComponent { if (this.strategy) { this.strategy.updateParams({ stiffness: this.stiffness, - elongation: this.elongation, - isWindActive: this.isWindActive, + elongation: this.elongation(), + isWindActive: this.isWindActive(), simulationTime: this.simulationTime, deltaTime: engine.getDeltaTime() / 1000.0 }); diff --git a/src/app/pages/algorithms/conway-gol/conway-gol.component.html b/src/app/pages/algorithms/conway-gol/conway-gol.component.html index 2cab315..dae6f6d 100644 --- a/src/app/pages/algorithms/conway-gol/conway-gol.component.html +++ b/src/app/pages/algorithms/conway-gol/conway-gol.component.html @@ -33,7 +33,7 @@ play_arrow {{ 'GOL.START' | translate }} } -

{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime }} ms

+

{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime() }} ms

@@ -44,7 +44,7 @@ [(ngModel)]="gridRows" [min]="MIN_GRID_SIZE" [max]="MAX_GRID_SIZE" - (ngModelChange)="pauseGame(); genericGridComponent.gridRows = gridRows; genericGridComponent.applyGridSize()" + (ngModelChange)="applyGridRows($event)" /> @@ -55,7 +55,7 @@ [(ngModel)]="gridCols" [min]="MIN_GRID_SIZE" [max]="MAX_GRID_SIZE" - (ngModelChange)="pauseGame(); genericGridComponent.gridCols = gridCols; genericGridComponent.applyGridSize()" + (ngModelChange)="applyGridCols($event)" /> @@ -78,8 +78,8 @@
= 0 && r < this.gridRows && c >= 0 && c < this.gridCols) { + if (r >= 0 && r < this.gridRows() && c >= 0 && c < this.gridCols()) { this.readGrid[r][c].alive = true; } } diff --git a/src/app/pages/algorithms/four-color/four-color.component.html b/src/app/pages/algorithms/four-color/four-color.component.html index 05a3851..16c3aae 100644 --- a/src/app/pages/algorithms/four-color/four-color.component.html +++ b/src/app/pages/algorithms/four-color/four-color.component.html @@ -49,13 +49,13 @@
{{ 'FOUR_COLOR.STATUS.LABEL' | translate }}: - {{ 'FOUR_COLOR.STATUS.' + solutionStatus | translate }} + {{ 'FOUR_COLOR.STATUS.' + solutionStatus() | translate }}
diff --git a/src/app/pages/algorithms/four-color/four-color.component.ts b/src/app/pages/algorithms/four-color/four-color.component.ts index bbedce1..a0676e1 100644 --- a/src/app/pages/algorithms/four-color/four-color.component.ts +++ b/src/app/pages/algorithms/four-color/four-color.component.ts @@ -1,4 +1,4 @@ -import {AfterViewInit, Component, ElementRef, inject, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {AfterViewInit, Component, ElementRef, inject, signal, ViewChild} from '@angular/core'; import {CommonModule} from '@angular/common'; import {FormsModule} from '@angular/forms'; import {MatButtonModule} from '@angular/material/button'; @@ -29,7 +29,6 @@ import {UrlConstants} from '../../../constants/UrlConstants'; Information ], templateUrl: './four-color.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './four-color.component.scss' }) export class FourColorComponent implements AfterViewInit { @@ -59,12 +58,12 @@ export class FourColorComponent implements AfterViewInit { ] }; - gridRows = DEFAULT_GRID_ROWS; - gridCols = DEFAULT_GRID_COLS; + readonly gridRows = signal(DEFAULT_GRID_ROWS); + readonly gridCols = signal(DEFAULT_GRID_COLS); grid: FourColorNode[][] = []; regions: Region[] = []; executionTime = 0; - solutionStatus: 'INCOMPLETE' | 'SOLVED' | 'CONFLICTS' | 'INVALID' = 'INCOMPLETE'; + readonly solutionStatus = signal<'INCOMPLETE' | 'SOLVED' | 'CONFLICTS' | 'INVALID'>('INCOMPLETE'); @ViewChild('fourColorCanvas') canvasRef!: ElementRef; private ctx!: CanvasRenderingContext2D; @@ -76,20 +75,18 @@ export class FourColorComponent implements AfterViewInit { } applyGridSize(): void { - if (this.gridRows < MIN_GRID_SIZE) this.gridRows = MIN_GRID_SIZE; - if (this.gridRows > MAX_GRID_SIZE) this.gridRows = MAX_GRID_SIZE; - if (this.gridCols < MIN_GRID_SIZE) this.gridCols = MIN_GRID_SIZE; - if (this.gridCols > MAX_GRID_SIZE) this.gridCols = MAX_GRID_SIZE; + this.gridRows.set(Math.min(Math.max(this.gridRows(), MIN_GRID_SIZE), MAX_GRID_SIZE)); + this.gridCols.set(Math.min(Math.max(this.gridCols(), MIN_GRID_SIZE), MAX_GRID_SIZE)); this.initializeGrid(); } initializeGrid(): void { this.grid = []; - this.solutionStatus = 'INCOMPLETE'; - for (let r = 0; r < this.gridRows; r++) { + this.solutionStatus.set('INCOMPLETE'); + for (let r = 0; r < this.gridRows(); r++) { const row: FourColorNode[] = []; - for (let c = 0; c < this.gridCols; c++) { + for (let c = 0; c < this.gridCols(); c++) { row.push({ row: r, col: c, @@ -107,15 +104,15 @@ export class FourColorComponent implements AfterViewInit { private resizeCanvas(): void { const canvas = this.canvasRef.nativeElement; - const maxDim = Math.max(this.gridRows, this.gridCols); + const maxDim = Math.max(this.gridRows(), this.gridCols()); this.nodeSize = Math.floor(MAX_GRID_PX / maxDim); - canvas.width = this.gridCols * this.nodeSize; - canvas.height = this.gridRows * this.nodeSize; + canvas.width = this.gridCols() * this.nodeSize; + canvas.height = this.gridRows() * this.nodeSize; } generateRegions(): void { - const numRegions = Math.floor((this.gridRows * this.gridCols) / 30); + const numRegions = Math.floor((this.gridRows() * this.gridCols()) / 30); this.regions = []; const seeds = this.determineSeeds(numRegions); this.regionGrowth(seeds); @@ -123,8 +120,8 @@ export class FourColorComponent implements AfterViewInit { } private determineAdjacency() { - for (let row = 0; row < this.gridRows; row++) { - for (let col = 0; col < this.gridCols; col++) { + for (let row = 0; row < this.gridRows(); row++) { + for (let col = 0; col < this.gridCols(); col++) { const currentRegionId = this.grid[row][col].regionId; const neighbors = this.getNeighbors(row, col); for (const neighbor of neighbors) { @@ -157,11 +154,11 @@ export class FourColorComponent implements AfterViewInit { private determineSeeds(numRegions: number) { const seeds: GridPos[] = []; for (let i = 0; i < numRegions; i++) { - let r = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1); - let c = SharedFunctions.randomIntFromInterval(0, this.gridCols - 1); + let r = SharedFunctions.randomIntFromInterval(0, this.gridRows() - 1); + let c = SharedFunctions.randomIntFromInterval(0, this.gridCols() - 1); while (this.grid[r][c].regionId !== -1) { - r = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1); - c = SharedFunctions.randomIntFromInterval(0, this.gridCols - 1); + r = SharedFunctions.randomIntFromInterval(0, this.gridRows() - 1); + c = SharedFunctions.randomIntFromInterval(0, this.gridCols() - 1); } this.grid[r][c].regionId = i; seeds.push({row: r, col: c}); @@ -173,9 +170,9 @@ export class FourColorComponent implements AfterViewInit { private getNeighbors(row: number, col: number): GridPos[] { const res: GridPos[] = []; if (row > 0) res.push({row: row - 1, col}); - if (row < this.gridRows - 1) res.push({row: row + 1, col}); + if (row < this.gridRows() - 1) res.push({row: row + 1, col}); if (col > 0) res.push({row, col: col - 1}); - if (col < this.gridCols - 1) res.push({row, col: col + 1}); + if (col < this.gridCols() - 1) res.push({row, col: col + 1}); return res; } @@ -185,8 +182,8 @@ export class FourColorComponent implements AfterViewInit { this.ctx.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); // 1. Draw Cell Backgrounds - for (let r = 0; r < this.gridRows; r++) { - for (let c = 0; c < this.gridCols; c++) { + for (let r = 0; r < this.gridRows(); r++) { + for (let c = 0; c < this.gridCols(); c++) { const node = this.grid[r][c]; this.ctx.fillStyle = this.getNodeColor(node); this.ctx.fillRect(c * this.nodeSize, r * this.nodeSize, this.nodeSize, this.nodeSize); @@ -197,18 +194,18 @@ export class FourColorComponent implements AfterViewInit { this.ctx.strokeStyle = '#000'; this.ctx.lineWidth = 2; this.ctx.beginPath(); - for (let r = 0; r < this.gridRows; r++) { - for (let c = 0; c < this.gridCols; c++) { + for (let r = 0; r < this.gridRows(); r++) { + for (let c = 0; c < this.gridCols(); c++) { const currentRegion = this.grid[r][c].regionId; // Right border - if (c < this.gridCols - 1 && this.grid[r][c+1].regionId !== currentRegion) { + if (c < this.gridCols() - 1 && this.grid[r][c+1].regionId !== currentRegion) { this.ctx.moveTo((c + 1) * this.nodeSize, r * this.nodeSize); this.ctx.lineTo((c + 1) * this.nodeSize, (r + 1) * this.nodeSize); } // Bottom border - if (r < this.gridRows - 1 && this.grid[r+1][c].regionId !== currentRegion) { + if (r < this.gridRows() - 1 && this.grid[r+1][c].regionId !== currentRegion) { this.ctx.moveTo(c * this.nodeSize, (r + 1) * this.nodeSize); this.ctx.lineTo((c + 1) * this.nodeSize, (r + 1) * this.nodeSize); } @@ -219,7 +216,7 @@ export class FourColorComponent implements AfterViewInit { // 3. Draw Outer Border this.ctx.strokeStyle = '#000'; this.ctx.lineWidth = 2; - this.ctx.strokeRect(0, 0, this.gridCols * this.nodeSize, this.gridRows * this.nodeSize); + this.ctx.strokeRect(0, 0, this.gridCols() * this.nodeSize, this.gridRows() * this.nodeSize); } private getNodeColor(node: FourColorNode): string { @@ -260,10 +257,10 @@ export class FourColorComponent implements AfterViewInit { const x = event.clientX - rect.left; const y = event.clientY - rect.top; - const col = Math.floor(x / (rect.width / this.gridCols)); - const row = Math.floor(y / (rect.height / this.gridRows)); + const col = Math.floor(x / (rect.width / this.gridCols())); + const row = Math.floor(y / (rect.height / this.gridRows())); - if (row >= 0 && row < this.gridRows && col >= 0 && col < this.gridCols) { + if (row >= 0 && row < this.gridRows() && col >= 0 && col < this.gridCols()) { return {row, col}; } return null; @@ -283,8 +280,8 @@ export class FourColorComponent implements AfterViewInit { } private updateRegionColors(region: Region): void { - for (let row = 0; row < this.gridRows; row++) { - for (let col = 0; col < this.gridCols; col++) { + for (let row = 0; row < this.gridRows(); row++) { + for (let col = 0; col < this.gridCols(); col++) { if (this.grid[row][col].regionId === region.id) { this.grid[row][col].color = region.color; } @@ -297,7 +294,7 @@ export class FourColorComponent implements AfterViewInit { region.color = 0; this.updateRegionColors(region); } - this.solutionStatus = 'INCOMPLETE'; + this.solutionStatus.set('INCOMPLETE'); this.drawGrid(); } @@ -363,9 +360,9 @@ export class FourColorComponent implements AfterViewInit { } if (hasConflicts) { - this.solutionStatus = allColored ? 'INVALID' : 'CONFLICTS'; + this.solutionStatus.set(allColored ? 'INVALID' : 'CONFLICTS'); } else { - this.solutionStatus = allColored ? 'SOLVED' : 'INCOMPLETE'; + this.solutionStatus.set(allColored ? 'SOLVED' : 'INCOMPLETE'); } } diff --git a/src/app/pages/algorithms/fractal/fractal.component.ts b/src/app/pages/algorithms/fractal/fractal.component.ts index 8008d43..c1c6707 100644 --- a/src/app/pages/algorithms/fractal/fractal.component.ts +++ b/src/app/pages/algorithms/fractal/fractal.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, OnInit, signal} from '@angular/core'; import {Information} from '../information/information'; import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; import {TranslatePipe} from '@ngx-translate/core'; @@ -35,7 +35,6 @@ import {NgxSliderModule, Options} from '@angular-slider/ngx-slider'; NgxSliderModule ], templateUrl: './fractal.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './fractal.component.scss', }) export class FractalComponent implements OnInit { diff --git a/src/app/pages/algorithms/fractal3d/fractal3d.component.ts b/src/app/pages/algorithms/fractal3d/fractal3d.component.ts index 30a158f..b9244b7 100644 --- a/src/app/pages/algorithms/fractal3d/fractal3d.component.ts +++ b/src/app/pages/algorithms/fractal3d/fractal3d.component.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ArcRotateCamera, Camera, ShaderMaterial} from '@babylonjs/core'; import {MANDELBULB_FRAGMENT, MANDELBULB_VERTEX} from './fractal3d.shader'; import {Information} from '../information/information'; @@ -22,7 +22,6 @@ import {BabylonCanvas, RenderCallback, RenderConfig} from '../../../shared/compo BabylonCanvas ], templateUrl: './fractal3d.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './fractal3d.component.scss', }) export class Fractal3dComponent { diff --git a/src/app/pages/algorithms/information/information.html b/src/app/pages/algorithms/information/information.html index 13125e9..51b5f2b 100644 --- a/src/app/pages/algorithms/information/information.html +++ b/src/app/pages/algorithms/information/information.html @@ -1,8 +1,9 @@ +@let info = algorithmInformation();
-

{{ algorithmInformation.title | translate }}

+

{{ info.title | translate }}

- @if(algorithmInformation.entries && algorithmInformation.entries.length > 0){ - @for (algo of algorithmInformation.entries; track algo) + @if(info.entries && info.entries.length > 0){ + @for (algo of info.entries; track algo) {

@@ -18,24 +19,24 @@ } } - @if (algorithmInformation.disclaimer !== '') + @if (info.disclaimer !== '') {

- {{ 'ALGORITHM.NOTE' | translate}} {{ algorithmInformation.disclaimer | translate}} + {{ 'ALGORITHM.NOTE' | translate}} {{ info.disclaimer | translate}}

- @if (algorithmInformation.disclaimerListEntry && algorithmInformation.disclaimerListEntry.length > 0) + @if (info.disclaimerListEntry && info.disclaimerListEntry.length > 0) {
    - @for (entry of algorithmInformation.disclaimerListEntry; track entry) + @for (entry of info.disclaimerListEntry; track entry) {
  • {{ entry | translate}}
  • }
} - @if (algorithmInformation.disclaimerBottom !== '') + @if (info.disclaimerBottom !== '') {

- {{ algorithmInformation.disclaimerBottom | translate}} + {{ info.disclaimerBottom | translate}}

} } diff --git a/src/app/pages/algorithms/information/information.ts b/src/app/pages/algorithms/information/information.ts index 9669c94..a80387d 100644 --- a/src/app/pages/algorithms/information/information.ts +++ b/src/app/pages/algorithms/information/information.ts @@ -1,4 +1,4 @@ -import {Component, Input, ChangeDetectionStrategy} from '@angular/core'; +import {Component, input} from '@angular/core'; import {TranslatePipe} from "@ngx-translate/core"; import {AlgorithmInformation} from './information.models'; @@ -8,10 +8,9 @@ import {AlgorithmInformation} from './information.models'; TranslatePipe ], templateUrl: './information.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './information.scss', }) export class Information { - @Input({ required: true }) algorithmInformation!: AlgorithmInformation; + readonly algorithmInformation = input.required(); } diff --git a/src/app/pages/algorithms/pathfinding/labyrinth/labyrinth.component.html b/src/app/pages/algorithms/pathfinding/labyrinth/labyrinth.component.html index f394cc3..3d2d88c 100644 --- a/src/app/pages/algorithms/pathfinding/labyrinth/labyrinth.component.html +++ b/src/app/pages/algorithms/pathfinding/labyrinth/labyrinth.component.html @@ -22,8 +22,8 @@ {{ 'PATHFINDING.PATH' | translate }}
-

{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength }}

-

{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime | number:'1.2-2' }} ms

+

{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength() }}

+

{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime() | number:'1.2-2' }} ms

@@ -46,7 +46,7 @@ [min]="MIN_GRID_SIZE" [max]="MAX_GRID_SIZE" [(ngModel)]="gridCols" - (ngModelChange)="genericGridComponent.gridCols = gridCols; genericGridComponent.applyGridSize()" + (ngModelChange)="applyGridCols($event)" /> @@ -59,14 +59,14 @@ {{ 'PATHFINDING.PATH' | translate }}
-

{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength }}

-

{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime | number:'1.2-2' }} ms

+

{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength() }}

+

{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime() | number:'1.2-2' }} ms

(NodeType.None); private shouldAddWall = true; // Moved here animationSpeed = 3; - pathLength = "0"; - executionTime = 0; + readonly pathLength = signal("0"); + readonly executionTime = signal(0); private timeoutIds: number[] = []; @@ -101,8 +100,8 @@ export class PathfindingComponent implements AfterViewInit { this.genericGridComponent.createNodeFn = this.createPathfindingNode; this.genericGridComponent.getNodeColorFn = this.getPathfindingNodeColor; this.genericGridComponent.applySelectionFn = this.applyPathfindingSelection; - this.genericGridComponent.gridRows = this.gridRows; - this.genericGridComponent.gridCols = this.gridCols; + this.genericGridComponent.gridRows = this.gridRows(); + this.genericGridComponent.gridCols = this.gridCols(); this.genericGridComponent.minGridSize = this.MIN_GRID_SIZE; this.genericGridComponent.maxGridSize = this.MAX_GRID_SIZE; this.genericGridComponent.maxGridPx = MAX_GRID_PX; @@ -111,6 +110,16 @@ export class PathfindingComponent implements AfterViewInit { this.createCase({withWalls: true, scenario: "normal"}); } + protected applyGridRows(rows: number): void { + this.genericGridComponent.gridRows = rows; + this.genericGridComponent.applyGridSize(); + } + + protected applyGridCols(cols: number): void { + this.genericGridComponent.gridCols = cols; + this.genericGridComponent.applyGridSize(); + } + // --- Callbacks for GenericGridComponent --- createPathfindingNode = (row: number, col: number): Node => { return { @@ -142,11 +151,11 @@ export class PathfindingComponent implements AfterViewInit { const node = grid[pos.row][pos.col]; // Determine if we should add or remove a wall - if (this.selectedNodeType === NodeType.Wall && this.genericGridComponent.isDrawing && this.genericGridComponent['lastCell'] === null) { + if (this.selectedNodeType() === NodeType.Wall && this.genericGridComponent.isDrawing && this.genericGridComponent['lastCell'] === null) { this.shouldAddWall = !node.isWall; } - switch (this.selectedNodeType) { + switch (this.selectedNodeType()) { case NodeType.Start: this.trySetStart(node); break; @@ -200,13 +209,13 @@ export class PathfindingComponent implements AfterViewInit { const lengthOfShortestPath = result.nodesInShortestPathOrder.length; if (lengthOfShortestPath === 0) { - this.pathLength = "∞" + this.pathLength.set("∞"); } else { - this.pathLength = result.nodesInShortestPathOrder.length + ""; + this.pathLength.set(result.nodesInShortestPathOrder.length + ""); } - this.executionTime = endTime - startTime; + this.executionTime.set(endTime - startTime); this.animateAlgorithm(result.visitedNodesInOrder, result.nodesInShortestPathOrder); } @@ -312,31 +321,31 @@ export class PathfindingComponent implements AfterViewInit { if (scenario === 'edge') { return { start: {row: 0, col: 0}, - end: {row: this.gridRows - 1, col: this.gridCols - 1} + end: {row: this.gridRows() - 1, col: this.gridCols() - 1} }; } else if (scenario === 'random') { return this.createRandomStartEndPosition(); } else { // normal: mid-left -> mid-right - const midRow = Math.floor(this.gridRows / 2); + const midRow = Math.floor(this.gridRows() / 2); return { start: {row: midRow, col: 0}, - end: {row: midRow, col: this.gridCols - 1} + end: {row: midRow, col: this.gridCols() - 1} }; } } private createRandomStartEndPosition(): { start: GridPos; end: GridPos } { - const midCol = Math.floor(this.gridCols / 2); + const midCol = Math.floor(this.gridCols() / 2); - const startRow: number = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1); - const startCol: number = SharedFunctions.randomIntFromInterval(0, this.gridCols - 1); + const startRow: number = SharedFunctions.randomIntFromInterval(0, this.gridRows() - 1); + const startCol: number = SharedFunctions.randomIntFromInterval(0, this.gridCols() - 1); - const endRow: number = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1); + const endRow: number = SharedFunctions.randomIntFromInterval(0, this.gridRows() - 1); let endCol: number; if (startCol <= midCol) { - endCol = SharedFunctions.randomIntFromInterval(midCol + 1, this.gridCols - 1); + endCol = SharedFunctions.randomIntFromInterval(midCol + 1, this.gridCols() - 1); } else { endCol = SharedFunctions.randomIntFromInterval(0, midCol); } @@ -358,12 +367,12 @@ export class PathfindingComponent implements AfterViewInit { } private createRandomWalls() { - const maxNumberOfWalls = Math.floor(MAX_RANDOM_WALLS_FACTORS * this.gridCols * this.gridRows); + const maxNumberOfWalls = Math.floor(MAX_RANDOM_WALLS_FACTORS * this.gridCols() * this.gridRows()); for (let wall = 0; wall < maxNumberOfWalls; wall++) { - const row: number = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1); - const col: number = SharedFunctions.randomIntFromInterval(0, this.gridCols - 1); + const row: number = SharedFunctions.randomIntFromInterval(0, this.gridRows() - 1); + const col: number = SharedFunctions.randomIntFromInterval(0, this.gridCols() - 1); if (!this.grid[row][col]) { // Use the grid passed from GenericGrid wall--; @@ -382,8 +391,8 @@ export class PathfindingComponent implements AfterViewInit { } private createVerticalWall() { - const height = this.gridRows; - const startCol = Math.floor(this.gridCols / 2); + const height = this.gridRows(); + const startCol = Math.floor(this.gridCols() / 2); for (let i = 5; i < (height - 5); i++) { const row = i; @@ -404,8 +413,8 @@ export class PathfindingComponent implements AfterViewInit { private createDiagonalWall() { // Diagonal-ish wall; avoids start/end - const len = Math.min(this.gridRows, this.gridCols); - const startCol = Math.floor((this.gridCols - len) / 2); + const len = Math.min(this.gridRows(), this.gridCols()); + const startCol = Math.floor((this.gridCols() - len) / 2); for (let i = 0; i < Math.max(0, len - 10); i++) { const row = len - i - 1; @@ -433,8 +442,8 @@ export class PathfindingComponent implements AfterViewInit { } private clearPath(): void { - for (let row = 0; row < this.gridRows; row++) { - for (let col = 0; col < this.gridCols; col++) { + for (let row = 0; row < this.gridRows(); row++) { + for (let col = 0; col < this.gridCols(); col++) { const node = this.grid[row][col]; node.isVisited = false; node.isPath = false; diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.html b/src/app/pages/algorithms/pendulum/pendulum.component.html index 055acb6..c9f42e0 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.component.html +++ b/src/app/pages/algorithms/pendulum/pendulum.component.html @@ -8,34 +8,34 @@

{{ 'PENDULUM.TRAIL_DECAY_TIME' | translate }}

- +

{{ 'PENDULUM.ATTRACTION' | translate }}

- +

{{ 'PENDULUM.L1_LENGTH' | translate }}

- +

{{ 'PENDULUM.L2_LENGTH' | translate }}

- +

{{ 'PENDULUM.M1_MASS' | translate }}

- +

{{ 'PENDULUM.M2_MASS' | translate }}

- +

{{ 'PENDULUM.DAMPING' | translate }}

- +
diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.ts b/src/app/pages/algorithms/pendulum/pendulum.component.ts index e82eb12..43d0de4 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.component.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.component.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {BabylonCanvas, RenderConfig, SceneEventData} from '../../../shared/components/render-canvas/babylon-canvas.component'; import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; @@ -29,7 +29,6 @@ import {PendulumCpuStrategy} from './strategies/pendulum-cpu.strategy'; Information, ], templateUrl: './pendulum.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './pendulum.component.scss', }) class PendulumComponent { @@ -103,16 +102,17 @@ class PendulumComponent { hidePointerLabels: false }; - readonly simParams = { + readonly trailDecay = signal(DEFAULT_TRAIL_DECAY); + readonly g = signal(DEFAULT_G); + readonly l1 = signal(DEFAULT_L1_LENGTH); + readonly l2 = signal(DEFAULT_L2_LENGTH); + readonly m1 = signal(DEFAULT_M1_MASS); + readonly m2 = signal(DEFAULT_M2_MASS); + readonly damping = signal(DEFAULT_DAMPING); + + private readonly loop = { time: 0, dt: 0.015, - g: DEFAULT_G, - m1: DEFAULT_M1_MASS, - m2: DEFAULT_M2_MASS, - l1: DEFAULT_L1_LENGTH, - l2: DEFAULT_L2_LENGTH, - damping: DEFAULT_DAMPING, - trailDecay: DEFAULT_TRAIL_DECAY, impulseM1: 0.0, impulseM2: 0.0, }; @@ -147,10 +147,19 @@ class PendulumComponent { private startParamUpdateLoop(scene: any): void { scene.onAfterRenderObservable.clear(); scene.onAfterRenderObservable.add(() => { - this.simParams.time += this.simParams.dt; + this.loop.time += this.loop.dt; if (this.strategy) { - this.strategy.updateParams({...this.simParams}); + this.strategy.updateParams({ + ...this.loop, + g: this.g(), + m1: this.m1(), + m2: this.m2(), + l1: this.l1(), + l2: this.l2(), + damping: this.damping(), + trailDecay: this.trailDecay() + }); } this.resetImpulses(); @@ -158,22 +167,22 @@ class PendulumComponent { } private resetImpulses() { - if (this.simParams.impulseM1 !== 0.0) { - this.simParams.impulseM1 = 0; + if (this.loop.impulseM1 !== 0.0) { + this.loop.impulseM1 = 0; } - if (this.simParams.impulseM2 !== 0.0) { - this.simParams.impulseM2 = 0; + if (this.loop.impulseM2 !== 0.0) { + this.loop.impulseM2 = 0; } } pushPendulum(m1: boolean) { if (m1) { - this.simParams.impulseM1 = IMPULSE_M1; + this.loop.impulseM1 = IMPULSE_M1; return; } - this.simParams.impulseM2 = IMPULSE_M2; + this.loop.impulseM2 = IMPULSE_M2; } resetPendulum() { diff --git a/src/app/pages/algorithms/sorting/sorting.component.html b/src/app/pages/algorithms/sorting/sorting.component.html index ababbbe..3b9de29 100644 --- a/src/app/pages/algorithms/sorting/sorting.component.html +++ b/src/app/pages/algorithms/sorting/sorting.component.html @@ -38,19 +38,20 @@ add_box {{ 'SORTING.GENERATE_NEW_ARRAY' | translate }}
-

{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime }} ms

+

{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime() }} ms

- @for (item of sortArray; track $index) { + @for (item of sortArray(); track $index) {
Number.parseFloat((bar.nativeElement as HTMLElement).style.height)); + } + + it('shows every bar as sorted once the animation has run', () => { + fixture.componentInstance.startSorting(); + vi.runAllTimers(); + fixture.detectChanges(); + + expect(bars()).toHaveLength(50); + expect(bars().every(bar => bar.nativeElement.classList.contains('bg-[#4caf50]'))).toBe(true); + const sorted = heights(); + expect(sorted).toEqual([...sorted].sort((a, b) => a - b)); + }); + + it('restores the unsorted array when reset during the animation', () => { + const initial = heights(); + + fixture.componentInstance.startSorting(); + vi.advanceTimersByTime(300); + fixture.componentInstance.resetSorting(); + fixture.detectChanges(); + + expect(heights()).toEqual(initial); + expect(bars().some(bar => bar.nativeElement.classList.contains('bg-[#ffeb3b]'))).toBe(false); + }); +}); diff --git a/src/app/pages/algorithms/sorting/sorting.component.ts b/src/app/pages/algorithms/sorting/sorting.component.ts index d71ded6..24d13d6 100644 --- a/src/app/pages/algorithms/sorting/sorting.component.ts +++ b/src/app/pages/algorithms/sorting/sorting.component.ts @@ -1,4 +1,4 @@ -import {ChangeDetectorRef, Component, inject, OnInit, ChangeDetectionStrategy} from '@angular/core'; +import {Component, inject, OnInit, signal} from '@angular/core'; import { CommonModule } from '@angular/common'; import {MatCardModule} from "@angular/material/card"; import {MatFormFieldModule} from "@angular/material/form-field"; @@ -18,14 +18,12 @@ import {Information} from '../information/information'; selector: 'app-sorting', imports: [CommonModule, MatCardModule, MatFormFieldModule, MatSelectModule, MatButtonModule, MatIconModule, TranslatePipe, FormsModule, MatInput, Information], templateUrl: './sorting.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './sorting.component.scss' }) export class SortingComponent implements OnInit { private readonly sortingService: SortingService = inject(SortingService); private readonly audioService: SortingAudioService = inject(SortingAudioService); - private readonly cdr: ChangeDetectorRef = inject(ChangeDetectorRef); readonly MAX_ARRAY_SIZE: number = 200; readonly MIN_ARRAY_SIZE: number = 20; @@ -69,14 +67,14 @@ export class SortingComponent implements OnInit { }; private timeoutIds: number[] = []; - sortArray: SortData[] = []; + readonly sortArray = signal([]); unsortedArrayCopy: SortData[] = []; - arraySize = 50; + readonly arraySize = signal(50); maxArrayValue = 100; animationSpeed = 100; // Milliseconds per step - selectedAlgorithm: string = this.algoInformation.entries[0].name; - executionTime = 0; - isSoundEnabled = false; + readonly selectedAlgorithm = signal(this.algoInformation.entries[0].name); + readonly executionTime = signal(0); + readonly isSoundEnabled = signal(false); ngOnInit(): void { this.generateNewArray(); @@ -84,8 +82,9 @@ export class SortingComponent implements OnInit { newArraySizeSet() { - this.arraySize = Math.min(Math.max(this.arraySize, this.MIN_ARRAY_SIZE), this.MAX_ARRAY_SIZE); - if (this.arraySize == this.sortArray.length) + const clamped = Math.min(Math.max(this.arraySize(), this.MIN_ARRAY_SIZE), this.MAX_ARRAY_SIZE); + this.arraySize.set(clamped); + if (clamped == this.sortArray().length) { return; } @@ -94,35 +93,27 @@ export class SortingComponent implements OnInit { generateNewArray(): void { this.resetSorting(); - this.executionTime = 0; - this.unsortedArrayCopy = []; - this.sortArray = []; + this.executionTime.set(0); + const unsorted: SortData[] = []; - for (let i = 0; i < this.arraySize; i++) { + for (let i = 0; i < this.arraySize(); i++) { const randomValue = Math.floor(Math.random() * this.maxArrayValue) + 1; - this.sortArray.push({ - value: randomValue, - state: 'unsorted' - }); - - this.unsortedArrayCopy.push({ + unsorted.push({ value: randomValue, state: 'unsorted' }); } + + this.unsortedArrayCopy = unsorted; + this.resetSortState(); } private resetSortState() { - for (let i = 0; i < this.sortArray.length; i++) { - const element = this.sortArray[i]; - const unsortedElement = this.unsortedArrayCopy[i]; - element.value = unsortedElement.value; - element.state = 'unsorted'; - } + this.sortArray.set(this.unsortedArrayCopy.map(item => ({...item}))); } toggleSound(): void { - this.isSoundEnabled = !this.isSoundEnabled; + this.isSoundEnabled.update(v => !v); } async startSorting(): Promise { @@ -132,26 +123,26 @@ export class SortingComponent implements OnInit { const startTime = performance.now(); let snapshots: SortSnapshot[] = []; - switch (this.selectedAlgorithm) { + switch (this.selectedAlgorithm()) { case 'Bubble Sort': - snapshots = this.sortingService.bubbleSort(this.sortArray); + snapshots = this.sortingService.bubbleSort(this.sortArray()); break; case 'Quick Sort': - snapshots = this.sortingService.quickSort(this.sortArray); + snapshots = this.sortingService.quickSort(this.sortArray()); break; case 'Heap Sort': - snapshots = this.sortingService.heapSort(this.sortArray); + snapshots = this.sortingService.heapSort(this.sortArray()); break; case 'Cocktail Sort': - snapshots = this.sortingService.cocktailSort(this.sortArray); + snapshots = this.sortingService.cocktailSort(this.sortArray()); break; case 'Tim Sort': - snapshots = this.sortingService.timSort(this.sortArray); + snapshots = this.sortingService.timSort(this.sortArray()); break; } const endTime = performance.now(); - this.executionTime = Number.parseFloat((endTime - startTime).toFixed(4)); + this.executionTime.set(Number.parseFloat((endTime - startTime).toFixed(4))); this.animateSorting(snapshots); } @@ -159,14 +150,9 @@ export class SortingComponent implements OnInit { private animateSorting(snapshots: SortSnapshot[]): void { snapshots.forEach((snapshot, index) => { const id = setTimeout(() => { - for (let i = 0; i < this.sortArray.length; i++) { - if (snapshot.array[i]) { - this.sortArray[i].value = snapshot.array[i].value; - this.sortArray[i].state = snapshot.array[i].state; - } - } + this.sortArray.set(snapshot.array); - if (this.isSoundEnabled) { + if (this.isSoundEnabled()) { // Play a tone for each comparing bar (max 2 at once to avoid noise) snapshot.array .filter(item => item.state === 'comparing') @@ -174,13 +160,10 @@ export class SortingComponent implements OnInit { .forEach(item => this.audioService.playTone(item.value, this.maxArrayValue, this.animationSpeed)); } - this.cdr.detectChanges(); - if (index === snapshots.length - 1) { - this.sortArray.forEach(item => item.state = 'sorted'); - this.cdr.detectChanges(); - if (this.isSoundEnabled) { - this.audioService.playSortedSweep(this.sortArray.map(item => item.value), this.maxArrayValue); + this.sortArray.set(snapshot.array.map(item => ({...item, state: 'sorted'}))); + if (this.isSoundEnabled()) { + this.audioService.playSortedSweep(this.sortArray().map(item => item.value), this.maxArrayValue); } } }, index * this.animationSpeed); diff --git a/src/app/pages/imprint/imprint.component.ts b/src/app/pages/imprint/imprint.component.ts index 4f35f47..c1579c7 100644 --- a/src/app/pages/imprint/imprint.component.ts +++ b/src/app/pages/imprint/imprint.component.ts @@ -1,4 +1,4 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { Component } from '@angular/core'; import {MatCard} from '@angular/material/card'; import {TranslatePipe} from '@ngx-translate/core'; import {SharedFunctions} from '../../shared/SharedFunctions'; @@ -10,7 +10,6 @@ import {SharedFunctions} from '../../shared/SharedFunctions'; TranslatePipe ], templateUrl: './imprint.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './imprint.component.scss', }) export class ImprintComponent { diff --git a/src/app/pages/not-found/not-found.component.ts b/src/app/pages/not-found/not-found.component.ts index bca38af..193475e 100644 --- a/src/app/pages/not-found/not-found.component.ts +++ b/src/app/pages/not-found/not-found.component.ts @@ -1,4 +1,4 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { Component } from '@angular/core'; import { RouterLink } from '@angular/router'; import { MatCard } from '@angular/material/card'; import { MatButton } from '@angular/material/button'; @@ -8,7 +8,6 @@ import { RouterConstants } from '../../constants/RouterConstants'; @Component({ selector: 'app-not-found', imports: [RouterLink, MatCard, MatButton, TranslatePipe], - changeDetection: ChangeDetectionStrategy.Eager, templateUrl: './not-found.component.html', }) export class NotFoundComponent { diff --git a/src/app/pages/projects/dialog/project-dialog.component.ts b/src/app/pages/projects/dialog/project-dialog.component.ts index c7c47c9..f79ca06 100644 --- a/src/app/pages/projects/dialog/project-dialog.component.ts +++ b/src/app/pages/projects/dialog/project-dialog.component.ts @@ -1,4 +1,4 @@ -import {AfterViewInit, Component, inject, CUSTOM_ELEMENTS_SCHEMA, ViewChild, ElementRef, ChangeDetectionStrategy} from '@angular/core'; +import {AfterViewInit, Component, inject, CUSTOM_ELEMENTS_SCHEMA, ViewChild, ElementRef} from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogActions, @@ -30,7 +30,6 @@ register(); MatButton, MatDialogClose ], - changeDetection: ChangeDetectionStrategy.Eager, schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class ProjectDialogComponent implements AfterViewInit { diff --git a/src/app/pages/projects/projects.component.ts b/src/app/pages/projects/projects.component.ts index 17bc785..0a81a6e 100644 --- a/src/app/pages/projects/projects.component.ts +++ b/src/app/pages/projects/projects.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, inject, CUSTOM_ELEMENTS_SCHEMA, OnDestroy, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { Component, computed, inject, CUSTOM_ELEMENTS_SCHEMA, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { Subscription } from "rxjs"; import { MatCardModule } from "@angular/material/card"; @@ -43,7 +43,6 @@ export interface Projects { ], templateUrl: './projects.component.html', styleUrl: './projects.component.scss', - changeDetection: ChangeDetectionStrategy.Eager, schemas: [CUSTOM_ELEMENTS_SCHEMA], }) export class ProjectsComponent implements OnInit, OnDestroy { diff --git a/src/app/pages/stopwatch/stopwatch.component.ts b/src/app/pages/stopwatch/stopwatch.component.ts index 7b1fd46..e3d3a38 100644 --- a/src/app/pages/stopwatch/stopwatch.component.ts +++ b/src/app/pages/stopwatch/stopwatch.component.ts @@ -1,4 +1,4 @@ -import {Component, computed, effect, inject, OnDestroy, signal, WritableSignal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, computed, effect, inject, OnDestroy, signal, WritableSignal} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatButtonModule} from '@angular/material/button'; import {MatCardModule} from '@angular/material/card'; @@ -30,7 +30,6 @@ const SECOND_INTERVAL_FREQUENCY_HZ = 587; MatCheckboxModule, TranslatePipe ], - changeDetection: ChangeDetectionStrategy.Eager, templateUrl: './stopwatch.component.html' }) export class StopwatchComponent implements OnDestroy { diff --git a/src/app/shared/components/generic-grid/generic-grid.ts b/src/app/shared/components/generic-grid/generic-grid.ts index 2aedb47..77c3c86 100644 --- a/src/app/shared/components/generic-grid/generic-grid.ts +++ b/src/app/shared/components/generic-grid/generic-grid.ts @@ -1,4 +1,4 @@ -import {AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, ViewChild} from '@angular/core'; import {CommonModule} from '@angular/common'; export interface GridPos { row: number; col: number } @@ -7,7 +7,6 @@ export interface GridPos { row: number; col: number } selector: 'app-generic-grid', imports: [CommonModule], templateUrl: './generic-grid.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './generic-grid.scss', }) export class GenericGridComponent implements AfterViewInit { diff --git a/src/app/shared/components/particles-background/particles-background.component.ts b/src/app/shared/components/particles-background/particles-background.component.ts index 957c85c..8a76159 100644 --- a/src/app/shared/components/particles-background/particles-background.component.ts +++ b/src/app/shared/components/particles-background/particles-background.component.ts @@ -1,11 +1,10 @@ -import {AfterViewInit, Component, ElementRef, HostListener, inject, NgZone, OnDestroy, PLATFORM_ID, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {AfterViewInit, Component, ElementRef, HostListener, inject, NgZone, OnDestroy, PLATFORM_ID, ViewChild} from '@angular/core'; import {isPlatformBrowser} from '@angular/common'; @Component({ selector: 'app-particles-background', imports: [], templateUrl: './particles-background.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './particles-background.component.scss', }) export class ParticleBackgroundComponent implements AfterViewInit, OnDestroy { diff --git a/src/app/shared/components/render-canvas/babylon-canvas.component.ts b/src/app/shared/components/render-canvas/babylon-canvas.component.ts index aaf8cab..362b330 100644 --- a/src/app/shared/components/render-canvas/babylon-canvas.component.ts +++ b/src/app/shared/components/render-canvas/babylon-canvas.component.ts @@ -1,4 +1,4 @@ -import {AfterViewInit, Component, ElementRef, EventEmitter, inject, Input, NgZone, OnDestroy, Output, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {AfterViewInit, Component, ElementRef, EventEmitter, inject, input, NgZone, OnDestroy, Output, ViewChild} from '@angular/core'; import {MatSnackBar} from '@angular/material/snack-bar'; import {TranslateService} from '@ngx-translate/core'; import {ArcRotateCamera, Camera, Engine, MeshBuilder, Scene, ShaderLanguage, ShaderMaterial, Vector2, Vector3, WebGPUEngine} from '@babylonjs/core'; @@ -26,7 +26,6 @@ export interface SceneEventData { selector: 'app-babylon-canvas', imports: [], templateUrl: './babylon-canvas.component.html', - changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './babylon-canvas.component.scss', }) export class BabylonCanvas implements AfterViewInit, OnDestroy { @@ -37,8 +36,8 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { @ViewChild('renderCanvas', { static: true }) canvasRef!: ElementRef; - @Input({ required: true }) config!: RenderConfig; - @Input() renderCallback?: RenderCallback; + readonly config = input.required(); + readonly renderCallback = input(); @Output() sceneReady = new EventEmitter(); @Output() sceneResized = new EventEmitter(); @@ -131,7 +130,7 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { } private setupCamera(canvas: HTMLCanvasElement) { - if (this.config.mode === '3D') { + if (this.config().mode === '3D') { this.setup3dCamera(canvas); return; } @@ -144,7 +143,7 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { cam.mode = Camera.ORTHOGRAPHIC_CAMERA; const aspect = canvas.width / canvas.height; - const viewSize = this.config?.initialViewSize ?? 10; + const viewSize = this.config().initialViewSize ?? 10; cam.orthoLeft = -viewSize * aspect / 2; cam.orthoRight = viewSize * aspect / 2; cam.orthoTop = viewSize / 2; @@ -161,17 +160,18 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { cam.maxZ = 100; cam.lowerRadiusLimit = 1.5; cam.upperRadiusLimit = 20; - cam.radius = this.config?.initialViewSize ?? 1; + cam.radius = this.config().initialViewSize ?? 1; cam.attachControl(canvas, true); this.camera = cam; } private createFullScreenRect() { - if (!this.config.vertexShader || !this.config.fragmentShader) { + const config = this.config(); + if (!config.vertexShader || !config.fragmentShader) { return; } const plane = MeshBuilder.CreatePlane("plane", {size: 100}, this.scene); - if (this.config.mode === '3D') { + if (config.mode === '3D') { plane.parent = this.camera; plane.position.z = 1; } else { @@ -183,7 +183,8 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { } private createShaderMaterial() { - if (!this.config.vertexShader || !this.config.fragmentShader || !this.config.uniformNames) { + const config = this.config(); + if (!config.vertexShader || !config.fragmentShader || !config.uniformNames) { return; } @@ -191,14 +192,14 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { "shaderMaterial", this.scene, { - vertexSource: this.config.vertexShader, - fragmentSource: this.config.fragmentShader + vertexSource: config.vertexShader, + fragmentSource: config.fragmentShader }, { attributes: ["position", "uv"], - uniforms: ["resolution", "cameraPosition", ...this.config.uniformNames], - uniformBuffers: this.config.uniformBufferNames ?? [], - shaderLanguage: this.config.shaderLanguage ?? ShaderLanguage.GLSL + uniforms: ["resolution", "cameraPosition", ...config.uniformNames], + uniformBuffers: config.uniformBufferNames ?? [], + shaderLanguage: config.shaderLanguage ?? ShaderLanguage.GLSL } ); this.shaderMaterial.disableDepthWrite = true; @@ -209,8 +210,9 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { this.engine.runRenderLoop(() => { // callback call to call specific uniforms - if (this.renderCallback) { - this.renderCallback(this.shaderMaterial, this.camera, canvas, this.scene); + const callback = this.renderCallback(); + if (callback) { + callback(this.shaderMaterial, this.camera, canvas, this.scene); } // default uniforms which maybe each scene has @@ -228,8 +230,9 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { this.engine.resize(); } - if (this.config.mode === '2D' && this.camera instanceof ArcRotateCamera) { - const viewSize = this.config?.initialViewSize ?? 10; + const config = this.config(); + if (config.mode === '2D' && this.camera instanceof ArcRotateCamera) { + const viewSize = config.initialViewSize ?? 10; this.camera.orthoLeft = -viewSize / 2; this.camera.orthoRight = viewSize / 2; this.camera.orthoTop = viewSize / 2;