This commit is contained in:
miqlangelo 2025-05-07 13:00:33 +02:00
parent 70d54d3384
commit ba20e7ea18
70 changed files with 15881 additions and 0 deletions

View File

@ -0,0 +1,17 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
ij_typescript_use_double_quotes = false
[*.md]
max_line_length = off
trim_trailing_whitespace = false

42
src/09 - Decorators/angular/.gitignore vendored Normal file
View File

@ -0,0 +1,42 @@
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db

View File

@ -0,0 +1,4 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}

View File

@ -0,0 +1,20 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ng serve",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: start",
"url": "http://localhost:4200/"
},
{
"name": "ng test",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: test",
"url": "http://localhost:9876/debug.html"
}
]
}

View File

@ -0,0 +1,42 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
},
{
"type": "npm",
"script": "test",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
}
]
}

View File

@ -0,0 +1,59 @@
# DecoratorDemo
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.2.1.
## Development server
To start a local development server, run:
```bash
ng serve
```
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
## Code scaffolding
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
```bash
ng generate component component-name
```
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
```bash
ng generate --help
```
## Building
To build the project run:
```bash
ng build
```
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
## Running unit tests
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
```bash
ng test
```
## Running end-to-end tests
For end-to-end (e2e) testing, run:
```bash
ng e2e
```
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
## Additional Resources
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.

View File

@ -0,0 +1,99 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"decorator-demo": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/decorator-demo",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "decorator-demo:build:production"
},
"development": {
"buildTarget": "decorator-demo:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.css"
],
"scripts": []
}
}
}
}
},
"cli": {
"analytics": "3954409e-ab36-40d8-9254-9dbfbdd3e2e6"
}
}

14476
src/09 - Decorators/angular/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
{
"name": "decorator-demo",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/common": "^19.2.0",
"@angular/compiler": "^19.2.0",
"@angular/core": "^19.2.0",
"@angular/forms": "^19.2.0",
"@angular/platform-browser": "^19.2.0",
"@angular/platform-browser-dynamic": "^19.2.0",
"@angular/router": "^19.2.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^19.2.1",
"@angular/cli": "^19.2.1",
"@angular/compiler-cli": "^19.2.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.6.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.7.2"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,7 @@
<main class="main">
<app-greeting name="World 123"></app-greeting>
<div>Lorem <span appHighlight="white" bgColor="hotpink">ipsum dolor</span>, sit amet consectetur adipisicing elit. Molestias culpa facilis voluptas dolorum fuga explicabo possimus consequuntur. Impedit amet sint, adipisci architecto animi distinctio officiis praesentium. Praesentium cumque sapiente dolorem!</div>
</main>
<router-outlet />

View File

@ -0,0 +1,29 @@
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have the 'decorator-demo' title`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('decorator-demo');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, decorator-demo');
});
});

View File

@ -0,0 +1,14 @@
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { HighlightDirective } from './directives/highlight.directive';
import { GreetingComponent } from './greeting/greeting.component';
@Component({
selector: 'app-root',
imports: [RouterOutlet, GreetingComponent, HighlightDirective],
templateUrl: './app.component.html',
styleUrl: './app.component.css',
})
export class AppComponent {
title = 'decorator-demo';
}

View File

@ -0,0 +1,8 @@
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
};

View File

@ -0,0 +1,3 @@
import { Routes } from '@angular/router';
export const routes: Routes = [];

View File

@ -0,0 +1,8 @@
import { HighlightDirective } from './highlight.directive';
describe('HighlightDirective', () => {
it('should create an instance', () => {
const directive = new HighlightDirective();
expect(directive).toBeTruthy();
});
});

View File

@ -0,0 +1,27 @@
import {
Directive,
ElementRef,
Input,
OnChanges,
Renderer2,
} from '@angular/core';
@Directive({
selector: '[appHighlight]',
})
export class HighlightDirective implements OnChanges {
@Input('appHighlight') highlightColor: string = 'black';
@Input() bgColor: string = 'yellow';
constructor(private el: ElementRef, private renderer: Renderer2) {}
ngOnChanges(): void {
this.renderer.setStyle(this.el.nativeElement, 'color', this.highlightColor);
this.renderer.setStyle(
this.el.nativeElement,
'backgroundColor',
this.bgColor
);
}
}

View File

@ -0,0 +1,24 @@
import { Component, Input, OnInit } from '@angular/core';
import { DataService } from '../services/data.service';
@Component({
selector: 'app-greeting',
imports: [],
template: ` <div>
<h1>Hallo, {{ name }}!</h1>
<p>{{ data }}</p>
</div>`,
})
export class GreetingComponent implements OnInit {
@Input() name: string = 'Gast';
public data = 'No data';
constructor(private dataService: DataService) {}
ngOnInit(): void {
setTimeout(() => {
this.data = this.dataService.getData();
}, 2000);
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { DataService } from './data.service';
describe('DataService', () => {
let service: DataService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(DataService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,12 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class DataService {
constructor() {}
getData(): string {
return 'Daten aus dem DataService';
}
}

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DecoratorDemo</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -0,0 +1,6 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));

View File

@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

View File

@ -0,0 +1,15 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts"
],
"include": [
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,27 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"moduleResolution": "bundler",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022"
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@ -0,0 +1,15 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,67 @@
# Ausführen von TypeScript mit Node.js
Da die Decorators in Deno derzeit nicht unterstützt werden, zeigen wir hier, wie du dein TypeScript-Projekt mit Node.js ausführst.
## Voraussetzungen
- [Node.js](https://nodejs.org/) installiert
- Ein Paketmanager wie npm (wird mit Node.js mitgeliefert)
## Lokale Installation von TypeScript und ts-node
1. **Projekt initialisieren**
Starte in deinem Projektverzeichnis ein neues Node.js-Projekt:
```bash
npm init -y
```
2. **TypeScript und ts-node installieren**
Installiere `typescript` und `ts-node` als Entwicklungsabhängigkeiten:
```bash
npm install typescript ts-node --save-dev
```
3. **tsconfig.json erstellen**
Erstelle eine `tsconfig.json` im Projektstamm (falls noch nicht vorhanden) und aktiviere die Option für Decorators:
```json
{
"compilerOptions": {
"target": "ES6",
"module": "ESNext",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
```
## Package-Script anpassen
Du kannst ein Skript in deiner `package.json` hinzufügen, um dein TypeScript-Programm mit `ts-node` auszuführen. Füge beispielsweise folgenden Abschnitt unter `"scripts"` hinzu:
```json
{
"scripts": {
"start": "ts-node simple-decorators-demo.ts"
}
}
```
Anschließend kannst du dein Programm mit folgendem Befehl starten:
```bash
npm start
```
## Alternative: Ausführen mit npx
Falls du `ts-node` nicht dauerhaft im Projekt installieren möchtest, kannst du es auch direkt mit `npx` ausführen:
```bash
npx ts-node demo.ts
```

View File

@ -0,0 +1,93 @@
// Property Decorator: Validiert, dass die Property nicht leer ist.
function NonEmpty(target: any, propertyKey: string) {
let value: string;
const getter = function () {
return value;
};
const setter = function (newVal: string) {
if (!newVal || newVal.trim() === '') {
throw new Error(`Property "${propertyKey}" darf nicht leer sein.`);
}
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
});
}
// Parameter Decorator: Loggt Informationen über die Parameter einer Methode.
function LogParameter(
target: any,
propertyKey: string,
parameterIndex: number
) {
console.log(
`Parameter-Dekorator: Methode "${propertyKey}" - Parameter an Position ${parameterIndex} wurde dekoriert.`
);
// In realen Anwendungen kannst du hier Metadaten speichern.
}
// Accessor Decorator: Loggt den Zugriff auf einen Getter.
function LogAccessor(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalGet = descriptor.get;
if (originalGet) {
descriptor.get = function () {
console.log(`Accessor "${propertyKey}" wurde aufgerufen.`);
return originalGet.call(this);
};
}
}
class AdvancedProduct {
@NonEmpty
name: string;
private _price: number;
constructor(name: string, price: number) {
this.name = name; // Validierung über @NonEmpty
this._price = price;
}
@LogAccessor
get price(): number {
return this._price;
}
set price(newPrice: number) {
if (newPrice < 0) {
throw new Error('Der Preis darf nicht negativ sein.');
}
this._price = newPrice;
}
// Die Parameter der Methode werden mit @LogParameter dekoriert.
updateProduct(@LogParameter newName: string, @LogParameter newPrice: number) {
this.name = newName;
this.price = newPrice;
}
}
// Demo: Erstellen eines Produktes und Ausführen von Methoden
const advancedProduct = new AdvancedProduct('Smartphone', 500);
// Aufruf des Accessors löst Log-Ausgabe aus.
console.log('Aktueller Preis:', advancedProduct.price);
// Aufruf der Methode, bei der die Parameter dekoriert sind.
advancedProduct.updateProduct('Premium Smartphone', 750);
console.log('Neuer Name:', advancedProduct.name);
// Test: Versuch, einen leeren Namen zu setzen dies löst einen Fehler aus.
try {
advancedProduct.name = '';
} catch (error) {
console.error('Fehler:', (error as Error).message);
}

View File

@ -0,0 +1,33 @@
// Klassen-Dekorator: Loggt, wenn die Klasse definiert wird.
function ClassLogger(target: Function) {
console.log(`Klasse ${target.name} wurde definiert.`);
}
// Methoden-Dekorator: Loggt den Aufruf der Methode, ihre Argumente und den Rückgabewert.
function MethodLogger(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Methode ${propertyKey} wird aufgerufen mit Argumenten:`, args);
const result = originalMethod.apply(this, args);
console.log(`Methode ${propertyKey} gibt zurück:`, result);
return result;
};
}
@ClassLogger
class Person {
constructor(private name: string) {}
@MethodLogger
greet(greeting: string): string {
return `${greeting}, mein Name ist ${this.name}`;
}
}
// Live Demo: Instanziierung und Methodenaufruf
const person = new Person('John');
console.log(person.greet('Hallo'));

View File

@ -0,0 +1,96 @@
// simple-decorators-demo.ts
// Klassen-Dekorator
function SimpleClassDecorator(constructor: Function) {
console.log(
'SimpleClassDecorator: Auf Klasse',
constructor.name,
'angewendet.'
);
}
// Methoden-Dekorator
function SimpleMethodDecorator(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log(
`SimpleMethodDecorator: Auf Methode "${propertyKey}" angewendet.`
);
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(
`SimpleMethodDecorator: Methode "${propertyKey}" wurde aufgerufen mit Argumenten:`,
args
);
return originalMethod.apply(this, args);
};
}
// Property-Dekorator
function SimplePropertyDecorator(target: any, propertyKey: string) {
console.log(
`SimplePropertyDecorator: Auf Property "${propertyKey}" angewendet.`
);
}
// Parameter-Dekorator
function SimpleParameterDecorator(
target: any,
propertyKey: string,
parameterIndex: number
) {
console.log(
`SimpleParameterDecorator: Auf Parameter an Position ${parameterIndex} in Methode "${propertyKey}" angewendet.`
);
}
// Accessor-Dekorator
function SimpleAccessorDecorator(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log(
`SimpleAccessorDecorator: Auf Accessor "${propertyKey}" angewendet.`
);
const originalGet = descriptor.get;
if (originalGet) {
descriptor.get = function () {
console.log(
`SimpleAccessorDecorator: Getter von "${propertyKey}" wurde aufgerufen.`
);
return originalGet.call(this);
};
}
}
// Anwendung der Dekoratoren in einer Demo-Klasse
@SimpleClassDecorator
class Demo {
@SimplePropertyDecorator
demoProperty: string = 'Demo Property';
private _demoValue: number = 0;
@SimpleAccessorDecorator
get demoValue(): number {
return this._demoValue;
}
set demoValue(val: number) {
this._demoValue = val;
}
@SimpleMethodDecorator
demoMethod(@SimpleParameterDecorator param: string) {
console.log('Innerhalb von demoMethod mit Parameter:', param);
}
}
// Demo: Instanz erstellen und Methoden aufrufen
const demoInstance = new Demo();
demoInstance.demoMethod('Test');
console.log('Lese demoValue:', demoInstance.demoValue);
demoInstance.demoValue = 42;
console.log('Neuer demoValue:', demoInstance.demoValue);

233
src/09 - Decorators/demo/package-lock.json generated Normal file
View File

@ -0,0 +1,233 @@
{
"name": "09 - Decorators",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
}
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@tsconfig/node10": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.13.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.9.tgz",
"integrity": "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/acorn": {
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-walk": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.11.0"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true,
"license": "MIT"
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true,
"license": "ISC"
},
"node_modules/ts-node": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"bin": {
"ts-node": "dist/bin.js",
"ts-node-cwd": "dist/bin-cwd.js",
"ts-node-esm": "dist/bin-esm.js",
"ts-node-script": "dist/bin-script.js",
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
}
},
"node_modules/typescript": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true,
"license": "MIT"
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
}
}
}

View File

@ -0,0 +1,6 @@
{
"devDependencies": {
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
}
}

View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
/* Decorator config */
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}

View File

@ -0,0 +1,22 @@
{
"tasks": {
"cli": "deno run --allow-net src/cli.ts",
"move:forward": "deno run --allow-net src/cli.ts move forward 50 1",
"move:backward": "deno run --allow-net src/cli.ts move backward 50 1",
"beep": "deno task cli beep",
"status:on": "deno run --allow-net src/cli.ts statuslight on",
"status:off": "deno run --allow-net src/cli.ts statuslight off",
"light:off": "deno run --allow-net src/cli.ts light off",
"light:red": "deno run --allow-net src/cli.ts light red",
"light:green": "deno run --allow-net src/cli.ts light green",
"light:blue": "deno run --allow-net src/cli.ts light blue"
},
"imports": {
"@std/assert": "jsr:@std/assert@1"
}
}

23
src/11-robo-project/solution/deno.lock generated Normal file
View File

@ -0,0 +1,23 @@
{
"version": "4",
"specifiers": {
"jsr:@std/assert@1": "1.0.11",
"jsr:@std/internal@^1.0.5": "1.0.5"
},
"jsr": {
"@std/assert@1.0.11": {
"integrity": "2461ef3c368fe88bc60e186e7744a93112f16fd110022e113a0849e94d1c83c1",
"dependencies": [
"jsr:@std/internal"
]
},
"@std/internal@1.0.5": {
"integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba"
}
},
"workspace": {
"dependencies": [
"jsr:@std/assert@1"
]
}
}

View File

@ -0,0 +1,63 @@
@echo off
REM
if "%~1"=="" (
echo Usage: robo.bat [command]
echo Beispiel: robo.bat light:green
exit /b 1
)
set "cmd=%~1"
REM
if /I "%cmd%"=="cli" (
deno run --allow-net src/cli.ts
goto :EOF
)
if /I "%cmd%"=="move:forward" (
deno run --allow-net src/cli.ts move forward 50 1
goto :EOF
)
if /I "%cmd%"=="move:backward" (
deno run --allow-net src/cli.ts move backward 50 1
goto :EOF
)
if /I "%cmd%"=="beep" (
deno task cli beep
goto :EOF
)
if /I "%cmd%"=="status:on" (
deno run --allow-net src/cli.ts statuslight on
goto :EOF
)
if /I "%cmd%"=="status:off" (
deno run --allow-net src/cli.ts statuslight off
goto :EOF
)
if /I "%cmd%"=="light:off" (
deno run --allow-net src/cli.ts light off
goto :EOF
)
if /I "%cmd%"=="light:red" (
deno run --allow-net src/cli.ts light red
goto :EOF
)
if /I "%cmd%"=="light:green" (
deno run --allow-net src/cli.ts light green
goto :EOF
)
if /I "%cmd%"=="light:blue" (
deno run --allow-net src/cli.ts light blue
goto :EOF
)
echo Unbekannter Befehl: %cmd%
exit /b 1

View File

@ -0,0 +1,63 @@
import { LightColor, setLight } from './core/light.ts';
import { MotorDirection, move } from './core/motor.ts';
import { beep, StatusLight, statusLight } from './core/robot.ts';
const args = Deno.args;
const command = args[0];
if (!command) {
console.log('Bitte einen Befehl angeben: beep | statuslight | light | move');
Deno.exit(1);
}
switch (command) {
case 'beep':
await beep();
break;
case 'statuslight': {
if (args.length < 2) {
console.log('Syntax: statuslight <on|off>');
Deno.exit(1);
}
const action = args[1] as StatusLight;
await statusLight(action);
break;
}
case 'light': {
let color: LightColor = 'blue';
if (args.length === 2) {
color = args[1] as LightColor;
}
setLight(color);
break;
}
case 'move': {
if (args.length < 4) {
console.log('Syntax: move <forward|backward> <speed> <duration>');
Deno.exit(1);
}
const direction = args[1] as MotorDirection;
const speed = Number(args[2]);
const durationInSeconds = Number(args[3]);
move({
motorLeft: { direction, speed },
motorRight: { direction, speed },
durationInSeconds,
});
break;
}
default:
console.log('Unbekannter Befehl');
Deno.exit(1);
}

View File

@ -0,0 +1,44 @@
import { sendRequest } from '../robot-client.ts';
export type LED = { red: number; green: number; blue: number };
export type LightColor = 'off' | 'red' | 'green' | 'blue';
const Lights: Record<LightColor, LED> = {
off: {
red: 0,
green: 0,
blue: 0,
},
red: {
red: 255,
green: 0,
blue: 0,
},
blue: {
red: 0,
green: 0,
blue: 255,
},
green: {
red: 0,
green: 255,
blue: 0,
},
};
interface LedState {
[key: string]: LED;
}
export async function setLight(color: LightColor): Promise<void> {
const light = Lights[color];
const body: LedState = {
led_0: light,
led_1: light,
led_2: light,
led_3: light,
};
await sendRequest('/light', 'POST', body);
}

View File

@ -0,0 +1,16 @@
import { sendRequest } from '../robot-client.ts';
export type MotorDirection = 'forward' | 'backward';
export type MoveRequest = {
motorLeft: { direction: MotorDirection; speed: number };
motorRight: { direction: MotorDirection; speed: number };
durationInSeconds: number;
};
export async function move(body: MoveRequest): Promise<void> {
if (body.motorLeft.speed > 100 || body.motorRight.speed > 100) {
throw new Error('Speed must be between 0 and 100');
}
await sendRequest('/move', 'POST', body);
}

View File

@ -0,0 +1,11 @@
import { sendRequest } from '../robot-client.ts';
export type StatusLight = 'on' | 'off';
export async function beep(): Promise<void> {
await sendRequest('/horn/beep', 'GET');
}
export async function statusLight(action: StatusLight): Promise<void> {
await sendRequest(`/statuslight/${action}`, 'GET');
}

View File

@ -0,0 +1,31 @@
type Method = 'GET' | 'POST';
const buildUrl = (endpoint: string): string => `http://172.20.10.2${endpoint}`;
export async function sendRequest<T>(
endpoint: string,
method: Method,
body: unknown = null
): Promise<void> {
try {
const options: RequestInit = {
method,
headers: {
'Content-Type': 'application/json',
},
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(buildUrl(endpoint), options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error) {
console.error('Error sending request: ', error);
throw error;
}
}

View File

@ -0,0 +1,45 @@
# REST Client - Huachao Mao
### Get Status Light
GET http://172.20.10.2/statuslight/state
### POST
POST http://172.20.10.2/move
{
"motorLeft": {
"direction": "forward",
"speed": 50
},
"motorRight": {
"direction": "forward",
"speed": 20
},
"durationInSeconds": 3
}
### POST light
POST http://172.20.10.2/light
{
"led_0": {
"red": 0,
"green": 255,
"blue": 0
},
"led_1": {
"red": 0,
"green": 255,
"blue": 0
},
"led_2": {
"red": 0,
"green": 255,
"blue": 255
},
"led_3": {
"red": 0,
"green": 255,
"blue": 255
}
}