diff --git a/angular.json b/angular.json index fbcae5a..9afefbb 100644 --- a/angular.json +++ b/angular.json @@ -23,6 +23,9 @@ "polyfills": [ "zone.js" ], + "allowedCommonJsDependencies": [ + "crypto-js" + ], "tsConfig": "tsconfig.app.json", "inlineStyleLanguage": "sass", "assets": [ diff --git a/package-lock.json b/package-lock.json index 5378f9a..1fa6364 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@types/crypto-js": "^4.1.1", "angular-cli-ghpages": "^1.0.6", "crypto-js": "^4.1.1", + "moment": "^2.29.4", "rxjs": "~7.8.0", "tslib": "^2.3.0", "xp.css": "^0.2.6", @@ -8242,6 +8243,14 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, "node_modules/mrmime": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", diff --git a/package.json b/package.json index 5e373fb..38cbec9 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@types/crypto-js": "^4.1.1", "angular-cli-ghpages": "^1.0.6", "crypto-js": "^4.1.1", + "moment": "^2.29.4", "rxjs": "~7.8.0", "tslib": "^2.3.0", "xp.css": "^0.2.6", diff --git a/src/app/about-window/about-window.component.html b/src/app/about-window/about-window.component.html index ba4dff2..6203978 100644 --- a/src/app/about-window/about-window.component.html +++ b/src/app/about-window/about-window.component.html @@ -1,5 +1,5 @@
- +
@@ -13,7 +13,7 @@ href="https://www.deviantart.com/marchmountain/art/Windows-XP-High-Resolution-Icon-Pack-916042853">marchmountain at DeviantArt -
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index e63830c..e9675d9 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, + path: `${PathsEnum.CORRECT_ANSWER}/:points`, component: CorrectAnswerWindowComponent, }, { - path: PathsEnum.WRONG_ANSWER, + path: `${PathsEnum.WRONG_ANSWER}/:answer`, component: WrongAnswerWindowComponent } ] diff --git a/src/app/app.module.ts b/src/app/app.module.ts index dc46fb1..220cfc0 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -13,6 +13,7 @@ import {ScoreWindowComponent} from './score-window/score-window.component'; import { CorrectAnswerWindowComponent } from './correct-answer-window/correct-answer-window.component'; import { WrongAnswerWindowComponent } from './wrong-answer-window/wrong-answer-window.component'; import { AboutWindowComponent } from './about-window/about-window.component'; +import {CopyClipboardDirective} from "./directives/CopyClipboardDirective"; @NgModule({ declarations: [ @@ -25,6 +26,7 @@ import { AboutWindowComponent } from './about-window/about-window.component'; CorrectAnswerWindowComponent, WrongAnswerWindowComponent, AboutWindowComponent, + CopyClipboardDirective ], imports: [ BrowserModule, diff --git a/src/app/common/icon-button/icon-button.component.html b/src/app/common/icon-button/icon-button.component.html index 7d0c3bc..5bc91f2 100644 --- a/src/app/common/icon-button/icon-button.component.html +++ b/src/app/common/icon-button/icon-button.component.html @@ -1,6 +1,6 @@ diff --git a/src/app/common/window-title-bar/window-title-bar.component.html b/src/app/common/window-title-bar/window-title-bar.component.html index 99daec4..3e91e8a 100644 --- a/src/app/common/window-title-bar/window-title-bar.component.html +++ b/src/app/common/window-title-bar/window-title-bar.component.html @@ -1,7 +1,7 @@
{{this.title}}
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 aab3ecb..1c629d4 100644 --- a/src/app/correct-answer-window/correct-answer-window.component.html +++ b/src/app/correct-answer-window/correct-answer-window.component.html @@ -1,5 +1,5 @@
- +
@@ -7,7 +7,7 @@
-
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 a30ccbd..5ed149c 100644 --- a/src/app/correct-answer-window/correct-answer-window.component.sass +++ b/src/app/correct-answer-window/correct-answer-window.component.sass @@ -13,5 +13,4 @@ label text-align: center - - + 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 6bf2bd6..bb3e6ed 100644 --- a/src/app/correct-answer-window/correct-answer-window.component.ts +++ b/src/app/correct-answer-window/correct-answer-window.component.ts @@ -1,8 +1,9 @@ import {Component, OnInit} from '@angular/core'; -import {Router} from "@angular/router"; +import {ActivatedRoute, Router} from "@angular/router"; import {PathsEnum} from "../../model/PathsEnum"; import {StorageService} from "../../service/storage.service"; -import {StorageKeyEnum} from "../../model/StorageKeyEnum"; +import {AppStorage, WeekScore} from "../../model/AppStorage"; +import * as moment from "moment"; @Component({ selector: 'app-correct-answer-window', @@ -11,14 +12,16 @@ import {StorageKeyEnum} from "../../model/StorageKeyEnum"; }) export class CorrectAnswerWindowComponent implements OnInit { - public questionScore: number = 10; + public questionScore: number = 0; private correctAnswerSound: HTMLAudioElement = new Audio('assets/sounds/tada.wav'); constructor( private readonly router: Router, + private readonly route: ActivatedRoute, private readonly storageService: StorageService ) { + this.questionScore = parseInt(this.route.snapshot.paramMap.get('points') ?? '0'); } public async ngOnInit(): Promise { @@ -31,12 +34,26 @@ export class CorrectAnswerWindowComponent implements OnInit { } private saveCurrentScore(): void { - const currentScore: string | null = this.storageService.get(StorageKeyEnum.CURRENT_SCORE); - let currentScoreNumber: number = (currentScore === null || currentScore === '') ? 0 : parseInt(currentScore); - - currentScoreNumber += this.questionScore; - - this.storageService.save(StorageKeyEnum.CURRENT_SCORE, currentScoreNumber.toString()); - this.storageService.save(StorageKeyEnum.LAST_QUIZ_RESPONSE_DATE, new Date().getTime().toString()); + const appStorage: AppStorage = this.storageService.get(); + const currentWeek: number = moment().isoWeek(); + const newCurrentScoreMap: Map = new Map([[currentWeek, { + score: 0, + rightAnswers: 0, + wrongAnswers: 0, + }]]); + const currentWeekScoreMap: Map = appStorage.weekScoreMap ?? newCurrentScoreMap; + const currentWeekScore: WeekScore = currentWeekScoreMap.get(currentWeek)!; + + currentWeekScore.rightAnswers += 1; + currentWeekScore.score += this.questionScore; + currentWeekScoreMap.set(currentWeek, currentWeekScore!); + + this.storageService.save( + { + ...appStorage, + weekScoreMap: currentWeekScoreMap, + lastQuizResponseDate: moment().toISOString() + } + ) } } diff --git a/src/app/directives/CopyClipboardDirective.ts b/src/app/directives/CopyClipboardDirective.ts new file mode 100644 index 0000000..938b7f8 --- /dev/null +++ b/src/app/directives/CopyClipboardDirective.ts @@ -0,0 +1,32 @@ +import {Directive, Input, Output, EventEmitter, HostListener} from "@angular/core"; + +@Directive({selector: '[copy-clipboard]'}) +export class CopyClipboardDirective { + + @Input("copy-clipboard") + public payload: string = ''; + + @Output("copied") + public copied: EventEmitter = new EventEmitter(); + + @HostListener("click", ["$event"]) + public onClick(event: MouseEvent): void { + + event.preventDefault(); + if (!this.payload) + return; + + let listener = (e: ClipboardEvent) => { + // @ts-ignore + let clipboard = e.clipboardData || window["clipboardData"]; + clipboard.setData("text", this.payload.toString()); + e.preventDefault(); + + this.copied.emit(this.payload); + }; + + document.addEventListener("copy", listener, false) + document.execCommand("copy"); + document.removeEventListener("copy", listener, false); + } +} diff --git a/src/app/main-window/main-window.component.html b/src/app/main-window/main-window.component.html index 163175b..0dcd1a5 100644 --- a/src/app/main-window/main-window.component.html +++ b/src/app/main-window/main-window.component.html @@ -1,22 +1,22 @@
- +
- +
- - -
diff --git a/src/app/main-window/main-window.component.ts b/src/app/main-window/main-window.component.ts index b0aff4f..3983359 100644 --- a/src/app/main-window/main-window.component.ts +++ b/src/app/main-window/main-window.component.ts @@ -1,8 +1,10 @@ import {Component, OnInit} from '@angular/core'; import {StorageService} from "../../service/storage.service"; -import {StorageKeyEnum} from "../../model/StorageKeyEnum"; import {Router} from "@angular/router"; import {PathsEnum} from "../../model/PathsEnum"; +import {AppStorage} from "../../model/AppStorage"; +import * as moment from "moment"; +import {Duration, Moment} from "moment"; @Component({ selector: 'app-main-window', @@ -12,7 +14,7 @@ import {PathsEnum} from "../../model/PathsEnum"; export class MainWindowComponent implements OnInit { public quizCanBeAnswered: boolean = true; - public countdown: string = ''; + public remainingTime: string = ''; protected readonly PathsEnum = PathsEnum; @@ -23,7 +25,8 @@ export class MainWindowComponent implements OnInit { } public async ngOnInit(): Promise { - const lastQuizResponseDate: string | null = this.storageService.get(StorageKeyEnum.LAST_QUIZ_RESPONSE_DATE); + const appStorage: AppStorage = this.storageService.get(); + const lastQuizResponseDate: string | null = appStorage.lastQuizResponseDate; this.quizCanBeAnswered = this.checkIfQuizCanBeAnswered(lastQuizResponseDate); @@ -31,57 +34,52 @@ export class MainWindowComponent implements OnInit { this.startCountdown(lastQuizResponseDate); } - private checkIfQuizCanBeAnswered(lastQuizResponseDate: string | null): boolean { - if (lastQuizResponseDate === null || lastQuizResponseDate === '') return true; + public async redirectTo(route: PathsEnum): Promise { + await this.router.navigateByUrl(route); + } - const now: Date = new Date(); - const lastAnsweredDate: Date = new Date(); - const threeHoursInMs: number = 1000 * 60 * 60 * 3; + private checkIfQuizCanBeAnswered(lastQuizResponseDate: string | null): boolean { + if (lastQuizResponseDate === null) return true; - lastAnsweredDate.setTime(parseInt(lastQuizResponseDate) + threeHoursInMs); + const now: Moment = moment(); + const nextResponseMinimumDate: Moment = moment(lastQuizResponseDate).add(3, "hours"); - return now.getTime() >= lastAnsweredDate.getTime(); + return now.isSame(nextResponseMinimumDate) || now.isAfter(nextResponseMinimumDate); } private startCountdown(lastQuizResponseDate: string | null): void { - if (lastQuizResponseDate === null || lastQuizResponseDate === '') return; - - const nextAnswerDate: Date = new Date(); - const threeHoursInMs: number = 1000 * 60 * 60 * 3; + if (lastQuizResponseDate === null) return; - nextAnswerDate.setTime(parseInt(lastQuizResponseDate) + threeHoursInMs); + const nextResponseMinimumDate: Moment = moment(lastQuizResponseDate).add(3, "hours"); - new Promise(async (resolve, reject): Promise => { + new Promise(async (resolve): Promise => { while (true) { - const now: Date = new Date(); - - const sameHour: boolean = now.getUTCHours() === nextAnswerDate.getUTCHours(); - const sameMinute: boolean = now.getUTCMinutes() === nextAnswerDate.getUTCMinutes(); - const sameSecond: boolean = now.getUTCSeconds() === nextAnswerDate.getUTCSeconds(); + const now: Moment = moment(); - if (sameHour && sameMinute && sameSecond) { + if (now.isSame(nextResponseMinimumDate) || now.isAfter(nextResponseMinimumDate)) { this.quizCanBeAnswered = true; - this.storageService.clear(StorageKeyEnum.LAST_QUIZ_RESPONSE_DATE); + this.clearLastAnsweredDate(); resolve(); break; } - const newTime: Date = new Date(); + const timeLeft: Duration = moment.duration(nextResponseMinimumDate.valueOf() - now.valueOf()); - newTime.setTime(nextAnswerDate.getTime() - now.getTime()); - - const hours: number = newTime.getUTCHours(); - const minutes: number = newTime.getUTCMinutes(); - const seconds: number = newTime.getUTCSeconds(); - - this.countdown = `${hours} h, ${minutes} min, ${seconds} s` + this.remainingTime = `${timeLeft.hours()} hours, ${timeLeft.minutes()} minutes, ${timeLeft.seconds()} seconds` await new Promise(f => setTimeout(f, 1000)); } }); } - public async redirectTo(route: PathsEnum): Promise { - await this.router.navigateByUrl(route); + private clearLastAnsweredDate(): void { + const appStorage: AppStorage = this.storageService.get(); + + this.storageService.save( + { + ...appStorage, + lastQuizResponseDate: null + } + ); } } diff --git a/src/app/question-window/question-window.component.html b/src/app/question-window/question-window.component.html index 5a0b515..228c080 100644 --- a/src/app/question-window/question-window.component.html +++ b/src/app/question-window/question-window.component.html @@ -1,6 +1,6 @@
- +
@@ -12,6 +12,7 @@
+