Merge pull request 'feature/optimize' (#14) from feature/optimize into main
Reviewed-on: #14
This commit was merged in pull request #14.
This commit is contained in:
@@ -1,12 +1,84 @@
|
|||||||
name: Build & Push Frontend A
|
name: Build, Test & Push Frontend
|
||||||
|
run-name: ${{ gitea.actor }} build and test Angular 🚀
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
# ------------------------------------------------------------------
|
||||||
|
# JOB 1: Code integrity and quality gates (CI)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
quality-check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install Linux Libs
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y \
|
||||||
|
libnss3 \
|
||||||
|
libnspr4 \
|
||||||
|
libatk1.0-0 \
|
||||||
|
libatk-bridge2.0-0 \
|
||||||
|
libcups2t64 \
|
||||||
|
libdrm2 \
|
||||||
|
libxkbcommon0 \
|
||||||
|
libxcomposite1 \
|
||||||
|
libxdamage1 \
|
||||||
|
libxfixes3 \
|
||||||
|
libxrandr2 \
|
||||||
|
libgbm1 \
|
||||||
|
libasound2t64 \
|
||||||
|
libpango-1.0-0 \
|
||||||
|
libcairo2
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
# 1. Linting (Code-Style)
|
||||||
|
- name: Lint & Type Check
|
||||||
|
run: npm run lint --if-present
|
||||||
|
|
||||||
|
# 2. Unit Tests (Logik) Not necessary, because atm no tests written
|
||||||
|
#- name: Unit Tests
|
||||||
|
# run: npx ng test --watch=false --browsers=ChromeHeadless
|
||||||
|
|
||||||
|
# 3. Build Production (necessary for lighthouse)
|
||||||
|
- name: Build Production
|
||||||
|
run: npx ng build --configuration production
|
||||||
|
|
||||||
|
# 4. Lighthouse Audit (Performance & SEO)
|
||||||
|
- name: Install Puppeteer
|
||||||
|
run: npm install puppeteer --no-save
|
||||||
|
|
||||||
|
- name: Lighthouse CI
|
||||||
|
run: |
|
||||||
|
CHROME_PATH=$(node -e 'console.log(require("puppeteer").executablePath())')
|
||||||
|
export CHROME_PATH=$CHROME_PATH
|
||||||
|
npx lhci autorun
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# JOB 2: Docker Build & Push (CD)
|
||||||
|
# Runs only if 'quality-check' are successfully and we are on branch main.
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
docker:
|
||||||
|
needs: quality-check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
@@ -36,8 +108,7 @@ jobs:
|
|||||||
docker.io/${{ secrets.DOCKERHUB_USERNAME }}/playground:frontend-a-${{ steps.prep.outputs.branch }}
|
docker.io/${{ secrets.DOCKERHUB_USERNAME }}/playground:frontend-a-${{ steps.prep.outputs.branch }}
|
||||||
docker.io/${{ secrets.DOCKERHUB_USERNAME }}/playground:frontend-a-${{ steps.prep.outputs.branch }}-${{ steps.prep.outputs.sha }}
|
docker.io/${{ secrets.DOCKERHUB_USERNAME }}/playground:frontend-a-${{ steps.prep.outputs.branch }}-${{ steps.prep.outputs.sha }}
|
||||||
|
|
||||||
- name: Also push moving main tag (only on main)
|
- name: Also push moving main tag
|
||||||
if: ${{ github.ref == 'refs/heads/main' }}
|
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -41,3 +41,6 @@ __screenshots__/
|
|||||||
# System files
|
# System files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
# Lighthouse
|
||||||
|
.lighthouseci/
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ module.exports = defineConfig([
|
|||||||
],
|
],
|
||||||
processor: angular.processInlineTemplates,
|
processor: angular.processInlineTemplates,
|
||||||
rules: {
|
rules: {
|
||||||
|
"@typescript-eslint/no-inferrable-types": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@angular-eslint/directive-selector": [
|
"@angular-eslint/directive-selector": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
|||||||
14
lighthouserc.json
Normal file
14
lighthouserc.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"ci": {
|
||||||
|
"collect": {
|
||||||
|
"numberOfRuns": 1,
|
||||||
|
"staticDistDir": "./dist/playground-frontend/browser",
|
||||||
|
"settings": {
|
||||||
|
"chromeFlags": "--no-sandbox --headless --disable-gpu --disable-dev-shm-usage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"target": "temporary-public-storage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3082
package-lock.json
generated
3082
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,7 @@
|
|||||||
"@angular/build": "~21.1.0",
|
"@angular/build": "~21.1.0",
|
||||||
"@angular/cli": "~21.1.0",
|
"@angular/cli": "~21.1.0",
|
||||||
"@angular/compiler-cli": "~21.1.0",
|
"@angular/compiler-cli": "~21.1.0",
|
||||||
|
"@lhci/cli": "~0.15.1",
|
||||||
"@types/jasmine": "~5.1.15",
|
"@types/jasmine": "~5.1.15",
|
||||||
"angular-eslint": "21.2.0",
|
"angular-eslint": "21.2.0",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
<mat-icon>play_arrow</mat-icon> {{ 'GOL.START' | translate }}
|
<mat-icon>play_arrow</mat-icon> {{ 'GOL.START' | translate }}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
<p>{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime }} ms</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-size">
|
<div class="grid-size">
|
||||||
<mat-form-field appearance="outline" class="grid-field">
|
<mat-form-field appearance="outline" class="grid-field">
|
||||||
@@ -69,6 +70,7 @@
|
|||||||
(keyup.enter)="applySpeed()"
|
(keyup.enter)="applySpeed()"
|
||||||
/>
|
/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="legend">
|
<div class="legend">
|
||||||
<span><span class="legend-color alive"></span> {{ 'GOL.ALIVE' | translate }}</span>
|
<span><span class="legend-color alive"></span> {{ 'GOL.ALIVE' | translate }}</span>
|
||||||
@@ -84,7 +86,8 @@
|
|||||||
[createNodeFn]="createConwayNode"
|
[createNodeFn]="createConwayNode"
|
||||||
[getNodeColorFn]="getConwayNodeColor"
|
[getNodeColorFn]="getConwayNodeColor"
|
||||||
[applySelectionFn]="applyConwaySelection"
|
[applySelectionFn]="applyConwaySelection"
|
||||||
(gridChange)="grid = $event"
|
[backgroundColor]="'lightgray'"
|
||||||
|
(gridChange)="readGrid = $event"
|
||||||
></app-generic-grid>
|
></app-generic-grid>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const DEFAULT_GRID_ROWS = 50;
|
|||||||
export const DEFAULT_GRID_COLS = 50;
|
export const DEFAULT_GRID_COLS = 50;
|
||||||
|
|
||||||
export const MIN_GRID_SIZE = 20;
|
export const MIN_GRID_SIZE = 20;
|
||||||
export const MAX_GRID_SIZE = 100;
|
export const MAX_GRID_SIZE = 200;
|
||||||
export const DEFAULT_TIME_PER_GENERATION = 30;
|
export const DEFAULT_TIME_PER_GENERATION = 30;
|
||||||
|
|
||||||
export const MIN_TIME_PER_GENERATION = 20;
|
export const MIN_TIME_PER_GENERATION = 20;
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ export class ConwayGol implements AfterViewInit {
|
|||||||
protected readonly MAX_GRID_SIZE = MAX_GRID_SIZE;
|
protected readonly MAX_GRID_SIZE = MAX_GRID_SIZE;
|
||||||
protected readonly MAX_GRID_PX = MAX_GRID_PX;
|
protected readonly MAX_GRID_PX = MAX_GRID_PX;
|
||||||
|
|
||||||
grid: Node[][] = [];
|
readGrid: Node[][] = [];
|
||||||
|
writeGrid: Node[][] = [];
|
||||||
|
executionTime = 0;
|
||||||
currentScenario: Scenario = Scenario.SIMPLE;
|
currentScenario: Scenario = Scenario.SIMPLE;
|
||||||
readonly gameStarted = signal(false);
|
readonly gameStarted = signal(false);
|
||||||
|
|
||||||
@@ -96,45 +98,42 @@ export class ConwayGol implements AfterViewInit {
|
|||||||
};
|
};
|
||||||
|
|
||||||
getConwayNodeColor = (node: Node): string => {
|
getConwayNodeColor = (node: Node): string => {
|
||||||
if (node.alive) {
|
return node.alive ? 'black' : 'lightgray';
|
||||||
return 'black';
|
|
||||||
}
|
|
||||||
return 'lightgray';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
applyConwaySelection = (pos: GridPos, grid: Node[][]): void => {
|
applyConwaySelection = (pos: GridPos, grid: Node[][]): void => {
|
||||||
this.grid = grid; // Keep internal grid in sync
|
this.readGrid = grid; // Keep internal grid in sync
|
||||||
const node = grid[pos.row][pos.col];
|
const node = grid[pos.row][pos.col];
|
||||||
node.alive = !node.alive; // Toggle alive status
|
node.alive = !node.alive; // Toggle alive status
|
||||||
};
|
};
|
||||||
|
|
||||||
initializeConwayGrid = (grid: Node[][]): void => {
|
initializeConwayGrid = (grid: Node[][]): void => {
|
||||||
this.gameStarted.set(false);
|
this.gameStarted.set(false);
|
||||||
this.grid = grid;
|
this.readGrid = grid;
|
||||||
|
|
||||||
switch(this.currentScenario) {
|
switch(this.currentScenario) {
|
||||||
case Scenario.RANDOM: this.setupRandomLives(); break;
|
case Scenario.RANDOM: this.setupRandomLives(); break;
|
||||||
case Scenario.SIMPLE: this.setupSimpleLive(); break;
|
case Scenario.SIMPLE: this.setupSimpleLive(); break;
|
||||||
case Scenario.PULSAR: this.setupPulsar(); break;
|
case Scenario.PULSAR: this.setupPulsar(); break;
|
||||||
case Scenario.GUN: this.setupGliderGun(); break;
|
case Scenario.GUN: this.setupGliderGun(); break;
|
||||||
}
|
}
|
||||||
|
this.writeGrid = structuredClone(this.readGrid);
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Conway-specific logic (kept local) ---
|
// --- Conway-specific logic (kept local) ---
|
||||||
setupRandomLives(): void {
|
setupRandomLives(): void {
|
||||||
for (let row = 0; row < this.gridRows; row++) {
|
for (let row = 0; row < this.gridRows; row++) {
|
||||||
for (let col = 0; col < this.gridCols; col++) {
|
for (let col = 0; col < this.gridCols; col++) {
|
||||||
this.grid[row][col].alive = Math.random() <= LIVE_SPAWN_PROBABILITY;
|
this.readGrid[row][col].alive = Math.random() <= LIVE_SPAWN_PROBABILITY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupSimpleLive(): void {
|
setupSimpleLive(): void {
|
||||||
this.grid[3][4].alive = true;
|
this.readGrid[3][4].alive = true;
|
||||||
this.grid[4][5].alive = true;
|
this.readGrid[4][5].alive = true;
|
||||||
this.grid[5][3].alive = true;
|
this.readGrid[5][3].alive = true;
|
||||||
this.grid[5][4].alive = true;
|
this.readGrid[5][4].alive = true;
|
||||||
this.grid[5][5].alive = true;
|
this.readGrid[5][5].alive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
setupPulsar(): void {
|
setupPulsar(): void {
|
||||||
@@ -181,39 +180,44 @@ export class ConwayGol implements AfterViewInit {
|
|||||||
this.gameStarted.set(true);
|
this.gameStarted.set(true);
|
||||||
let lifeIsDead = false;
|
let lifeIsDead = false;
|
||||||
while (this.gameStarted()){
|
while (this.gameStarted()){
|
||||||
let gridClone = structuredClone(this.grid);
|
const startTime = performance.now();
|
||||||
lifeIsDead = true;
|
lifeIsDead = true;
|
||||||
for (let row = 0; row < this.gridRows; row++) {
|
for (let row = 0; row < this.gridRows; row++) {
|
||||||
for (let col = 0; col < this.gridCols; col++) {
|
for (let col = 0; col < this.gridCols; col++) {
|
||||||
lifeIsDead = this.checkLifeRules(row, col, gridClone, lifeIsDead);
|
lifeIsDead = this.checkLifeRules(row, col, this.writeGrid) && lifeIsDead;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.swapGrid(gridClone);
|
this.swapGrids();
|
||||||
|
const endTime = performance.now();
|
||||||
|
this.executionTime = Number.parseFloat((endTime - startTime).toFixed(4));
|
||||||
if (lifeIsDead){
|
if (lifeIsDead){
|
||||||
this.gameStarted.set(false);
|
this.gameStarted.set(false);
|
||||||
}
|
}
|
||||||
await this.delay(this.lifeSpeed);
|
const delta = Math.max(this.lifeSpeed - this.executionTime, 0);
|
||||||
|
await this.delay(delta);
|
||||||
}
|
}
|
||||||
|
this.executionTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkLifeRules(row: number, col: number, gridClone: Node[][], lifeIsDead: boolean) {
|
private checkLifeRules(row: number, col: number, writeGrid: Node[][]): boolean {
|
||||||
const itsMe = this.grid[row][col];
|
const currentCell = this.readGrid[row][col];
|
||||||
let aliveNeighbors = this.howManyNeighborsAreLiving(row, col);
|
const aliveNeighbors = this.howManyNeighborsAreLiving(row, col);
|
||||||
if (itsMe.alive && (aliveNeighbors < 2 || aliveNeighbors > 3)) {
|
const oldLifeState = currentCell.alive;
|
||||||
gridClone[row][col].alive = false;
|
|
||||||
lifeIsDead = false;
|
const nextStateAlive = (currentCell.alive && (aliveNeighbors === 2 || aliveNeighbors === 3)) || (!currentCell.alive && aliveNeighbors === 3);
|
||||||
} else if (!itsMe.alive && aliveNeighbors === 3) {
|
writeGrid[row][col].alive = nextStateAlive;
|
||||||
gridClone[row][col].alive = true;
|
|
||||||
lifeIsDead = false;
|
//only if at least one cell changes the game is still alive
|
||||||
}
|
return (nextStateAlive == oldLifeState);
|
||||||
return lifeIsDead;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private swapGrid(gridClone: Node[][]) {
|
private swapGrids() {
|
||||||
this.grid = gridClone;
|
const tmp = this.readGrid;
|
||||||
|
this.readGrid = this.writeGrid;
|
||||||
|
this.writeGrid = tmp;
|
||||||
if (this.genericGridComponent) {
|
if (this.genericGridComponent) {
|
||||||
this.genericGridComponent.grid = this.grid;
|
this.genericGridComponent.grid = this.readGrid;
|
||||||
this.genericGridComponent.drawGrid();
|
this.genericGridComponent.drawGrid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,7 +235,7 @@ export class ConwayGol implements AfterViewInit {
|
|||||||
if (nRow == row && nCol == col) {
|
if (nRow == row && nCol == col) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (this.grid[nRow][nCol].alive) {
|
if (this.readGrid[nRow][nCol].alive) {
|
||||||
aliveNeighborCount++;
|
aliveNeighborCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,7 +254,7 @@ export class ConwayGol implements AfterViewInit {
|
|||||||
|
|
||||||
private setAlive(r: number, c: number): void {
|
private setAlive(r: number, c: number): void {
|
||||||
if (r >= 0 && r < this.gridRows && c >= 0 && c < this.gridCols) {
|
if (r >= 0 && r < this.gridRows && c >= 0 && c < this.gridCols) {
|
||||||
this.grid[r][c].alive = true;
|
this.readGrid[r][c].alive = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@
|
|||||||
[createNodeFn]="createPathfindingNode"
|
[createNodeFn]="createPathfindingNode"
|
||||||
[getNodeColorFn]="getPathfindingNodeColor"
|
[getNodeColorFn]="getPathfindingNodeColor"
|
||||||
[applySelectionFn]="applyPathfindingSelection"
|
[applySelectionFn]="applyPathfindingSelection"
|
||||||
|
[backgroundColor]="'lightgray'"
|
||||||
(gridChange)="grid = $event"
|
(gridChange)="grid = $event"
|
||||||
></app-generic-grid>
|
></app-generic-grid>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
|
|||||||
@@ -136,7 +136,6 @@ export class SortingComponent implements OnInit {
|
|||||||
const endTime = performance.now();
|
const endTime = performance.now();
|
||||||
this.executionTime = Number.parseFloat((endTime - startTime).toFixed(4));
|
this.executionTime = Number.parseFloat((endTime - startTime).toFixed(4));
|
||||||
|
|
||||||
console.log(snapshots.length);
|
|
||||||
this.animateSorting(snapshots);
|
this.animateSorting(snapshots);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export class GenericGridComponent implements AfterViewInit {
|
|||||||
@Input() minGridSize: number = 5;
|
@Input() minGridSize: number = 5;
|
||||||
@Input() maxGridSize: number = 50;
|
@Input() maxGridSize: number = 50;
|
||||||
@Input() drawNodeBorderColor: string = '#ccc';
|
@Input() drawNodeBorderColor: string = '#ccc';
|
||||||
|
@Input() backgroundColor: string = 'lightgray';
|
||||||
|
|
||||||
// Callbacks from parent component
|
// Callbacks from parent component
|
||||||
@Input() createNodeFn!: (row: number, col: number) => any;
|
@Input() createNodeFn!: (row: number, col: number) => any;
|
||||||
@@ -99,19 +100,70 @@ export class GenericGridComponent implements AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
drawGrid(): void {
|
drawGrid(): void {
|
||||||
this.ctx.clearRect(0, 0, this.canvas.nativeElement.width, this.canvas.nativeElement.height);
|
if (!this.ctx || !this.grid.length) return;
|
||||||
|
|
||||||
|
const width = this.canvas.nativeElement.width;
|
||||||
|
const height = this.canvas.nativeElement.height;
|
||||||
|
const size = this.nodeSize;
|
||||||
|
|
||||||
|
this.ctx.fillStyle = this.backgroundColor;
|
||||||
|
this.ctx.fillRect(0, 0, width, height);
|
||||||
|
this.ctx.fillStyle = 'black';
|
||||||
|
|
||||||
for (let row = 0; row < this.gridRows; row++) {
|
for (let row = 0; row < this.gridRows; row++) {
|
||||||
for (let col = 0; col < this.gridCols; col++) {
|
for (let col = 0; col < this.gridCols; col++) {
|
||||||
this.drawNode(this.grid[row][col]);
|
const node = this.grid[row][col];
|
||||||
|
|
||||||
|
const color = this.getNodeColorFn(node);
|
||||||
|
|
||||||
|
if (color !== this.backgroundColor) {
|
||||||
|
if (this.ctx.fillStyle !== color) {
|
||||||
|
this.ctx.fillStyle = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = col * this.nodeSize;
|
||||||
|
const y = row * this.nodeSize;
|
||||||
|
|
||||||
|
this.ctx.fillRect(x, y, this.nodeSize, this.nodeSize);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (size > 2) {
|
||||||
|
this.drawGridLines(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private drawGridLines(width: number, height: number): void {
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.strokeStyle = this.drawNodeBorderColor;
|
||||||
|
this.ctx.lineWidth = 1;
|
||||||
|
|
||||||
|
for (let col = 0; col <= this.gridCols; col++) {
|
||||||
|
const x = col * this.nodeSize;
|
||||||
|
this.ctx.moveTo(x, 0);
|
||||||
|
this.ctx.lineTo(x, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let row = 0; row <= this.gridRows; row++) {
|
||||||
|
const y = row * this.nodeSize;
|
||||||
|
this.ctx.moveTo(0, y);
|
||||||
|
this.ctx.lineTo(width, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
drawNode(node: any): void {
|
drawNode(node: any): void {
|
||||||
this.ctx.fillStyle = this.getNodeColorFn(node);
|
this.ctx.fillStyle = this.getNodeColorFn(node);
|
||||||
this.ctx.fillRect(node.col * this.nodeSize, node.row * this.nodeSize, this.nodeSize, this.nodeSize);
|
this.ctx.fillRect(node.col * this.nodeSize, node.row * this.nodeSize, this.nodeSize, this.nodeSize);
|
||||||
this.ctx.strokeStyle = this.drawNodeBorderColor;
|
|
||||||
this.ctx.strokeRect(node.col * this.nodeSize, node.row * this.nodeSize, this.nodeSize, this.nodeSize);
|
if (this.nodeSize > 4) {
|
||||||
|
this.ctx.strokeStyle = this.drawNodeBorderColor;
|
||||||
|
this.ctx.strokeRect(node.col * this.nodeSize, node.row * this.nodeSize, this.nodeSize, this.nodeSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getContextOrThrow(): CanvasRenderingContext2D {
|
private getContextOrThrow(): CanvasRenderingContext2D {
|
||||||
|
|||||||
Reference in New Issue
Block a user