diff --git a/angular.json b/angular.json index 9afefbb..13bce35 100644 --- a/angular.json +++ b/angular.json @@ -24,7 +24,8 @@ "zone.js" ], "allowedCommonJsDependencies": [ - "crypto-js" + "crypto-js", + "moment" ], "tsConfig": "tsconfig.app.json", "inlineStyleLanguage": "sass", diff --git a/package.json b/package.json index db42b35..1124d1d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xpquiz.github.io", - "version": "1.2.2", + "version": "1.3.0", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/src/app/about-window/about-window.component.html b/src/app/about-window/about-window.component.html index fb6e7e7..61c71cf 100644 --- a/src/app/about-window/about-window.component.html +++ b/src/app/about-window/about-window.component.html @@ -1,7 +1,7 @@
- + diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index ad32d7b..905f5cd 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -31,11 +31,11 @@ const routes: Routes = [ component: QuestionWindowComponent, }, { - path: `${PathsEnum.CORRECT_ANSWER}/:points`, + path: `${PathsEnum.CORRECT_ANSWER}/:points/:result`, component: CorrectAnswerWindowComponent, }, { - path: `${PathsEnum.WRONG_ANSWER}/:answer`, + path: `${PathsEnum.WRONG_ANSWER}/:result`, component: WrongAnswerWindowComponent } ] diff --git a/src/app/correct-answer-window/correct-answer-window.component.html b/src/app/correct-answer-window/correct-answer-window.component.html index 1c629d4..c2826ee 100644 --- a/src/app/correct-answer-window/correct-answer-window.component.html +++ b/src/app/correct-answer-window/correct-answer-window.component.html @@ -6,8 +6,14 @@
- - +
+ + +
+
+ +
diff --git a/src/app/correct-answer-window/correct-answer-window.component.sass b/src/app/correct-answer-window/correct-answer-window.component.sass index 5ed149c..89ad445 100644 --- a/src/app/correct-answer-window/correct-answer-window.component.sass +++ b/src/app/correct-answer-window/correct-answer-window.component.sass @@ -14,3 +14,17 @@ label text-align: center margin: 5px + + &_buttons + display: flex + flex-direction: row + align-items: center + justify-content: space-between + + app-icon-button + margin: 3px + + &_clipboard-text + color: green + font-weight: bold + margin: 5px diff --git a/src/app/correct-answer-window/correct-answer-window.component.ts b/src/app/correct-answer-window/correct-answer-window.component.ts index 22a05a0..fce19d0 100644 --- a/src/app/correct-answer-window/correct-answer-window.component.ts +++ b/src/app/correct-answer-window/correct-answer-window.component.ts @@ -2,6 +2,9 @@ import {Component, OnInit} from '@angular/core'; import {ActivatedRoute, Router} from "@angular/router"; import {PathsEnum} from "../../model/enums/PathsEnum"; import {AppStorageService} from "../../service/app-storage.service"; +import {EncryptionService} from "../../service/encryption.service"; +import {TemplateService} from "../../service/template.service"; +import {QuestionResultTemplateParams, TemplateEnum} from "../../model/enums/Template"; @Component({ selector: 'app-correct-answer-window', @@ -11,18 +14,23 @@ import {AppStorageService} from "../../service/app-storage.service"; export class CorrectAnswerWindowComponent implements OnInit { public questionScore: number = 0; + public clipboardText: string = ''; + public displayClipboardMessage: boolean = false; private correctAnswerSound: HTMLAudioElement = new Audio('assets/sounds/tada.wav'); constructor( private readonly router: Router, private readonly route: ActivatedRoute, + private readonly encryptionService: EncryptionService, + private readonly templateService: TemplateService, private readonly appStorageService: AppStorageService ) { - this.questionScore = parseInt(this.route.snapshot.paramMap.get('points') ?? '0'); } public async ngOnInit(): Promise { + await this.retrieveRouteParams(); + if (!this.appStorageService.canQuizBeAnswered()) { await this.returnHome(); return; @@ -36,7 +44,28 @@ export class CorrectAnswerWindowComponent implements OnInit { await this.router.navigateByUrl(PathsEnum.HOME); } + public async showClipboardMessage(): Promise { + this.displayClipboardMessage = true; + + await new Promise(f => setTimeout(f, 5000)); + + this.displayClipboardMessage = false; + } + private saveCurrentScore(): void { this.appStorageService.saveAnswer(true, this.questionScore); } + + private async retrieveRouteParams(): Promise { + const encryptedQuestionScore: string = this.route.snapshot.paramMap.get('points')!; + const encryptedQuestionResult: string = this.route.snapshot.paramMap.get('result')!; + + const decryptedQuestionScore: string = this.encryptionService.decrypt(encryptedQuestionScore); + const decryptedQuestionResult: string = this.encryptionService.decrypt(encryptedQuestionResult); + const questionResult: QuestionResultTemplateParams = JSON.parse(decryptedQuestionResult); + const questionResultText: string = await this.templateService.render(TemplateEnum.QUESTION_RESULT, questionResult); + + this.questionScore = parseInt(decryptedQuestionScore); + this.clipboardText = questionResultText; + } } diff --git a/src/app/question-window/question-window.component.ts b/src/app/question-window/question-window.component.ts index cd6fd56..1bcb51e 100644 --- a/src/app/question-window/question-window.component.ts +++ b/src/app/question-window/question-window.component.ts @@ -4,6 +4,8 @@ import {TriviaResponse} from "../../model/TriviaResponse"; import {Router} from "@angular/router"; import {PathsEnum} from "../../model/enums/PathsEnum"; import {AppStorageService} from "../../service/app-storage.service"; +import {QuestionResultTemplateParams} from "../../model/enums/Template"; +import {EncryptionService} from "../../service/encryption.service"; @Component({ selector: 'app-question-window', @@ -29,6 +31,7 @@ export class QuestionWindowComponent implements OnInit { constructor( private readonly triviaService: TriviaService, private readonly router: Router, + private readonly encryptionService: EncryptionService, private readonly appStorageService: AppStorageService ) { } @@ -96,11 +99,21 @@ export class QuestionWindowComponent implements OnInit { private async redirectFromAnswer(): Promise { const correctAnswer: boolean = this.selectedAnswer === this.correctAnswer; + const questionResult: QuestionResultTemplateParams = { + question: this.question, + selectedAnswer: `${correctAnswer ? '🟩' : '🟥'} ${this.selectedAnswer}`, + rightAnswer: this.correctAnswer, + wrongAnswers: this.answers.filter(value => value !== this.correctAnswer) + }; + + const questionResultData: string = this.encryptionService.encrypt(JSON.stringify(questionResult)); if (correctAnswer) { - await this.router.navigate([PathsEnum.CORRECT_ANSWER, this.questionPoints]); + const questionPointsData: string = this.encryptionService.encrypt(this.questionPoints.toString()); + + await this.router.navigate([PathsEnum.CORRECT_ANSWER, questionPointsData, questionResultData]); } else { - await this.router.navigate([PathsEnum.WRONG_ANSWER, this.correctAnswer]); + await this.router.navigate([PathsEnum.WRONG_ANSWER, questionResultData]); } } diff --git a/src/app/wrong-answer-window/wrong-answer-window.component.html b/src/app/wrong-answer-window/wrong-answer-window.component.html index c2f5ace..54b42e5 100644 --- a/src/app/wrong-answer-window/wrong-answer-window.component.html +++ b/src/app/wrong-answer-window/wrong-answer-window.component.html @@ -6,7 +6,14 @@ - +
+ + +
+
+ +
diff --git a/src/app/wrong-answer-window/wrong-answer-window.component.sass b/src/app/wrong-answer-window/wrong-answer-window.component.sass index 2ae99cb..f1d1bb4 100644 --- a/src/app/wrong-answer-window/wrong-answer-window.component.sass +++ b/src/app/wrong-answer-window/wrong-answer-window.component.sass @@ -13,4 +13,16 @@ label margin: 5px + &_buttons + display: flex + flex-direction: row + align-items: center + justify-content: space-between + + app-icon-button + margin: 3px + &_clipboard-text + color: green + font-weight: bold + margin: 5px diff --git a/src/app/wrong-answer-window/wrong-answer-window.component.ts b/src/app/wrong-answer-window/wrong-answer-window.component.ts index ee9fbc5..b14bdcf 100644 --- a/src/app/wrong-answer-window/wrong-answer-window.component.ts +++ b/src/app/wrong-answer-window/wrong-answer-window.component.ts @@ -2,6 +2,9 @@ import {Component, OnInit} from '@angular/core'; import {ActivatedRoute, Router} from "@angular/router"; import {PathsEnum} from "../../model/enums/PathsEnum"; import {AppStorageService} from "../../service/app-storage.service"; +import {QuestionResultTemplateParams, TemplateEnum} from "../../model/enums/Template"; +import {EncryptionService} from "../../service/encryption.service"; +import {TemplateService} from "../../service/template.service"; @Component({ selector: 'app-wrong-answer-window', @@ -11,17 +14,23 @@ import {AppStorageService} from "../../service/app-storage.service"; export class WrongAnswerWindowComponent implements OnInit { public correctAnswer: string | null = ''; + public clipboardText: string = ''; + public displayClipboardMessage: boolean = false; + private wrongAnswerSound: HTMLAudioElement = new Audio('assets/sounds/critical_stop.wav'); constructor( private readonly router: Router, private readonly route: ActivatedRoute, + private readonly encryptionService: EncryptionService, + private readonly templateService: TemplateService, private readonly appStorageService: AppStorageService ) { - this.correctAnswer = this.route.snapshot.paramMap.get('answer'); } public async ngOnInit(): Promise { + await this.retrieveRouteParams(); + if (!this.appStorageService.canQuizBeAnswered()) { await this.returnHome(); return; @@ -35,7 +44,25 @@ export class WrongAnswerWindowComponent implements OnInit { await this.router.navigateByUrl(PathsEnum.HOME); } + public async showClipboardMessage(): Promise { + this.displayClipboardMessage = true; + + await new Promise(f => setTimeout(f, 5000)); + + this.displayClipboardMessage = false; + } + private saveCurrentScore() { this.appStorageService.saveAnswer(false); } + + private async retrieveRouteParams(): Promise { + const encryptedQuestionResult: string = this.route.snapshot.paramMap.get('result')!; + + const decryptedQuestionResult: string = this.encryptionService.decrypt(encryptedQuestionResult); + const questionResult: QuestionResultTemplateParams = JSON.parse(decryptedQuestionResult); + + this.correctAnswer = questionResult.rightAnswer; + this.clipboardText = await this.templateService.render(TemplateEnum.QUESTION_RESULT, questionResult); + } } diff --git a/src/assets/templates/question_result.mustache b/src/assets/templates/question_result.mustache new file mode 100644 index 0000000..f57d753 --- /dev/null +++ b/src/assets/templates/question_result.mustache @@ -0,0 +1,15 @@ +❔ XPQuiz - xpquiz.github.io 🤔 +The question was... + +👉 {{{question}}} 👈 + +With the following answers... + +🟩 {{{rightAnswer}}} +{{#wrongAnswers}} +🟥 {{{.}}} +{{/wrongAnswers}} + +And i picked... + +{{{selectedAnswer}}} diff --git a/src/assets/templates/week_score.mustache b/src/assets/templates/week_score.mustache index 6098294..86de5b1 100644 --- a/src/assets/templates/week_score.mustache +++ b/src/assets/templates/week_score.mustache @@ -1,4 +1,4 @@ -❔ XP Quiz - xpquiz.github.io 🤔 +❔ XPQuiz - xpquiz.github.io 🤔 Here's my week score! 🟩 Right answers: {{rightAnswers}} diff --git a/src/index.html b/src/index.html index a86b411..877cc1c 100644 --- a/src/index.html +++ b/src/index.html @@ -2,7 +2,7 @@ - XP Quiz + XPQuiz diff --git a/src/model/enums/Template.ts b/src/model/enums/Template.ts index c4380ae..c354b64 100644 --- a/src/model/enums/Template.ts +++ b/src/model/enums/Template.ts @@ -1,5 +1,6 @@ export enum TemplateEnum { - WEEK_SCORE = "/assets/templates/week_score.mustache" + WEEK_SCORE = "/assets/templates/week_score.mustache", + QUESTION_RESULT = "/assets/templates/question_result.mustache" } export interface TemplateParams { @@ -11,3 +12,10 @@ export interface WeekScoreTemplateParams extends TemplateParams { wrongAnswers: number, totalScore: number } + +export interface QuestionResultTemplateParams extends TemplateParams { + question: string, + selectedAnswer: string, + rightAnswer: string, + wrongAnswers: string[] +} diff --git a/src/service/encryption.service.spec.ts b/src/service/encryption.service.spec.ts new file mode 100644 index 0000000..c5cdd8a --- /dev/null +++ b/src/service/encryption.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { EncryptionService } from './encryption.service'; + +describe('EncryptionService', () => { + let service: EncryptionService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(EncryptionService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/service/encryption.service.ts b/src/service/encryption.service.ts new file mode 100644 index 0000000..c7b1aab --- /dev/null +++ b/src/service/encryption.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@angular/core'; +import {AppStorage} from "../model/AppStorage"; +import {AES, enc} from "crypto-js"; + +@Injectable({ + providedIn: 'root' +}) +export class EncryptionService { + + private readonly encryptionkey: string = 'ENCRYPTION_KEY_XPQUIZ'; + + constructor() { + } + + public encrypt(value: string): string { + const encrypted = AES.encrypt(value, this.encryptionkey); + return encrypted.toString(); + } + + public decrypt(value: string): string { + const decrypted = AES.decrypt(value, this.encryptionkey); + return decrypted.toString(enc.Utf8); + } + + public replacer(key: any, value: any) { + if (value instanceof Map) { + return { + dataType: 'Map', + value: Array.from(value.entries()), // or with spread: value: [...value] + }; + } else { + return value; + } + } + + public reviver(key: any, value: any) { + if (typeof value === 'object' && value !== null) { + if (value.dataType === 'Map') { + return new Map(value.value); + } + } + return value; + } +} diff --git a/src/service/storage.service.ts b/src/service/storage.service.ts index 72ae830..d7a8743 100644 --- a/src/service/storage.service.ts +++ b/src/service/storage.service.ts @@ -1,6 +1,7 @@ import {Injectable} from '@angular/core'; import {AES, enc} from 'crypto-js'; import {AppStorage} from "../model/AppStorage"; +import {EncryptionService} from "./encryption.service"; @Injectable({ providedIn: 'root' @@ -9,15 +10,16 @@ export class StorageService { private readonly localStorage: Storage; private readonly storageKey: string = 'app_storage' - private readonly encryptionkey: string = 'ENCRYPTION_KEY_XPQUIZ'; - constructor() { + constructor( + private readonly encryptionService: EncryptionService + ) { this.localStorage = window.localStorage; } public save(value: AppStorage): void { - const stringfiedObject: string = JSON.stringify(value, this.replacer); - const encryptedObject: string = this.encrypt(stringfiedObject); + const stringfiedObject: string = JSON.stringify(value, this.encryptionService.replacer); + const encryptedObject: string = this.encryptionService.encrypt(stringfiedObject); this.localStorage.setItem(this.storageKey, encryptedObject); } @@ -28,37 +30,7 @@ export class StorageService { if (encryptedItem === null || encryptedItem === '') return null; else - return JSON.parse(this.decrypt(encryptedItem), this.reviver); - } - - private encrypt(value: string): string { - const encrypted = AES.encrypt(value, this.encryptionkey); - return encrypted.toString(); - } - - private decrypt(value: string): string { - const decrypted = AES.decrypt(value, this.encryptionkey); - return decrypted.toString(enc.Utf8); - } - - private replacer(key: any, value: any) { - if (value instanceof Map) { - return { - dataType: 'Map', - value: Array.from(value.entries()), // or with spread: value: [...value] - }; - } else { - return value; - } - } - - private reviver(key: any, value: any) { - if (typeof value === 'object' && value !== null) { - if (value.dataType === 'Map') { - return new Map(value.value); - } - } - return value; + return JSON.parse(this.encryptionService.decrypt(encryptedItem), this.encryptionService.reviver); } } diff --git a/src/service/template.service.ts b/src/service/template.service.ts index 9d4f81e..069b1cb 100644 --- a/src/service/template.service.ts +++ b/src/service/template.service.ts @@ -2,14 +2,13 @@ import {Injectable} from '@angular/core'; import {TemplateEnum, TemplateParams} from "../model/enums/Template"; import Mustache from "mustache"; import {HttpClient} from "@angular/common/http"; -import {firstValueFrom, take} from "rxjs"; +import {firstValueFrom} from "rxjs"; @Injectable({ providedIn: 'root' }) export class TemplateService { - constructor( private readonly http: HttpClient ) {