change repo

This commit is contained in:
Ruben Kallinich
2024-07-11 10:44:44 +02:00
commit 4a034b660f
88 changed files with 21963 additions and 0 deletions

16
.editorconfig Normal file
View File

@@ -0,0 +1,16 @@
# 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
[*.md]
max_line_length = off
trim_trailing_whitespace = false

4
.vscode/extensions.json vendored Normal file
View File

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

20
.vscode/launch.json vendored Normal file
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"
}
]
}

42
.vscode/tasks.json vendored Normal file
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"
}
}
}
}
]
}

27
README.md Normal file
View File

@@ -0,0 +1,27 @@
# LLCE
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.1.5.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

21
SECURITY.md Normal file
View File

@@ -0,0 +1,21 @@
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 5.1.x | :white_check_mark: |
| 5.0.x | :x: |
| 4.0.x | :white_check_mark: |
| < 4.0 | :x: |
## Reporting a Vulnerability
Use this section to tell people how to report a vulnerability.
Tell them where to go, how often they can expect to get an update on a
reported vulnerability, what to expect if the vulnerability is accepted or
declined, etc.

102
angular.json Normal file
View File

@@ -0,0 +1,102 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"LLCE": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "LL",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/llce",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/custom-theme.scss",
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "LLCE:build:production"
},
"development": {
"browserTarget": "LLCE:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "LLCE:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
}
}
}
}
},
"cli": {
"analytics": false
}
}

13119
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

41
package.json Normal file
View File

@@ -0,0 +1,41 @@
{
"name": "llce",
"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/animations": "^16.1.0",
"@angular/cdk": "^16.2.1",
"@angular/common": "^16.1.0",
"@angular/compiler": "^16.1.0",
"@angular/core": "^16.1.0",
"@angular/forms": "^16.1.0",
"@angular/material": "^16.2.1",
"@angular/platform-browser": "^16.1.0",
"@angular/platform-browser-dynamic": "^16.1.0",
"@angular/router": "^16.1.0",
"ngx-cookie-service": "^16.0.1",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.13.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^16.1.5",
"@angular/cli": "~16.1.5",
"@angular/compiler-cli": "^16.1.0",
"@types/jasmine": "~4.3.0",
"jasmine-core": "~4.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.1.3"
}
}

View File

@@ -0,0 +1,7 @@
import { Question } from "./question";
export interface AnswerData {
question: Question;
userAnswer: string;
isCorrect: boolean;
}

6
src/app/Model/answers.ts Normal file
View File

@@ -0,0 +1,6 @@
import { FrageOption } from "./frage-option";
export interface Answers {
selectedChoice: FrageOption[]
userAnswer: string;
}

View File

@@ -0,0 +1,4 @@
export interface AntwortOption {
text: string;
selected: boolean;
}

View File

@@ -0,0 +1,4 @@
export interface FrageOption {
text: string;
selected: boolean;
}

View File

@@ -0,0 +1,9 @@
import { FrageOption } from "./frage-option";
export interface Question {
questionNumber: string;
questionText: string;
choices: FrageOption[];
answer: string | string[];
questionType: 'single' | 'multiple' | 'input';
}

View File

@@ -0,0 +1,8 @@
<h2 mat-dialog-title>Statistic</h2>
<mat-dialog-content>
<div [innerHTML]="formattedMessage"></div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="errorPopup()">OK</button>
</mat-dialog-actions>

View File

@@ -0,0 +1,20 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'LL-error-message',
templateUrl: './error-message.component.html',
styleUrls: ['./error-message.component.css'],
})
export class ErrorMessageComponent {
formattedMessage: string;
constructor(
@Inject(MAT_DIALOG_DATA) public data: { message: string },
public dialogRef: MatDialogRef<ErrorMessageComponent>
) {
this.formattedMessage = data.message.replace(/\n/g, '<br>');
}
errorPopup(): void {
window.location.href = '/home';
}
}

View File

@@ -0,0 +1,19 @@
.errorpopup {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.errorpopup-content {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
text-align: center;
}

View File

@@ -0,0 +1,9 @@
<h2 mat-dialog-title>Warning</h2>
<mat-dialog-content>
<div [innerHTML]="formattedMessage"></div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="closePopUp()">OK</button>
</mat-dialog-actions>

View File

@@ -0,0 +1,21 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'LL-pop-up',
templateUrl: './pop-up.component.html',
styleUrls: ['./pop-up.component.css'],
})
export class PopUpComponent {
formattedMessage: string;
constructor(
@Inject(MAT_DIALOG_DATA) public data: { message: string },
public dialogRef: MatDialogRef<PopUpComponent>
) {
this.formattedMessage = data.message.replace(/\n/g, '<br>');
}
closePopUp(): void {
this.dialogRef.close();
}
}

View File

@@ -0,0 +1,8 @@
<h2 mat-dialog-title>Statistic</h2>
<mat-dialog-content>
<div [innerHTML]="formattedMessage"></div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="openStatisticPopup()">OK</button>
</mat-dialog-actions>

View File

@@ -0,0 +1,20 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'LL-statistik',
templateUrl: './statistik.component.html',
styleUrls: ['./statistik.component.css'],
})
export class StatistikComponent {
formattedMessage: string;
constructor(
@Inject(MAT_DIALOG_DATA) public data: { message: string },
public dialogRef: MatDialogRef<StatistikComponent>
) {
this.formattedMessage = data.message.replace(/\n/g, '<br>');
}
openStatisticPopup(): void {
window.location.href = '/home';
}
}

View File

@@ -0,0 +1,37 @@
<div class="questions-section">
<div class="question-type-buttons">
<button class="show-answer-button" *ngFor="let type of questionTypes" [class.selected]="selectedKatalog === type" (click)="onTypeButtonClick(type)">{{ type }}</button>
<div class="button-container">
<button class="show-answer-button" [class.selected]="selectedKatalog === 'LPI101'" (click)="selectCatalog('LPI101')">Katalog 1</button>
<button class="show-answer-button" [class.selected]="selectedKatalog === 'LPI102'"(click)="selectCatalog('LPI102')">Katalog 2</button>
</div>
</div>
<div class="question-container">
<div class="question-block" *ngFor="let question of filteredQuestions; let i = index">
<h3>Question {{i+1}}</h3>
<div class="question-text">{{question.questionText}}</div>
<div class="choice-text">
<ng-container *ngIf="question.questionType === 'single'">
<LL-single-choice-question [choices]="question.choices"></LL-single-choice-question>
</ng-container>
<ng-container *ngIf="question.questionType === 'multiple'">
<LL-multiple-choice-question [choices]="question.choices"></LL-multiple-choice-question>
</ng-container>
<ng-container *ngIf="question.questionType === 'input'">
<LL-input-choice-question
[userAnswer]="getTrueFalse().getCurrentAnswers().userAnswer"></LL-input-choice-question>
</ng-container>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,87 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Question } from 'src/app/Model/question';
import { FilterService } from 'src/app/services/filter.service';
import { QuestionNavigationService } from 'src/app/services/question-navigation.service';
import { QuestionToggleService } from 'src/app/services/question-toggle.service';
import { TrueOrFalesService } from 'src/app/services/true-or-false.service';
@Component({
selector: 'LL-filter',
templateUrl: './filter.component.html',
styleUrls: ['./filter.component.css']
})
export class FilterComponent {
@Input() selectedType: string = '';
@Output() selectedTypeChange = new EventEmitter<string>();
filteredQuestions: Question[] = [];
selectedKatalog: string = 'LPI101';
questionTypes: string[] = ['single', 'multiple', 'input'];
constructor(
private trueFalse: TrueOrFalesService,
private filterService: FilterService,
private questNav: QuestionNavigationService,
private questService: QuestionToggleService,
private route: ActivatedRoute,
) {
}
ngOnInit(): void {
this.route.params.subscribe((params) => {
const katalog = params['katalog'];
if (katalog === 'LPI101') {
this.trueFalse.loadKatalogAndQuestions().subscribe(() => {});
} else if (katalog === 'LPI102') {
this.trueFalse.loadKatalog2AndQuestions().subscribe(() => {});
}
});
this.filterService.selectedType$.subscribe(selectedType => {
this.filterQuestions(selectedType);
});
}
filterQuestions(selectedType: string): void {
if (this.selectedKatalog === 'LPI101') {
this.filteredQuestions = this.filterService.filteredQuestionsLPI101;
} else if (this.selectedKatalog === 'LPI102') {
this.filteredQuestions = this.filterService.filteredQuestionsLPI102;
}
console.log('fSfQL1',this.filterService.filteredQuestionsLPI101,'fQ',this.filteredQuestions)
}
selectCatalog(katalog: string): void{
this.selectedKatalog = katalog;
this.filterQuestions(this.selectedType);
}
onTypeButtonClick(type: string): void {
this.selectedType = type;
this.filterService.setSelectedType(type);
}
onTypeChange(event: any): void {
const selectedType = event?.target?.value;
if (selectedType !== undefined) {
this.selectedTypeChange.emit(selectedType);
this.filterService.setSelectedType(selectedType);
}
}
onKatalogChange(selectedKatalog: string): void {
this.selectedKatalog = selectedKatalog;
this.filterQuestions(this.selectedType);
}
getQuestionNav(): QuestionNavigationService {
return this.questNav;
}
getTrueFalse(): TrueOrFalesService {
return this.trueFalse;
}
getQuestionServ(): QuestionToggleService{
return this.questService;
}
}

View File

@@ -0,0 +1,8 @@
<!-- InputFragen / Fillin -->
<div>
<label>
<div>Your answer:</div>
<input type="text" [(ngModel)]="inputUserAnswer" (ngModelChange)="onInputUserAnswerChange()" placeholder="Please enter your text" name="userAnswer">
</label>
</div>

View File

@@ -0,0 +1,36 @@
import { Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core';
import { QuestionToggleService } from '../../services/question-toggle.service';
import { TrueOrFalesService } from '../../services/true-or-false.service';
@Component({
selector: 'LL-input-choice-question',
templateUrl: './input-choice-questeion.component.html',
styleUrls: ['./input-choice-questeion.component.css']
})
export class InputChoiceQuesteionComponent {
@Input() userAnswer: string | undefined;
@Output() userAnswerChange = new EventEmitter<string>();
inputUserAnswer: string = '';
constructor(
private trueFalse: TrueOrFalesService,
) {
}
ngOnChanges(changes: SimpleChanges): void{
if(changes['userAnswer']){
this.inputUserAnswer = this.userAnswer || '';
}
}
onInputUserAnswerChange(): void{
const currentAnswers = this.trueFalse.getCurrentAnswers();
currentAnswers.userAnswer = this.inputUserAnswer || '';
this.trueFalse.updateCurrentAnswers(currentAnswers);
this.userAnswerChange.emit(this.inputUserAnswer);
}
}

View File

@@ -0,0 +1,15 @@
<!-- MultipleChoice -->
<div *ngFor="let choice of choices; index as k">
<div *ngIf="!choice.selected">
<label>
<input type="checkbox" (click)="toggleChoiceSelection(choice)" name="choices">
{{ choice.text }}
</label>
</div>
<div *ngIf="choice.selected">
<label>
<input type="checkbox" (click)="toggleChoiceSelection(choice)" name="choices" checked>
{{ choice.text }}
</label>
</div>
</div>

View File

@@ -0,0 +1,24 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FrageOption } from '../../Model/frage-option';
import { QuestionToggleService } from '../../services/question-toggle.service';
@Component({
selector: 'LL-multiple-choice-question',
templateUrl: './multiple-choice-questeion.component.html',
styleUrls: ['./multiple-choice-questeion.component.css']
})
export class MultipleChoiceQuestionComponent {
@Input() choices: FrageOption[] | undefined;
@Output() choiceSelectionToggle = new EventEmitter<FrageOption>();
constructor(private questService: QuestionToggleService) {
}
toggleChoiceSelection(choice: FrageOption): void{
this.questService.toggleChoiceSelectionMulti(choice);
}
}

View File

@@ -0,0 +1,18 @@
<div>
<button class="show-answer-button" (click)="changeLanguage('german')">German</button>
<button class="show-answer-button" (click)="changeLanguage('english')">English</button>
</div>
<div>
<h2>{{privacyPolicy[currentLanguage].introduction}}</h2>
<p>{{privacyPolicy[currentLanguage].dataController}}</p>
<p>{{privacyPolicy[currentLanguage].dataTypes}}</p>
<p>{{privacyPolicy[currentLanguage].dataProcessingPurpose}}</p>
<p>{{privacyPolicy[currentLanguage].legalBases}}</p>
<p>{{privacyPolicy[currentLanguage].dataDeletion}}</p>
<p>{{privacyPolicy[currentLanguage].userRights}}</p>
<p>{{privacyPolicy[currentLanguage].objectionRight}}</p>
<p>{{privacyPolicy[currentLanguage].complaintRight}}</p>
<p>{{privacyPolicy[currentLanguage].changes}}</p>
<p>{{privacyPolicy[currentLanguage].lastUpdated}}</p>
</div>

View File

@@ -0,0 +1,49 @@
import { Component } from '@angular/core';
@Component({
selector: 'LL-privacy-policy',
templateUrl: './privacy-policy.component.html',
styleUrls: ['./privacy-policy.component.css']
})
export class PrivacyPolicyComponent{
currentLanguage: string = "english";
privacyPolicy: { [key: string]:any } = {
"german": {
"introduction": "Diese Datenschutzerklärung klärt die Nutzer über die Art, den Umfang und den Zweck der Erhebung und Verwendung personenbezogener Daten durch [Ihr Unternehmen] auf dieser Website ([Ihre Website-URL]) auf.",
"dataController": "Verantwortlicher im Sinne der Datenschutz-Grundverordnung (DSGVO):\n\n[Ihr Unternehmen]\n[Ihre Adresse]\n[Ihre E-Mail-Adresse]\n[Ihre Telefonnummer]",
"dataTypes": "- Personenbezogene Daten (z.B. Name, E-Mail-Adresse)\n- Kontaktdaten (z.B. Adresse, Telefonnummer)\n- Nutzungsdaten (z.B. besuchte Webseiten, Interesse an Inhalten, Zugriffszeiten)\n- Meta-/Kommunikationsdaten (z.B. Geräte-Informationen, IP-Adressen)",
"dataProcessingPurpose": "- Bereitstellung der Website und ihrer Funktionen\n- Beantwortung von Kontaktanfragen und Kommunikation mit Nutzern\n- Sicherheitsmaßnahmen\n- Reichweitenmessung und Analyse der Nutzeraktivitäten",
"legalBases": "Die Verarbeitung personenbezogener Daten erfolgt aufgrund der folgenden Rechtsgrundlagen:\n\n- Einwilligung gemäß Artikel 6 Absatz 1 Buchstabe a DSGVO\n- Erfüllung eines Vertrags oder vorvertraglicher Maßnahmen gemäß Artikel 6 Absatz 1 Buchstabe b DSGVO\n- Erfüllung einer rechtlichen Verpflichtung gemäß Artikel 6 Absatz 1 Buchstabe c DSGVO\n- Schutz lebenswichtiger Interessen der betroffenen Person oder einer anderen natürlichen Person gemäß Artikel 6 Absatz 1 Buchstabe d DSGVO\n- Wahrnehmung einer Aufgabe, die im öffentlichen Interesse liegt oder in Ausübung öffentlicher Gewalt erfolgt, die dem Verantwortlichen übertragen wurde gemäß Artikel 6 Absatz 1 Buchstabe e DSGVO\n- Wahrung der berechtigten Interessen des Verantwortlichen oder eines Dritten gemäß Artikel 6 Absatz 1 Buchstabe f DSGVO",
"dataDeletion": "Die personenbezogenen Daten der betroffenen Person werden gelöscht oder gesperrt, sobald der Zweck der Speicherung entfällt. Eine Speicherung kann darüber hinaus dann erfolgen, wenn dies durch den europäischen oder nationalen Gesetzgeber in unionsrechtlichen Verordnungen, Gesetzen oder sonstigen Vorschriften vorgesehen wurde.",
"userRights": "- Das Recht auf Auskunft gemäß Artikel 15 DSGVO\n- Das Recht auf Berichtigung gemäß Artikel 16 DSGVO\n- Das Recht auf Löschung gemäß Artikel 17 DSGVO\n- Das Recht auf Einschränkung der Verarbeitung gemäß Artikel 18 DSGVO\n- Das Recht auf Datenübertragbarkeit gemäß Artikel 20 DSGVO\n- Das Recht auf Widerspruch gegen die Verarbeitung gemäß Artikel 21 DSGVO",
"objectionRight": "Sofern Ihre personenbezogenen Daten auf Grundlage von berechtigten Interessen gemäß Artikel 6 Absatz 1 Buchstabe f DSGVO verarbeitet werden, haben Sie das Recht, gemäß Artikel 21 DSGVO Widerspruch gegen die Verarbeitung Ihrer personenbezogenen Daten einzulegen, soweit dafür Gründe vorliegen, die sich aus Ihrer besonderen Situation ergeben oder sich der Widerspruch gegen Direktwerbung richtet.",
"complaintRight": "Sie haben das Recht, eine Beschwerde bei der zuständigen Aufsichtsbehörde einzulegen, wenn Sie der Ansicht sind, dass die Verarbeitung Ihrer personenbezogenen Daten gegen Datenschutzgesetze verstößt.",
"changes": "Wir behalten uns vor, diese Datenschutzerklärung anzupassen, damit sie stets den aktuellen rechtlichen Anforderungen entspricht oder um Änderungen unserer Leistungen in der Datenschutzerklärung umzusetzen, z.B. bei der Einführung neuer Services. Für Ihren erneuten Besuch gilt dann die neue Datenschutzerklärung.",
"lastUpdated": "Letzte Aktualisierung: 06/09/2023"
},
"english": {
"introduction": "This privacy policy informs users about the nature, scope, and purpose of the collection and use of personal data by [Your Company] on this website ([Your Website URL]).",
"dataController": "Data Controller according to the General Data Protection Regulation (GDPR):\n\n[Your Company]\n[Your Address]\n[Your Email Address]\n[Your Phone Number]",
"dataTypes": "- Personal data (e.g., name, email address)\n- Contact details (e.g., address, phone number)\n- Usage data (e.g., visited pages, interest in content, access times)\n- Meta/communication data (e.g., device information, IP addresses)",
"dataProcessingPurpose": "- Providing the website and its features\n- Responding to contact requests and communicating with users\n- Security measures\n- Audience measurement and analysis of user activities",
"legalBases": "The processing of personal data is based on the following legal bases:\n\n- Consent pursuant to Article 6(1)(a) GDPR\n- Performance of a contract or pre-contractual measures pursuant to Article 6(1)(b) GDPR\n- Compliance with a legal obligation pursuant to Article 6(1)(c) GDPR\n- Protection of vital interests of the data subject or another natural person pursuant to Article 6(1)(d) GDPR\n- Performance of a task carried out in the public interest or in the exercise of official authority vested in the controller pursuant to Article 6(1)(e) GDPR\n- Legitimate interests pursued by the controller or a third party pursuant to Article 6(1)(f) GDPR",
"dataDeletion": "Personal data of the data subject will be deleted or blocked as soon as the purpose of storage ceases to apply. Storage may also take place if this has been provided for by the European or national legislator in EU regulations, laws, or other provisions.",
"userRights": "- The right to information pursuant to Article 15 GDPR\n- The right to rectification pursuant to Article 16 GDPR\n- The right to erasure ('right to be forgotten') pursuant to Article 17 GDPR\n- The right to restriction of processing pursuant to Article 18 GDPR\n- The right to data portability pursuant to Article 20 GDPR\n- The right to object to processing pursuant to Article 21 GDPR",
"objectionRight": "If personal data is processed based on legitimate interests pursuant to Article 6(1)(f) GDPR, data subjects have the right to object to the processing of their personal data for reasons arising from their particular situation or if the objection is directed against direct marketing.",
"complaintRight": "You have the right to lodge a complaint with the supervisory authority if you believe that the processing of your personal data violates data protection laws.",
"changes": "We reserve the right to amend this privacy policy to ensure that it complies with current legal requirements or to implement changes to our services within the privacy policy, such as the introduction of new services. In such cases, the new privacy policy will apply to your next visit.",
"lastUpdated": "Last Updated: 09/06/2023"
}
};
changeLanguage(language: string) {
this.currentLanguage = language;
}
}

View File

@@ -0,0 +1,16 @@
<!-- SingeleChoice -->
<div *ngFor="let choice of choices; index as k">
<div *ngIf="!choice.selected">
<label>
<input type="radio" (click)="toggleChoiceSelection(choice)" name="choices">
{{ choice.text }}
</label>
</div>
<div *ngIf="choice.selected">
<label>
<input type="radio" (click)="toggleChoiceSelection(choice)" name="choices" checked>
{{ choice.text }}
</label>
</div>
</div>

View File

@@ -0,0 +1,21 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FrageOption } from '../../Model/frage-option';
import { QuestionToggleService } from '../../services/question-toggle.service';
@Component({
selector: 'LL-single-choice-question',
templateUrl: './single-choice-questeion.component.html',
styleUrls: ['./single-choice-questeion.component.css']
})
export class SingleChoiceQuestionComponent {
@Input() choices: FrageOption[] | undefined;
@Output() choiceSelectionToggle = new EventEmitter<FrageOption>()
constructor(private questService: QuestionToggleService) {
}
toggleChoiceSelection(choice: FrageOption): void{
this.questService.toggleChoiceSelectionSingle(choice);
}
}

View File

@@ -0,0 +1,84 @@
/* layout.css */
.side-navigation {
position: sticky;
top: 0;
width: 200px; /* Breite der Seitennavigation */
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); /* Schatten hinzufügen */
padding: 20px 0; /* Vertikaler Abstand oben und unten */
}
.side-navigation a {
display: block;
padding: 10px 20px; /* Horizontaler Abstand innen */
border-radius: 5px;
text-decoration: none;
color: #333;
position: relative;
}
.side-navigation a::after {
content: "";
position: absolute;
top: 50%;
right: -10px; /* Position des Balkens */
transform: translateY(-50%);
width: 5px; /* Breite des Balkens */
height: 30px; /* Höhe des Balkens */
background-color: transparent; /* Anfangs transparent */
transition: background-color 0.3s; /* Übergangseffekt hinzufügen */
}
.side-navigation a.active::after,
.side-navigation a:hover::after {
background-color: #0ca9f193;
/* Farbe ändern, wenn aktiv oder bei Hover */
}
.side-navigation a.active:hover::after {
/* Zusätzliche Markierung für aktives Element bei Hover */
background-color: #0ca9f193;
}
/* Zusätzlicher Effekt für geklickte Elemente */
.side-navigation a.clicked::after {
background-color: #0ca9f193;
}
/* Fügen Sie den Unterpunkten eine zusätzliche Einrückung hinzu */
.side-navigation .sub-navigation {
margin-left: 20px;
}
.side-navigation .sub-navigation a.active,
.side-navigation .sub-navigation a:hover {
background-color: #0ca9f193;
}
/* Zusätzlicher Effekt für geklickte Elemente */
.side-navigation .sub-navigation a.clicked {
background-color: #0ca9f193;
}
/* Fügen Sie den Unterpunkten eine zusätzliche Einrückung hinzu */
.sub-navigation {
margin-left: 20px; /* Hier können Sie den Einrückungsabstand anpassen */
}
/* Fügen Sie den Unterpunkten eine Hintergrundfarbe hinzu, um sie zu markieren */
.sub-navigation a {
background-color: #f2f2f2; /* Hintergrundfarbe der Unterpunkte */
padding-left: 10px; /* Abstand vom linken Rand */
}
.sub-navigation a.active,
.sub-navigation a:hover {
background-color: #0ca9f193; /* Hintergrundfarbe ändern, wenn aktiv oder bei Hover */
}
/* Zusätzlicher Effekt für geklickte Elemente */
.sub-navigation a.clicked {
background-color: #0ca9f193; /* Hintergrundfarbe für geklicktes Element */
}

View File

@@ -0,0 +1,32 @@
<nav class="side-navigation">
<a routerLink="/home" routerLinkActive="active">Home</a>
<a (click)="toggleSubMenu('learningMode')" [class.active]="isSubMenuOpen('learningMode')">Learning Mode</a>
<div class="sub-navigation" *ngIf="subMenus['learningMode']">
<a (click)="toggleSubMenu('fragenliste')" [class.active]="isSubMenuOpen('fragenliste')">Question list</a>
<div class="sub-sub-navigation" *ngIf="subMenus['fragenliste']">
<a routerLink="/learning-mode/questionlist/LPI101" routerLinkActive="active">LPI101</a>
<a routerLink="/learning-mode/questionlist/LPI102" routerLinkActive="active">LPI102</a>
</div>
<a (click)="toggleSubMenu('einzelfragen')" [class.active]="isSubMenuOpen('einzelfragen')">Single question</a>
<div class="sub-sub-navigation" *ngIf="subMenus['einzelfragen']">
<a routerLink="/learning-mode/singlequestion/LPI101" routerLinkActive="active">LPI101</a>
<a routerLink="/learning-mode/singlequestion/LPI102" routerLinkActive="active">LPI102</a>
</div>
</div>
<a (click)="toggleSubMenu('checkMode')" [class.active]="isSubMenuOpen('checkMode')">Check Mode</a>
<div class="sub-navigation" *ngIf="subMenus['checkMode']">
<a routerLink="/check-mode/LPI101" routerLinkActive="active">LPI101</a>
<a routerLink="/check-mode/LPI102" routerLinkActive="active">LPI102</a>
</div>
<a (click)="toggleSubMenu('examMode')" [class.active]="isSubMenuOpen('examMode')">Exam Mode</a>
<div class="sub-navigation" *ngIf="subMenus['examMode']">
<a routerLink="/exam-mode/LPI101" routerLinkActive="active">LPI101</a>
<a routerLink="/exam-mode/LPI102" routerLinkActive="active">LPI102</a>
</div>
</nav>

View File

@@ -0,0 +1,39 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'LL-app-navigation',
templateUrl: './app-navigation.component.html',
styleUrls: ['./app-navigation.component.css']
})
export class AppNavigationComponent {
constructor(
private router: Router,
) {}
subMenus: { [menuName: string]: boolean} = {
'learningMode': false,
'checkMode': false,
'examMode': false,
'fragenliste': false,
'einzelfragen': false,
};
toggleSubMenu(menuName: string): void {
if (this.subMenus[menuName]) {
this.router.navigateByUrl('/home'); // Hier wird zur "Home"-Seite navigiert
for (const key in this.subMenus) {
this.subMenus[key] = false; // Zurücksetzen aller anderen Untermenüs
}
} else {
this.subMenus[menuName] = !this.subMenus[menuName];
}
}
isSubMenuOpen(menuName: string): boolean {
return this.subMenus[menuName];
}
}

View File

@@ -0,0 +1,26 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LearningCenterComponent } from './learning-center/learning-center.component';
import { CheckModeComponent } from './check-mode/check-mode.component';
import { ExamModeComponent } from './exam-mode/exam-mode.component';
import { StatistikComponent } from './Popup/statistik/statistik.component';
import { FilterComponent } from './Templates/filter/filter.component';
import { PrivacyPolicyComponent } from './Templates/privacy-policy/privacy-policy.component';
const routes: Routes = [
{path: '', redirectTo: 'home', pathMatch:'full'},
{path: 'home', component: LearningCenterComponent,},
{path: 'learning-mode', loadChildren:() => import('./learning-mode/learning-mode.module').then(m=>m.LearningModeModule)},
{path: 'check-mode/:katalog', component: CheckModeComponent},
{path: 'exam-mode/:katalog', component: ExamModeComponent},
{path: 'statistik', component: StatistikComponent},
{path: 'filter', component:FilterComponent},
{path: 'privacy', component:PrivacyPolicyComponent},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

View File

@@ -0,0 +1,19 @@
<div class="main-container">
<nav class="side-navigation">
<LL-app-navigation></LL-app-navigation>
</nav>
<div class="questions-section">
<main>
<router-outlet></router-outlet>
</main>
</div>
<div class="empty-column">
<!-- hier könnte Ihre Werbung stehen -->
</div>
<LL-cookie-banner></LL-cookie-banner>
</div>

20
src/app/app.component.ts Normal file
View File

@@ -0,0 +1,20 @@
import { Component } from '@angular/core';
import { CookieService } from 'ngx-cookie-service';
@Component({
selector: 'LL-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(
private cookieServ: CookieService,
) {
this.cookieServ.set('cookieName', 'cookieValue')
const cookieValue = this.cookieServ.get('cookieName');
console.log('Cookie value', cookieValue);
}
//title = 'LLCE';
}

57
src/app/app.module.ts Normal file
View File

@@ -0,0 +1,57 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { LearningCenterComponent } from './learning-center/learning-center.component';
import { CheckModeComponent } from './check-mode/check-mode.component';
import { ExamModeComponent } from './exam-mode/exam-mode.component';
import { StatistikComponent } from './Popup/statistik/statistik.component';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { PopUpComponent } from './Popup/pop-up/pop-up.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatDialogModule } from '@angular/material/dialog';
// import { SingleChoiceQuestionComponent } from './Templates/single-choice-questeion/single-choice-questeion.component';
// import { MultipleChoiceQuestionComponent } from './Templates/multiple-choice-questeion/multiple-choice-questeion.component';
// import { InputChoiceQuesteionComponent } from './Templates/input-choice-questeion/input-choice-questeion.component';
import { AppNavigationComponent } from './app-navigation/app-navigation.component';
import { FilterComponent } from './Templates/filter/filter.component';
import { CookieBannerComponent } from './cookie-banner/cookie-banner.component';
import { PrivacyPolicyComponent } from './Templates/privacy-policy/privacy-policy.component';
import { ErrorMessageComponent } from './Popup/error-message/error-message.component';
import { SharedModule } from './shared/shared.module';
@NgModule({
declarations: [
AppComponent,
// LearningCenterComponent,
CheckModeComponent,
ExamModeComponent,
StatistikComponent,
PopUpComponent,
// SingleChoiceQuestionComponent,
// MultipleChoiceQuestionComponent,
// InputChoiceQuesteionComponent,
AppNavigationComponent,
FilterComponent,
CookieBannerComponent,
PrivacyPolicyComponent,
ErrorMessageComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FormsModule,
BrowserAnimationsModule,
MatDialogModule,
SharedModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@@ -0,0 +1,43 @@
<!-- Begin -->
<div class="questions-section">
<div class="question-container">
<div class="question-block" *ngIf="getQuestionNav().getCurrentQuestion()">
<h3>Question {{ getTrueFalse().getCurrentQuestionIndex() + 1 }}</h3>
<div class="question-text">{{ getQuestionNav().getCurrentQuestion().questionText }}</div>
<div class="choice-text">
<LL-single-choice-question
*ngIf="getQuestionNav().getCurrentQuestion().questionType === 'single'"
[choices]="getQuestionNav().getCurrentQuestion().choices"
(choiceSelectionToggle)="toggleChoiceSelectionSingle($event)"
></LL-single-choice-question>
<LL-multiple-choice-question
*ngIf="getQuestionNav().getCurrentQuestion().questionType === 'multiple'"
[choices]="getQuestionNav().getCurrentQuestion().choices"
(choiceSelectionToggle)="toggleChoiceSelectionMulti($event)"
></LL-multiple-choice-question>
<LL-input-choice-question
*ngIf="getQuestionNav().getCurrentQuestion().questionType === 'input'"
[userAnswer]="getTrueFalse().getCurrentAnswers().userAnswer"
(userAnswerChange)="toggleInputAnswer($event)"
></LL-input-choice-question>
</div>
</div>
<!-- Question Navigation -->
<div class="button-container">
<button *ngIf="!getQuestionNav().thisIsLastQuestion()" (click)="getQuestionNav().movePreviusQuestion()">Back</button>
<button *ngIf="!getQuestionNav().thisIsLastQuestion()" (click)="getQuestionNav().moveNextQuestionCheckMode()">Forward</button>
<button *ngIf="getQuestionNav().thisIsLastQuestion()" (click)="getQuestionNav().endExam()">Finish</button>
</div>
<div class="button-container">
<button *ngIf="!getQuestionNav().thisIsLastQuestion()" (click)="getQuestionNav().skipQuestion()">Skip</button>
</div>
</div>

View File

@@ -0,0 +1,59 @@
import { Component, OnInit } from '@angular/core';
import { TrueOrFalesService } from '../services/true-or-false.service';
import { QuestionNavigationService } from '../services/question-navigation.service';
import { ActivatedRoute } from '@angular/router';
import { QuestionToggleService } from '../services/question-toggle.service';
import { StatistikService } from '../services/statistik.service';
import { FrageOption } from '../Model/frage-option';
@Component({
selector: 'LL-check-mode',
templateUrl: './check-mode.component.html',
styleUrls: ['./check-mode.component.css'],
})
export class CheckModeComponent implements OnInit{
constructor(
private trueFalse: TrueOrFalesService,
private questNav: QuestionNavigationService,
private questService: QuestionToggleService,
private route: ActivatedRoute,
private statServ: StatistikService,
) {}
ngOnInit(): void {
this.route.params.subscribe((params) => {
const katalog = params['katalog'];
if (katalog === 'LPI101') {
this.trueFalse.loadKatalogAndQuestions().subscribe(() => {
});
} else if (katalog === 'LPI102') {
this.trueFalse.loadKatalog2AndQuestions().subscribe(() => {
});
}
});
this.statServ.resetStatistics();
}
getQuestionNav(): QuestionNavigationService {
return this.questNav;
}
getTrueFalse(): TrueOrFalesService {
return this.trueFalse;
}
getQuestionServ(): QuestionToggleService{
return this.questService;
}
toggleChoiceSelectionSingle(choice: FrageOption): void{
this.questService.toggleChoiceSelectionSingle(choice);
}
toggleChoiceSelectionMulti(choice: FrageOption): void{
this.questService.toggleChoiceSelectionMulti(choice);
}
toggleInputAnswer(answer: string | undefined): void{
this.questService.toggleInputAnswer(answer);
}
}

View File

@@ -0,0 +1,22 @@
.cookie-banner {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: #f0f0f0;
padding: 10px;
text-align: center;
box-shadow: 0px -2px 5px rgba(0, 0, 0, 0.2);
}
.cookie-banner button {
background-color: #007bff;
color: white;
border: none;
padding: 5px 10px;
cursor: pointer;
}
.cookie-banner button:hover {
background-color: #0056b3;
}

View File

@@ -0,0 +1,10 @@
<div class="cookie-banner" *ngIf="!hasConsent()">
<span>This website uses cookies to enhance your experience. Please accept the use of cookies.</span>
<div>
<a href="/privacy">Privacy Policy</a>
</div>
<div>
<button (click)="giveConsent()">Accept</button>
</div>
</div>

View File

@@ -0,0 +1,45 @@
import { Component, Input } from '@angular/core';
import { CookieService } from 'ngx-cookie-service';
import { DataInput } from '../services/data-input';
@Component({
selector: 'LL-cookie-banner',
templateUrl: './cookie-banner.component.html',
styleUrls: ['./cookie-banner.component.css']
})
export class CookieBannerComponent {
privacyPolice: any;
currentLanguage: string = 'english';
constructor(
private cookieServ: CookieService,
){}
giveConsent(){
this.cookieServ.set('cookieConsent', 'true');
this.setNecessaryCookies();
}
setNecessaryCookies(){
this.cookieServ.set('exampleCookie', 'exampleValue');
this.cookieServ.set('authCookie', 'authValue');
}
revokeConsent(){
this.cookieServ.delete('cookieConsent');
this.cookieServ.delete('exampleCookie');
this.cookieServ.delete('authCookie');
}
hasConsent(): boolean{
return this.cookieServ.get('cookieConsent') === 'true';
}
changeLanguage(language: string) {
this.currentLanguage = language;
this.cookieServ.set('selectedLanguage', language);
// Füge hier den Code zum Laden der Datenschutzrichtlinieninhalte in der ausgewählten Sprache hinzu
}
}

View File

@@ -0,0 +1,60 @@
/* CSS-Stile für das Schiebeelement (Toggle) */
.toggle-container {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.toggle-label {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.toggle-label input[type="checkbox"] {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: 0.4s;
border-radius: 34px;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: 0.4s;
border-radius: 50%;
}
input[type="checkbox"]:checked + .slider {
background-color: #2196F3;
}
input[type="checkbox"]:checked + .slider:before {
transform: translateX(26px);
}
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}

View File

@@ -0,0 +1,52 @@
<!-- Begin -->
<div class="questions-section">
<div class="toggle-container">
<label class="toggle-label">
<input type="checkbox" [(ngModel)]="examModeEnabled" (change)="toggleExamMode()">
<span class="slider round"></span>
</label>
</div>
<div class="question-container">
<div class="question-block" *ngIf="getQuestionNav().getCurrentQuestion()">
<h3>Question {{ getTrueFalse().getCurrentQuestionIndex() + 1 }}</h3>
<div class="question-text">{{ getQuestionNav().getCurrentQuestion().questionText }}</div>
<div class="choice-text">
<LL-single-choice-question
*ngIf="getQuestionNav().getCurrentQuestion().questionType === 'single'"
[choices]="getQuestionNav().getCurrentQuestion().choices"
(choiceSelectionToggle)="toggleChoiceSelectionSingle($event)"
></LL-single-choice-question>
<LL-multiple-choice-question
*ngIf="getQuestionNav().getCurrentQuestion().questionType === 'multiple'"
[choices]="getQuestionNav().getCurrentQuestion().choices"
(choiceSelectionToggle)="toggleChoiceSelectionMulti($event)"
></LL-multiple-choice-question>
<LL-input-choice-question
*ngIf="getQuestionNav().getCurrentQuestion().questionType === 'input'"
[userAnswer]="getTrueFalse().getCurrentAnswers().userAnswer"
(userAnswerChange)="toggleInputAnswer($event)"
></LL-input-choice-question>
</div>
</div>
<!-- Question Navigation -->
<div class="button-container">
<button *ngIf="!getQuestionNav().thisIsLastQuestion()" (click)="getQuestionNav().movePreviusQuestion()">Back</button>
<button *ngIf="!getQuestionNav().thisIsLastQuestion()" (click)="getQuestionNav().moveNextQuestionExamMode()">Forward</button>
<button *ngIf="getQuestionNav().thisIsLastQuestion()" (click)="getQuestionNav().endExam()">Finish</button>
</div>
<div class="button-container">
<button *ngIf="!getQuestionNav().thisIsLastQuestion()" (click)="getQuestionNav().endExam()">Quit Exam</button>
</div>

View File

@@ -0,0 +1,104 @@
import { Component, OnInit } from '@angular/core';
import { TrueOrFalesService } from '../services/true-or-false.service';
import { QuestionNavigationService } from '../services/question-navigation.service';
import { FrageOption } from '../Model/frage-option';
import { ActivatedRoute } from '@angular/router';
import { QuestionToggleService } from '../services/question-toggle.service';
import { StatistikService } from '../services/statistik.service';
import { Question } from '../Model/question';
@Component({
selector: 'LL-exam-mode',
templateUrl: './exam-mode.component.html',
styleUrls: ['./exam-mode.component.css'],
})
export class ExamModeComponent implements OnInit {
examModeEnabled: boolean = false;
constructor(
private trueFalse: TrueOrFalesService,
private questNav: QuestionNavigationService,
private toggle: QuestionToggleService,
private route: ActivatedRoute,
private statServ: StatistikService,
) {}
ngOnInit(): void {
this.route.params.subscribe((params) => {
const katalog = params['katalog'];
if (katalog === 'LPI101') {
this.trueFalse.loadKatalogAndQuestions().subscribe(() => {});
} else if (katalog === 'LPI102') {
this.trueFalse.loadKatalog2AndQuestions().subscribe(() => {});
}
});
this.statServ.resetStatistics();
}
getQuestionNav(): QuestionNavigationService {
return this.questNav;
}
getTrueFalse(): TrueOrFalesService {
return this.trueFalse;
}
getToggle(): QuestionToggleService {
return this.toggle;
}
toggleChoiceSelectionSingle(choice: FrageOption): void {
this.toggle.toggleChoiceSelectionSingle(choice);
}
toggleChoiceSelectionMulti(choice: FrageOption): void {
this.toggle.toggleChoiceSelectionMulti(choice);
}
toggleInputAnswer(answer: string | undefined): void {
this.toggle.toggleInputAnswer(answer);
}
toggleExamMode(): void {
this.examModeEnabled = !this.examModeEnabled;
if (this.examModeEnabled) {
this.applyExamModeToCurrentKatalog();
}
}
applyExamModeToCurrentKatalog(): void {
const katalog = this.route.snapshot.params['katalog']; // Holt sich den aktuellen Katalog aus den Route-Parametern.
if (katalog === 'LPI101') {
this.applyExamModeToKatalog1();
} else if (katalog === 'LPI102') {
this.applyExamModeToKatalog2();
}
}
applyExamModeToKatalog1(): void {
const questionsArray = this.trueFalse.getKatalog1()
this.processQuestions(questionsArray)
}
applyExamModeToKatalog2(): void {
const questionsArray = this.trueFalse.getKatalog2()
this.processQuestions(questionsArray)
}
processQuestions(questionsArray: Question[]): void {
for (let i = 0; i < questionsArray.length; i++) {
const currentQuestion = questionsArray[i];
if (currentQuestion.questionType === 'single' || currentQuestion.questionType === 'multiple') {
// Mischen der Antwortmöglichkeiten
currentQuestion.choices = this.shuffleArray(currentQuestion.choices);
}
}
}
shuffleArray(array: any[]): any[] {
const shuffledArray = [...array];
for (let i = shuffledArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]];
}
return shuffledArray;
}
}

View File

@@ -0,0 +1,50 @@
.navigation-links {
display: flex;
gap: 10px; /* Abstand zwischen den Links */
}
.centered-navigation {
margin-top: 100px;
display: flex;
justify-content: center; /* Zentrieren der Navigation */
}
.navigation-links a {
padding: 10px;
border: 1px solid #ccc; /* Rahmen um die Links */
border-radius: 5px; /* Abrundung des Rahmens */
text-decoration: none;
color: #333;
}
.navigation-links a.active {
background-color: #f0f0f0; /* Hintergrundfarbe für aktiven Link */
}
.fixed-navigation {
position: fixed;
top: 0;
width: 100%;
background-color: #fff; /* Hintergrundfarbe der Navigation */
z-index: 1000; /* Stelle sicher, dass die Navigation über anderen Inhalten liegt */
border-bottom: 1px solid #ccc; /* Optional: Trennlinie unter der Navigation */
}
.infoTextBox{
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
text-decoration: none;
color: #333;
}
.centered-infoTextBox{
margin-top: 100px;
display: flex;
justify-content: center;
}
.centeredHeadline{
display: flex;
justify-content: center;
}

View File

@@ -0,0 +1,12 @@
<h1 class="centeredHeadline">Linux Learch Check Exam LLCE</h1>
<div class="centered-infoTextBox">
<div class="infoTextBox">
<h4>Welcome to the Linux LPIC1 101 and 102 Certification Exam Simulator!</h4>
<p>Are you ready to embark on your journey towards becoming a certified
Linux professional? Look no further our comprehensive platform is
your ultimate resource for mastering the LPIC1 101 and 102 certification exams.</p>
</div>
</div>

View File

@@ -0,0 +1,13 @@
import { Component } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
@Component({
selector: 'LL-learning-center',
templateUrl: './learning-center.component.html',
styleUrls: ['./learning-center.component.css']
})
export class LearningCenterComponent {
}

View File

@@ -0,0 +1,40 @@
<!--Einzelfragen -->
<div class="questions-section">
<div class="question-container">
<div class="question-block" *ngIf="getQuestionNav().getCurrentQuestion()">
<h3>Question {{getTrueFalse().getCurrentQuestionIndex() + 1 }}</h3>
<div class="question-text">{{getQuestionNav().getCurrentQuestion().questionText}}</div>
<div class="choice-text">
<ng-container *ngIf="getQuestionNav().getCurrentQuestion().questionType === 'single'">
<LL-single-choice-question [choices]="getQuestionNav().getCurrentQuestion().choices"></LL-single-choice-question>
</ng-container>
<ng-container *ngIf="getQuestionNav().getCurrentQuestion().questionType === 'multiple'">
<LL-multiple-choice-question [choices]="getQuestionNav().getCurrentQuestion().choices"></LL-multiple-choice-question>
</ng-container>
<ng-container *ngIf="getQuestionNav().getCurrentQuestion().questionType === 'input'">
<LL-input-choice-question
[userAnswer]="getTrueFalse().getCurrentAnswers().userAnswer"></LL-input-choice-question>
</ng-container>
</div>
<button class="show-answer-button" (click)="showAnswer = !showAnswer">Answer</button>
<div class="answer" *ngIf="showAnswer && getQuestionNav().getCurrentQuestion().answer">
Answer: {{getQuestionNav().getCurrentQuestion().answer}}
</div>
</div>
<div class="button-container">
<button (click)="getQuestionNav().movePreviusQuestion()">Back</button>
<button (click)="getQuestionNav().moveNextQuestionWithOutCheck(); showAnswer = false;">Forward</button>
</div>

View File

@@ -0,0 +1,47 @@
import { Component, OnInit } from '@angular/core';
import { TrueOrFalesService } from 'src/app/services/true-or-false.service';
import { QuestionNavigationService } from 'src/app/services/question-navigation.service';
import { Question } from 'src/app/Model/question';
import { ActivatedRoute } from '@angular/router';
import { QuestionToggleService } from 'src/app/services/question-toggle.service';
@Component({
selector: 'LL-einzelfragen',
templateUrl: './einzelfragen.component.html',
styleUrls: ['./einzelfragen.component.css'],
})
export class EinzelfragenComponent implements OnInit {
filteredQuestions: Question[] = [];
showAnswer: boolean = false;
constructor(
private trueFalse: TrueOrFalesService,
private questNav: QuestionNavigationService,
private questService: QuestionToggleService,
private route: ActivatedRoute,
) {}
ngOnInit(): void {
this.route.params.subscribe((params) => {
const katalog = params['katalog'];
if (katalog === 'LPI101') {
this.trueFalse.loadKatalogAndQuestions().subscribe(() => {});
} else if (katalog === 'LPI102') {
this.trueFalse.loadKatalog2AndQuestions().subscribe(() => {});
}
});
}
getQuestionNav(): QuestionNavigationService {
return this.questNav;
}
getTrueFalse(): TrueOrFalesService {
return this.trueFalse;
}
getQuestService(): QuestionToggleService{
return this.questService;
}
}

View File

@@ -0,0 +1,4 @@
.answer {
color: green; /* Beispiel: Ändere die Farbe auf Grün */
font-weight: bold; /* Beispiel: Ändere die Schriftart auf fett */
}

View File

@@ -0,0 +1,31 @@
<div class="questions-section">
<div class="question-type-buttons">
<button class="show-answer-button" mat-button (click)="navigateToFilterPage()">Filter Questions</button>
</div>
<div class="question-container">
<div class="question-block" *ngFor="let question of getKatalog(); let i = index">
<h3>Question {{i+1}}</h3>
<div class="question-text">{{question.questionText}}</div>
<div class="choice-text">
<ng-container *ngIf="question.questionType === 'single'">
<LL-single-choice-question [choices]="question.choices"></LL-single-choice-question>
</ng-container>
<ng-container *ngIf="question.questionType === 'multiple'">
<LL-multiple-choice-question [choices]="question.choices"></LL-multiple-choice-question>
</ng-container>
<ng-container *ngIf="question.questionType === 'input'">
<LL-input-choice-question
[userAnswer]="getTrueFlase().getCurrentAnswers().userAnswer"></LL-input-choice-question>
</ng-container>
</div>
<button class="show-answer-button" (click)="getQuestService().toggleAnswerVisibility()">Answer</button>
<div class="answer" *ngIf="getQuestService().getShowAnswer()">Right Answer: {{question.answer}}</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,63 @@
import { Component, OnInit } from '@angular/core';
import { TrueOrFalesService } from 'src/app/services/true-or-false.service';
import { QuestionNavigationService } from 'src/app/services/question-navigation.service';
import { Question } from 'src/app/Model/question';
import { ActivatedRoute, Router } from '@angular/router';
import { QuestionToggleService } from 'src/app/services/question-toggle.service';
@Component({
selector: 'LL-fragenliste',
templateUrl: './fragenliste.component.html',
styleUrls: ['./fragenliste.component.css'],
})
export class FragenlisteComponent implements OnInit {
constructor(
private trueFalse: TrueOrFalesService,
private questService: QuestionToggleService,
private questNav: QuestionNavigationService,
private route: ActivatedRoute,
private router: Router,
) {}
selectedQuestionIndex: number = -1;
showAnswerIndex: number | null = null;
ngOnInit(): void {
this.route.params.subscribe((params) => {
const katalog = params['katalog'];
if (katalog === 'LPI101') {
this.trueFalse.loadKatalogAndQuestions().subscribe(() => {});
} else if (katalog === 'LPI102') {
this.trueFalse.loadKatalog2AndQuestions().subscribe(() => {});
}
});
}
showAnswer(index: number):void{
this.showAnswerIndex = index;
}
getTrueFlase(): TrueOrFalesService {
return this.trueFalse;
}
getQuestNav(): QuestionNavigationService {
return this.questNav;
}
getKatalog(): Question[] {
return this.trueFalse.getKatalog1();
}
getQuestService(): QuestionToggleService{
return this.questService;
}
selectedQuestion(index:number):void{
this.selectedQuestionIndex = index;
}
navigateToFilterPage(): void {
this.router.navigate(['/filter']); // Hier den Pfad zur Filterseite einfügen
}
navigateToRoot():void{
this.router.navigate(['/'])
}
}

View File

@@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { FragenlisteComponent } from './fragenliste/fragenliste.component';
import { EinzelfragenComponent } from './einzelfragen/einzelfragen.component';
import { LearningModeComponent } from './learning-mode.component';
const routes: Routes = [
{ path: '', component: LearningModeComponent},
{ path: 'questionlist/:katalog', component: FragenlisteComponent},
{ path: 'singlequestion/:katalog', component: EinzelfragenComponent},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class LearningModeRoutingModule { }

View File

@@ -0,0 +1,53 @@
.navigation-links {
display: flex;
gap: 10px; /* Abstand zwischen den Links */
}
.centered-navigation {
margin-top: 100px;
display: flex;
justify-content: center; /* Zentrieren der Navigation */
}
.navigation-links a {
padding: 10px;
border: 1px solid #ccc; /* Rahmen um die Links */
border-radius: 5px; /* Abrundung des Rahmens */
text-decoration: none;
color: #333;
}
.navigation-links a.active {
background-color: #0ca9f193; /* Hintergrundfarbe für aktiven Link */
}
.infoTextBox{
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
text-decoration: none;
color: #333;
}
.centered-infoTextBox{
margin-top: 100px;
display: flex;
justify-content: center;
}
.subNavigation-links {
display: flex;
gap: 50px; /* Abstand zwischen den Links */
}
.subNavigation-links a {
padding: 10px;
border: 1px solid #ccc; /* Rahmen um die Links */
border-radius: 5px; /* Abrundung des Rahmens */
text-decoration: none;
color: #333;
}
.subNavigation-links a.active {
background-color: #0ca9f193; /* Hintergrundfarbe für aktiven Link */
}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'LL-learning-mode',
templateUrl: './learning-mode.component.html',
styleUrls: ['./learning-mode.component.css']
})
export class LearningModeComponent {
}

View File

@@ -0,0 +1,25 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LearningModeRoutingModule } from './learning-mode-routing.module';
import { LearningModeComponent } from './learning-mode.component';
import { FragenlisteComponent } from './fragenliste/fragenliste.component';
import { EinzelfragenComponent } from './einzelfragen/einzelfragen.component';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [
LearningModeComponent,
FragenlisteComponent,
EinzelfragenComponent,
],
imports: [
CommonModule,
LearningModeRoutingModule,
SharedModule,
]
})
export class LearningModeModule { }

View File

@@ -0,0 +1,40 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, combineLatest, map } from 'rxjs';
import { Question } from '../Model/question';
@Injectable({
providedIn: 'root',
})
export class DataInput {
constructor(private http: HttpClient) {}
private katalog1Url = 'assets/LPI-2019-1-101d-QA.json';
private katalog2Url = 'assets/LPI-2019-1-102d-QA.json';
getQuestion1(): Observable<Question[]> {
return this.http.get<Question[]>(this.katalog1Url);
}
getQuestion2(): Observable<Question[]> {
return this.http.get<Question[]>(this.katalog2Url);
}
getKatalog1Url(): string {
return this.katalog1Url;
}
getKaltalog2Url(): string {
return this.katalog2Url;
}
getAllQuestions(): Observable<Question[]>{
const question1 = this.getQuestion1();
const question2 = this.getQuestion2();
return combineLatest([question1, question2]).pipe(
map(([question1,question2]) => [...question1, ...question2])
);
}
}

View File

@@ -0,0 +1,62 @@
import { Injectable } from '@angular/core';
import { Question } from '../Model/question';
import { BehaviorSubject } from 'rxjs';
import { DataInput } from './data-input';
@Injectable({
providedIn: 'root'
})
export class FilterService {
selectedType: string = '';
filteredQuestionsLPI101: Question[] = [];
filteredQuestionsLPI102: Question[] = [];
questions: Question[] = [];
qustionTypesToShow: string[] = [];
constructor(
private mainService: DataInput,
) {
}
private selectedTypeSource = new BehaviorSubject<string>(''); // Initialwert
selectedType$ = this.selectedTypeSource.asObservable();
setQuestionTypesToShow(questionTypes: string[]): void {
this.qustionTypesToShow = questionTypes;
}
setSelectedType(selectedType: string): void {
this.selectedTypeSource.next(selectedType);
this.updateFilteredQuestions(selectedType);
}
setQuestions(questions: Question[]): void {
this.questions = questions;
}
filterByQuestionsTypes(questions: Question[], questionType: string): Question[] {
console.log(questions,'Questions');
console.log(this.questions,'Katalog');
const filtered = questions.filter(question => question.questionType == questionType)
console.log(questionType,'Filtered');
console.log(filtered,'Filtered');
return filtered;
}
updateFilteredQuestions(selectedType: string): void {
this.mainService.getQuestion1().subscribe(questionsLPI101 => {
this.filteredQuestionsLPI101 = this.filterByQuestionsTypes(questionsLPI101, selectedType);
});
this.mainService.getQuestion2().subscribe(questionsLPI102 => {
const filteredQuestionsLPI102 = this.filterByQuestionsTypes(questionsLPI102, selectedType);
this.filteredQuestionsLPI102 = filteredQuestionsLPI102;
});
}
}

View File

@@ -0,0 +1,54 @@
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { PopUpComponent } from '../Popup/pop-up/pop-up.component';
import { StatistikComponent } from '../Popup/statistik/statistik.component';
import { ErrorMessageComponent } from '../Popup/error-message/error-message.component';
@Injectable({
providedIn: 'root',
})
export class PopupService {
constructor(
private dialog: MatDialog,
) {}
openPopUp(message: string): void {
this.dialog.open(PopUpComponent, {
data: { message: message },
});
}
errorPopup(message: string): void {
this.dialog.open(ErrorMessageComponent , {
data: { message: message },
});
}
openStatisticPopup(message: string): void {
this.dialog.open(StatistikComponent, {
data: { message: message },
});
}
dontMoveWitNowAnswer(): void {
this.openPopUp('Please provide an answer before proceeding');
}
wrongAnswerMessage(): void {
this.openPopUp( //ändern
"There are two possibilities:\n\nA) You were not paying attention\nB) You were not honest\nIf you really don't know, skip the question."
);
}
backToLearningCenterExam(): void {
this.errorPopup(
'You have already answered more than 20% of the questions incorrectly. The exam will be terminated.'
);
}
backToLearningCenterCheck(): void {
this.errorPopup(
'You have already answered 7 of the questions incorrectly. The check will be terminated.'
);
}
}

View File

@@ -0,0 +1,292 @@
import { Injectable } from '@angular/core';
import { TrueOrFalesService } from './true-or-false.service';
import { Question } from '../Model/question';
import { AnswerData } from '../Model/answer-data';
import { PopupService } from './popup.service';
import { StatistikService } from './statistik.service';
import { Router } from '@angular/router';
@Injectable({
providedIn: 'root',
})
export class QuestionNavigationService {
private currentQuestionIndex: number = 0;
private currentQuestion!: Question;
private isFirstQuestion: boolean = true;
private isLastQuestion: boolean = false;
private answeredQuestions: boolean[] = [];
private totalQuestions: number = 0;
private answerData: AnswerData[] = [];
constructor(
private trueOrFalseService: TrueOrFalesService,
private popupServ: PopupService,
private statistServ: StatistikService,
private router: Router
) {}
initializeNavigation(totalQuestions: number): void {
this.totalQuestions = totalQuestions;
this.currentQuestionIndex = 0;
this.isFirstQuestion = true;
this.isLastQuestion = totalQuestions === 1;
}
private recordAnswerData(userAnswer: string, isAnswerCorrect: boolean): void {
this.answerData.push({
question: this.trueOrFalseService.getCurrentQuestion(),
userAnswer: userAnswer,
isCorrect: isAnswerCorrect,
});
}
moveNextQuestionCheckMode(): void {
const currentAnswers = this.trueOrFalseService.getCurrentAnswers();
const userAnswer = currentAnswers.userAnswer;
if (!this.isLastQuestion) {
//Überprüfen ob die Antwort korrekt ist
if (this.shouldSkipQuestion(userAnswer)) {
if (this.trueOrFalseService.getSkipped()) {
this.statistServ.incrementCounterOfSkip();
this.moveNextQuestionWithOutCheck();
this.trueOrFalseService.setSkipped(false);
} else {
this.popupServ.dontMoveWitNowAnswer();
}
} else {
const isAnswerCorrect = this.trueOrFalseService.checkAnswer(userAnswer);
this.evalueteAnswer(userAnswer, isAnswerCorrect);
}
} else {
if (this.shouldSkipQuestion(userAnswer)) {
if (this.trueOrFalseService.getSkipped()) {
this.statistServ.incrementCounterOfSkip();
this.moveNextQuestionWithOutCheck();
this.trueOrFalseService.setSkipped(false);
} else {
this.popupServ.dontMoveWitNowAnswer();
}
} else {
const isAnswerCorrect = this.trueOrFalseService.checkAnswer(userAnswer);
this.evalueteAnswer(userAnswer, isAnswerCorrect);
}
}
}
shouldSkipQuestion(userAnser: string): boolean {
return userAnser === undefined || userAnser.trim() === '';
}
evalueteAnswer(userAnswer: string, isAnswerCorrect: boolean): void {
this.recordAnswerData(userAnswer, isAnswerCorrect);
if (isAnswerCorrect) {
this.statistServ.incrementCounterOfCorrect();
if (this.isLastQuestion) {
this.endExam();
} else {
this.forwardQuestion();
}
} else {
//Answer is wrong
if (this.statistServ.getCounterOfIncorrect() === 6) {
this.popupServ.backToLearningCenterCheck();
} else{
this.statistServ.incrementCounterOfIncorrect();
this.popupServ.wrongAnswerMessage();
}
if (this.isLastQuestion) {
this.popupServ.wrongAnswerMessage();
this.statistServ.incrementCounterOfIncorrect();
this.previusQuestion();
} else{
this.previusQuestion();
}
}
}
forwardQuestion(): void {
const currentIndex = this.trueOrFalseService.getCurrentQuestionIndex();
if (
!(
this.trueOrFalseService.getCurrentQuestionIndex() ===
this.totalQuestions - 1
)
) {
this.trueOrFalseService.incrementQuestionIndex();
this.isFirstQuestion = false;
this.isLastQuestion = currentIndex === this.totalQuestions - 1;
this.loadCurrentQuestion();
}
}
previusQuestion(): void {
const currentIndex = this.trueOrFalseService.getCurrentQuestionIndex();
if (currentIndex > 0 ) {
this.trueOrFalseService.decrementQuestionIndex();
this.isFirstQuestion = currentIndex === 1;
this.isLastQuestion = currentIndex === this.totalQuestions - 1;
}
}
moveNextQuestionWithOutCheck(): void {
if (!this.isLastQuestion) {
this.trueOrFalseService.incrementQuestionIndex();
this.isFirstQuestion = false;
this.isLastQuestion =
this.trueOrFalseService.getCurrentQuestionIndex() ===
this.totalQuestions - 1;
this.loadCurrentQuestion();
}
}
skipQuestion(): void {
if (!this.isLastQuestion) {
this.trueOrFalseService.setSkipped(true);
this.trueOrFalseService.updateCurrentAnswers({
selectedChoice: [],
userAnswer: '',
});
this.moveNextQuestionCheckMode();
}
}
movePreviusQuestion(): void {
if (!this.isFirstQuestion) {
const currentAnswers = this.trueOrFalseService.getCurrentAnswers();
this.trueOrFalseService.updateCurrentAnswers(currentAnswers);
this.trueOrFalseService.decrementQuestionIndex();
this.isFirstQuestion =
this.trueOrFalseService.getCurrentQuestionIndex() === 0;
this.isLastQuestion = false;
this.loadCurrentQuestion();
}
}
moveNextQuestionExamMode(): void {
const currentAnswers = this.trueOrFalseService.getCurrentAnswers();
const userAnswer = currentAnswers.userAnswer;
if (!this.isLastQuestion) {
//Überprüfen ob die Antwort korrekt ist
if (this.shouldSkipQuestion(userAnswer)) {
if (this.trueOrFalseService.getSkipped()) {
this.statistServ.incrementCounterOfSkip();
this.trueOrFalseService.setSkipped(false);
} else {
this.skipQuestion();
}
} else {
const isAnswerCorrect = this.trueOrFalseService.checkAnswer(userAnswer);
this.evalueteAnswerExam(userAnswer, isAnswerCorrect);
}
} else {
if (this.shouldSkipQuestion(userAnswer)) {
if (this.trueOrFalseService.getSkipped()) {
this.statistServ.incrementCounterOfSkip();
this.trueOrFalseService.setSkipped(false);
} else {
this.skipQuestion();
}
}
this.evalueteAnswerExam(userAnswer,this.trueOrFalseService.checkAnswer(userAnswer));
}
}
evalueteAnswerExam(userAnswer: string, isAnswerCorrect: boolean): void {
this.recordAnswerData(userAnswer, isAnswerCorrect);
if (isAnswerCorrect) {
//Answer is correct
this.statistServ.incrementCounterOfCorrect();
} else {
//Answer is incorrect
this.statistServ.incrementCounterOfIncorrect();
this.twentyPercentThrsholdRule();
}
if(this.isLastQuestion){
this.endExam();
}else{
this.forwardQuestion();
}
}
twentyPercentThrsholdRule(): void{
const incorrectAnswers = this.statistServ.getCounterOfIncorrect();
const totalQuestions = this.trueOrFalseService.getKatalog1().length;
const twentyPercentThrshold = totalQuestions * 0.2;
if (incorrectAnswers >= twentyPercentThrshold) {
this.popupServ.backToLearningCenterExam();
} else {
if (this.thisIsLastQuestion()) {
this.endExam();
}
}
}
endExam(): void {
this.popupSaticMessages()
}
thisIsLastQuestion(): boolean {
if (
this.trueOrFalseService.getCurrentQuestionIndex() ===
this.trueOrFalseService.getKatalog1().length - 1
) {
this.isLastQuestion = true;
return this.isLastQuestion;
} else {
return false;
}
}
thisIsFirstQuesteion(): boolean {
return this.isFirstQuestion;
}
getCurrentQuestion(): Question {
return this.trueOrFalseService.getKatalog1()[
this.trueOrFalseService.getCurrentQuestionIndex()
];
}
setCurrentQuestion(question: Question): void {
this.currentQuestion = question;
}
loadCurrentQuestion(): void {
const currentQuestion = this.trueOrFalseService.getCurrentQuestion();
if (currentQuestion) {
this.setCurrentQuestion(currentQuestion);
}
}
markQuestionAnswered(index: number): void {
if (index >= 0 && index < this.answeredQuestions.length) {
this.answeredQuestions[index] = true;
}
}
hasQuestionBeenAnswered(index: number): boolean {
if (index >= 0 && index < this.answeredQuestions.length) {
return this.answeredQuestions[index];
}
return false;
}
popupSaticMessages():void{
const totalQuestions = this.trueOrFalseService.getKatalog1().length;
const correctCounter = this.statistServ.getCounterOfCorrect();
const incorrectCounter = this.statistServ.getCounterOfIncorrect();
const skipCount = this.statistServ.getCounterOfSkip();
const message = `Total number of questions: ${totalQuestions}\nCorrectly answered: ${correctCounter}\nIncorrectly answered: ${incorrectCounter}\nSkipped: ${skipCount}`;
this.popupServ.openStatisticPopup(message);
}
}

View File

@@ -0,0 +1,67 @@
import { Injectable } from '@angular/core';
import { TrueOrFalesService } from './true-or-false.service';
import { FrageOption } from '../Model/frage-option';
import { NavigationEnd, Router } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class QuestionToggleService {
private showAnswer: boolean = false;
private shuffleEnabled: boolean = false;
constructor(
private trueFalse: TrueOrFalesService,
) { }
toggleChoiceSelectionSingle(choice: FrageOption) {
const currentQuestion = this.trueFalse.getCurrentQuestion();
if (currentQuestion) {
currentQuestion.choices.map((c) => (c.selected = false));
choice.selected = !choice.selected;
this.trueFalse.updateCurrentAnswers({
selectedChoice: currentQuestion.choices,
userAnswer: currentQuestion.answer as string,
});
}
}
toggleChoiceSelectionMulti(choice: FrageOption) {
const currentQuestion = this.trueFalse.getCurrentQuestion();
if (currentQuestion) {
choice.selected = !choice.selected;
const selectedChoices = currentQuestion.choices.filter((c) => c.selected);
this.trueFalse.updateCurrentAnswers({
selectedChoice: selectedChoices,
userAnswer: currentQuestion.answer as string,
});
}
}
toggleInputAnswer(answer: string | undefined) {
if (answer !== undefined) {
const currentQuestion = this.trueFalse.getCurrentQuestion();
if (currentQuestion) {
const normalizedUserAnswer = this.trueFalse.normalizeUserAnswer(answer);
if (normalizedUserAnswer !== undefined) {
this.trueFalse.setCurrentAnswers(normalizedUserAnswer);
} else {
console.log('Normalized answer is undefined.'); // Handle undefined normalized answer
}
}
} else {
console.log('Answer is undefined.'); // Handle undefined answer
}
}
toggleAnswerVisibility(): void{
this.showAnswer = !this.showAnswer;
}
getShowAnswer(): boolean{
return this.showAnswer;
}
}

View File

@@ -0,0 +1,37 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class StatistikService {
private correct: number = 0;
private incorrect: number = 0;
private skip: number = 0;
incrementCounterOfCorrect(): void {
this.correct++;
}
incrementCounterOfIncorrect(): void {
this.incorrect++;
}
incrementCounterOfSkip(): void {
this.skip++;
}
getCounterOfCorrect(): number {
return this.correct;
}
getCounterOfIncorrect(): number {
return this.incorrect;
}
getCounterOfSkip(): number {
return this.skip;
}
resetStatistics(): void{
this.correct = 0;
this.incorrect = 0;
this.skip = 0;
}
}

View File

@@ -0,0 +1,206 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { EMPTY, Observable, catchError,tap } from 'rxjs';
import { Question } from '../Model/question';
import { DataInput } from './data-input';
import { Answers } from '../Model/answers';
@Injectable({
providedIn: 'root',
})
export class TrueOrFalesService {
private katalog1: Question[] = [];
private katalog2: Question[] = [];
private answer: Answers[] = [];
private currentQuestionIndex: number = 0;
public isLastQuestion: boolean = true;
public isFirstQuestion: boolean = false;
private skipped: boolean = false;
private userAnswer:string = '';
private shuffleEnabled: boolean = false;
constructor(
private fragenService: DataInput,
private http: HttpClient,
) {
}
initializeAnswer(): void {
this.answer = this.katalog1.map(() => ({
selectedChoice: [],
userAnswer: '',
}));
}
setQuestionsAndAnswers(questions: Question[]): void {
this.katalog1 = questions;
this.isFirstQuestion = true;
this.isLastQuestion = this.katalog1.length === 1;
this.initializeAnswer();
}
getCurrentQuestionIndex(): number {
return this.currentQuestionIndex;
}
setCurrentQuestionIndex(index: number):void {
this.currentQuestionIndex = index;
}
incrementQuestionIndex():void{
this.currentQuestionIndex++;
}
decrementQuestionIndex():void{
this.currentQuestionIndex--;
}
getCurrentQuestion(): Question {
return this.katalog1[this.getCurrentQuestionIndex()];
}
getCurrentAnswers(): Answers {
const currentAnswers = this.answer[this.getCurrentQuestionIndex()];
return currentAnswers || {selectedChoice: [], userAnswer: ''}
}
setCurrentAnswers(userAnswer: string): void {
this.setUserAnswer(userAnswer);
const currentAnswers = this.getCurrentAnswers();
this.updateCurrentAnswers(currentAnswers);
}
setUserAnswer(userAnswer: string): void {
this.userAnswer = userAnswer;
}
getUserAnswer(): string {
return this.userAnswer;
}
updateCurrentAnswers(updatedAnswers: Answers): void {
this.answer[this.getCurrentQuestionIndex()] = updatedAnswers;
}
private setKatalogAndQuestions(data: Question[]): void {
this.katalog1 = data;
this.currentQuestionIndex = 0;
this.isLastQuestion = false;
this.isFirstQuestion = true;
}
loadKatalogAndQuestions(): Observable<Question[]> {
this.initializeAnswer()
return this.http
.get<Question[]>(this.fragenService.getKatalog1Url())
.pipe(
tap((data) => {
this.setKatalogAndQuestions(data);
}),
catchError((error) => {
console.log('Fehler beim Laden des Katalogs aufgetreten', error);
return EMPTY;
})
);
}
loadKatalog2AndQuestions(): Observable<Question[]> {
this.initializeAnswer()
return this.http
.get<Question[]>(this.fragenService.getKaltalog2Url())
.pipe(
tap((data) => {
this.setKatalogAndQuestions(data);
}),
catchError((error) => {
console.log('Fehler beim laden des Katalaogs aufgetreten', error);
return EMPTY;
})
);
}
checkAnswer(userAnswer: string | string[]): boolean {
const currentQuestion = this.getCurrentQuestion();
//SingleChoiceQuestions
if (currentQuestion.questionType === 'single') {
return this.checkSingleChoiceAnswer(currentQuestion);
//MultipleChoiceQuestions
} else if (currentQuestion.questionType === 'multiple') {
return this.checkMultipleChoiceAnswer(currentQuestion);
//InputQuestions
} else if (currentQuestion.questionType === 'input') {
return this.checkInputAnswer(currentQuestion, userAnswer);
}
return false;
}
private checkSingleChoiceAnswer(question: Question): boolean{
const selectedChoice = this.getCurrentAnswers().selectedChoice.find(choice => choice.selected);
if (selectedChoice) {
const selectedFirstLatter = selectedChoice.text[0];
return selectedFirstLatter === question.answer;
}
return false;
}
private checkMultipleChoiceAnswer(qustion: Question): boolean{
const selectedChoices = this.getCurrentAnswers().selectedChoice.filter(choice => choice.selected);
const selectedTexts = selectedChoices.map(choice => choice.text[0]).sort().join('');
const sortedCorrectAnswer = qustion.answer.toString().split('').sort().join('');
return selectedTexts === sortedCorrectAnswer;
}
private checkInputAnswer(question: Question, userAnswer: string | string[]): boolean{
const formattedCorrectAnswers = Array.isArray(question.answer)
? question.answer.map(answer => answer.trim().toLowerCase())
: [question.answer.trim().toLowerCase()];
const normalizeUserAnswer = this.normalizeUserAnswer(userAnswer)||'';
const includes = formattedCorrectAnswers.includes(normalizeUserAnswer);
return includes;
}
//Normalizsation
normalizeUserAnswer(userAnswer: string | string[] | boolean): string | undefined {
if (typeof userAnswer === 'boolean') {
return [userAnswer.toString()].join('');
} else if (Array.isArray(userAnswer)) {
return userAnswer.map((answer: string | boolean) => (typeof answer === 'string' ? answer.trim() : answer.toString())).join('');
} else if (userAnswer === undefined) {
return undefined;
} else {
return [userAnswer.trim()].join('');
}
}
compareInputAnswers(userAnswer: string, correctAnswers: string): boolean {
const formattedUserAnswer = userAnswer.trim().replace(/\r?\n|\r/g, '');
const formattedCorrectAnswers = correctAnswers
.trim()
.replace(/\r?\n|\r/g, '');
return (
formattedUserAnswer.toLowerCase() === formattedCorrectAnswers.toLowerCase()
);
}
setSkipped(value: boolean): void{
this.skipped = value;
}
getSkipped():boolean{
return this.skipped;
}
getKatalog1(): Question[] {
return this.katalog1;
}
getKatalog2(): Question[] {
return this.katalog2;
}
}

View File

@@ -0,0 +1,26 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SingleChoiceQuestionComponent } from '../Templates/single-choice-questeion/single-choice-questeion.component';
import { MultipleChoiceQuestionComponent } from '../Templates/multiple-choice-questeion/multiple-choice-questeion.component';
import { InputChoiceQuesteionComponent } from '../Templates/input-choice-questeion/input-choice-questeion.component';
import { FormsModule } from '@angular/forms';
@NgModule({
declarations: [
SingleChoiceQuestionComponent,
MultipleChoiceQuestionComponent,
InputChoiceQuesteionComponent,
],
exports:[
SingleChoiceQuestionComponent,
MultipleChoiceQuestionComponent,
InputChoiceQuesteionComponent,
],
imports: [
CommonModule,
FormsModule,
]
})
export class SharedModule { }

0
src/assets/.gitkeep Normal file
View File

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

35
src/custom-theme.scss Normal file
View File

@@ -0,0 +1,35 @@
// Custom Theming for Angular Material
// For more information: https://material.angular.io/guide/theming
@use '@angular/material' as mat;
// Plus imports for other components in your app.
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@include mat.core();
// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue. Available color palettes: https://material.io/design/color/
$LLCE-primary: mat.define-palette(mat.$indigo-palette);
$LLCE-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
// The warn palette is optional (defaults to red).
$LLCE-warn: mat.define-palette(mat.$red-palette);
// Create the theme object. A theme consists of configurations for individual
// theming systems such as "color" or "typography".
$LLCE-theme: mat.define-light-theme((
color: (
primary: $LLCE-primary,
accent: $LLCE-accent,
warn: $LLCE-warn,
)
));
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
@include mat.all-component-themes($LLCE-theme);

BIN
src/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 B

16
src/index.html Normal file
View File

@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>LLCE</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body class="mat-typography">
<LL-root></LL-root>
</body>
</html>

7
src/main.ts Normal file
View File

@@ -0,0 +1,7 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

132
src/styles.css Normal file
View File

@@ -0,0 +1,132 @@
h1 {
display: flex;
justify-content: center;
}
h2 {
display: flex;
justify-content: center;
}
button {
padding: 10px;
border: 1px solid #ccc;
/* Rahmen um die Links */
border-radius: 5px;
/* Abrundung des Rahmens */
text-decoration: none;
color: #333;
}
button:hover {
background-color: rgb(131, 169, 250);
}
.button-container {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 20px;
/* Add some top margin for spacing */
}
.main-container {
display: flex;
align-items: flex-start;
gap: 20px;
}
.questions-section {
flex-grow: 1;
/* Stilisierung für den Bereich der Fragen oder des Inhalts hier */
}
.questions-container {
max-width: 700px;
width: 50%;
padding: 20px;
margin: 20px auto;
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 5px;
}
.question-block {
border: 1px solid #ccc;
padding: 15px;
margin-bottom: 20px;
background-color: #fff;
border-radius: 5px;
}
/* Stil für die Antwortmöglichkeiten */
.question-block ul {
list-style: disc;
margin-left: 20px;
}
/* Stil für die richtige Antwort */
.question-block p:last-child {
font-weight: bold;
}
.question-text{
width: 500px;
height: 85px;
border: 0px;
padding: 1em;
}
.choice-text{
width: 500;
height: 250px;
border: 0px;
padding: 1em;
}
.infoTextBox {
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
text-decoration: none;
color: #333;
}
.centered-infoTextBox {
margin-top: 100px;
display: flex;
justify-content: center;
}
.show-answer-button {
margin-top: 10px;
padding: 5px 10px;
background-color: #ddd;
border: none;
border-radius: 5px;
cursor: pointer;
}
.show-answer-button.selected {
background-color: rgb(131, 169, 250); /* Hintergrundfarbe für ausgewählten Button */
color: #fff; /* Textfarbe für ausgewählten Button */
}
.show-answer-button:hover {
background-color: rgb(131, 169, 250); /* Hintergrundfarbe für gehoverten Button */
color: #fff; /* Textfarbe für gehoverten Button */
}
.answer {
margin-top: 10px;
font-weight: bold;
color: #333;
}
.empty-column {
width: 200px; /* Breite der leeren Spalte */
background-color: #f7f7f7; /* Hintergrundfarbe der leeren Spalte */
/* Weitere Stilisierung hier, z. B. Abstand oder Werbung einfügen */
}

14
tsconfig.app.json Normal file
View File

@@ -0,0 +1,14 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts"
],
"include": [
"src/**/*.d.ts"
]
}

33
tsconfig.json Normal file
View File

@@ -0,0 +1,33 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": [
"ES2022",
"dom"
]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

14
tsconfig.spec.json Normal file
View File

@@ -0,0 +1,14 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}