This commit is contained in:
Mel M. van Veen
2024-12-01 20:16:06 +01:00
parent 2f416eb53e
commit 4cd7873f47
21 changed files with 16151 additions and 4 deletions

View File

@@ -3,15 +3,14 @@ FROM --platform=linux/amd64 node:18.19.0 as build
WORKDIR /app WORKDIR /app
RUN npm run bump-version
COPY package*.json ./ COPY package*.json ./
RUN npm install RUN npm install
RUN npm install -g @angular/cli RUN npm install -g @angular/cli
# Verhoog de versie
RUN npm run bump-version
COPY . . COPY . .
RUN ng build --configuration=production RUN ng build --configuration=production

69
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,69 @@
pipeline {
agent any
environment {
DOCKER_IMAGE_NAME = "weetikveel"
DOCKER_REGISTRY = "veenm" // Optioneel als je Docker image naar Docker Hub wilt pushen
TRUENAS_HOST = "77.175.130.12:9000" // IP-adres van TrueNAS
TRUENAS_SSH_USER = "jenkins" // Gebruiker voor SSH
TRUENAS_SSH_KEY = credentials('ssh-true-nas') // Voeg een SSH-sleutel toe in Jenkins credentials
}
stages {
stage('Checkout') {
steps {
git 'https://github.com/veenm/weetikveel.git' // Vervang door jouw repository URL
}
}
stage('Build Angular') {
steps {
script {
// Installeer de Angular dependencies en bouw de app
sh 'npm install'
sh 'npm run build --prod'
}
}
}
stage('Build Docker Image') {
steps {
script {
// Bouw het Docker image
sh 'docker build -t $DOCKER_IMAGE_NAME .'
}
}
}
stage('Deploy to TrueNAS') {
steps {
script {
// SSH naar TrueNAS en stop de oude container (indien nodig)
sh """
ssh -i $TRUENAS_SSH_KEY $TRUENAS_SSH_USER@$TRUENAS_HOST 'docker stop $DOCKER_IMAGE_NAME || true && docker rm $DOCKER_IMAGE_NAME || true'
"""
// Push het Docker image naar TrueNAS (optioneel als je een registry gebruikt)
sh 'docker tag $DOCKER_IMAGE_NAME $TRUENAS_HOST/$DOCKER_IMAGE_NAME'
sh 'docker push $TRUENAS_HOST/$DOCKER_IMAGE_NAME' // Alleen als je Docker Hub gebruikt, of een lokaal registry.
// Deploy het Docker image naar TrueNAS via SSH
sh """
ssh -i $TRUENAS_SSH_KEY $TRUENAS_SSH_USER@$TRUENAS_HOST '
docker pull $TRUENAS_HOST/$DOCKER_IMAGE_NAME &&
docker run -d -p 80:80 --name $DOCKER_IMAGE_NAME $TRUENAS_HOST/$DOCKER_IMAGE_NAME
'
"""
}
}
}
}
post {
success {
echo "Deployment succesvol!"
}
failure {
echo "Er is een fout opgetreden tijdens de deployment."
}
}
}

15226
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "wiv", "name": "wiv",
"version": "0.1.2", "version": "0.1.3",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve",

BIN
public/Logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

View File

@@ -0,0 +1,28 @@
button {
padding: 15px 30px;
font-size: 1.1rem;
margin: 10px 0;
width: 100%;
border-radius: 10px;
border: none;
cursor: pointer;
background-color: #ff561e;
color: white;
}
/* Maak de pagina responsief voor kleinere schermen zoals iPhones */
@media (max-width: 768px) {
.vertical-center {
position: absolute;
top: 50%;
-ms-transform: translateY(-50%);
transform: translateY(-50%);
width: 100%;
}
.text{
margin-left: 24px;
}
}

View File

@@ -0,0 +1,11 @@
<div class="vertical-center">
<canvas id="confetti-canvas"></canvas>
<div class="text">
<h2 (click)="easterEgg()">Je cijfer is: {{ calculatedScore }}</h2>
<h4>{{scoreMessage}}</h4>
<br>
<h4>Aantal vragen goed: {{localStorage.getItem('score')}}/50</h4>
</div>
<a href="#"><button>Spel opnieuw spelen</button></a>
</div>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FinalComponent } from './final.component';
describe('FinalComponent', () => {
let component: FinalComponent;
let fixture: ComponentFixture<FinalComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FinalComponent]
})
.compileComponents();
fixture = TestBed.createComponent(FinalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,60 @@
import {Component, OnInit} from '@angular/core';
import confetti from 'canvas-confetti';
@Component({
selector: 'app-final',
imports: [
],
templateUrl: './final.component.html',
styleUrl: './final.component.css'
})
export class FinalComponent implements OnInit{
rawScore: number = 0;
calculatedScore: number = 0;
scoreMessage = ''
ngOnInit(): void {
// Haal de score op uit localStorage
const storedScore = localStorage.getItem('score');
if (storedScore) {
this.rawScore = parseInt(storedScore, 10);
// Pas de formule toe om de berekende score te krijgen
this.calculatedScore = (this.rawScore * 2) / 10;
}
this.setScoreMessage()
this.launchConfetti()
}
setScoreMessage(): void {
if (this.calculatedScore === 10) {
this.scoreMessage = "Vals speler! 😉";
} else if (this.calculatedScore > 7) {
this.scoreMessage = "Goed bezig! 😁";
} else if (this.calculatedScore >= 5.5) {
this.scoreMessage = "Gefeliciteerd, een voldoende! 🫣";
} else {
this.scoreMessage = "Helaas, je bent niet slimmer dan Monica Geuze... 🫠";
}
}
launchConfetti() {
confetti({
particleCount: 600,
startVelocity: 40,
spread: 3600,
});
}
easterEgg(){
confetti({
particleCount: 600,
startVelocity: 40,
spread: 3600,
});
}
protected readonly localStorage = localStorage;
}

View File

@@ -0,0 +1,137 @@
/* Algemene stijl voor de game-container */
.game-container {
display: flex;
flex-direction: column;
justify-content: center; /* Verticaal centreren */
align-items: center; /* Horizontaal centreren */
height: 100vh; /* Volledige hoogte van het scherm */
padding: 20px;
max-width: 600px; /* Maximaliseer de breedte van de container */
margin: 0 auto;
}
/* Styling voor de vraag */
.question p {
font-size: 1.5rem;
font-weight: bold;
}
/* Styling voor de vraagtype instructie */
.question-type p {
font-size: 1.2rem;
margin-bottom: 10px;
}
/* Knoppen onder elkaar */
.buttons {
display: flex;
flex-direction: column;
gap: 10px;
}
.buttons-questions {
display: flex;
flex-direction: column;
gap: 10px;
width: 120px;
}
/* Styling voor de knoppen */
.buttons-questions button {
padding: 10px;
font-size: 1.2rem;
background-color: #ff561e;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
/* Styling voor de knoppen */
.buttons button {
padding: 10px;
font-size: 1.2rem;
background-color: #ff561e;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.buttons-questions button.selected {
background: white;
border-color: #ff561e;
border-style: solid;
border-width: 5px;
color: #ff561e;
}
.buttons-confirm button[disabled] {
background-color: #cccccc;
color: #666666;
}
.drag-and-drop-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 50px;
}
.left-column, .right-column {
display: flex;
flex-direction: column;
gap: 10px;
width: 100px;
}
.draggable-button {
padding: 10px;
background-color: #ff561e;
border: 1px solid #ccc;
border-radius: 5px;
cursor: grab;
text-align: center;
width: 100%;
margin: 5px;
color: white;
}
.draggable-button:active {
cursor: grabbing;
}
.draggable-button.dragging {
background-color: #c0e4ff;
}
/* Voor kleine schermen zoals de iPhone (max-width: 600px) */
@media (max-width: 600px) {
.game-container {
padding: 10px;
}
.buttons button {
font-size: 1rem; /* Verklein de tekstgrootte op kleine schermen */
padding: 8px; /* Verklein de knopgrootte op kleine schermen */
}
}
.buttons-confirm {
display: flex;
flex-direction: column;
gap: 10px;
width: 180px;
margin-top: 24px;
}
.buttons-confirm button {
padding: 10px;
font-size: 1.2rem;
background-color: #ff561e;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}

View File

@@ -0,0 +1,151 @@
<app-menu></app-menu>
<div>
</div>
<div class="game-container" *ngIf="currentQuestion.type == 0 && !confirmed">
<div class="question">
<p>Vraag {{ localStorage.getItem('question') }}</p>
</div>
<div class="question-type">
<p>Kies de soort vraag:</p>
</div>
<div class="buttons">
<button (click)="setQuestionType(1)">Meerkeuze</button>
<button (click)="setQuestionType(2)">Volgorde</button>
<button (click)="setQuestionType(3)">Waar / Niet Waar</button>
</div>
</div>
<div class="game-container" *ngIf="currentQuestion.type == 1 && !confirmed">
<div class="question">
<p>Vraag {{ localStorage.getItem('question') }}</p>
</div>
<div class="question-type">
<p>Kies het antwoord: </p>
</div>
<div class="buttons-questions">
<button *ngFor="let item of multipleChoices"
[class.selected]="item.selected"
(click)="toggleChoice(item.choice)">
{{ item.choice }}
</button>
</div>
<div class="buttons-confirm">
<button (click)="confirmAnswer()" [disabled]="answer.length === 0">Bevestigen</button>
</div>
</div>
<div class="game-container" *ngIf="currentQuestion.type == 2 && !confirmed">
<div class="question">
<p>Vraag {{ localStorage.getItem('question') }}</p>
</div>
<div class="drag-and-drop-container">
<div id="left" class="left-column" cdkDropList (cdkDropListDropped)="onDrop($event)">
<div *ngFor="let item of leftButtons; let i = index"
cdkDrag
[cdkDragData]="item"
class="draggable-button">
{{ item.position }}
</div>
</div>
<div id="right" class="right-column" cdkDropList (cdkDropListDropped)="onDrop($event)">
<div *ngFor="let item of rightButtons; let i = index"
cdkDrag
[cdkDragData]="item"
class="draggable-button">
{{ item.label }}
</div>
</div>
</div>
<div class="buttons-confirm">
<button (click)="confirmAnswer()">Bevestigen</button>
</div>
</div>
<div class="game-container" *ngIf="currentQuestion.type == 3 && !confirmed">
<div class="question">
<p>Vraag {{ localStorage.getItem('question') }}</p>
</div>
<div class="question-type">
<p>Kies het antwoord: </p>
</div>
<div class="buttons-questions">
<button *ngFor="let item of trueFalseChoices"
[class.selected]="item.selected"
(click)="toggleChoiceTrueOrFalse(item.choice)">
{{ item.choice }}
</button>
</div>
<div class="buttons-confirm">
<button (click)="confirmAnswer()" [disabled]="answer.length === 0">Bevestigen</button>
</div>
</div>
<div class="game-container" *ngIf="(currentQuestion.type == 1 || currentQuestion.type == 3) && confirmed">
<div class="question">
<p>Vraag {{ localStorage.getItem('question') }}</p>
</div>
<div>
<p>Gekozen antwoord: {{ answer.toString() }} </p>
</div>
<div class="question-type">
<p>Klopt het antwoord?</p>
</div>
<div class="buttons-questions">
<button (click)="isAnswerCorrect(true)">Ja</button>
<button (click)="isAnswerCorrect(false)">Nee</button>
</div>
</div>
<div class="game-container" *ngIf="currentQuestion.type == 2 && confirmed">
<div class="question">
<p>Vraag {{ localStorage.getItem('question') }}</p>
</div>
<div>
<p>Gekozen antwoord:</p>
</div>
<div class="drag-and-drop-container">
<div id="leftConfirmed" class="left-column" cdkDropList (cdkDropListDropped)="onDrop($event)">
<div *ngFor="let item of leftButtons; let i = index"
cdkDrag
[cdkDragData]="item"
class="draggable-button">
{{ item.position }}
</div>
</div>
<div id="rightConfirmed" class="right-column" cdkDropList (cdkDropListDropped)="onDrop($event)">
<div *ngFor="let item of rightButtons; let i = index"
cdkDrag
[cdkDragData]="item"
class="draggable-button">
{{ item.label }}
</div>
</div>
</div>
<div class="question-type">
<p>Klopt het antwoord?</p>
</div>
<div class="buttons-questions">
<button (click)="isAnswerCorrect(true)">Ja</button>
<button (click)="isAnswerCorrect(false)">Nee</button>
</div>
</div>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GameComponent } from './game.component';
describe('GameComponent', () => {
let component: GameComponent;
let fixture: ComponentFixture<GameComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GameComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GameComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,157 @@
import {Component, OnInit} from '@angular/core';
import {MenuComponent} from '../menu/menu.component';
import {CommonModule, NgIf} from '@angular/common';
import {CdkDrag, CdkDragDrop, CdkDropList} from '@angular/cdk/drag-drop';
import {Router} from '@angular/router';
@Component({
selector: 'app-game',
imports: [
MenuComponent,
NgIf,
CdkDrag,
CdkDropList,
CommonModule
],
templateUrl: './game.component.html',
styleUrl: './game.component.css'
})
export class GameComponent implements OnInit {
questions = [{index: 1, correct: false, type: 0, answered: false}]
currentQuestion = this.questions[0]
confirmed = false
answer: string[] = []
constructor(private router: Router) {
}
ngOnInit(): void {
let question: number = +localStorage.getItem('question')
localStorage.setItem('question', question.toString())
this.questions = [{index: question, correct: false, type: 0, answered: false}]
this.resetAnswer()
}
multipleChoices = [
{choice: 'A', selected: false},
{choice: 'B', selected: false},
{choice: 'C', selected: false},
{choice: 'D', selected: false}
]
trueFalseChoices = [
{choice: 'Waar', selected: false},
{choice: 'Niet waar', selected: false}
]
// Linkerkolom knoppen (1, 2, 3)
leftButtons: Button[] = [
{position: 1},
{position: 2},
{position: 3}
];
// Rechterkolom knoppen (A, B, C)
rightButtons: Button[] = [
{label: 'A'},
{label: 'B'},
{label: 'C'}
];
onDrop(event: CdkDragDrop<Button[]>) {
const previousIndex = event.previousIndex;
const currentIndex = event.currentIndex;
if (event.container === event.previousContainer) {
let movedButton: Button;
if (event.container.id === 'left') {
movedButton = this.leftButtons.splice(previousIndex, 1)[0];
this.leftButtons.splice(currentIndex, 0, movedButton);
} else if (event.container.id === 'right') {
movedButton = this.rightButtons.splice(previousIndex, 1)[0];
this.rightButtons.splice(currentIndex, 0, movedButton);
}
}
}
setQuestionType(type: number): void {
this.currentQuestion.type = type;
}
confirmAnswer() {
this.confirmed = true
}
isAnswerCorrect(correct: boolean) {
this.currentQuestion.correct = correct
if (correct) {
let score: number = +localStorage.getItem('score')
score = score + 1
localStorage.setItem('score', score.toString())
}
let question = +localStorage.getItem('question')
if (question < 50){
this.confirmed = false
this.setNewQuestion()
this.resetAnswer()
} else {
this.router.navigate(['final'])
}
}
setNewQuestion() {
let question: number = +localStorage.getItem('question')
question = question + 1
localStorage.setItem('question', question.toString())
this.questions.push({index: question, correct: false, type: 0, answered: false})
this.currentQuestion = this.questions[this.questions.length - 1]
}
resetAnswer() {
this.answer = []
this.multipleChoices.forEach(anwser => anwser.selected = false)
this.trueFalseChoices.forEach(anwser => anwser.selected = false)
}
toggleChoice(choice: string) {
const selectedChoice = this.multipleChoices.find(item => item.choice === choice);
if (selectedChoice) {
selectedChoice.selected = !selectedChoice.selected;
if (selectedChoice.selected) {
this.answer.push(choice); // Voeg toe aan antwoorden
} else {
this.answer = this.answer.filter(item => item !== choice);
}
}
}
toggleChoiceTrueOrFalse(choice: string) {
const selectedChoice = this.trueFalseChoices.find(item => item.choice === choice);
if (selectedChoice) {
if (!selectedChoice.selected) {
this.trueFalseChoices.forEach(item => item.selected = false);
selectedChoice.selected = true;
this.answer = [choice];
} else {
selectedChoice.selected = false;
this.answer = [];
}
}
}
protected readonly localStorage = localStorage;
}
interface Button {
position?: number;
label?: string;
}

View File

@@ -0,0 +1,31 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.menu {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 10px 20px;
background-color: #ff561e;
color: white;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
}
.left .name, .right .score {
font-size: 1.2rem;
}
@media (max-width: 600px) {
.menu {
flex-direction: column;
align-items: center;
}
}

View File

@@ -0,0 +1,8 @@
<div class="menu">
<div class="left">
<span class="name">Naam: {{ getName() }}</span>
</div>
<div class="right">
<span class="score">Score: {{ getScore() }}</span>
</div>
</div>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MenuComponent } from './menu.component';
describe('MenuComponent', () => {
let component: MenuComponent;
let fixture: ComponentFixture<MenuComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MenuComponent]
})
.compileComponents();
fixture = TestBed.createComponent(MenuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,18 @@
import {Component} from '@angular/core';
@Component({
selector: 'app-menu',
imports: [],
templateUrl: './menu.component.html',
styleUrl: './menu.component.css'
})
export class MenuComponent {
getName() {
return localStorage.getItem('name')
}
getScore() {
return localStorage.getItem('score');
}
}

View File

@@ -0,0 +1,83 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body, html {
height: 100%;
}
.container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 90%;
max-width: 1200px;
margin: auto;
height: 100vh;
}
.logo img {
height: 150px;
}
.form {
text-align: right;
}
.form p {
margin-bottom: 10px;
font-size: 1.2rem;
}
.form input {
padding: 10px;
font-size: 1rem;
margin-right: 10px;
border: 1px solid #ccc;
border-radius: 5px;
width: 300px;
max-width: 300px;
}
.form button {
padding: 10px 20px;
font-size: 1rem;
width: 300px;
color: #fff;
background-color: #ff561e;
border: none;
border-radius: 5px;
cursor: pointer;
}
@media (max-width: 600px) {
.container {
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
}
.logo img {
height: 100px;
margin-bottom: 20px;
}
.form {
text-align: center;
}
.form input {
margin-right: 0;
margin-bottom: 10px;
width: calc(100% - 20px);
}
.form button {
width: 100%;
}
}

View File

@@ -0,0 +1,11 @@
<div class="container">
<div class="logo">
<img src="Logo.png" alt="Logo">
</div>
<div class="form" [formGroup]="form">
<p>Voer hier je naam in om te beginnen</p>
<input type="text" placeholder="Naam" formControlName="naam">
<button (click)="start()">Beginnen</button>
</div>
</div>
<footer>versie {{version}}</footer>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { WelcomeComponent } from './welcome.component';
describe('WelcomeComponent', () => {
let component: WelcomeComponent;
let fixture: ComponentFixture<WelcomeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [WelcomeComponent]
})
.compileComponents();
fixture = TestBed.createComponent(WelcomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,66 @@
import {Component, OnInit} from '@angular/core';
import {FormControl, FormGroup, FormsModule, ReactiveFormsModule} from "@angular/forms";
import {Router} from '@angular/router';
import { version } from '../../../package.json';
import confetti from 'canvas-confetti';
@Component({
selector: 'app-welcome',
imports: [
FormsModule,
ReactiveFormsModule
],
templateUrl: './welcome.component.html',
styleUrl: './welcome.component.css'
})
export class WelcomeComponent implements OnInit{
form: FormGroup
version = version
constructor(private router: Router) {
}
ngOnInit(): void {
this.form = new FormGroup({
naam: new FormControl('')
})
}
start(){
this.setName()
this.setScore()
this.setQuestion()
this.router.navigate(['start'])
}
setName(){
if (this.form.get('naam').value.toString().toLowerCase() === 'danthe'){
localStorage.setItem('name', 'Danthe❤')
this.launchConfetti()
} else {
localStorage.setItem('name', this.form.get('naam').value)
}
}
setScore(){
localStorage.setItem('score', '0')
}
setQuestion(){
localStorage.setItem('question', '1')
}
launchConfetti() {
const scalar = 2;
const pineapple = confetti.shapeFromText({text: '❤️', scalar});
confetti({
particleCount: 600,
startVelocity: 40,
spread: 3600,
shapes:[pineapple],
scalar
});
}
}