Skip to content

Commit

Permalink
Finished GBAPI#299.
Browse files Browse the repository at this point in the history
  • Loading branch information
sei-bstein committed Dec 5, 2023
1 parent f57e166 commit c1fd648
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<!-- Copyright 2021 Carnegie Mellon University. All Rights Reserved. -->
<!-- Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information. -->

<div class="mb-4">
<h1 class="admin-header mb-0">Administration</h1>
<div class="mb-4 container">
<h1 class="admin-header mb-0 pl-0">Administration</h1>
</div>

<div class="container mb-4 pb-4">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
<!-- Copyright 2021 Carnegie Mellon University. All Rights Reserved. -->
<!-- Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information. -->

<div class="mb-4">
<h1 class="support-header mb-0">Support</h1>
<div class="mb-4 container">
<h1 class="support-header mb-0 pl-0">Support</h1>
</div>

<main class=" mb-4 pb-4">

<router-outlet></router-outlet>

</main>

<footer class="p-4 mt-4"></footer>
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@ <h1 class="mt-3">Notifications</h1>
</td>

<td>
<button type="button" class="btn btn-info"
<button type="button" class="btn btn-sm btn-info mr-2"
(click)="handleEditNotificationClick(notification)">Edit</button>
<app-confirm-button btnClass="btn btn-sm btn-danger"
(confirm)="handleDelete(notification.id)">Delete</app-confirm-button>
</td>
</tr>
</tbody>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { Observable, Subject, startWith, switchMap, tap } from 'rxjs';
import { Observable, Subject, firstValueFrom, startWith, switchMap } from 'rxjs';
import { AdminViewSystemNotification } from '@/system-notifications/system-notifications.models';
import { ModalConfirmService } from '@/services/modal-confirm.service';
import { CreateEditSystemNotificationModalComponent, CreatedEditSystemNotificationModalContext } from '../create-edit-system-notification-modal/create-edit-system-notification-modal.component';
Expand All @@ -24,7 +24,7 @@ export class AdminSystemNotificationsComponent implements OnInit {

this.notifications$ = this._forceLoad$.pipe(
startWith(true),
switchMap(() => this.systemNotificationsService.getAllNotifications()),
switchMap(() => this.systemNotificationsService.getAllNotifications())
);

this.unsub.add(
Expand Down Expand Up @@ -60,4 +60,9 @@ export class AdminSystemNotificationsComponent implements OnInit {
}
});
}

protected async handleDelete(id: string) {
await firstValueFrom(this.systemNotificationsService.deleteNotification(id));
this._forceLoad$.next(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ <h2 class="modal-title pull-left">
<div class="form-group">
<label for="available-from">Availability dates</label>
<div class="d-flex align-content-middle">
<input type="date" class="form-control" id="available-from" [(ngModel)]="context.model.startsOn">
<input type="datetime" class="form-control" id="available-from" [(ngModel)]="context.model.startsOn"
bsDatepicker>
<div class="mx-2">&dash;</div>
<input type="date" class="form-control" id="available-to" [(ngModel)]="context.model.endsOn">
<input type="datetime" class="form-control" id="available-to" [(ngModel)]="context.model.endsOn"
bsDatepicker>
</div>
<small>
Availability dates aren't required. If set, the notification will only be shown to players
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<div class="notificatons-container">
<alert *ngFor="let notification of (notifications$ | async)" [dismissible]="true"
<alert *ngFor="let notification of notifications" [dismissible]="true"
[type]="notification.notificationType | notificationTypeToAlertType" (onClose)="handleClosed(notification)">
<h2 class="fs-11 m-0 cursor-pointer" (click)="handleClicked(notification)">{{notification.title}}</h2>
<div class="container" (click)="handleClicked(notification)">
<h2 class="fs-11 m-0 cursor-pointer">
{{notification.title}}
</h2>
<small class="d-block fs-08 cursor-pointer text-upper font-bold">[more info]</small>
</div>
</alert>
</div>
Original file line number Diff line number Diff line change
@@ -1,39 +1,66 @@
import { Component, OnInit } from '@angular/core';
import { firstValueFrom, timer } from 'rxjs';
import { ModalConfirmService } from '@/services/modal-confirm.service';
import { ViewSystemNotification } from '@/system-notifications/system-notifications.models';
import { SystemNotificationsService } from '@/system-notifications/system-notifications.service';
import { Component } from '@angular/core';
import { Observable, firstValueFrom, tap } from 'rxjs';
import { UserService as LocalUserService } from "@/utility/user.service";
import { UnsubscriberService } from '@/services/unsubscriber.service';

@Component({
selector: 'app-system-notifications',
templateUrl: './system-notifications.component.html',
styleUrls: ['./system-notifications.component.scss']
})
export class SystemNotificationsComponent {
notifications$: Observable<ViewSystemNotification[]>;
export class SystemNotificationsComponent implements OnInit {
protected notifications: ViewSystemNotification[] = [];
private UPDATE_INTERVAL = 1000 * 60 * 60 * 30;

constructor(
private localUserService: LocalUserService,
private modalService: ModalConfirmService,
private systemNotificationsService: SystemNotificationsService) {
this.notifications$ = systemNotificationsService.getVisibleNotifications().pipe(
tap(notifications => {
private systemNotificationsService: SystemNotificationsService,
private unsub: UnsubscriberService) { }

})
async ngOnInit(): Promise<void> {
this.unsub.add(
this.localUserService.user$.subscribe(async u => {
await this.loadNotifications();
}),
this.systemNotificationsService.systemNotificationsUpdated$.subscribe(async () => await this.loadNotifications()),
timer(0, this.UPDATE_INTERVAL).subscribe(async u => await this.loadNotifications())
);
}

protected async handleClosed(notification: ViewSystemNotification) {
await firstValueFrom(this.systemNotificationsService.logInteraction(notification.id, "dismissed"));
await firstValueFrom(this.systemNotificationsService.logInteraction("dismissed", notification.id));
}

protected async handleClicked(notification: ViewSystemNotification) {
await firstValueFrom(this.systemNotificationsService.logInteraction("sawFull", notification.id));

this.modalService.openConfirm({
title: notification.title,
bodyContent: notification.markdownContent,
renderBodyAsMarkdown: true,
hideCancel: true
hideCancel: true,
onConfirm: async () => {
await this.loadNotifications();
await firstValueFrom(this.systemNotificationsService.logInteraction("dismissed", notification.id));
}
});
}

private async loadNotifications(): Promise<void> {
if (!this.localUserService.user$.value) {
this.notifications = [];
return;
}

this.notifications = await firstValueFrom(this.systemNotificationsService.getVisibleNotifications());
const shownNotificationIds = this.notifications.map(n => n.id);

await firstValueFrom(this.systemNotificationsService.logInteraction(notification.id, "sawFull"));
if (shownNotificationIds.length) {
await firstValueFrom(this.systemNotificationsService.logInteraction("sawCallout", ...shownNotificationIds));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, Subject, filter, map, switchMap, tap } from 'rxjs';
import { AdminViewSystemNotification, CreateEditSystemNotification, ViewSystemNotification } from './system-notifications.models';
import { Observable, filter, firstValueFrom, switchMap } from 'rxjs';
import { ApiUrlService } from '@/services/api-url.service';
import { HttpClient } from '@angular/common/http';
import { UserService as LocalUserService } from "@/utility/user.service";

export type SystemNotificationInteractionType = "sawCallout" | "sawFull" | "dismissed";

@Injectable({ providedIn: 'root' })
export class SystemNotificationsService {
private _systemNotificationsUpdated$ = new Subject<void>();
public systemNotificationsUpdated$ = this._systemNotificationsUpdated$.asObservable();

constructor(
private apiUrl: ApiUrlService,
private http: HttpClient,
Expand All @@ -17,26 +20,45 @@ export class SystemNotificationsService {
public createNotification(createNotification: CreateEditSystemNotification): Observable<ViewSystemNotification> {
const model = { ...createNotification, id: undefined };

return this.http.post<ViewSystemNotification>(this.apiUrl.build("system-notifications"), model);
}

public logInteraction(notificationId: string, interactionType: SystemNotificationInteractionType) {
return this.http.post(this.apiUrl.build(`system-notifications/${notificationId}/interaction`), { interactionType });
return this.http.post<ViewSystemNotification>(this.apiUrl.build("system-notifications"), model).pipe(
tap(newNotification => this._systemNotificationsUpdated$.next())
);
}

public updateNotification(updateNotification: CreateEditSystemNotification): Observable<ViewSystemNotification> {
return this.http.put<ViewSystemNotification>(this.apiUrl.build(`system-notifications/${updateNotification.id}`), updateNotification);
public deleteNotification(id: string): Observable<void> {
return this.http.delete<void>(this.apiUrl.build(`system-notifications/${id}`)).pipe(
tap(() => this._systemNotificationsUpdated$.next())
);
}

public getVisibleNotifications(): Observable<ViewSystemNotification[]> {
return this.localUser.user$.pipe(
filter(u => !!u),
switchMap(u => this.http.get<ViewSystemNotification[]>(this.apiUrl.build("system-notifications"))
)
switchMap(u => this.http.get<ViewSystemNotification[]>(this.apiUrl.build("system-notifications")))
);
}

public getAllNotifications(): Observable<AdminViewSystemNotification[]> {
return this.http.get<AdminViewSystemNotification[]>(this.apiUrl.build("admin/system-notifications"));
return this.http.get<AdminViewSystemNotification[]>(this.apiUrl.build("admin/system-notifications")).pipe(
map(notifications => {
for (const notification of notifications) {
if (notification.startsOn)
notification.startsOn = new Date(Date.parse(notification.startsOn as any));

if (notification.endsOn)
notification.endsOn = new Date(Date.parse(notification.endsOn as any));
}

return notifications;
})
);
}

public logInteraction(interactionType: SystemNotificationInteractionType, ...notificationIds: string[]) {
return this.http.post(this.apiUrl.build(`system-notifications/interaction`), { systemNotificationIds: notificationIds, interactionType });
}

public updateNotification(updateNotification: CreateEditSystemNotification): Observable<ViewSystemNotification> {
return this.http.put<ViewSystemNotification>(this.apiUrl.build(`system-notifications/${updateNotification.id}`), updateNotification);
}
}
4 changes: 4 additions & 0 deletions projects/gameboard-ui/src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,10 @@ th[align="left"] {
width: 100%;
}

.font-bold {
font-weight: bold !important;
}

.gameboard-table {
width: 100%;

Expand Down

0 comments on commit c1fd648

Please sign in to comment.