Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display how much time is left in ScheduledEvents #193

Merged
merged 8 commits into from
Jan 31, 2024
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
14 changes: 11 additions & 3 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<clr-dropdown *ngIf="ctx.valid">
<button class="nav-text" clrDropdownToggle>
<cds-icon shape="layers" solid></cds-icon>
{{ ctx.scheduledEventName }}
{{ ctx.scheduledEvent.name }}
<cds-icon
shape="angle"
direction="down"
Expand All @@ -28,7 +28,7 @@
clrDropdownItem
*ngFor="let a of scheduledEvents | keyvalue"
(click)="setAccessCode(a.key)"
>{{ a.value }}</a
>{{ a.value.name }}</a
>
</clr-dropdown-menu>
</clr-dropdown>
Expand Down Expand Up @@ -124,7 +124,11 @@ <h3 class="modal-title">{{ aboutTitle }}</h3>
</div>
</clr-modal>

<clr-modal #accesscodemodal [(clrModalOpen)]="accessCodeModalOpened">
<clr-modal
#accesscodemodal
[(clrModalOpen)]="accessCodeModalOpened"
[clrModalSize]="'lg'"
>
<h3 class="modal-title">Manage Access Codes</h3>
<div class="modal-body">
<clr-alert
Expand Down Expand Up @@ -222,6 +226,7 @@ <h3 class="modal-title">Manage Access Codes</h3>
>
<clr-dg-column>Access Code</clr-dg-column>
<clr-dg-column>Valid</clr-dg-column>
<clr-dg-column>Access until</clr-dg-column>
<clr-dg-row *clrDgItems="let a of accesscodes" [clrDgItem]="a">
<clr-dg-cell>{{ a }}</clr-dg-cell>
<clr-dg-cell>
Expand All @@ -240,6 +245,9 @@ <h3 class="modal-title">Manage Access Codes</h3>
</ng-container>
<span> {{ getScheduledEventNameForAccessCode(a) }}</span>
</clr-dg-cell>
<clr-dg-cell [class]="getTimestampColor(a)">
<span> {{ getScheduledEventEndTimestampForAccessCode(a) }}</span>
</clr-dg-cell>
</clr-dg-row>
</clr-datagrid>
</ng-container>
Expand Down
4 changes: 4 additions & 0 deletions src/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@
color: var(--clr-color-success-500, green);
}

clr-dg-cell.red {
color: var(--clr-color-danger-500, red);
}

.header-actions {
clr-dropdown-menu {
a {
Expand Down
25 changes: 22 additions & 3 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
TypedInput,
TypedSettingsService,
} from './services/typedSettings.service';
import { ScheduledEvent } from 'src/data/ScheduledEvent';

@Component({
selector: 'app-root',
Expand Down Expand Up @@ -56,7 +57,7 @@

public accesscodes: string[] = [];
public selectedAccesscodesForDeletion: string[] = [];
public scheduledEvents: Map<string, string> = new Map();
public scheduledEvents: Map<string, ScheduledEvent> = new Map();
public ctx: Context = {} as Context;

public email = '';
Expand Down Expand Up @@ -161,12 +162,12 @@
const addAccessCode = this.route.snapshot.params['accesscode'];
if (addAccessCode) {
this.userService.addAccessCode(addAccessCode).subscribe({
next: (_s: ServerResponse) => {

Check warning on line 165 in src/app/app.component.ts

View workflow job for this annotation

GitHub Actions / Build

'_s' is defined but never used
this.accesscodes.push(addAccessCode);
this.setAccessCode(addAccessCode);
this.doHomeAccessCode(addAccessCode);
},
error: (_s: ServerResponse) => {

Check warning on line 170 in src/app/app.component.ts

View workflow job for this annotation

GitHub Actions / Build

'_s' is defined but never used
// failure
this.doHomeAccessCodeError(addAccessCode);
},
Expand All @@ -177,7 +178,7 @@
this.ctx = c;
this.userService
.getScheduledEvents()
.subscribe((se: Map<string, string>) => {
.subscribe((se: Map<string, ScheduledEvent>) => {
se = new Map(Object.entries(se));
this.scheduledEvents = se;
});
Expand Down Expand Up @@ -298,7 +299,25 @@
}

public getScheduledEventNameForAccessCode(ac: string) {
return this.scheduledEvents?.get(ac);
return this.scheduledEvents?.get(ac)?.name;
}

public getScheduledEventEndTimestampForAccessCode(ac: string) {
return this.scheduledEvents?.get(ac)?.end_timestamp;
}

public getTimestampColor(ac: string) {
const target = this.scheduledEvents?.get(ac)?.end_timestamp;
if (target) {
const now = new Date();
const targetDate = new Date(target);
const timeDiff = targetDate.getTime() - now.getTime();
if (timeDiff <= 0) {
return 'red';
}
}

return 'green';
}

public saveAccessCode(activate = false) {
Expand Down Expand Up @@ -355,7 +374,7 @@

public doSaveSettings() {
this.settingsService.update(this.settingsForm.value).subscribe({
next: (_s: ServerResponse) => {

Check warning on line 377 in src/app/app.component.ts

View workflow job for this annotation

GitHub Actions / Build

'_s' is defined but never used
this.settingsModalOpened = false;
const theme: 'light' | 'dark' | 'system' =
this.settingsForm.controls['theme'].value;
Expand All @@ -374,7 +393,7 @@
}
}
},
error: (_s: ServerResponse) => {

Check warning on line 396 in src/app/app.component.ts

View workflow job for this annotation

GitHub Actions / Build

'_s' is defined but never used
setTimeout(() => (this.settingsModalOpened = false), 2000);
},
});
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 @@ -67,6 +67,7 @@ import {
hostIcon,
eyeIcon,
eyeHideIcon,
clockIcon,
} from '@cds/core/icon';

ClarityIcons.addIcons(
Expand Down Expand Up @@ -95,6 +96,7 @@ ClarityIcons.addIcons(
hostIcon,
eyeIcon,
eyeHideIcon,
clockIcon,
);

export function tokenGetter() {
Expand Down
21 changes: 18 additions & 3 deletions src/app/home.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,27 @@ <h1>Active session</h1>

<div class="clr-row" *ngIf="ctx.valid">
<div class="clr-col">
<h1>{{ ctx.scheduledEventName }}</h1>
<h3>Select a scenario</h3>
<h1>{{ ctx.scheduledEvent.name }}</h1>
<ng-container *ngIf="isTimeLeft()">
<div>
<cds-icon shape="clock"></cds-icon>
{{ getTimeLeftString(ctx.scheduledEvent.end_timestamp) }} left
</div>
<p>{{ ctx.scheduledEvent.description }}</p>
<h3>Select a scenario</h3>
</ng-container>
<ng-container *ngIf="!isTimeLeft()">
<cds-icon shape="clock"></cds-icon> Your time ran out at
{{ ctx.scheduledEvent.end_timestamp }}.
<p>
You will be able to finish any active sessions, but you are unable to
start any new scenario.
</p>
</ng-container>
</div>
</div>

<div class="clr-row">
<div class="clr-row" *ngIf="isTimeLeft()">
<div class="clr-col-12" *ngIf="!ctx.valid">
<h1>Add AccessCode</h1>
Add AccessCodes to your account by clicking on your username on the top
Expand Down
4 changes: 4 additions & 0 deletions src/app/home.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@
.alert-home {
width: 100%;
}

cds-icon[shape='clock'] {
color: black;
}
41 changes: 41 additions & 0 deletions src/app/home.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,45 @@ export class HomeComponent implements OnInit, OnDestroy {
this.contextSubscription.unsubscribe();
this.progressSubscription.unsubscribe();
}

isTimeLeft() {
const target = this.ctx?.scheduledEvent?.end_timestamp;

if (target) {
const now = new Date();
const targetDate = new Date(target);
const timeDiff = targetDate.getTime() - now.getTime();
return timeDiff > 0;
}

return false;
}

getTimeLeftString(target: string) {
const now = new Date();
const targetDate = new Date(target);
const timeDiff = targetDate.getTime() - now.getTime();

if (timeDiff <= 0) {
return 'Time already passed';
}

// Convert time difference from milliseconds
const minutes = Math.floor((timeDiff / 1000 / 60) % 60);
const hours = Math.floor((timeDiff / (1000 * 60 * 60)) % 24);
const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24));

let timeUntil = '';
if (days > 0) {
timeUntil += `${days}d `;
}
if (hours > 0) {
timeUntil += `${hours}h `;
}
if (minutes > 0) {
timeUntil += `${minutes}m `;
}

return timeUntil.trim();
}
}
10 changes: 6 additions & 4 deletions src/app/services/context.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { SettingsService } from './settings.service';
import { UserService } from './user.service';
import { ScheduledEvent } from 'src/data/ScheduledEvent';

export interface Context {
accessCode: string;
scheduledEventName: string;
scheduledEvent: ScheduledEvent;
valid: boolean;
}

Expand Down Expand Up @@ -50,7 +51,7 @@ export class ContextService {
refresh(force = false) {
this.userService
.getScheduledEvents(force)
.subscribe((se: Map<string, string>) => {
.subscribe((se: Map<string, ScheduledEvent>) => {
se = new Map(Object.entries(se));

if (se.size == 0) {
Expand All @@ -69,8 +70,9 @@ export class ContextService {
ctxAccessCode = se.keys().next().value;
}
this.currentContext.accessCode = ctxAccessCode;
this.currentContext.scheduledEventName =
se.get(this.currentContext.accessCode) ?? 'None';
this.currentContext.scheduledEvent =
se.get(this.currentContext.accessCode) ??
({ name: 'None' } as ScheduledEvent);
this.currentContext.valid = true;
this.updateContext(this.currentContext);
});
Expand Down
23 changes: 14 additions & 9 deletions src/app/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
extractResponseContent,
GargantuaClientFactory,
} from './gargantua.service';
import { ScheduledEvent } from 'src/data/ScheduledEvent';

@Injectable({
providedIn: 'root',
Expand All @@ -17,11 +18,11 @@ export class UserService {
private _acModified = new BehaviorSubject(false);

private fetchedSEs = false;
private scheduledEvents$: Observable<Map<string, string>> | null = null;
private cachedScheduledEventsList: Map<string, string> = new Map();
private bh: BehaviorSubject<Map<string, string>> = new BehaviorSubject(
this.cachedScheduledEventsList,
);
private scheduledEvents$: Observable<Map<string, ScheduledEvent>> | null =
null;
private cachedScheduledEventsList: Map<string, ScheduledEvent> = new Map();
private bh: BehaviorSubject<Map<string, ScheduledEvent>> =
new BehaviorSubject(this.cachedScheduledEventsList);

public getModifiedObservable() {
return this._acModified.asObservable();
Expand Down Expand Up @@ -65,16 +66,20 @@ export class UserService {
);
}

public getScheduledEvents(force = false): Observable<Map<string, string>> {
public getScheduledEvents(
force = false,
): Observable<Map<string, ScheduledEvent>> {
if (!force && this.fetchedSEs) {
return of(this.cachedScheduledEventsList);
} else if (this.scheduledEvents$) {
// If request is in-flight, return the ongoing Observable
return this.scheduledEvents$;
} else {
this.scheduledEvents$ = this.garg.get('/scheduledevents').pipe(
map<any, Map<string, string>>(extractResponseContent),
tap((p: Map<string, string>) => this.setScheduledEventsCache(p)),
map<any, Map<string, ScheduledEvent>>(extractResponseContent),
tap((p: Map<string, ScheduledEvent>) =>
this.setScheduledEventsCache(p),
),
// Use shareReplay to multicast and replay the last emitted value to new subscribers
shareReplay(1),
// On complete or error, set the inflight observable to null
Expand All @@ -84,7 +89,7 @@ export class UserService {
return this.scheduledEvents$;
}
}
public setScheduledEventsCache(list: Map<string, string>) {
public setScheduledEventsCache(list: Map<string, ScheduledEvent>) {
this.cachedScheduledEventsList = list;
this.fetchedSEs = true;
this.bh.next(list);
Expand Down
6 changes: 6 additions & 0 deletions src/data/ScheduledEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class ScheduledEvent {
id: string;
name: string;
description: string;
end_timestamp: string;
}