Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
"polyfills": [
"zone.js"
],
"allowedCommonJsDependencies": [
"crypto-js"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "sass",
"assets": [
Expand Down
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions src/app/about-window/about-window.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="window">
<app-window-title-bar iconPath="assets/icons/about.png" title="About XPQuiz"></app-window-title-bar>
<app-window-title-bar iconPath="about.png" title="About XPQuiz"></app-window-title-bar>
<div class="window-body">
<label><b>XPQuiz</b>&nbsp;- Version 1.0.1</label>
<label class="main">Created by&nbsp;<b><a href="https://isahann.github.io">Isahann Hanacleto</a></b></label>
Expand All @@ -13,7 +13,7 @@
href="https://www.deviantart.com/marchmountain/art/Windows-XP-High-Resolution-Icon-Pack-916042853">marchmountain
at DeviantArt</a></b></label>

<app-icon-button iconPath="assets/icons/home.png" title="Return to home"
<app-icon-button iconPath="home.png" title="Return to home"
(onButtonClick)="this.returnHome()"></app-icon-button>
</div>
</div>
4 changes: 2 additions & 2 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
]
Expand Down
2 changes: 2 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -25,6 +26,7 @@ import { AboutWindowComponent } from './about-window/about-window.component';
CorrectAnswerWindowComponent,
WrongAnswerWindowComponent,
AboutWindowComponent,
CopyClipboardDirective
],
imports: [
BrowserModule,
Expand Down
2 changes: 1 addition & 1 deletion src/app/common/icon-button/icon-button.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<button [disabled]="this.disabled" (click)="this.onClick()">
<img
[ngSrc]="this.iconPath" [ngClass]="this.disabled ? 'disabled' : ''"
[ngSrc]="'assets/icons/20x20/' + this.iconPath" [ngClass]="this.disabled ? 'disabled' : ''"
alt="" [height]="this.iconSize" [width]="this.iconSize"/>
{{this.title}}
</button>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="title-bar">
<div class="title-bar-text">
<img
[ngSrc]="this.iconPath"
[ngSrc]="'assets/icons/15x15/' + this.iconPath"
alt="" [height]="this.iconSize" [width]="this.iconSize" />
{{this.title}}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<div class="window">
<app-window-title-bar iconPath="assets/icons/success.png" title="You did it!"></app-window-title-bar>
<app-window-title-bar iconPath="success.png" title="You did it!"></app-window-title-bar>
<div class="window-body">
<div class="window-body_title">
<label><b>Congratulations!</b></label>
<label>You earned {{this.questionScore}} points!</label>
<label>Come back in 3 hours for another question!</label>
</div>

<app-icon-button iconPath="assets/icons/home.png" title="Return to home"
<app-icon-button iconPath="home.png" title="Return to home"
(onButtonClick)="this.returnHome()"></app-icon-button>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,4 @@

label
text-align: center


margin: 5px
37 changes: 27 additions & 10 deletions src/app/correct-answer-window/correct-answer-window.component.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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<void> {
Expand All @@ -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<number, WeekScore> = new Map<number, WeekScore>([[currentWeek, {
score: 0,
rightAnswers: 0,
wrongAnswers: 0,
}]]);
const currentWeekScoreMap: Map<number, WeekScore> = 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()
}
)
}
}
32 changes: 32 additions & 0 deletions src/app/directives/CopyClipboardDirective.ts
Original file line number Diff line number Diff line change
@@ -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<string> = new EventEmitter<string>();

@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);
}
}
10 changes: 5 additions & 5 deletions src/app/main-window/main-window.component.html
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
<div class="window">
<app-window-title-bar iconPath="assets/icons/home.png" title="Welcome!"></app-window-title-bar>
<app-window-title-bar iconPath="home.png" title="Welcome!"></app-window-title-bar>
<div class="window-body">
<label>Welcome to &nbsp;<b>XPQuiz</b>!</label>

<ng-container *ngIf="!this.quizCanBeAnswered">
<div class="window-body_quiz-answered">
<label>You already answered for now... </label>
<label>Come back in: &nbsp;<b>[{{this.countdown}}]</b></label>
<label>Come back in: &nbsp;<b>[{{this.remainingTime}}]</b></label>
</div>
</ng-container>

<div class="window-body_buttons">
<app-icon-button iconPath="assets/icons/quiz.png" title="New quiz"
<app-icon-button iconPath="quiz.png" title="New quiz"
(onButtonClick)="this.redirectTo(PathsEnum.QUIZ)"
[disabled]="!this.quizCanBeAnswered"></app-icon-button>
<app-icon-button iconPath="assets/icons/score.png" title="My score"
<app-icon-button iconPath="score.png" title="My score"
(onButtonClick)="this.redirectTo(PathsEnum.SCORES)"></app-icon-button>
<app-icon-button iconPath="assets/icons/about.png" title="About"
<app-icon-button iconPath="about.png" title="About"
(onButtonClick)="this.redirectTo(PathsEnum.ABOUT)"></app-icon-button>
</div>
</div>
Expand Down
64 changes: 31 additions & 33 deletions src/app/main-window/main-window.component.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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;

Expand All @@ -23,65 +25,61 @@ export class MainWindowComponent implements OnInit {
}

public async ngOnInit(): Promise<void> {
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);

if (!this.quizCanBeAnswered)
this.startCountdown(lastQuizResponseDate);
}

private checkIfQuizCanBeAnswered(lastQuizResponseDate: string | null): boolean {
if (lastQuizResponseDate === null || lastQuizResponseDate === '') return true;
public async redirectTo(route: PathsEnum): Promise<void> {
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<void>(async (resolve, reject): Promise<void> => {
new Promise<void>(async (resolve): Promise<void> => {
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<void> {
await this.router.navigateByUrl(route);
private clearLastAnsweredDate(): void {
const appStorage: AppStorage = this.storageService.get();

this.storageService.save(
{
...appStorage,
lastQuizResponseDate: null
}
);
}
}
Loading