diff --git a/src/app/group-monitor/group-monitor.component.css b/src/app/group-monitor/group-monitor.component.css index a0d18be526120d2efd864a51c042dafe7d0888b5..a92c9325102888ac57f4f4eef57ba1b68872992c 100644 --- a/src/app/group-monitor/group-monitor.component.css +++ b/src/app/group-monitor/group-monitor.component.css @@ -5,7 +5,7 @@ } .adminbackground { - flex: 10 0 900px; + flex: 10 0 900px; box-shadow: 5px 10px 20px black; background-color: white; min-height: 85%; @@ -13,6 +13,10 @@ padding: 25px; } +.test-view-table-wrapper { + overflow-x: auto; +} + .page-header { display: flex; flex-wrap: wrap; diff --git a/src/app/group-monitor/group-monitor.component.html b/src/app/group-monitor/group-monitor.component.html index dc7f571ed3cbbb6c979de846e2cd5c3bfd8a2f9e..dfe2d7148f9c4dd4eb3e47ba3bf914d647e3ba65 100644 --- a/src/app/group-monitor/group-monitor.component.html +++ b/src/app/group-monitor/group-monitor.component.html @@ -56,29 +56,28 @@ <mat-icon>settings</mat-icon> </button> + <div class="test-view-table-wrapper"> + <table class="test-view-table" matSort (matSortChange)="setTableSorting($event)"> - <table class="test-view-table" matSort (matSortChange)="setTableSorting($event)"> + <thead> + <tr class="mat-sort-container"> + <td mat-sort-header="personLabel">Benutzer</td> + <td mat-sort-header="groupLabel" *ngIf="displayOptions.groupColumn === 'show'">Gruppe</td> + <td mat-sort-header="bookletName">Testheft</td> + <td mat-sort-header="timestamp">Aktivität</td> + </tr> + </thead> - <thead> - <tr class="mat-sort-container"> - <td mat-sort-header="personLabel">Benutzer</td> - <td mat-sort-header="groupLabel" *ngIf="displayOptions.groupColumn === 'show'">Gruppe</td> - <td mat-sort-header="bookletName">Testheft</td> - <td mat-sort-header="timestamp">Aktivität</td> - </tr> - </thead> + <tc-test-view + *ngFor="let session of sessions$ | async; trackBy: trackSession" + [testSession]="session" + [displayOptions]="displayOptions" + (bookletId$)="adjustViewModeOnBookletLoad($event)" + > + </tc-test-view> + </table> + </div> - <tc-test-view - *ngFor="let session of sessions$ | async; trackBy: trackSession" - [testSession]="session" - [displayOptions]="displayOptions" - > - </tc-test-view> - </table> - -<!-- <p>Connected Admins: {{clientCount$ | async}}</p>--> - -<!-- <pre>{{sessions$ | async | json}}</pre>--> </div> </div> diff --git a/src/app/group-monitor/group-monitor.component.ts b/src/app/group-monitor/group-monitor.component.ts index 852bbd626f3c7188abb1e7d567dce3fe69ae9ac7..833bf017bc8b71310de0167f46ceddab68510694 100644 --- a/src/app/group-monitor/group-monitor.component.ts +++ b/src/app/group-monitor/group-monitor.component.ts @@ -1,7 +1,12 @@ import {Component, HostListener, OnDestroy, OnInit} from '@angular/core'; import {BackendService} from './backend.service'; import {BehaviorSubject, combineLatest, Observable, Subject, Subscription} from 'rxjs'; -import {GroupData, TestSession, TestViewDisplayOptions, TestViewDisplayOptionKey} from './group-monitor.interfaces'; +import { + GroupData, + TestSession, + TestViewDisplayOptions, + TestViewDisplayOptionKey, +} from './group-monitor.interfaces'; import {ActivatedRoute} from '@angular/router'; import {ConnectionStatus} from './websocket-backend.service'; import {map} from 'rxjs/operators'; @@ -14,6 +19,11 @@ import {Sort} from '@angular/material/sort'; styleUrls: ['./group-monitor.component.css'] }) export class GroupMonitorComponent implements OnInit, OnDestroy { + + constructor( + private route: ActivatedRoute, + private bs: BackendService, + ) {} private routingSubscription: Subscription = null; ownGroup$: Observable<GroupData>; @@ -28,14 +38,10 @@ export class GroupMonitorComponent implements OnInit, OnDestroy { groupColumn: 'hide' }; - constructor( - private route: ActivatedRoute, - private bs: BackendService, - ) {} + private bookletIdsViewIsAdjustedFor: string[] = []; + private lastWindowSize = Infinity; ngOnInit(): void { - this.autoSelectViewMode(); - this.routingSubscription = this.route.params.subscribe(params => { this.ownGroup$ = this.bs.getGroupData(params['group-name']); }); @@ -58,42 +64,66 @@ export class GroupMonitorComponent implements OnInit, OnDestroy { } @HostListener('window:resize', ['$event']) - autoSelectViewMode(): void { - const screenWidth = window.innerWidth; - if (screenWidth > 1200) { - this.displayOptions.view = 'full'; - } else if (screenWidth > 800) { - this.displayOptions.view = 'medium'; + adjustViewModeOnWindowResize(): void { + if (this.lastWindowSize > window.innerWidth) { + this.shrinkViewIfNecessary(); } else { - this.displayOptions.view = 'small'; + this.growViewIfPossible(); } + this.lastWindowSize = window.innerWidth; } - trackSession(index: number, session: TestSession): number { - return session.personId * 10000 + session.testId; + adjustViewModeOnBookletLoad(bookletId: string): void { + if (bookletId && this.bookletIdsViewIsAdjustedFor.indexOf(bookletId) === -1) { + this.bookletIdsViewIsAdjustedFor.push(bookletId); + this.growViewIfPossible(); + } } - sortSessions(sort: Sort, sessions: TestSession[]): TestSession[] { - return sessions.sort( + private growViewIfPossible() { + if (this.getOverflow() <= 0) { + this.displayOptions.view = 'full'; + setTimeout(() => this.shrinkViewIfNecessary(), 15); + } + } + + private shrinkViewIfNecessary(): void { + if (this.getOverflow() > 0) { + if (this.displayOptions.view === 'full') { + this.displayOptions.view = 'medium'; + setTimeout(() => this.shrinkViewIfNecessary(), 15); + } else if (this.displayOptions.view === 'medium') { + this.displayOptions.view = 'small'; + } + } + } - (testSession1, testSession2) => { + private getOverflow = (): number => { + const backgroundElement = document.querySelector('.adminbackground'); + const testViewTableElement = document.querySelector('.test-view-table'); + return testViewTableElement.scrollWidth - (backgroundElement.clientWidth - 50); // 50 = adminbackground's padding *2 + } - if (sort.active === 'timestamp') { - return (testSession2.timestamp - testSession1.timestamp) * (sort.direction === 'asc' ? 1 : -1); - } + trackSession(index: number, session: TestSession): number { + return session.personId * 10000 + session.testId; + } - const stringA = (testSession1[sort.active] || 'zzzzz'); - const stringB = (testSession2[sort.active] || 'zzzzz'); - return stringA.localeCompare(stringB) * (sort.direction === 'asc' ? 1 : -1); - } - ); + sortSessions(sort: Sort, sessions: TestSession[]): TestSession[] { + return sessions + .sort((testSession1, testSession2) => { + if (sort.active === 'timestamp') { + return (testSession2.timestamp - testSession1.timestamp) * (sort.direction === 'asc' ? 1 : -1); + } + const stringA = (testSession1[sort.active] || 'zzzzz'); + const stringB = (testSession2[sort.active] || 'zzzzz'); + return stringA.localeCompare(stringB) * (sort.direction === 'asc' ? 1 : -1); + }); } setTableSorting(sort: Sort): void { if (!sort.active || sort.direction === '') { return; } - this.sortBy$.next(sort); } diff --git a/src/app/group-monitor/test-view/test-view.component.ts b/src/app/group-monitor/test-view/test-view.component.ts index 84a85746b65745765e3f53f60be3c3a15dde0658..3c547754a8087ac2aeb53cb22ddfcf8e6a596587 100644 --- a/src/app/group-monitor/test-view/test-view.component.ts +++ b/src/app/group-monitor/test-view/test-view.component.ts @@ -1,6 +1,6 @@ -import {Component, Input, OnChanges, OnInit, SimpleChange} from '@angular/core'; +import {Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChange} from '@angular/core'; import {BookletService} from '../booklet.service'; -import {combineLatest, Observable, Subject} from 'rxjs'; +import {combineLatest, Observable, Subject, Subscription} from 'rxjs'; import {Booklet, TestSession, Testlet, Unit, TestViewDisplayOptions, BookletError} from '../group-monitor.interfaces'; import {map} from 'rxjs/operators'; import {TestMode} from '../../config/test-mode'; @@ -28,17 +28,19 @@ interface UnitContext { templateUrl: './test-view.component.html', styleUrls: ['./test-view.component.css', './test-view-table.css'] }) -export class TestViewComponent implements OnInit, OnChanges { +export class TestViewComponent implements OnInit, OnChanges, OnDestroy { @Input() testSession: TestSession; @Input() displayOptions: TestViewDisplayOptions; + @Output() bookletId$ = new EventEmitter<string>(); public testSession$: Subject<TestSession> = new Subject<TestSession>(); public booklet$: Observable<Booklet|BookletError>; public featuredUnit$: Observable<UnitContext|null>; - public maxTimeLeft: object|null; // TODO make observable maybe + private bookletSubscription: Subscription; + constructor( private bookletsService: BookletService, ) { @@ -49,6 +51,10 @@ export class TestViewComponent implements OnInit, OnChanges { this.booklet$ = this.bookletsService.getBooklet(this.testSession.bookletName || ''); + this.bookletSubscription = this.booklet$.subscribe((booklet: Booklet|BookletError) => { + this.bookletId$.emit(this.isBooklet(booklet) ? booklet.metadata.id : ''); + }); + this.featuredUnit$ = combineLatest<[Booklet|BookletError, TestSession]>([this.booklet$, this.testSession$]) .pipe(map((bookletAndSession: [Booklet|BookletError, TestSession]): UnitContext|null => { const booklet: Booklet|BookletError = bookletAndSession[0]; @@ -74,6 +80,10 @@ export class TestViewComponent implements OnInit, OnChanges { this.maxTimeLeft = this.parseJsonState(this.testSession.testState, 'MAXTIMELEFT'); } + ngOnDestroy() { + this.bookletSubscription.unsubscribe(); + } + isBooklet(bookletOrBookletError: Booklet|BookletError): bookletOrBookletError is Booklet { return !('error' in bookletOrBookletError); }