From 17db9973987e4d2e8e2cd1f0ad07e244851ee0cc Mon Sep 17 00:00:00 2001 From: Lobo Date: Sun, 1 Feb 2026 16:00:10 +0100 Subject: [PATCH 1/3] Add algorithms section with pathfinding visualizer Introduces a new 'Algorithms' section, replacing the previous 'Hobbies' page. Adds components, services, and models for algorithm categories and a pathfinding visualizer supporting Dijkstra and A* algorithms. Updates navigation and i18n files to reflect the new section and removes all hobbies-related files. --- src/app/app.routes.ts | 6 +- src/app/layout/topbar/topbar.component.html | 6 +- src/app/pages/about/about.component.html | 2 +- .../algorithms/algorithms.component.html | 13 + .../algorithms/algorithms.component.scss | 19 ++ .../pages/algorithms/algorithms.component.ts | 26 ++ .../algorithms/models/algorithm-category.ts | 6 + .../pathfinding/pathfinding.component.html | 29 ++ .../pathfinding/pathfinding.component.scss | 49 +++ .../pathfinding/pathfinding.component.ts | 284 ++++++++++++++++++ .../pathfinding/pathfinding.models.ts | 16 + .../service/pathfinding.service.ts | 146 +++++++++ .../algorithms/service/algorithms.service.ts | 28 ++ src/app/pages/hobbies/hobbies.component.html | 3 - src/app/pages/hobbies/hobbies.component.scss | 19 -- src/app/pages/hobbies/hobbies.component.ts | 11 - src/assets/i18n/de.json | 18 +- src/assets/i18n/en.json | 18 +- 18 files changed, 658 insertions(+), 41 deletions(-) create mode 100644 src/app/pages/algorithms/algorithms.component.html create mode 100644 src/app/pages/algorithms/algorithms.component.scss create mode 100644 src/app/pages/algorithms/algorithms.component.ts create mode 100644 src/app/pages/algorithms/models/algorithm-category.ts create mode 100644 src/app/pages/algorithms/pathfinding/pathfinding.component.html create mode 100644 src/app/pages/algorithms/pathfinding/pathfinding.component.scss create mode 100644 src/app/pages/algorithms/pathfinding/pathfinding.component.ts create mode 100644 src/app/pages/algorithms/pathfinding/pathfinding.models.ts create mode 100644 src/app/pages/algorithms/pathfinding/service/pathfinding.service.ts create mode 100644 src/app/pages/algorithms/service/algorithms.service.ts delete mode 100644 src/app/pages/hobbies/hobbies.component.html delete mode 100644 src/app/pages/hobbies/hobbies.component.scss delete mode 100644 src/app/pages/hobbies/hobbies.component.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 79294b4..464633b 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,14 +1,16 @@ import { Routes } from '@angular/router'; import {AboutComponent} from './pages/about/about.component'; import {ProjectsComponent} from './pages/projects/projects.component'; -import {HobbiesComponent} from './pages/hobbies/hobbies.component'; import {ImprintComponent} from './pages/imprint/imprint.component'; +import {AlgorithmsComponent} from './pages/algorithms/algorithms.component'; +import {PathfindingComponent} from './pages/algorithms/pathfinding/pathfinding.component'; export const routes: Routes = [ { path: '', component: AboutComponent }, { path: 'about', component: AboutComponent}, { path: 'projects', component: ProjectsComponent}, - { path: 'hobbies', component: HobbiesComponent}, + { path: 'algorithms', component: AlgorithmsComponent}, + { path: 'algorithms/pathfinding', component: PathfindingComponent }, { path: 'imprint', component: ImprintComponent}, ]; diff --git a/src/app/layout/topbar/topbar.component.html b/src/app/layout/topbar/topbar.component.html index 1777ec1..bd00119 100644 --- a/src/app/layout/topbar/topbar.component.html +++ b/src/app/layout/topbar/topbar.component.html @@ -12,7 +12,7 @@ @@ -35,8 +35,8 @@ - + + + + + +
+ {{ 'PATHFINDING.START_NODE' | translate }} + {{ 'PATHFINDING.END_NODE' | translate }} + {{ 'PATHFINDING.WALL' | translate }} + {{ 'PATHFINDING.VISITED' | translate }} + {{ 'PATHFINDING.PATH' | translate }} +
+ + + + diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.component.scss b/src/app/pages/algorithms/pathfinding/pathfinding.component.scss new file mode 100644 index 0000000..94346e3 --- /dev/null +++ b/src/app/pages/algorithms/pathfinding/pathfinding.component.scss @@ -0,0 +1,49 @@ +.container { + padding: 2rem; +} + +.controls-container { + display: flex; + flex-direction: column; + margin-bottom: 1rem; +} + +.controls { + display: flex; + flex-wrap: wrap; + gap: 1rem; + margin-bottom: 1rem; + + mat-button-toggle-group { + border-radius: 4px; + overflow: hidden; + } +} + +.legend { + display: flex; + flex-wrap: wrap; + gap: 1rem; + align-items: center; + font-size: 0.9em; + + .legend-color { + display: inline-block; + width: 15px; + height: 15px; + border: 1px solid #ccc; + vertical-align: middle; + margin-right: 5px; + + &.start { background-color: green; } + &.end { background-color: red; } + &.wall { background-color: black; } + &.visited { background-color: skyblue; } + &.path { background-color: gold; } + } +} + +canvas { + border: 1px solid #ccc; + display: block; +} \ No newline at end of file diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.component.ts b/src/app/pages/algorithms/pathfinding/pathfinding.component.ts new file mode 100644 index 0000000..8ea27ae --- /dev/null +++ b/src/app/pages/algorithms/pathfinding/pathfinding.component.ts @@ -0,0 +1,284 @@ +import {AfterViewInit, Component, ElementRef, HostListener, ViewChild} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatButtonModule } from '@angular/material/button'; +import {GRID_COLS, GRID_ROWS, NODE_SIZE, Node} from './pathfinding.models'; +import {MatButtonToggleModule} from '@angular/material/button-toggle'; +import {FormsModule} from '@angular/forms'; +import {NgIf} from '@angular/common'; +import { PathfindingService } from './service/pathfinding.service'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; + +// Define an enum for node types that can be placed by the user +enum NodeType { + Start = 'start', + End = 'end', + Wall = 'wall', + None = 'none' +} + +@Component({ + selector: 'app-pathfinding', + standalone: true, + imports: [CommonModule, MatButtonModule, MatButtonToggleModule, FormsModule, NgIf, TranslateModule], + templateUrl: './pathfinding.component.html', + styleUrls: ['./pathfinding.component.scss'] +}) +export class PathfindingComponent implements AfterViewInit { + + @ViewChild('gridCanvas', { static: true }) + canvas!: ElementRef; + ctx!: CanvasRenderingContext2D; + + grid: Node[][] = []; + startNode: Node | null = null; + endNode: Node | null = null; + + isDrawing: boolean = false; + selectedNodeType: NodeType = NodeType.None; // Default to no selection + animationSpeed: number = 10; // milliseconds + + readonly NodeType = NodeType; // Expose enum to template + + constructor(private pathfindingService: PathfindingService, + private translate: TranslateService) { + } + + ngAfterViewInit(): void { + this.ctx = this.canvas.nativeElement.getContext('2d') as CanvasRenderingContext2D; + this.canvas.nativeElement.width = GRID_COLS * NODE_SIZE; + this.canvas.nativeElement.height = GRID_ROWS * NODE_SIZE; + this.initializeGrid(); + this.drawGrid(); + + // Add event listeners for mouse interactions + this.canvas.nativeElement.addEventListener('mousedown', this.onMouseDown.bind(this)); + this.canvas.nativeElement.addEventListener('mousemove', this.onMouseMove.bind(this)); + this.canvas.nativeElement.addEventListener('mouseup', this.onMouseUp.bind(this)); + this.canvas.nativeElement.addEventListener('mouseleave', this.onMouseUp.bind(this)); // Stop drawing if mouse leaves canvas + } + + initializeGrid(): void { + this.grid = []; + for (let row = 0; row < GRID_ROWS; row++) { + const currentRow: Node[] = []; + for (let col = 0; col < GRID_COLS; col++) { + currentRow.push({ + row, + col, + isStart: false, + isEnd: false, + isWall: false, + isVisited: false, + isPath: false, + distance: Infinity, + previousNode: null, + fScore: 0 + }); + } + this.grid.push(currentRow); + } + + // Set default start and end nodes + this.startNode = this.grid[Math.floor(GRID_ROWS / 2)][Math.floor(GRID_COLS / 4)]; + this.startNode.isStart = true; + this.endNode = this.grid[Math.floor(GRID_ROWS / 2)][Math.floor(3 * GRID_COLS / 4)]; + this.endNode.isEnd = true; + } + + drawGrid(): void { + if (!this.ctx) { + return; + } + + this.ctx.clearRect(0, 0, this.canvas.nativeElement.width, this.canvas.nativeElement.height); + + for (let row = 0; row < GRID_ROWS; row++) { + for (let col = 0; col < GRID_COLS; col++) { + const node = this.grid[row][col]; + let color = 'lightgray'; // Default color + + if (node.isStart) { + color = 'green'; + } else if (node.isEnd) { + color = 'red'; + } else if (node.isPath) { + color = 'gold'; + } else if (node.isVisited) { + color = 'skyblue'; + } else if (node.isWall) { + color = 'black'; + } + + this.ctx.fillStyle = color; + this.ctx.fillRect(col * NODE_SIZE, row * NODE_SIZE, NODE_SIZE, NODE_SIZE); + this.ctx.strokeStyle = '#ccc'; + this.ctx.strokeRect(col * NODE_SIZE, row * NODE_SIZE, NODE_SIZE, NODE_SIZE); + } + } + } + + onMouseDown(event: MouseEvent): void { + this.isDrawing = true; + this.placeNode(event); + } + + onMouseMove(event: MouseEvent): void { + if (this.isDrawing) { + this.placeNode(event); + } + } + + onMouseUp(event: MouseEvent): void { + this.isDrawing = false; + } + + placeNode(event: MouseEvent): void { + const rect = this.canvas.nativeElement.getBoundingClientRect(); + const x = event.clientX - rect.left; + const y = event.clientY - rect.top; + + const col = Math.floor(x / NODE_SIZE); + const row = Math.floor(y / NODE_SIZE); + + if (row < 0 || row >= GRID_ROWS || col < 0 || col >= GRID_COLS) { + return; + } + + const node = this.grid[row][col]; + + switch (this.selectedNodeType) { + case NodeType.Start: + if (!node.isEnd && !node.isWall) { + if (this.startNode) { + this.startNode.isStart = false; + } + node.isStart = true; + this.startNode = node; + } + break; + case NodeType.End: + if (!node.isStart && !node.isWall) { + if (this.endNode) { + this.endNode.isEnd = false; + } + node.isEnd = true; + this.endNode = node; + } + break; + case NodeType.Wall: + if (!node.isStart && !node.isEnd) { + node.isWall = !node.isWall; + } + break; + case NodeType.None: + // Clear a node + if (node.isStart) { + node.isStart = false; + this.startNode = null; + } else if (node.isEnd) { + node.isEnd = false; + this.endNode = null; + } else if (node.isWall) { + node.isWall = false; + } + break; + } + this.drawGrid(); + } + + visualizeDijkstra(): void { + if (!this.startNode || !this.endNode) { + alert(this.translate.instant('PATHFINDING.ALERT.START_END_NODES')); + return; + } + this.clearPath(); + const gridCopy = this.getCleanGrid(); + const { visitedNodesInOrder, nodesInShortestPathOrder } = this.pathfindingService.dijkstra(gridCopy, + gridCopy[this.startNode.row][this.startNode.col], + gridCopy[this.endNode.row][this.endNode.col] + ); + this.animateAlgorithm(visitedNodesInOrder, nodesInShortestPathOrder); + } + + visualizeAStar(): void { + if (!this.startNode || !this.endNode) { + alert(this.translate.instant('PATHFINDING.ALERT.START_END_NODES')); + return; + } + this.clearPath(); + const gridCopy = this.getCleanGrid(); + const { visitedNodesInOrder, nodesInShortestPathOrder } = this.pathfindingService.aStar(gridCopy, + gridCopy[this.startNode.row][this.startNode.col], + gridCopy[this.endNode.row][this.endNode.col] + ); + this.animateAlgorithm(visitedNodesInOrder, nodesInShortestPathOrder); + } + + animateAlgorithm(visitedNodesInOrder: Node[], nodesInShortestPathOrder: Node[]): void { + for (let i = 0; i <= visitedNodesInOrder.length; i++) { + if (i === visitedNodesInOrder.length) { + setTimeout(() => { + this.animateShortestPath(nodesInShortestPathOrder); + }, this.animationSpeed * i); + return; + } + const node = visitedNodesInOrder[i]; + setTimeout(() => { + if (!node.isStart && !node.isEnd) { + node.isVisited = true; + this.drawGrid(); + } + }, this.animationSpeed * i); + } + } + + animateShortestPath(nodesInShortestPathOrder: Node[]): void { + for (let i = 0; i < nodesInShortestPathOrder.length; i++) { + const node = nodesInShortestPathOrder[i]; + setTimeout(() => { + if (!node.isStart && !node.isEnd) { + node.isPath = true; + this.drawGrid(); + } + }, 50 * i); // Speed up path animation + } + } + + clearBoard(): void { + this.initializeGrid(); + this.drawGrid(); + } + + clearPath(): void { + for (let row = 0; row < GRID_ROWS; row++) { + for (let col = 0; col < GRID_COLS; col++) { + const node = this.grid[row][col]; + node.isVisited = false; + node.isPath = false; + node.distance = Infinity; + node.previousNode = null; + } + } + this.drawGrid(); + } + + // Helper to get a deep copy of the grid for algorithm execution + private getCleanGrid(): Node[][] { + const newGrid: Node[][] = []; + for (let row = 0; row < GRID_ROWS; row++) { + const currentRow: Node[] = []; + for (let col = 0; col < GRID_COLS; col++) { + const node = this.grid[row][col]; + currentRow.push({ + ...node, + isVisited: false, + isPath: false, + distance: Infinity, + previousNode: null, + }); + } + newGrid.push(currentRow); + } + return newGrid; + } +} diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.models.ts b/src/app/pages/algorithms/pathfinding/pathfinding.models.ts new file mode 100644 index 0000000..d55af22 --- /dev/null +++ b/src/app/pages/algorithms/pathfinding/pathfinding.models.ts @@ -0,0 +1,16 @@ +export interface Node { + row: number; + col: number; + isStart: boolean; + isEnd: boolean; + isWall: boolean; + isVisited: boolean; + isPath: boolean; + distance: number; + previousNode: Node | null; + fScore: number; +} + +export const GRID_ROWS = 25; +export const GRID_COLS = 50; +export const NODE_SIZE = 20; // in pixels diff --git a/src/app/pages/algorithms/pathfinding/service/pathfinding.service.ts b/src/app/pages/algorithms/pathfinding/service/pathfinding.service.ts new file mode 100644 index 0000000..dd19f39 --- /dev/null +++ b/src/app/pages/algorithms/pathfinding/service/pathfinding.service.ts @@ -0,0 +1,146 @@ +import { Injectable } from '@angular/core'; +import { Node, GRID_ROWS, GRID_COLS } from '../pathfinding.models'; + +@Injectable({ + providedIn: 'root' +}) +export class PathfindingService { + + constructor() { } + + // Helper function to get all unvisited neighbors of a given node + getUnvisitedNeighbors(node: Node, grid: Node[][]): Node[] { + const neighbors: Node[] = []; + const { col, row } = node; + + if (row > 0) neighbors.push(grid[row - 1][col]); + if (row < GRID_ROWS - 1) neighbors.push(grid[row + 1][col]); + if (col > 0) neighbors.push(grid[row][col - 1]); + if (col < GRID_COLS - 1) neighbors.push(grid[row][col + 1]); + + return neighbors.filter(neighbor => !neighbor.isVisited && !neighbor.isWall); + } + + // Helper function to get the nodes in the shortest path + getNodesInShortestPath(endNode: Node): Node[] { + const shortestPathNodes: Node[] = []; + let currentNode: Node | null = endNode; + while (currentNode !== null) { + shortestPathNodes.unshift(currentNode); + currentNode = currentNode.previousNode; + } + return shortestPathNodes; + } + + // Dijkstra's Algorithm + dijkstra(grid: Node[][], startNode: Node, endNode: Node): { visitedNodesInOrder: Node[], nodesInShortestPathOrder: Node[] } { + const visitedNodesInOrder: Node[] = []; + startNode.distance = 0; + const unvisitedNodes: Node[] = this.getAllNodes(grid); + + while (!!unvisitedNodes.length) { + this.sortNodesByDistance(unvisitedNodes); + const closestNode = unvisitedNodes.shift() as Node; + + // If we encounter a wall, skip it + if (closestNode.isWall) continue; + + // If the closest node is at an infinite distance, we're trapped + if (closestNode.distance === Infinity) return { visitedNodesInOrder, nodesInShortestPathOrder: [] }; + + closestNode.isVisited = true; + visitedNodesInOrder.push(closestNode); + + if (closestNode === endNode) return { visitedNodesInOrder, nodesInShortestPathOrder: this.getNodesInShortestPath(endNode) }; + + this.updateUnvisitedNeighbors(closestNode, grid); + } + + return { visitedNodesInOrder, nodesInShortestPathOrder: [] }; + } + + private sortNodesByDistance(unvisitedNodes: Node[]): void { + unvisitedNodes.sort((nodeA, nodeB) => nodeA.distance - nodeB.distance); + } + + private updateUnvisitedNeighbors(node: Node, grid: Node[][]): void { + const unvisitedNeighbors = this.getUnvisitedNeighbors(node, grid); + for (const neighbor of unvisitedNeighbors) { + neighbor.distance = node.distance + 1; + neighbor.previousNode = node; + } + } + + // A* Search Algorithm + aStar(grid: Node[][], startNode: Node, endNode: Node): { visitedNodesInOrder: Node[], nodesInShortestPathOrder: Node[] } { + const visitedNodesInOrder: Node[] = []; + startNode.distance = 0; + // hueristic distance + startNode['distance'] = this.calculateHeuristic(startNode, endNode); + // fScore = gScore + hScore + startNode['fScore'] = startNode.distance + startNode['distance']; + + const openSet: Node[] = [startNode]; + const allNodes = this.getAllNodes(grid); + + // Initialize all nodes' fScore to infinity except for the startNode + for (const node of allNodes) { + if (node !== startNode) { + node['fScore'] = Infinity; + node.distance = Infinity; // gScore + } + } + + + while (openSet.length > 0) { + openSet.sort((nodeA, nodeB) => nodeA['fScore'] - nodeB['fScore']); + const currentNode = openSet.shift() as Node; + + if (currentNode.isWall) continue; + + // If the closest node is at an infinite distance, we're trapped + if (currentNode.distance === Infinity) return { visitedNodesInOrder, nodesInShortestPathOrder: [] }; + + + currentNode.isVisited = true; + visitedNodesInOrder.push(currentNode); + + if (currentNode === endNode) { + return { visitedNodesInOrder, nodesInShortestPathOrder: this.getNodesInShortestPath(endNode) }; + } + + const neighbors = this.getUnvisitedNeighbors(currentNode, grid); + for (const neighbor of neighbors) { + const tentativeGScore = currentNode.distance + 1; // Distance from start to neighbor + + if (tentativeGScore < neighbor.distance) { + neighbor.previousNode = currentNode; + neighbor.distance = tentativeGScore; + neighbor['distance'] = this.calculateHeuristic(neighbor, endNode); + neighbor['fScore'] = neighbor.distance + neighbor['distance']; + + if (!openSet.includes(neighbor)) { + openSet.push(neighbor); + } + } + } + } + + return { visitedNodesInOrder, nodesInShortestPathOrder: [] }; + } + + private calculateHeuristic(node: Node, endNode: Node): number { + // Manhattan distance heuristic + return Math.abs(node.row - endNode.row) + Math.abs(node.col - endNode.col); + } + + private getAllNodes(grid: Node[][]): Node[] { + const nodes: Node[] = []; + for (const row of grid) { + for (const node of row) { + nodes.push(node); + } + } + return nodes; + } +} diff --git a/src/app/pages/algorithms/service/algorithms.service.ts b/src/app/pages/algorithms/service/algorithms.service.ts new file mode 100644 index 0000000..c84b802 --- /dev/null +++ b/src/app/pages/algorithms/service/algorithms.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { AlgorithmCategory } from '../models/algorithm-category'; +import { Observable, of } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class AlgorithmsService { + + private categories: AlgorithmCategory[] = [ + { + id: 'pathfinding', + title: 'Pfadfindungsalgorithmen', + description: 'Vergleich von Pfadfindungsalgorithmen wie Dijkstra und A*.', + routerLink: 'pathfinding' + }, + // { + // id: 'sorting', + // title: 'Sortieralgorithmen', + // description: 'Visualisierung von Sortieralgorithmen wie Bubble Sort, Merge Sort und Quick Sort.', + // routerLink: 'sorting' + // } + ]; + + getCategories(): Observable { + return of(this.categories); + } +} diff --git a/src/app/pages/hobbies/hobbies.component.html b/src/app/pages/hobbies/hobbies.component.html deleted file mode 100644 index 3e705ce..0000000 --- a/src/app/pages/hobbies/hobbies.component.html +++ /dev/null @@ -1,3 +0,0 @@ -
- > Work in progress -
diff --git a/src/app/pages/hobbies/hobbies.component.scss b/src/app/pages/hobbies/hobbies.component.scss deleted file mode 100644 index 07ff5e5..0000000 --- a/src/app/pages/hobbies/hobbies.component.scss +++ /dev/null @@ -1,19 +0,0 @@ -.terminal-loader { - font-family: monospace; - font-size: 1.1rem; - display: inline-flex; - align-items: center; -} - -.cursor { - width: 10px; - height: 1.1rem; - background: var(--app-fg); - margin-left: .25rem; - animation: blink .8s infinite; -} - -@keyframes blink { - 0%, 50% { opacity: 1; } - 51%, 100% { opacity: 0; } -} diff --git a/src/app/pages/hobbies/hobbies.component.ts b/src/app/pages/hobbies/hobbies.component.ts deleted file mode 100644 index 8dfb207..0000000 --- a/src/app/pages/hobbies/hobbies.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-hobbies', - imports: [], - templateUrl: './hobbies.component.html', - styleUrl: './hobbies.component.scss', -}) -export class HobbiesComponent { - -} diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index efc920a..0c61726 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -7,7 +7,7 @@ "ABOUT": "Über mich", "IMPRINT": "Impressum", "PROJECTS": "Projekte", - "HOBBY": "Hobbies", + "ALGORITHMS": "Algorithmen", "SETTINGS": "Einstellungen", "LANGUAGE": "Sprache", "APPEARANCE": "Darstellung" @@ -292,5 +292,21 @@ "PARAGRAPH": "Angaben gemäß § 5 DDG", "COUNTRY": "Deutschland", "CONTACT": "Kontakt" + }, + "PATHFINDING": { + "TITLE": "Pfadfindungsalgorithmen", + "START_NODE": "Startknoten", + "END_NODE": "Endknoten", + "WALL": "Wand", + "CLEAR_NODE": "Löschen", + "DIJKSTRA": "Dijkstra", + "ASTAR": "A*", + "CLEAR_BOARD": "Board leeren", + "CLEAR_PATH": "Pfad löschen", + "VISITED": "Besucht", + "PATH": "Pfad", + "ALERT": { + "START_END_NODES": "Bitte wählen Sie einen Start- und Endknoten aus, bevor Sie den Algorithmus starten." + } } } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 81d2fb2..d8c5241 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -7,7 +7,7 @@ "ABOUT": "About me", "IMPRINT": "Impressum", "PROJECTS": "Projects", - "HOBBY": "Hobby's", + "ALGORITHMS": "Algorithms", "SETTINGS": "Settings", "LANGUAGE": "Language", "APPEARANCE": "Appearance" @@ -292,5 +292,21 @@ "PARAGRAPH": "Information pursuant to Section 5 DDG", "COUNTRY": "Germany", "CONTACT": "Contact" + }, + "PATHFINDING": { + "TITLE": "Pathfinding Algorithms", + "START_NODE": "Start Node", + "END_NODE": "End Node", + "WALL": "Wall", + "CLEAR_NODE": "Clear", + "DIJKSTRA": "Dijkstra", + "ASTAR": "A*", + "CLEAR_BOARD": "Clear Board", + "CLEAR_PATH": "Clear Path", + "VISITED": "Visited", + "PATH": "Path", + "ALERT": { + "START_END_NODES": "Please select a start and end node before running the algorithm." + } } } From b0ad2dc3d1722d07c36c836b3839add9d8e3f6c6 Mon Sep 17 00:00:00 2001 From: Lobo Date: Sun, 1 Feb 2026 16:18:55 +0100 Subject: [PATCH 2/3] Update pathfinding.component.ts --- .../pathfinding/pathfinding.component.ts | 38 ++++--------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.component.ts b/src/app/pages/algorithms/pathfinding/pathfinding.component.ts index 8ea27ae..d439e22 100644 --- a/src/app/pages/algorithms/pathfinding/pathfinding.component.ts +++ b/src/app/pages/algorithms/pathfinding/pathfinding.component.ts @@ -4,7 +4,6 @@ import { MatButtonModule } from '@angular/material/button'; import {GRID_COLS, GRID_ROWS, NODE_SIZE, Node} from './pathfinding.models'; import {MatButtonToggleModule} from '@angular/material/button-toggle'; import {FormsModule} from '@angular/forms'; -import {NgIf} from '@angular/common'; import { PathfindingService } from './service/pathfinding.service'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; @@ -19,7 +18,7 @@ enum NodeType { @Component({ selector: 'app-pathfinding', standalone: true, - imports: [CommonModule, MatButtonModule, MatButtonToggleModule, FormsModule, NgIf, TranslateModule], + imports: [CommonModule, MatButtonModule, MatButtonToggleModule, FormsModule, TranslateModule], templateUrl: './pathfinding.component.html', styleUrls: ['./pathfinding.component.scss'] }) @@ -35,7 +34,7 @@ export class PathfindingComponent implements AfterViewInit { isDrawing: boolean = false; selectedNodeType: NodeType = NodeType.None; // Default to no selection - animationSpeed: number = 10; // milliseconds + animationSpeed: number = 1; // milliseconds readonly NodeType = NodeType; // Expose enum to template @@ -192,10 +191,9 @@ export class PathfindingComponent implements AfterViewInit { return; } this.clearPath(); - const gridCopy = this.getCleanGrid(); - const { visitedNodesInOrder, nodesInShortestPathOrder } = this.pathfindingService.dijkstra(gridCopy, - gridCopy[this.startNode.row][this.startNode.col], - gridCopy[this.endNode.row][this.endNode.col] + const { visitedNodesInOrder, nodesInShortestPathOrder } = this.pathfindingService.dijkstra(this.grid, + this.grid[this.startNode.row][this.startNode.col], + this.grid[this.endNode.row][this.endNode.col] ); this.animateAlgorithm(visitedNodesInOrder, nodesInShortestPathOrder); } @@ -206,10 +204,9 @@ export class PathfindingComponent implements AfterViewInit { return; } this.clearPath(); - const gridCopy = this.getCleanGrid(); - const { visitedNodesInOrder, nodesInShortestPathOrder } = this.pathfindingService.aStar(gridCopy, - gridCopy[this.startNode.row][this.startNode.col], - gridCopy[this.endNode.row][this.endNode.col] + const { visitedNodesInOrder, nodesInShortestPathOrder } = this.pathfindingService.aStar(this.grid, + this.grid[this.startNode.row][this.startNode.col], + this.grid[this.endNode.row][this.endNode.col] ); this.animateAlgorithm(visitedNodesInOrder, nodesInShortestPathOrder); } @@ -262,23 +259,4 @@ export class PathfindingComponent implements AfterViewInit { this.drawGrid(); } - // Helper to get a deep copy of the grid for algorithm execution - private getCleanGrid(): Node[][] { - const newGrid: Node[][] = []; - for (let row = 0; row < GRID_ROWS; row++) { - const currentRow: Node[] = []; - for (let col = 0; col < GRID_COLS; col++) { - const node = this.grid[row][col]; - currentRow.push({ - ...node, - isVisited: false, - isPath: false, - distance: Infinity, - previousNode: null, - }); - } - newGrid.push(currentRow); - } - return newGrid; - } } From 2d25b568f52f3cf3c3d0e1d305ac923a087f8394 Mon Sep 17 00:00:00 2001 From: Lobo Date: Sun, 1 Feb 2026 17:04:07 +0100 Subject: [PATCH 3/3] Add ESLint integration and Angular linting support Configured ESLint for the project with Angular and TypeScript support. Added angular-eslint dependencies, updated angular.json to include linting, and created eslint.config.js for lint rules. Updated package.json and package-lock.json with new dev dependencies. --- angular.json | 14 +- eslint.config.js | 44 + package-lock.json | 1951 ++++++++++++++++- package.json | 10 +- .../background/particles-bg.component.ts | 8 +- src/app/layout/imageDialog/image.component.ts | 11 +- .../algorithms/algorithms.component.html | 4 +- .../pages/algorithms/algorithms.component.ts | 7 +- .../pathfinding/pathfinding.component.html | 6 + .../pathfinding/pathfinding.component.ts | 138 +- .../pathfinding/pathfinding.models.ts | 6 +- .../service/pathfinding.service.ts | 4 +- src/app/service/language.service.ts | 4 +- src/app/service/reload.service.ts | 8 +- src/app/service/theme.service.ts | 8 +- src/assets/i18n/de.json | 3 + src/assets/i18n/en.json | 3 + 17 files changed, 2172 insertions(+), 57 deletions(-) create mode 100644 eslint.config.js diff --git a/angular.json b/angular.json index 779fd7c..b73b1b4 100644 --- a/angular.json +++ b/angular.json @@ -3,7 +3,10 @@ "version": 1, "cli": { "packageManager": "npm", - "analytics": false + "analytics": false, + "schematicCollections": [ + "angular-eslint" + ] }, "newProjectRoot": "projects", "projects": { @@ -95,6 +98,15 @@ "src/styles.scss" ] } + }, + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": [ + "src/**/*.ts", + "src/**/*.html" + ] + } } } } diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..8fedb6a --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,44 @@ +// @ts-check +const eslint = require("@eslint/js"); +const { defineConfig } = require("eslint/config"); +const tseslint = require("typescript-eslint"); +const angular = require("angular-eslint"); + +module.exports = defineConfig([ + { + files: ["**/*.ts"], + extends: [ + eslint.configs.recommended, + tseslint.configs.recommended, + tseslint.configs.stylistic, + angular.configs.tsRecommended, + ], + processor: angular.processInlineTemplates, + rules: { + "@angular-eslint/directive-selector": [ + "error", + { + type: "attribute", + prefix: "app", + style: "camelCase", + }, + ], + "@angular-eslint/component-selector": [ + "error", + { + type: "element", + prefix: "app", + style: "kebab-case", + }, + ], + }, + }, + { + files: ["**/*.html"], + extends: [ + angular.configs.templateRecommended, + angular.configs.templateAccessibility, + ], + rules: {}, + } +]); diff --git a/package-lock.json b/package-lock.json index f1bcdff..5712382 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,8 +33,11 @@ "@angular/compiler-cli": "~21.1.0", "@angular/platform-browser-dynamic": "~21.1.0", "@types/jasmine": "~5.1.15", + "angular-eslint": "21.2.0", + "eslint": "^9.39.2", "jasmine-core": "~6.0.1", - "typescript": "~5.9.3" + "typescript": "~5.9.3", + "typescript-eslint": "8.50.1" } }, "node_modules/@algolia/abtesting": { @@ -474,6 +477,144 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-eslint/builder": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-21.2.0.tgz", + "integrity": "sha512-wcp3J9cbrDwSeI/o1D/DSvMQa8zpKjc5WhRGTx33omhWijCfiVNEAiBLWiEx5Sb/dWcoX8yFNWY5jSgFVy9Sjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": ">= 0.2100.0 < 0.2200.0", + "@angular-devkit/core": ">= 21.0.0 < 22.0.0" + }, + "peerDependencies": { + "@angular/cli": ">= 21.0.0 < 22.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-21.2.0.tgz", + "integrity": "sha512-J0DWL+j6t9ItFIyIADvzHGqwDA1qfVJ9bx+oTmJ/Hlo7cUpIRoXpcTXpug0CEEABFH0RfDu6PDG2b0FoZ1+7bg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular-eslint/eslint-plugin": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-21.2.0.tgz", + "integrity": "sha512-X2Qn2viDsjm91CEMxNrxDH3qkKpp6un0C1F1BW2p/m9J4AUVfOcXwWz9UpHFSHTRQ+YlTJbiH1ZwwAPeKhFaxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "21.2.0", + "@angular-eslint/utils": "21.2.0", + "ts-api-utils": "^2.1.0" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-21.2.0.tgz", + "integrity": "sha512-lJ13Dj0DjR6YiceQR0sRbyWzSzOQ6uZPwK9CJUF3wuZjYAUvL1D61zaU9QrVLtf89NVOxv+dYZHDdu3IDeIqbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "21.2.0", + "@angular-eslint/utils": "21.2.0", + "aria-query": "5.3.2", + "axobject-query": "4.1.0" + }, + "peerDependencies": { + "@angular-eslint/template-parser": "21.2.0", + "@typescript-eslint/types": "^7.11.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/schematics": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-21.2.0.tgz", + "integrity": "sha512-WtT4fPKIUQ/hswy+l2GF/rKOdD+42L3fUzzcwRzNutQbe2tU9SimoSOAsay/ylWEuhIOQTs7ysPB8fUgFQoLpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": ">= 21.0.0 < 22.0.0", + "@angular-devkit/schematics": ">= 21.0.0 < 22.0.0", + "@angular-eslint/eslint-plugin": "21.2.0", + "@angular-eslint/eslint-plugin-template": "21.2.0", + "ignore": "7.0.5", + "semver": "7.7.3", + "strip-json-comments": "3.1.1" + }, + "peerDependencies": { + "@angular/cli": ">= 21.0.0 < 22.0.0" + } + }, + "node_modules/@angular-eslint/template-parser": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-21.2.0.tgz", + "integrity": "sha512-TCb3qYOC/uXKZCo56cJ6N9sHeWdFhyVqrbbYfFjTi09081T6jllgHDZL5Ms7gOMNY8KywWGGbhxwvzeA0RwTgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "21.2.0", + "eslint-scope": "^9.0.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/template-parser/node_modules/eslint-scope": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.0.tgz", + "integrity": "sha512-CkWE42hOJsNj9FJRaoMX9waUFYhqY4jmyLFdAdzZr6VaCg3ynLYx4WnOdkaIifGfH4gsUcBTn4OZbHXkpLD0FQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@angular-eslint/template-parser/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@angular-eslint/utils": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-21.2.0.tgz", + "integrity": "sha512-E19/hkuvHoNFvctBkmEiGWpy2bbC6cgbr3GNVrn2nGtbI4jnwnDFCGHv50I4LBfvj0PA9E6TWe73ejJ5qoMJWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "21.2.0" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, "node_modules/@angular/animations": { "version": "21.1.0", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.1.0.tgz", @@ -2818,6 +2959,197 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@hono/node-server": { "version": "1.19.9", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", @@ -2831,6 +3163,58 @@ "hono": "^4" } }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@inquirer/ansi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", @@ -6172,6 +6556,13 @@ "@types/estree": "*" } }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -6329,6 +6720,781 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz", + "integrity": "sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/type-utils": "8.50.1", + "@typescript-eslint/utils": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.50.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/project-service": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.1.tgz", + "integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.50.1", + "@typescript-eslint/types": "^8.50.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz", + "integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz", + "integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.1.tgz", + "integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz", + "integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.50.1", + "@typescript-eslint/tsconfig-utils": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.1.tgz", + "integrity": "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz", + "integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.1.tgz", + "integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/project-service": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.1.tgz", + "integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.50.1", + "@typescript-eslint/types": "^8.50.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz", + "integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz", + "integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.1.tgz", + "integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz", + "integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.50.1", + "@typescript-eslint/tsconfig-utils": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz", + "integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", + "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.54.0", + "@typescript-eslint/types": "^8.54.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", + "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", + "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.1.tgz", + "integrity": "sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1", + "@typescript-eslint/utils": "8.50.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/project-service": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.1.tgz", + "integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.50.1", + "@typescript-eslint/types": "^8.50.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz", + "integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz", + "integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.1.tgz", + "integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz", + "integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.50.1", + "@typescript-eslint/tsconfig-utils": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.1.tgz", + "integrity": "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz", + "integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", + "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", + "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.54.0", + "@typescript-eslint/tsconfig-utils": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", + "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", + "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.54.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@vitejs/plugin-basic-ssl": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.0.tgz", @@ -6554,6 +7720,16 @@ "acorn": "^8.14.0" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/adjust-sourcemap-loader": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", @@ -6661,6 +7837,30 @@ "node": ">= 14.0.0" } }, + "node_modules/angular-eslint": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/angular-eslint/-/angular-eslint-21.2.0.tgz", + "integrity": "sha512-pERqqHIMwD34UT0FoHSNTt4V332vHiAzgkY0rgdUaqSamS94IzbF02EfFxygr53UogQQOXhpLbSSDMOyovB3TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": ">= 21.0.0 < 22.0.0", + "@angular-devkit/schematics": ">= 21.0.0 < 22.0.0", + "@angular-eslint/builder": "21.2.0", + "@angular-eslint/eslint-plugin": "21.2.0", + "@angular-eslint/eslint-plugin-template": "21.2.0", + "@angular-eslint/schematics": "21.2.0", + "@angular-eslint/template-parser": "21.2.0", + "@typescript-eslint/types": "^8.0.0", + "@typescript-eslint/utils": "^8.0.0" + }, + "peerDependencies": { + "@angular/cli": ">= 21.0.0 < 22.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*", + "typescript-eslint": "^8.0.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -6752,6 +7952,16 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -6794,6 +8004,16 @@ "postcss": "^8.1.0" } }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/babel-loader": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.0.0.tgz", @@ -6858,6 +8078,13 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/baseline-browser-mapping": { "version": "2.9.11", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", @@ -6954,6 +8181,17 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "license": "ISC" }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -7383,6 +8621,13 @@ "node": ">= 0.6" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/connect-history-api-fallback": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", @@ -7641,6 +8886,13 @@ } } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/default-browser": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", @@ -8037,6 +9289,79 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -8050,6 +9375,206 @@ "node": ">=8.0.0" } }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -8209,6 +9734,20 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -8254,6 +9793,19 @@ } } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -8313,6 +9865,27 @@ "flat": "cli.js" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -8520,6 +10093,19 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "license": "BSD-2-Clause" }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -8819,6 +10405,16 @@ "postcss": "^8.1.0" } }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/ignore-walk": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", @@ -9237,6 +10833,13 @@ "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-even-better-errors": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", @@ -9260,6 +10863,13 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -9297,6 +10907,16 @@ "source-map-support": "^0.5.5" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -9378,6 +10998,20 @@ "node": ">=0.10.0" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/license-webpack-plugin": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", @@ -9528,6 +11162,13 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "license": "MIT" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/log-symbols": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", @@ -10094,6 +11735,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/needle": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", @@ -10461,6 +12109,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ora": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/ora/-/ora-9.0.0.tgz", @@ -10976,6 +12642,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/proc-log": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", @@ -11026,6 +12702,16 @@ "license": "MIT", "optional": true }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.14.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", @@ -12192,6 +13878,19 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -12414,6 +14113,19 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -12470,6 +14182,19 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -12504,6 +14229,210 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.1.tgz", + "integrity": "sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.50.1", + "@typescript-eslint/parser": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1", + "@typescript-eslint/utils": "8.50.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/project-service": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.1.tgz", + "integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.50.1", + "@typescript-eslint/types": "^8.50.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz", + "integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz", + "integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.1.tgz", + "integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz", + "integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.50.1", + "@typescript-eslint/tsconfig-utils": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.1.tgz", + "integrity": "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz", + "integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/typescript-eslint/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/undici": { "version": "7.18.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.18.0.tgz", @@ -12624,6 +14553,16 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -13433,6 +15372,16 @@ "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "license": "MIT" }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/package.json b/package.json index fd1d999..431bbb3 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "start": "ng serve", "build": "ng build", "watch": "ng build --watch --configuration development", - "test": "ng test" + "test": "ng test", + "lint": "ng lint" }, "private": true, "dependencies": { @@ -35,7 +36,10 @@ "@angular/compiler-cli": "~21.1.0", "@angular/platform-browser-dynamic": "~21.1.0", "@types/jasmine": "~5.1.15", + "angular-eslint": "21.2.0", + "eslint": "^9.39.2", "jasmine-core": "~6.0.1", - "typescript": "~5.9.3" + "typescript": "~5.9.3", + "typescript-eslint": "8.50.1" } -} +} \ No newline at end of file diff --git a/src/app/layout/background/particles-bg.component.ts b/src/app/layout/background/particles-bg.component.ts index a25856c..991b270 100644 --- a/src/app/layout/background/particles-bg.component.ts +++ b/src/app/layout/background/particles-bg.component.ts @@ -1,11 +1,9 @@ import { Container, - MoveDirection, - OutMode, Engine } from "@tsparticles/engine"; import {NgParticlesService, NgxParticlesModule} from "@tsparticles/angular"; -import {Component} from '@angular/core'; +import { Component, inject } from '@angular/core'; import {loadFull} from 'tsparticles'; @Component({ @@ -18,6 +16,8 @@ import {loadFull} from 'tsparticles'; styleUrl: './particles-bg.component.scss', }) export class ParticlesBgComponent { + private readonly ngParticlesService = inject(NgParticlesService); + id = "tsparticles"; /* Starting from 1.19.0 you can use a remote url (AJAX request) to a JSON with the configuration */ @@ -92,7 +92,7 @@ export class ParticlesBgComponent { detectRetina: true,*/ }; - constructor(private readonly ngParticlesService: NgParticlesService) {} + async particlesInit(engine: Engine): Promise { await loadFull(engine); } diff --git a/src/app/layout/imageDialog/image.component.ts b/src/app/layout/imageDialog/image.component.ts index 53e51d9..6b09aff 100644 --- a/src/app/layout/imageDialog/image.component.ts +++ b/src/app/layout/imageDialog/image.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; @@ -63,7 +63,10 @@ import { MatIconModule } from '@angular/material/icon'; `], }) export class ImageDialogComponent { - constructor(@Inject(MAT_DIALOG_DATA) public data: { title: string; src: string }) { - console.log(data.title); - } + data = inject<{ + title: string; + src: string; +}>(MAT_DIALOG_DATA); + + } diff --git a/src/app/pages/algorithms/algorithms.component.html b/src/app/pages/algorithms/algorithms.component.html index a4b1827..45848db 100644 --- a/src/app/pages/algorithms/algorithms.component.html +++ b/src/app/pages/algorithms/algorithms.component.html @@ -1,7 +1,8 @@

Algorithmen

- +@for (category of categories$ | async; track category.id) { + {{ category.title }} @@ -9,5 +10,6 @@

{{ category.description }}

+ }
diff --git a/src/app/pages/algorithms/algorithms.component.ts b/src/app/pages/algorithms/algorithms.component.ts index 7c8535f..66b7540 100644 --- a/src/app/pages/algorithms/algorithms.component.ts +++ b/src/app/pages/algorithms/algorithms.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, inject } from '@angular/core'; import { AlgorithmsService } from './service/algorithms.service'; import { AlgorithmCategory } from './models/algorithm-category'; import { Observable } from 'rxjs'; @@ -14,11 +14,12 @@ import { MatCardModule } from '@angular/material/card'; imports: [CommonModule, RouterLink, MatCardModule], }) export class AlgorithmsComponent implements OnInit { + private algorithmsService = inject(AlgorithmsService); + categories$: Observable | undefined; - constructor(private algorithmsService: AlgorithmsService) { - } + ngOnInit(): void { this.categories$ = this.algorithmsService.getCategories(); diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.component.html b/src/app/pages/algorithms/pathfinding/pathfinding.component.html index db95196..840fef5 100644 --- a/src/app/pages/algorithms/pathfinding/pathfinding.component.html +++ b/src/app/pages/algorithms/pathfinding/pathfinding.component.html @@ -12,6 +12,7 @@ + @@ -26,4 +27,9 @@ + +
+

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

+

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

+
diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.component.ts b/src/app/pages/algorithms/pathfinding/pathfinding.component.ts index d439e22..90ed8bd 100644 --- a/src/app/pages/algorithms/pathfinding/pathfinding.component.ts +++ b/src/app/pages/algorithms/pathfinding/pathfinding.component.ts @@ -1,4 +1,4 @@ -import {AfterViewInit, Component, ElementRef, HostListener, ViewChild} from '@angular/core'; +import { AfterViewInit, Component, ElementRef, ViewChild, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; import {GRID_COLS, GRID_ROWS, NODE_SIZE, Node} from './pathfinding.models'; @@ -23,6 +23,11 @@ enum NodeType { styleUrls: ['./pathfinding.component.scss'] }) export class PathfindingComponent implements AfterViewInit { + private readonly pathfindingService = inject(PathfindingService); + private readonly translate = inject(TranslateService); + private lastRow = -1; + private lastCol = -1; + private timeoutIds: any[] = []; @ViewChild('gridCanvas', { static: true }) canvas!: ElementRef; @@ -32,21 +37,21 @@ export class PathfindingComponent implements AfterViewInit { startNode: Node | null = null; endNode: Node | null = null; - isDrawing: boolean = false; + isDrawing = false; + shouldAddWall = true; selectedNodeType: NodeType = NodeType.None; // Default to no selection - animationSpeed: number = 1; // milliseconds + animationSpeed = 3; // milliseconds + pathLength = 0; + executionTime = 0; - readonly NodeType = NodeType; // Expose enum to template - constructor(private pathfindingService: PathfindingService, - private translate: TranslateService) { - } + readonly NodeType = NodeType; ngAfterViewInit(): void { this.ctx = this.canvas.nativeElement.getContext('2d') as CanvasRenderingContext2D; this.canvas.nativeElement.width = GRID_COLS * NODE_SIZE; this.canvas.nativeElement.height = GRID_ROWS * NODE_SIZE; - this.initializeGrid(); + this.initializeGrid(true); this.drawGrid(); // Add event listeners for mouse interactions @@ -56,7 +61,7 @@ export class PathfindingComponent implements AfterViewInit { this.canvas.nativeElement.addEventListener('mouseleave', this.onMouseUp.bind(this)); // Stop drawing if mouse leaves canvas } - initializeGrid(): void { + initializeGrid(withWalls: boolean): void { this.grid = []; for (let row = 0; row < GRID_ROWS; row++) { const currentRow: Node[] = []; @@ -78,10 +83,24 @@ export class PathfindingComponent implements AfterViewInit { } // Set default start and end nodes - this.startNode = this.grid[Math.floor(GRID_ROWS / 2)][Math.floor(GRID_COLS / 4)]; + this.startNode = this.grid[0][Math.floor(GRID_COLS / 2)]; this.startNode.isStart = true; - this.endNode = this.grid[Math.floor(GRID_ROWS / 2)][Math.floor(3 * GRID_COLS / 4)]; + this.endNode = this.grid[this.grid.length-1][Math.floor(GRID_COLS / 2)]; this.endNode.isEnd = true; + + if (withWalls) + { + //setting walls + let offset = Math.floor(GRID_COLS / 4); + for (let startWall = 0; startWall < Math.floor(GRID_COLS /2 ); startWall++){ + this.grid[Math.floor(GRID_ROWS / 2)][offset + startWall].isWall = true; + } + } + } + + stopAnimations(): void { + this.timeoutIds.forEach((id) => clearTimeout(id)); + this.timeoutIds = []; } drawGrid(): void { @@ -117,6 +136,13 @@ export class PathfindingComponent implements AfterViewInit { } onMouseDown(event: MouseEvent): void { + const { row, col } = this.getGridPosition(event); + + if (this.isValidPosition(row, col)) { + const node = this.grid[row][col]; + this.shouldAddWall = !node.isWall; + } + this.isDrawing = true; this.placeNode(event); } @@ -127,8 +153,25 @@ export class PathfindingComponent implements AfterViewInit { } } - onMouseUp(event: MouseEvent): void { + getGridPosition(event: MouseEvent): { row: number, col: number } { + const rect = this.canvas.nativeElement.getBoundingClientRect(); + const x = event.clientX - rect.left; + const y = event.clientY - rect.top; + + const col = Math.floor(x / NODE_SIZE); + const row = Math.floor(y / NODE_SIZE); + + return { row, col }; + } + + isValidPosition(row: number, col: number): boolean { + return row >= 0 && row < GRID_ROWS && col >= 0 && col < GRID_COLS; + } + + onMouseUp(): void { this.isDrawing = false; + this.lastRow = -1; + this.lastCol = -1; } placeNode(event: MouseEvent): void { @@ -143,6 +186,12 @@ export class PathfindingComponent implements AfterViewInit { return; } + if (this.lastRow === row && this.lastCol === col) { + return; + } + this.lastRow = row; + this.lastCol = col; + const node = this.grid[row][col]; switch (this.selectedNodeType) { @@ -150,27 +199,33 @@ export class PathfindingComponent implements AfterViewInit { if (!node.isEnd && !node.isWall) { if (this.startNode) { this.startNode.isStart = false; + this.drawNode(this.startNode); } node.isStart = true; this.startNode = node; } break; + case NodeType.End: if (!node.isStart && !node.isWall) { if (this.endNode) { this.endNode.isEnd = false; + this.drawNode(this.endNode); } node.isEnd = true; this.endNode = node; } break; + case NodeType.Wall: if (!node.isStart && !node.isEnd) { - node.isWall = !node.isWall; + if (node.isWall !== this.shouldAddWall) { + node.isWall = this.shouldAddWall; + } } break; + case NodeType.None: - // Clear a node if (node.isStart) { node.isStart = false; this.startNode = null; @@ -182,71 +237,110 @@ export class PathfindingComponent implements AfterViewInit { } break; } - this.drawGrid(); + + this.drawNode(node); } visualizeDijkstra(): void { + this.stopAnimations(); if (!this.startNode || !this.endNode) { alert(this.translate.instant('PATHFINDING.ALERT.START_END_NODES')); return; } this.clearPath(); + const startTime = performance.now(); const { visitedNodesInOrder, nodesInShortestPathOrder } = this.pathfindingService.dijkstra(this.grid, this.grid[this.startNode.row][this.startNode.col], this.grid[this.endNode.row][this.endNode.col] ); + const endTime = performance.now(); + this.pathLength = nodesInShortestPathOrder.length; + this.executionTime = endTime - startTime; this.animateAlgorithm(visitedNodesInOrder, nodesInShortestPathOrder); } visualizeAStar(): void { + this.stopAnimations(); if (!this.startNode || !this.endNode) { alert(this.translate.instant('PATHFINDING.ALERT.START_END_NODES')); return; } this.clearPath(); + const startTime = performance.now(); const { visitedNodesInOrder, nodesInShortestPathOrder } = this.pathfindingService.aStar(this.grid, this.grid[this.startNode.row][this.startNode.col], this.grid[this.endNode.row][this.endNode.col] ); + const endTime = performance.now(); + this.pathLength = nodesInShortestPathOrder.length; + this.executionTime = endTime - startTime; this.animateAlgorithm(visitedNodesInOrder, nodesInShortestPathOrder); } animateAlgorithm(visitedNodesInOrder: Node[], nodesInShortestPathOrder: Node[]): void { for (let i = 0; i <= visitedNodesInOrder.length; i++) { if (i === visitedNodesInOrder.length) { - setTimeout(() => { + const timeoutId = setTimeout(() => { this.animateShortestPath(nodesInShortestPathOrder); }, this.animationSpeed * i); + this.timeoutIds.push(timeoutId); return; } + const node = visitedNodesInOrder[i]; - setTimeout(() => { + const timeoutId = setTimeout(() => { if (!node.isStart && !node.isEnd) { node.isVisited = true; - this.drawGrid(); + this.drawNode(node); } }, this.animationSpeed * i); + this.timeoutIds.push(timeoutId); } } animateShortestPath(nodesInShortestPathOrder: Node[]): void { for (let i = 0; i < nodesInShortestPathOrder.length; i++) { const node = nodesInShortestPathOrder[i]; - setTimeout(() => { + const timeoutId = setTimeout(() => { if (!node.isStart && !node.isEnd) { node.isPath = true; - this.drawGrid(); + this.drawNode(node); } - }, 50 * i); // Speed up path animation + }, this.animationSpeed * i); + this.timeoutIds.push(timeoutId); } } + drawNode(node: Node): void { + if (!this.ctx) return; + + let color = 'lightgray'; + if (node.isStart) color = 'green'; + else if (node.isEnd) color = 'red'; + else if (node.isPath) color = 'gold'; + else if (node.isVisited) color = 'skyblue'; + else if (node.isWall) color = 'black'; + + this.ctx.fillStyle = color; + this.ctx.fillRect(node.col * NODE_SIZE, node.row * NODE_SIZE, NODE_SIZE, NODE_SIZE); + this.ctx.strokeStyle = '#ccc'; + this.ctx.strokeRect(node.col * NODE_SIZE, node.row * NODE_SIZE, NODE_SIZE, NODE_SIZE); + } + + resetBoard(): void { + this.stopAnimations(); + this.initializeGrid(true); + this.drawGrid(); + } + clearBoard(): void { - this.initializeGrid(); + this.stopAnimations(); + this.initializeGrid(false); this.drawGrid(); } clearPath(): void { + this.stopAnimations(); for (let row = 0; row < GRID_ROWS; row++) { for (let col = 0; col < GRID_COLS; col++) { const node = this.grid[row][col]; diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.models.ts b/src/app/pages/algorithms/pathfinding/pathfinding.models.ts index d55af22..251af88 100644 --- a/src/app/pages/algorithms/pathfinding/pathfinding.models.ts +++ b/src/app/pages/algorithms/pathfinding/pathfinding.models.ts @@ -11,6 +11,6 @@ export interface Node { fScore: number; } -export const GRID_ROWS = 25; -export const GRID_COLS = 50; -export const NODE_SIZE = 20; // in pixels +export const GRID_ROWS = 150; +export const GRID_COLS = 100; +export const NODE_SIZE = 10; // in pixels diff --git a/src/app/pages/algorithms/pathfinding/service/pathfinding.service.ts b/src/app/pages/algorithms/pathfinding/service/pathfinding.service.ts index dd19f39..abebff3 100644 --- a/src/app/pages/algorithms/pathfinding/service/pathfinding.service.ts +++ b/src/app/pages/algorithms/pathfinding/service/pathfinding.service.ts @@ -6,7 +6,7 @@ import { Node, GRID_ROWS, GRID_COLS } from '../pathfinding.models'; }) export class PathfindingService { - constructor() { } + // Helper function to get all unvisited neighbors of a given node getUnvisitedNeighbors(node: Node, grid: Node[][]): Node[] { @@ -38,7 +38,7 @@ export class PathfindingService { startNode.distance = 0; const unvisitedNodes: Node[] = this.getAllNodes(grid); - while (!!unvisitedNodes.length) { + while (unvisitedNodes.length > 0) { this.sortNodesByDistance(unvisitedNodes); const closestNode = unvisitedNodes.shift() as Node; diff --git a/src/app/service/language.service.ts b/src/app/service/language.service.ts index 908b62d..31c8490 100644 --- a/src/app/service/language.service.ts +++ b/src/app/service/language.service.ts @@ -19,14 +19,14 @@ export class LanguageService { use(l: Lang) { this.lang.set(l); this.translate.use(l); - try { localStorage.setItem(LocalStoreConstants.LANGUAGE_KEY, l); } catch {} + try { localStorage.setItem(LocalStoreConstants.LANGUAGE_KEY, l); } catch (e) { void e; } } private getInitial(): Lang { try { const stored = localStorage.getItem(LocalStoreConstants.LANGUAGE_KEY) as Lang | null; if (stored === 'de' || stored === 'en') return stored; - } catch {} + } catch (e) { void e; } const browser = (navigator.language || 'en').toLowerCase(); return browser.startsWith('de') ? 'de' : 'en'; } diff --git a/src/app/service/reload.service.ts b/src/app/service/reload.service.ts index f6d0ce1..ff66db5 100644 --- a/src/app/service/reload.service.ts +++ b/src/app/service/reload.service.ts @@ -10,13 +10,7 @@ export class ReloadService { private readonly _languageChangedTick = signal(0); readonly languageChangedTick = this._languageChangedTick.asReadonly(); - constructor(zone: NgZone) { - zone.runOutsideAngular(() => { - globalThis.addEventListener('storage', (e: StorageEvent) => { - this.informListeners(e, zone); - }); - }); - } + private informListeners(e: StorageEvent, zone: NgZone) { diff --git a/src/app/service/theme.service.ts b/src/app/service/theme.service.ts index 4959abb..fe5016e 100644 --- a/src/app/service/theme.service.ts +++ b/src/app/service/theme.service.ts @@ -20,7 +20,7 @@ export class ThemeService { body.classList.toggle('dark', isDark); overlayEl.classList.toggle('dark', isDark); - try { localStorage.setItem(LocalStoreConstants.THEME_KEY, this.theme()); } catch {} + try { localStorage.setItem(LocalStoreConstants.THEME_KEY, this.theme()); } catch (e) { void e; } }); try { @@ -29,7 +29,7 @@ export class ThemeService { const stored = localStorage.getItem(LocalStoreConstants.THEME_KEY) as Theme | null; if (!stored) this.setTheme(e.matches ? 'dark' : 'light'); }); - } catch {} + } catch (e) { void e; } } toggle() { this.setTheme(this.theme() === 'dark' ? 'light' : 'dark'); } @@ -39,10 +39,10 @@ export class ThemeService { try { const stored = localStorage.getItem(LocalStoreConstants.THEME_KEY) as Theme | null; if (stored === 'dark' || stored === 'light') return stored; - } catch {} + } catch (e) { void e; } try { return globalThis.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; - } catch {} + } catch (e) { void e; } return 'light'; } } diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 0c61726..71ec48c 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -301,10 +301,13 @@ "CLEAR_NODE": "Löschen", "DIJKSTRA": "Dijkstra", "ASTAR": "A*", + "RESET_BOARD": "Board zurücksetzten", "CLEAR_BOARD": "Board leeren", "CLEAR_PATH": "Pfad löschen", "VISITED": "Besucht", "PATH": "Pfad", + "PATH_LENGTH": "Pfadlänge", + "EXECUTION_TIME": "Ausführungszeit", "ALERT": { "START_END_NODES": "Bitte wählen Sie einen Start- und Endknoten aus, bevor Sie den Algorithmus starten." } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index d8c5241..0c382f1 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -301,10 +301,13 @@ "CLEAR_NODE": "Clear", "DIJKSTRA": "Dijkstra", "ASTAR": "A*", + "RESET_BOARD": "Reset Board", "CLEAR_BOARD": "Clear Board", "CLEAR_PATH": "Clear Path", "VISITED": "Visited", "PATH": "Path", + "PATH_LENGTH": "Path length", + "EXECUTION_TIME": "Execution Time", "ALERT": { "START_END_NODES": "Please select a start and end node before running the algorithm." }