From a79ce561077625357bb6d2dc0b4d9ecc3a4fb7b2 Mon Sep 17 00:00:00 2001 From: "Luke J. Westfall" Date: Thu, 4 Jan 2024 19:43:29 -0500 Subject: [PATCH] feat: add qr code, many enhancements --- src/Client/package-lock.json | 132 ++++++++++++++++-- src/Client/package.json | 1 + .../books/book-card/book-card.component.html | 7 +- .../books/book-card/book-card.component.ts | 52 +------ .../books-page/books-page.component.html | 4 +- .../books/books-page/books-page.component.ts | 46 +++++- .../live-meeting/live-meeting.component.html | 95 +++++++------ .../live-meeting/live-meeting.component.ts | 24 +++- .../meeting-recommendations.component.css | 0 .../meeting-recommendations.component.html | 11 ++ .../meeting-recommendations.component.spec.ts | 28 ++++ .../meeting-recommendations.component.ts | 16 +++ .../meeting-card/meeting-card.component.html | 5 +- .../src/app/meetings/meetings.module.ts | 4 + src/Server/WebApi/Hubs/LiveMeetingHub.cs | 2 +- 15 files changed, 317 insertions(+), 110 deletions(-) create mode 100644 src/Client/src/app/meetings/live-meeting/meeting-recommendations/meeting-recommendations.component.css create mode 100644 src/Client/src/app/meetings/live-meeting/meeting-recommendations/meeting-recommendations.component.html create mode 100644 src/Client/src/app/meetings/live-meeting/meeting-recommendations/meeting-recommendations.component.spec.ts create mode 100644 src/Client/src/app/meetings/live-meeting/meeting-recommendations/meeting-recommendations.component.ts diff --git a/src/Client/package-lock.json b/src/Client/package-lock.json index 99210c8..fffdd03 100644 --- a/src/Client/package-lock.json +++ b/src/Client/package-lock.json @@ -23,6 +23,7 @@ "@ngrx/store": "^17.0.1", "@ngrx/store-devtools": "^17.0.1", "@popperjs/core": "^2.11.8", + "angularx-qrcode": "^17.0.0", "bootstrap": "^5.3.2", "bootstrap-icons": "^1.11.2", "lodash-es": "^4.17.21", @@ -4762,6 +4763,18 @@ "ajv": "^8.8.2" } }, + "node_modules/angularx-qrcode": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/angularx-qrcode/-/angularx-qrcode-17.0.0.tgz", + "integrity": "sha512-pgD9hFO/OgCp+tJiiwxDRxUplzE0w6juJH2+E08lYfDZbzImXFj6mAerey00sL9CeGYNW0/pHN3pnKsApgCAkA==", + "dependencies": { + "qrcode": "1.5.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": "^17.0.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -5666,7 +5679,6 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, "engines": { "node": ">=6" } @@ -6426,6 +6438,14 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -6693,6 +6713,11 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==" + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -6975,6 +7000,11 @@ "node": ">= 4" } }, + "node_modules/encode-utf8": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz", + "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==" + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -8171,7 +8201,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -10385,7 +10414,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -12149,7 +12177,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "dependencies": { "p-try": "^2.0.0" }, @@ -12164,7 +12191,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -12213,7 +12239,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, "engines": { "node": ">=6" } @@ -12352,7 +12377,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "engines": { "node": ">=8" } @@ -12559,6 +12583,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/portscanner": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", @@ -12898,6 +12930,71 @@ "node": ">=0.9" } }, + "node_modules/qrcode": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz", + "integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==", + "dependencies": { + "dijkstrajs": "^1.0.1", + "encode-utf8": "^1.0.3", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/qrcode/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/qrcode/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "node_modules/qrcode/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -13203,6 +13300,11 @@ "node": ">=0.10.0" } }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, "node_modules/require-relative": { "version": "0.8.7", "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", @@ -13803,6 +13905,11 @@ "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", "dev": true }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-cookie-parser": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", @@ -15619,6 +15726,11 @@ "node": ">= 8" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" + }, "node_modules/wildcard": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", @@ -15635,7 +15747,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -15700,7 +15811,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -15715,7 +15825,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -15726,8 +15835,7 @@ "node_modules/wrap-ansi/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/wrappy": { "version": "1.0.2", diff --git a/src/Client/package.json b/src/Client/package.json index 1c93831..1a876eb 100644 --- a/src/Client/package.json +++ b/src/Client/package.json @@ -31,6 +31,7 @@ "@ngrx/store": "^17.0.1", "@ngrx/store-devtools": "^17.0.1", "@popperjs/core": "^2.11.8", + "angularx-qrcode": "^17.0.0", "bootstrap": "^5.3.2", "bootstrap-icons": "^1.11.2", "lodash-es": "^4.17.21", diff --git a/src/Client/src/app/books/book-card/book-card.component.html b/src/Client/src/app/books/book-card/book-card.component.html index 89b7ef4..c0b9d89 100644 --- a/src/Client/src/app/books/book-card/book-card.component.html +++ b/src/Client/src/app/books/book-card/book-card.component.html @@ -1,5 +1,5 @@
-
+
@@ -18,6 +18,8 @@
@if (recommendedForNext) { +
Recommended for next meeting!
+ } @else {
Recommend for next meeting!
- } @else { -
Recommended for next meeting!
} -

diff --git a/src/Client/src/app/books/book-card/book-card.component.ts b/src/Client/src/app/books/book-card/book-card.component.ts index 3411ac9..1cfbcff 100644 --- a/src/Client/src/app/books/book-card/book-card.component.ts +++ b/src/Client/src/app/books/book-card/book-card.component.ts @@ -1,18 +1,15 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { Actions, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; -import { orderBy } from 'lodash-es'; import { Subscription } from 'rxjs'; -import { BookDto, MeetingSimpleDto } from '../../api/models'; +import { BookDto } from '../../api/models'; import { AppState } from '../../app-state'; -import { selectNextMeeting } from '../../meetings/state/meetings.selectors'; import { ToastsService } from '../../services/toasts.service'; import { deleteBook, deleteBookSuccess, recommendBookForMeeting, } from '../state/books.actions'; -import { selectMyRecommendations } from '../state/books.selectors'; @Component({ selector: 'app-book-card', @@ -21,14 +18,12 @@ import { selectMyRecommendations } from '../state/books.selectors'; }) export class BookCardComponent implements OnInit, OnDestroy { @Input({ required: true }) book!: BookDto; - recommendedForMeeting: MeetingSimpleDto | null = null; + @Input() recommendedForNext = false; @Input() mine = false; - @Input() noRipple = true; - - nextMeetingId: string | null = null; + @Input() ripple = false; + @Input() nextMeetingId: string | null = null; expanded = false; - recommendedForNext = false; subscriptions: Subscription[] = []; @@ -36,16 +31,9 @@ export class BookCardComponent implements OnInit, OnDestroy { private store: Store, private actions$: Actions, private toastService: ToastsService - ) { - const nextMeeting$ = this.store.select(selectNextMeeting); - - this.subscriptions.push( - nextMeeting$.subscribe(m => { - this.nextMeetingId = m?.id ?? null; - this.setRecommendedForNext(); - }) - ); + ) {} + ngOnInit(): void { this.subscriptions.push( this.actions$.pipe(ofType(deleteBookSuccess)).subscribe(action => { if (action.bookId === this.book.id) { @@ -55,34 +43,6 @@ export class BookCardComponent implements OnInit, OnDestroy { ); } - ngOnInit(): void { - const recommendations$ = this.store.select(selectMyRecommendations); - - this.subscriptions.push( - recommendations$.subscribe(recommendations => { - const thisBookRecommendations = orderBy( - recommendations.filter(r => r.book.id === this.book.id), - r => r.meeting.dateTime - ); - - if (thisBookRecommendations.length > 0) { - this.recommendedForMeeting = thisBookRecommendations[0].meeting; - } else { - this.recommendedForMeeting = null; - } - - this.setRecommendedForNext(); - }) - ); - } - - setRecommendedForNext(): void { - this.recommendedForNext = - (!!this.nextMeetingId && - this.recommendedForMeeting?.id === this.nextMeetingId) ?? - false; - } - ngOnDestroy(): void { this.subscriptions.forEach(sub => sub.unsubscribe()); } diff --git a/src/Client/src/app/books/books-page/books-page.component.html b/src/Client/src/app/books/books-page/books-page.component.html index 66de9f1..c889766 100644 --- a/src/Client/src/app/books/books-page/books-page.component.html +++ b/src/Client/src/app/books/books-page/books-page.component.html @@ -18,7 +18,9 @@

My Bookshelf

+ [nextMeetingId]="(nextMeeting$ | async)?.id ?? null" + [recommendedForNext]="nextRecommendedBookId === book.id" + [ripple]="nextRecommendedBookId === book.id">
; + nextMeeting$: Observable; + myRecommendations$: Observable; isLoadingMyBooks$: Observable; + nextRecommendedBookId?: string; + + subscriptions: Subscription[] = []; + constructor(private store: Store) { this.isLoadingMyBooks$ = this.store.select( bookSelectors.selectIsLoadingMyBooks ); this.myBooks$ = this.store.select(bookSelectors.selectMyBooks); + + this.nextMeeting$ = this.store.select(selectNextMeeting); + + this.myRecommendations$ = this.store.select( + bookSelectors.selectMyRecommendations + ); + + combineLatest([this.nextMeeting$, this.myRecommendations$]).subscribe( + ([nextMeeting, myRecommendations]) => { + if (!nextMeeting) { + return; + } + + const nextRecommendation = myRecommendations.find( + r => r.meeting.id === nextMeeting.id + ); + + if (nextRecommendation) { + this.nextRecommendedBookId = nextRecommendation.book.id; + } + } + ); + } + + ngOnDestroy(): void { + this.subscriptions.forEach(sub => sub.unsubscribe()); } httpToHttps(bookDto: BookDto | BookAnonymousDto) { diff --git a/src/Client/src/app/meetings/live-meeting/live-meeting.component.html b/src/Client/src/app/meetings/live-meeting/live-meeting.component.html index 638e02b..fb05eee 100644 --- a/src/Client/src/app/meetings/live-meeting/live-meeting.component.html +++ b/src/Client/src/app/meetings/live-meeting/live-meeting.component.html @@ -27,19 +27,14 @@
The meeting has started! This is your last chance to lock in your recommendation!
-
+

Recommendations

-
-
- -
-
+
} @case (MeetingState.Closed) { -
-

And the winner is...

- -
+ @if (meeting.winningBook) { +
+

And the winner is...

+ +
+
+
Recommendations
+ +
+ } } } -
-

Last Winner

-
- -
-
-
- -
-

Roll Call!

-
+
+
+
+
Join!
+ +
+
+

Roll Call!

@if (meeting.userStates.length) {
  • Roll Call! ? 'list-group-item-success' : 'list-group-item-warning' "> - {{ userState.user.firstName }} {{ userState.user.lastName[0] }}. + {{ userState.user.firstName }} + {{ userState.user.lastName[0] }}.
} @else { -
-
No one has joined yet!
-
+ No one has joined yet! }
+
+
Last Winner
+
+ +
+
+
- +
diff --git a/src/Client/src/app/meetings/live-meeting/live-meeting.component.ts b/src/Client/src/app/meetings/live-meeting/live-meeting.component.ts index b40e92d..602daea 100644 --- a/src/Client/src/app/meetings/live-meeting/live-meeting.component.ts +++ b/src/Client/src/app/meetings/live-meeting/live-meeting.component.ts @@ -4,6 +4,7 @@ import { Store } from '@ngrx/store'; import { Observable, Subscription } from 'rxjs'; import { BookDto, MeetingDto } from '../../api/models'; import { AppState } from '../../app-state'; +import { selectMyRecommendations } from '../../books/state/books.selectors'; import { LiveMeetingService } from '../../services/websockets/live-meeting.service'; import { selectAuthenticatedUserIsAdmin } from '../../users/state/users.selectors'; import { MeetingStatus } from '../meeting-status.enum'; @@ -32,15 +33,24 @@ export class LiveMeetingComponent implements OnInit, OnDestroy { presenterMode: boolean = false; subscriptions: Subscription[] = []; + myRecommendedBookId?: string; + routeUrl: URL; + constructor( private liveMeetingSvc: LiveMeetingService, private store: Store, private route: ActivatedRoute ) { + this.routeUrl = new URL(window.location.href); + this.routeUrl.searchParams.delete('presenterMode'); + + route.queryParams.subscribe(params => { + this.presenterMode = params['presenterMode'] === 'true'; + }); + this.subscriptions.push( this.route.paramMap.subscribe(params => { this.meetingId = params.get('id') ?? ''; - this.presenterMode = params.get('presenterMode') === 'true'; this.meetingState$ = this.store.select( selectMeetingState({ meetingId: this.meetingId }) @@ -55,6 +65,18 @@ export class LiveMeetingComponent implements OnInit, OnDestroy { this.isAdmin = isAdmin; }) ); + + const myRecommendations$ = this.store.select(selectMyRecommendations); + + this.subscriptions.push( + myRecommendations$.subscribe(recommendations => { + const myRecommendation = recommendations.find( + r => r.meeting.id === this.meetingId + ); + + this.myRecommendedBookId = myRecommendation?.book.id; + }) + ); } ngOnInit() { diff --git a/src/Client/src/app/meetings/live-meeting/meeting-recommendations/meeting-recommendations.component.css b/src/Client/src/app/meetings/live-meeting/meeting-recommendations/meeting-recommendations.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/Client/src/app/meetings/live-meeting/meeting-recommendations/meeting-recommendations.component.html b/src/Client/src/app/meetings/live-meeting/meeting-recommendations/meeting-recommendations.component.html new file mode 100644 index 0000000..99ff1fd --- /dev/null +++ b/src/Client/src/app/meetings/live-meeting/meeting-recommendations/meeting-recommendations.component.html @@ -0,0 +1,11 @@ +
+
+ +
+
diff --git a/src/Client/src/app/meetings/live-meeting/meeting-recommendations/meeting-recommendations.component.spec.ts b/src/Client/src/app/meetings/live-meeting/meeting-recommendations/meeting-recommendations.component.spec.ts new file mode 100644 index 0000000..f056f22 --- /dev/null +++ b/src/Client/src/app/meetings/live-meeting/meeting-recommendations/meeting-recommendations.component.spec.ts @@ -0,0 +1,28 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { MeetingRecommendationsComponent } from './meeting-recommendations.component'; + +describe('MeetingRecommendationsComponent', () => { + let component: MeetingRecommendationsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MeetingRecommendationsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MeetingRecommendationsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/Client/src/app/meetings/live-meeting/meeting-recommendations/meeting-recommendations.component.ts b/src/Client/src/app/meetings/live-meeting/meeting-recommendations/meeting-recommendations.component.ts new file mode 100644 index 0000000..5d63c50 --- /dev/null +++ b/src/Client/src/app/meetings/live-meeting/meeting-recommendations/meeting-recommendations.component.ts @@ -0,0 +1,16 @@ +import { Component, Input } from '@angular/core'; +import { BookRecommendationForMeetingDto } from '../../../api/models'; + +@Component({ + selector: 'app-meeting-recommendations', + templateUrl: './meeting-recommendations.component.html', + styleUrls: ['./meeting-recommendations.component.css'], +}) +export class MeetingRecommendationsComponent { + @Input({ required: true }) + bookRecommendations!: BookRecommendationForMeetingDto[]; + @Input() presenterMode: boolean = false; + @Input() myRecommendedBookId: string | undefined; + + constructor() {} +} diff --git a/src/Client/src/app/meetings/meeting-card/meeting-card.component.html b/src/Client/src/app/meetings/meeting-card/meeting-card.component.html index 8e6ce21..1a01a37 100644 --- a/src/Client/src/app/meetings/meeting-card/meeting-card.component.html +++ b/src/Client/src/app/meetings/meeting-card/meeting-card.component.html @@ -65,7 +65,8 @@
@@ -83,7 +84,7 @@
} @else {
diff --git a/src/Client/src/app/meetings/meetings.module.ts b/src/Client/src/app/meetings/meetings.module.ts index 94b8e76..82ce531 100644 --- a/src/Client/src/app/meetings/meetings.module.ts +++ b/src/Client/src/app/meetings/meetings.module.ts @@ -3,10 +3,12 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { EffectsModule } from '@ngrx/effects'; import { StoreModule } from '@ngrx/store'; +import { QRCodeModule } from 'angularx-qrcode'; import { DndModule } from 'ngx-drag-drop'; import { BooksModule } from '../books/books.module'; import { LiveMeetingComponent } from './live-meeting/live-meeting.component'; import { LiveVotingComponent } from './live-meeting/live-voting/live-voting.component'; +import { MeetingRecommendationsComponent } from './live-meeting/meeting-recommendations/meeting-recommendations.component'; import { MeetingCardComponent } from './meeting-card/meeting-card.component'; import { MeetingCountdownComponent } from './meeting-countdown/meeting-countdown.component'; import { MeetingsPageComponent } from './meetings-page/meetings-page.component'; @@ -21,11 +23,13 @@ import { meetingsReducer } from './state/meetings.reducer'; EffectsModule.forFeature([MeetingsEffects]), BooksModule, DndModule, + QRCodeModule, ], declarations: [ MeetingCountdownComponent, MeetingsPageComponent, MeetingCardComponent, + MeetingRecommendationsComponent, LiveMeetingComponent, LiveVotingComponent, ], diff --git a/src/Server/WebApi/Hubs/LiveMeetingHub.cs b/src/Server/WebApi/Hubs/LiveMeetingHub.cs index 1958d9f..fd82022 100644 --- a/src/Server/WebApi/Hubs/LiveMeetingHub.cs +++ b/src/Server/WebApi/Hubs/LiveMeetingHub.cs @@ -342,7 +342,7 @@ await dbContext.Entry(meeting) await dbContext.SaveChangesAsync(); } - if (meeting.Status is MeetingStatus.Started or MeetingStatus.Voting) + if (meeting.Status >= MeetingStatus.Started) { await dbContext.Entry(meeting) .Collection(e => e.BookRecommendations)