From cbc46cf85867cadf76637158d59fe26d79e09698 Mon Sep 17 00:00:00 2001 From: Lobo Date: Wed, 4 Feb 2026 09:22:05 +0100 Subject: [PATCH] initial implementation of sorting algorithms --- GEMINI.md | 79 +++++++++++++++ src/app/app.routes.ts | 1 + src/app/constants/RouterConstants.ts | 7 ++ .../algorithms/service/algorithms.service.ts | 6 ++ .../sorting/service/sorting.service.ts | 97 +++++++++++++++++++ .../algorithms/sorting/sorting.component.html | 44 +++++++++ .../algorithms/sorting/sorting.component.scss | 62 ++++++++++++ .../algorithms/sorting/sorting.component.ts | 92 ++++++++++++++++++ .../algorithms/sorting/sorting.models.ts | 4 + src/assets/i18n/de.json | 9 ++ src/assets/i18n/en.json | 9 ++ 11 files changed, 410 insertions(+) create mode 100644 GEMINI.md create mode 100644 src/app/pages/algorithms/sorting/service/sorting.service.ts create mode 100644 src/app/pages/algorithms/sorting/sorting.component.html create mode 100644 src/app/pages/algorithms/sorting/sorting.component.scss create mode 100644 src/app/pages/algorithms/sorting/sorting.component.ts create mode 100644 src/app/pages/algorithms/sorting/sorting.models.ts diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..5ef08da --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,79 @@ +# Gemini CLI Context for playground-frontend + +## Project Overview + +This is the frontend of the Playground project, built with Angular 21 and Angular Material. It includes features like a light/dark theme toggle and multi-language support via ngx-translate. The application is a static Single Page Application (SPA) served by NGINX. + +**Key Technologies:** +* **Frontend Framework:** Angular 21 +* **UI Components & Theming:** Angular Material +* **Internationalization:** ngx-translate +* **Server:** NGINX (for serving the SPA) +* **Containerization:** Docker +* **CI/CD:** GitHub Actions + +## Building and Running + +### Local Development + +1. **Install dependencies:** + ```bash + npm install + ``` +2. **Start development server:** + ```bash + ng serve --open + ``` + The app will run at `http://localhost:4200`. + +### Building for Production + +To build the project for production, which creates the optimized static files: +```bash +ng build +``` + +### Running Tests + +To run the unit tests: +```bash +ng test +``` + +### Linting + +To lint the codebase: +```bash +ng lint +``` + +### Docker + +To build and run the application using Docker locally: + +1. **Build Docker image:** + ```bash + docker build -t playground-frontend:local . + ``` +2. **Run Docker container:** + ```bash + docker run -p 8080:80 playground-frontend:local + ``` + Then open `http://localhost:8080` in your browser. + +## Development Conventions + +* **Language:** TypeScript +* **Framework:** Angular +* **Styling:** SCSS (based on `styles.scss` and component-specific `.scss` files). +* **Linting:** ESLint is configured (see `eslint.config.js` and `package.json` scripts). +* **Internationalization:** Uses `ngx-translate` with `en.json` and `de.json` asset files. + +## Project Structure (Key Areas) + +* `src/app/`: Contains the main application logic, components, services, and routing. +* `src/app/pages/`: Specific pages of the application (e.g., about, algorithms, imprint, projects). +* `src/assets/`: Static assets including images, internationalization files (`i18n`), and logos. +* `Dockerfile`: Defines the Docker image for the application. +* `nginx.conf`: NGINX configuration for serving the SPA. +* `.gitea/workflows/`: Contains CI/CD workflows (e.g., `build-Frontend-a.yml`). diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index c416983..55aa00e 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -8,6 +8,7 @@ export const routes: Routes = [ { path: RouterConstants.PROJECTS.PATH, component: RouterConstants.PROJECTS.COMPONENT}, { path: RouterConstants.ALGORITHMS.PATH, component: RouterConstants.ALGORITHMS.COMPONENT}, { path: RouterConstants.PATHFINDING.PATH, component: RouterConstants.PATHFINDING.COMPONENT}, + { path: RouterConstants.SORTING.PATH, component: RouterConstants.SORTING.COMPONENT}, { path: RouterConstants.IMPRINT.PATH, component: RouterConstants.IMPRINT.COMPONENT}, ]; diff --git a/src/app/constants/RouterConstants.ts b/src/app/constants/RouterConstants.ts index 50e2d6d..ce20694 100644 --- a/src/app/constants/RouterConstants.ts +++ b/src/app/constants/RouterConstants.ts @@ -3,6 +3,7 @@ import {ProjectsComponent} from '../pages/projects/projects.component'; import {ImprintComponent} from '../pages/imprint/imprint.component'; import {AlgorithmsComponent} from '../pages/algorithms/algorithms.component'; import {PathfindingComponent} from '../pages/algorithms/pathfinding/pathfinding.component'; +import {SortingComponent} from '../pages/algorithms/sorting/sorting.component'; export class RouterConstants { @@ -30,6 +31,12 @@ export class RouterConstants { COMPONENT: PathfindingComponent }; + static readonly SORTING = { + PATH: 'algorithms/sorting', + LINK: '/algorithms/sorting', + COMPONENT: SortingComponent + }; + static readonly IMPRINT = { PATH: 'imprint', LINK: '/imprint', diff --git a/src/app/pages/algorithms/service/algorithms.service.ts b/src/app/pages/algorithms/service/algorithms.service.ts index a317759..6fd5b0c 100644 --- a/src/app/pages/algorithms/service/algorithms.service.ts +++ b/src/app/pages/algorithms/service/algorithms.service.ts @@ -14,6 +14,12 @@ export class AlgorithmsService { title: 'ALGORITHM.PATHFINDING.TITLE', description: 'ALGORITHM.PATHFINDING.DESCRIPTION', routerLink: RouterConstants.PATHFINDING.LINK + }, + { + id: 'sorting', + title: 'ALGORITHM.SORTING.TITLE', + description: 'ALGORITHM.SORTING.DESCRIPTION', + routerLink: RouterConstants.SORTING.LINK } ]; diff --git a/src/app/pages/algorithms/sorting/service/sorting.service.ts b/src/app/pages/algorithms/sorting/service/sorting.service.ts new file mode 100644 index 0000000..bbad7b1 --- /dev/null +++ b/src/app/pages/algorithms/sorting/service/sorting.service.ts @@ -0,0 +1,97 @@ +import { Injectable } from '@angular/core'; +import { SortData } from '../sorting.models'; + +@Injectable({ + providedIn: 'root' +}) +export class SortingService { + + constructor() { } + + async bubbleSort(array: SortData[], animationSpeed: number): Promise { + console.log(animationSpeed); + const n = array.length; + for (let i = 0; i < n - 1; i++) { + console.log("1"); + for (let j = 0; j < n - i - 1; j++) { + console.log("2"); + array[j].state = 'comparing'; + array[j + 1].state = 'comparing'; + await this.delay(animationSpeed); + + if (array[j].value > array[j + 1].value) { + // Swap elements + const temp = array[j].value; + array[j].value = array[j + 1].value; + array[j + 1].value = temp; + } + + array[j].state = 'unsorted'; + array[j + 1].state = 'unsorted'; + } + array[n - 1 - i].state = 'sorted'; // Mark the largest element as sorted + } + array[0].state = 'sorted'; // Mark the last element as sorted (if n > 0) + } + + async selectionSort(array: SortData[], animationSpeed: number): Promise { + const n = array.length; + for (let i = 0; i < n - 1; i++) { + let minIdx = i; + array[i].state = 'comparing'; + for (let j = i + 1; j < n; j++) { + array[j].state = 'comparing'; + await this.delay(animationSpeed); + + if (array[j].value < array[minIdx].value) { + minIdx = j; + } + if (j !== minIdx) { // Only reset if it wasn't the minimum + array[j].state = 'unsorted'; + } + } + if (minIdx !== i) { + // Swap elements + const temp = array[i].value; + array[i].value = array[minIdx].value; + array[minIdx].value = temp; + } + array[i].state = 'sorted'; // Mark the current element as sorted + if (minIdx !== i) { + array[minIdx].state = 'unsorted'; + } + } + array[n - 1].state = 'sorted'; // Mark the last element as sorted + } + + async insertionSort(array: SortData[], animationSpeed: number): Promise { + const n = array.length; + for (let i = 1; i < n; i++) { + const key = array[i].value; + array[i].state = 'comparing'; + let j = i - 1; + + while (j >= 0 && array[j].value > key) { + array[j].state = 'comparing'; + await this.delay(animationSpeed); + + array[j + 1].value = array[j].value; + array[j + 1].state = 'unsorted'; // Reset after shifting + j = j - 1; + } + await this.delay(animationSpeed); // Delay after loop for final position + array[j + 1].value = key; + array[i].state = 'unsorted'; // Reset original 'key' position + array.forEach((item, idx) => { // Mark sorted up to i + if (idx <= i) { + item.state = 'sorted'; + } + }); + } + array.forEach(item => item.state = 'sorted'); // Mark all as sorted at the end + } + + private delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} diff --git a/src/app/pages/algorithms/sorting/sorting.component.html b/src/app/pages/algorithms/sorting/sorting.component.html new file mode 100644 index 0000000..8133128 --- /dev/null +++ b/src/app/pages/algorithms/sorting/sorting.component.html @@ -0,0 +1,44 @@ +
+ + + {{ 'ALGORITHM.SORTING.TITLE' | translate }} + + +
+ + {{ 'ALGORITHM.SORTING.ALGORITHM' | translate }} + + @for (algo of availableAlgorithms; track algo.value) { + {{ algo.name }} + } + + + + + + +
+ +
+

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

+
+
+ @for (item of sortArray; track $index) { +
+ } +
+
+
+
diff --git a/src/app/pages/algorithms/sorting/sorting.component.scss b/src/app/pages/algorithms/sorting/sorting.component.scss new file mode 100644 index 0000000..128fcaf --- /dev/null +++ b/src/app/pages/algorithms/sorting/sorting.component.scss @@ -0,0 +1,62 @@ +.sorting-container { + display: flex; + justify-content: center; + align-items: flex-start; + padding: 20px; + height: 100%; + box-sizing: border-box; + + .sorting-card { + width: 100%; + max-width: 1200px; + padding: 20px; + + .controls-panel { + display: flex; + gap: 10px; + margin-bottom: 20px; + align-items: center; + flex-wrap: wrap; + + mat-form-field { + width: 200px; + } + } + + .visualization-area { + display: flex; + align-items: flex-end; + height: 300px; /* Max height for bars */ + border-bottom: 1px solid #ccc; + margin-bottom: 20px; + gap: 1px; + background-color: #f0f0f0; + + .bar { + flex-grow: 1; + background-color: #424242; /* Default unsorted color */ + transition: height 0.1s ease-in-out, background-color 0.1s ease-in-out; + width: 10px; /* Default width, flex-grow will adjust */ + min-width: 1px; /* Ensure bars are always visible */ + + &.unsorted { + background-color: #424242; + } + + &.comparing { + background-color: #ffeb3b; /* Yellow for comparing */ + } + + &.sorted { + background-color: #4caf50; /* Green for sorted */ + } + } + } + + .info-panel { + margin-top: 10px; + font-size: 0.9em; + color: #555; + } + } +} diff --git a/src/app/pages/algorithms/sorting/sorting.component.ts b/src/app/pages/algorithms/sorting/sorting.component.ts new file mode 100644 index 0000000..43bd720 --- /dev/null +++ b/src/app/pages/algorithms/sorting/sorting.component.ts @@ -0,0 +1,92 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import {MatCardModule} from "@angular/material/card"; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {MatSelectModule} from "@angular/material/select"; +import {MatButtonModule} from "@angular/material/button"; +import {MatIconModule} from "@angular/material/icon"; +import {TranslateModule} from "@ngx-translate/core"; +import { SortingService } from './service/sorting.service'; +import { SortData } from './sorting.models'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-sorting', + standalone: true, + imports: [CommonModule, MatCardModule, MatFormFieldModule, MatSelectModule, MatButtonModule, MatIconModule, TranslateModule, FormsModule], + templateUrl: './sorting.component.html', + styleUrls: ['./sorting.component.scss'] +}) +export class SortingComponent implements OnInit { + + sortArray: SortData[] = []; + arraySize: number = 50; + maxArrayValue: number = 100; + animationSpeed: number = 10; // Milliseconds per step + + // Placeholder for available sorting algorithms + availableAlgorithms: { name: string; value: string }[] = [ + { name: 'Bubble Sort', value: 'bubbleSort' }, + { name: 'Selection Sort', value: 'selectionSort' }, + { name: 'Insertion Sort', value: 'insertionSort' }, + ]; + selectedAlgorithm: string = this.availableAlgorithms[0].value; + isSorting: boolean = false; + executionTime: number = 0; + + constructor(private sortingService: SortingService) { } + + ngOnInit(): void { + this.generateNewArray(); + } + + generateNewArray(): void { + this.isSorting = false; + this.executionTime = 0; + this.sortArray = []; + for (let i = 0; i < this.arraySize; i++) { + this.sortArray.push({ + value: Math.floor(Math.random() * this.maxArrayValue) + 1, + state: 'unsorted' + }); + } + } + + async startSorting(): Promise { + if (this.isSorting) { + return; + } + console.log('Starting sorting...'); + this.isSorting = true; + this.executionTime = 0; + + // Reset states for visualization + this.sortArray.forEach(item => item.state = 'unsorted'); + const startTime = performance.now(); + console.log("Select algorithm ", this.selectedAlgorithm); + + switch (this.selectedAlgorithm) { + case 'bubbleSort': + await this.sortingService.bubbleSort(this.sortArray, this.animationSpeed); + break; + case 'selectionSort': + await this.sortingService.selectionSort(this.sortArray, this.animationSpeed); + break; + case 'insertionSort': + await this.sortingService.insertionSort(this.sortArray, this.animationSpeed); + break; + default: + console.warn('Unknown sorting algorithm selected:', this.selectedAlgorithm); + break; + } + console.log("Done with sorting..."); + const endTime = performance.now(); + this.executionTime = Math.round(endTime - startTime); + this.isSorting = false; + this.sortArray.forEach(item => item.state = 'sorted'); // Mark all as sorted after completion + } + + resetSorting(): void { + this.generateNewArray(); + } +} diff --git a/src/app/pages/algorithms/sorting/sorting.models.ts b/src/app/pages/algorithms/sorting/sorting.models.ts new file mode 100644 index 0000000..2741353 --- /dev/null +++ b/src/app/pages/algorithms/sorting/sorting.models.ts @@ -0,0 +1,4 @@ +export interface SortData { + value: number; + state: 'sorted' | 'comparing' | 'unsorted'; +} diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index d30988a..1d0e778 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -326,6 +326,15 @@ "DESCRIPTION": "Vergleich von Dijkstra vs. A*.", "GRID_HEIGHT": "Höhe", "GRID_WIDTH": "Beite" + }, + "SORTING": { + "TITLE": "Sortieralgorithmen", + "DESCRIPTION": "Visualisierung verschiedener Sortieralgorithmen.", + "ALGORITHM": "Algorithmus", + "START": "Sortierung starten", + "RESET": "Zurücksetzen", + "GENERATE_NEW_ARRAY": "Neues Array generieren", + "EXECUTION_TIME": "Ausführungszeit" } } } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 71e8757..15c429a 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -326,6 +326,15 @@ "DESCRIPTION": "Comparing of Dijkstra vs. A*.", "GRID_HEIGHT": "Height", "GRID_WIDTH": "Width" + }, + "SORTING": { + "TITLE": "Sorting Algorithms", + "DESCRIPTION": "Visualizing various sorting algorithms.", + "ALGORITHM": "Algorithm", + "START": "Start Sorting", + "RESET": "Reset", + "GENERATE_NEW_ARRAY": "Generate New Array", + "EXECUTION_TIME": "Execution Time" } } }