File
Metadata
selector |
tc-test-session |
styleUrls |
./test-session.component.css |
templateUrl |
./test-session.component.html |
Index
Properties
|
|
Methods
|
|
Inputs
|
|
Outputs
|
|
Methods
Private
applySelection
|
applySelection(testletOrNull: Testlet | null, inversion)
|
|
Parameters :
Name |
Type |
Optional |
Default value |
testletOrNull |
Testlet | null
|
No
|
null
|
inversion |
|
No
|
false
|
|
Private
asSelectionObject
|
asSelectionObject(testletOrNull: Testlet | null, inversion)
|
|
Parameters :
Name |
Type |
Optional |
Default value |
testletOrNull |
Testlet | null
|
No
|
null
|
inversion |
|
No
|
false
|
|
check
|
check($event: MatCheckboxChange)
|
|
Parameters :
Name |
Type |
Optional |
$event |
MatCheckboxChange
|
No
|
|
deselect
|
deselect($event: MouseEvent | null)
|
|
Parameters :
Name |
Type |
Optional |
$event |
MouseEvent | null
|
No
|
|
deselectForce
|
deselectForce($event: Event)
|
|
Parameters :
Name |
Type |
Optional |
$event |
Event
|
No
|
|
invertSelection
|
invertSelection()
|
|
|
getTestletType
|
Default value : () => {...}
|
|
hasState
|
Default value : TestSessionUtil.hasState
|
|
stateString
|
Default value : TestSessionUtil.stateString
|
|
superStateIcons
|
Default value : superStates
|
|
trackUnits
|
Default value : () => {...}
|
|
import {
Component, EventEmitter, Input, Output
} from '@angular/core';
import { MatCheckboxChange } from '@angular/material/checkbox';
import {
Testlet, Unit, TestViewDisplayOptions,
isUnit, Selected, TestSession, TestSessionSuperState
} from '../group-monitor.interfaces';
import { TestSessionUtil } from './test-session.util';
import { superStates } from './super-states';
interface IconData {
icon: string,
tooltip: string,
class?: string,
description?: string
}
@Component({
selector: 'tc-test-session',
templateUrl: './test-session.component.html',
styleUrls: ['./test-session.component.css']
})
export class TestSessionComponent {
@Input() testSession: TestSession;
@Input() displayOptions: TestViewDisplayOptions;
@Input() marked: Selected;
@Input() selected: Selected;
@Input() checked: boolean;
@Output() markedElement$ = new EventEmitter<Selected>();
@Output() selectedElement$ = new EventEmitter<Selected>();
@Output() checked$ = new EventEmitter<boolean>();
superStateIcons: { [key in TestSessionSuperState]: IconData } = superStates;
stateString = TestSessionUtil.stateString;
hasState = TestSessionUtil.hasState;
getTestletType = (testletOrUnit: Unit|Testlet): 'testlet'|'unit' => (isUnit(testletOrUnit) ? 'unit' : 'testlet');
trackUnits = (index: number, testlet: Testlet|Unit): string => testlet.id || index.toString();
mark(testletOrNull: Testlet|null = null): void {
if ((testletOrNull != null) && !testletOrNull.blockId) {
return;
}
this.marked = this.asSelectionObject(testletOrNull);
this.markedElement$.emit(this.marked);
}
isSelected(testletOrNull: Testlet|null = null): boolean {
return testletOrNull &&
(this.selected?.element?.blockId === testletOrNull.blockId) &&
(this.selected?.originSession.booklet.species === this.testSession.booklet.species);
}
isSelectedHere(testletOrNull: Testlet|null = null): boolean {
return this.isSelected(testletOrNull) && (this.selected.originSession.data.testId === this.testSession.data.testId);
}
isMarked(testletOrNull: Testlet|null = null): boolean {
return testletOrNull &&
(this.marked?.element?.blockId === testletOrNull.blockId) &&
(this.marked?.originSession.booklet.species === this.testSession.booklet.species);
}
select($event: Event, testletOrNull: Testlet|null): void {
if ((testletOrNull != null) && !testletOrNull.blockId) {
return;
}
$event.stopPropagation();
this.applySelection(testletOrNull);
}
deselect($event: MouseEvent|null): void {
if ($event && ($event.currentTarget === $event.target)) {
this.applySelection();
}
}
deselectForce($event: Event): boolean {
this.applySelection();
$event.stopImmediatePropagation();
$event.stopPropagation();
$event.preventDefault();
return false;
}
invertSelection(): boolean {
this.applySelection(this.selected?.element, true);
return false;
}
check($event: MatCheckboxChange): void {
this.checked$.emit($event.checked);
}
private applySelection(testletOrNull: Testlet|null = null, inversion = false): void {
this.selected = this.asSelectionObject(testletOrNull, inversion);
this.selectedElement$.emit(this.selected);
}
private asSelectionObject(testletOrNull: Testlet|null = null, inversion = false): Selected {
return {
element: testletOrNull,
originSession: this.testSession,
spreading: this.isSelectedHere(testletOrNull) ? !(this.selected?.spreading) : !testletOrNull,
inversion
};
}
}
<td class="selected" *ngIf="displayOptions.manualChecking">
<mat-checkbox
*ngIf="testSession.data.testId >= 0"
(change)="check($event)"
(contextmenu)="invertSelection()"
[checked]="checked"
>
</mat-checkbox>
</td>
<td class="super-state" (click)="deselect($event)" (contextmenu)="deselectForce($event)">
<div class="vertical-align-middle" *ngIf="superStateIcons[testSession.state] as iconData">
<mat-icon class="unit-badge {{iconData?.class}}" matTooltip="{{iconData.tooltip}}">
{{iconData.icon}}
</mat-icon>
</div>
</td>
<td class="group" *ngIf="displayOptions.groupColumn === 'show'" (click)="deselect($event)" (contextmenu)="deselectForce($event)">
<div class="vertical-align-middle">{{testSession.data.groupLabel}}</div>
</td>
<td class="user" (click)="deselect($event)" (contextmenu)="deselectForce($event)">
<div class="vertical-align-middle">
<h1>{{testSession.data.personLabel}}</h1>
</div>
</td>
<td
class="booklet"
*ngIf="displayOptions.bookletColumn === 'show'"
(click)="deselect($event)"
(contextmenu)="deselectForce($event)"
>
<ng-container *ngIf="!testSession.booklet.error; else: noBooklet">
<div class="vertical-align-middle" [matTooltip]="testSession.booklet.metadata.label">
{{testSession.booklet.metadata.label}}
</div>
</ng-container>
<ng-template #noBooklet>
<div class="vertical-align-middle">{{testSession.data.bookletName}}</div>
</ng-template>
</td>
<td class="block" (click)="deselect($event)" (contextmenu)="deselectForce($event)" *ngIf="displayOptions.blockColumn === 'show'">
<div *ngIf="testSession.current as current;" class="vertical-align-middle">
{{current.parent.label || current.parent.blockId || current.parent.id}}
<mat-icon
class="unit-badge"
*ngIf="testSession.timeLeft && (testSession.timeLeft[current.parent.id] !== undefined)"
matBadge="{{testSession.timeLeft[current.parent.id].toString()}}"
matBadgeColor="accent"
[matTooltip]="'Verbleibende Zeit' | customtext:'gm_timeleft_tooltip' | async"
>schedule
</mat-icon>
</div>
</td>
<td class="test" (click)="deselect($event)" (contextmenu)="deselectForce($event)">
<ng-container *ngIf="!testSession.booklet['error']; else: noBookletReason">
<div
*ngIf="testSession.booklet.units as testlet"
class="units-container"
[class]="{
locked: hasState(testSession.data.testState, 'status', 'locked'),
paused: hasState(testSession.data.testState, 'CONTROLLER', 'PAUSED'),
error: hasState(testSession.data.testState, 'CONTROLLER', 'ERROR'),
pending: !hasState(testSession.data.testState, 'CONTROLLER')
}"
[ngSwitch]="displayOptions.view"
(mouseleave)="mark()"
(click)="deselect($event)"
>
<div class="units full" *ngSwitchCase="'full'" >
<ng-container *ngTemplateOutlet="testletFull; context: {$implicit: testlet}"></ng-container>
</div>
<div class="units medium" *ngSwitchCase="'medium'" >
<ng-container *ngTemplateOutlet="bookletMedium; context: {$implicit: testlet}"></ng-container>
</div>
<div class="units small" *ngSwitchCase="'small'" >
<ng-container *ngTemplateOutlet="bookletSmall; context: {$implicit: testlet}"></ng-container>
</div>
</div>
</ng-container>
<ng-template #noBookletReason>
<span *ngIf="testSession.booklet.error == 'missing-id'">
{{'Kein Testheft zugeordnet!' | customtext:'gm_booklet_error_missing_id' | async}}
</span>
<span *ngIf="testSession.booklet.error == 'missing-file'" class="warning">
{{'Kein Zugriff auf Testheft-Datei!' | customtext:'gm_booklet_error_missing_file' | async}}
</span>
<span *ngIf="testSession.booklet.error == 'xml'" class="warning">
{{'Konnte Testheft-Datei nicht lesen!' | customtext:'gm_booklet_error_xml' | async}}
</span>
<span *ngIf="testSession.booklet.error == 'general'" class="warning">
{{'Fehler beim Zugriff aus Testheft-Datei!' | customtext:'gm_booklet_error_general' | async}}
</span>
</ng-template>
</td>
<ng-template #testletFull let-testlet>
<span
*ngIf="testlet.restrictions && testlet.restrictions.codeToEnter as codeToEnter"
class="unit restriction"
matTooltip="{{'Freigabewort' | customtext:'booklet_codeToEnterTitle' | async}}: {{codeToEnter.code.toUpperCase()}}"
matTooltipPosition="above"
>
<mat-icon>{{testSession.clearedCodes && (testSession.clearedCodes.indexOf(testlet.id) > -1) ? 'lock_open' : 'lock'}}</mat-icon>
</span>
<ng-container *ngFor="let testletOrUnit of testlet.children; trackBy: trackUnits" [ngSwitch]="getTestletType(testletOrUnit)">
<span
*ngSwitchCase="'unit'"
[class]="{
unit: true,
current: testSession.data.unitName === testletOrUnit.id
}"
matTooltip="{{testletOrUnit.label}}"
matTooltipPosition="above"
>
{{testletOrUnit.labelShort || " "}}
</span>
<span *ngSwitchCase="'testlet'"
[class]="{
testlet: true,
selected: isSelected(testletOrUnit) && checked && testletOrUnit.blockId,
marked: isMarked(testletOrUnit) && testletOrUnit.blockId
}"
(mouseenter)="mark(testletOrUnit)"
(click)="select($event, testletOrUnit)"
matTooltip="{{testletOrUnit.label}}"
>
<ng-container *ngTemplateOutlet="testletFull; context: {$implicit: testletOrUnit}"></ng-container>
</span>
</ng-container>
</ng-template>
<ng-template #bookletMedium let-testlet>
<ng-container *ngTemplateOutlet="testletTemplateMedium; context: {testlet: testlet}">
</ng-container>
</ng-template>
<ng-template #testletTemplateMedium let-testlet="testlet">
<ng-container *ngFor="let testletOrUnit of testlet.children; let i = index; trackBy: trackUnits" [ngSwitch]="getTestletType(testletOrUnit)">
<span *ngSwitchCase="'unit'"
[class]="(testSession.data.unitName === testletOrUnit.id) ? 'unit current': 'unit'"
matTooltip="{{testletOrUnit.label}}"
matTooltipPosition="above"
>·
</span>
<span *ngSwitchCase="'testlet'" class="testlet" matTooltip="{{testletOrUnit.label}}">
<span
*ngIf="testletOrUnit.restrictions && testletOrUnit.restrictions.codeToEnter as codeToEnter"
class="unit restriction"
matTooltip="{{'Freigabewort' | customtext:'booklet_codeToEnterTitle' | async}}: {{codeToEnter.code.toUpperCase()}}"
matTooltipPosition="above"
>
<mat-icon>
{{testSession.clearedCodes && (testSession.clearedCodes.indexOf(testletOrUnit.id) > -1) ? 'lock_open' : 'lock'}}
</mat-icon>
</span>
<ng-container *ngIf="testSession.current; else: unFeaturedTestlet">
<span
*ngIf="testSession.current.ancestor.id === testletOrUnit.id; else: unFeaturedTestlet"
[class]="{
unit: true,
aggregated: true,
current: true,
selected: isSelected(testletOrUnit) && checked && testletOrUnit.blockId,
marked: isMarked(testletOrUnit) && testletOrUnit.blockId
}"
matTooltip="{{testSession.current.unit.label}}"
matTooltipPosition="above"
(mouseenter)="mark(testletOrUnit)"
(click)="select($event, testletOrUnit)"
>
{{testSession.current.indexAncestor + 1}} / {{testSession.current.ancestor.descendantCount}}
</span>
</ng-container>
<ng-template #unFeaturedTestlet>
<span
[class]="{
unit: true,
aggregated: true,
selected: isSelected(testletOrUnit) && checked,
marked: isMarked(testletOrUnit)
}"
(mouseenter)="mark(testletOrUnit)"
(click)="select($event, testletOrUnit)"
>{{testletOrUnit.descendantCount}}</span>
</ng-template>
</span>
</ng-container>
</ng-template>
<ng-template #bookletSmall let-testlet>
<span
class="testlet" *ngIf="testSession.current; else: unFeaturedTestlet"
matTooltip="{{testSession.current.parent?.label}}"
>
<span
class="unit current aggregated"
matTooltip="{{testSession.current.unit.label}}"
matTooltipPosition="above"
>
{{testSession.current.indexGlobal + 1}} / {{testSession.booklet.units.descendantCount}}
</span>
</span>
<ng-template #unFeaturedTestlet>
<span class="testlet" >
<span class="unit aggregated">{{testlet.descendantCount}}</span>
</span>
</ng-template>
</ng-template>
<td class="current-unit" (click)="deselect($event)" (contextmenu)="deselectForce($event)" *ngIf="displayOptions.unitColumn === 'show'">
<div *ngIf="testSession.current as current;" class="vertical-align-middle">
<h2 matTooltip="{{current.unit.label}}">{{current.unit.id}}</h2>
<mat-icon
class="unit-badge"
*ngIf="hasState(testSession.data.unitState, 'PRESENTATION_PROGRESS', 'complete')"
matTooltip="Vollständig betrachtet / angehört"
>remove_red_eye
</mat-icon>
<mat-icon
class="unit-badge"
*ngIf="hasState(testSession.data.unitState, 'RESPONSE_PROGRESS', 'complete')"
matTooltip="Fertig beantwortet"
>done_all
</mat-icon>
<mat-icon class="unit-badge"
*ngIf="hasState(testSession.data.unitState, 'CURRENT_PAGE_NR')"
matBadge="{{this.stateString(testSession.data.unitState, ['CURRENT_PAGE_NR', 'PAGES_COUNT'], '/')}}"
matBadgeColor="accent"
matTooltip="{{this.stateString(testSession.data.unitState, ['CURRENT_PAGE_ID'])}}"
>description
</mat-icon>
</div>
</td>
:host(tc-test-session) {
display: table-row;
vertical-align: middle;
}
td {
padding-bottom: 0.2em;
padding-top: 0.2em;
border-bottom: 1px solid silver;
padding-right: 2em;
}
:host(tc-test-session):last-of-type td {
border-bottom: none;
}
td.booklet div {
max-width: 15em;
overflow: hidden;
padding-right: 1em;
}
td.super-state,
td.selected,
td:last-child {
padding-right: 0;
}
td.selected {
padding-left: 5px;
}
td:last-child {
min-width: 100%;
}
:host(test-session:last-child) td {
border-bottom: none;
}
h1,
h2 {
font-size: 100%;
display: inline-block;
margin: 0 0.3em 0 0;
}
h2 {
font-weight: normal;
}
.cluster {
border-left: 5px solid white;
border-bottom: none;
width: 0;
padding-right: 0;
}
.units-container {
width: 100%
}
.units {
display: inline-block;
position: relative;
white-space: nowrap;
transform-style: preserve-3d;
}
.units:before {
background: #003333;
/*width: 100%;*/
position: absolute;
content: " ";
top: 45%;
height: 10%;
left: 3px;
right: 3px;
}
.unit {
position: relative;
display: inline-block;
padding: 3px 5px;
margin: 2px;
border-radius: 16px;
align-items: center;
text-transform: uppercase;
color: white;
background: #003333;
min-width: 1em;
text-align: center;
cursor: pointer;
transform-style: preserve-3d;
}
.unit.aggregated {
width: 4em;
}
.paused .unit {
background: #001C1C;
}
.pending .unit,
.locked .unit {
background: #333333;
}
.unit.restriction {
padding: 2px 3px;
}
.unit.restriction mat-icon {
font-size: 0.7em;
height: auto;
width: auto;
}
.unit.current {
background: #b2ff59;
color: #003333;
}
.paused .unit.current {
background: #446122;
color: #333333;
}
.pending .unit.current,
.locked .unit.current {
background: #b2b2b2;
color: #333333;
}
.testlet {
display: inline-block;
padding: 3px 4px;
margin: 2px;
border-radius: 20px;
border: 2px solid #003333;
position: relative;
transform-style: preserve-3d;
cursor: pointer;
}
.unit.marked::before,
.unit.selected::before,
.testlet.marked::before,
.testlet.selected::before {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
content: " ";
transform: translateZ(-10px);
margin: -12px -10px;
}
.testlet.marked::before,
.testlet.selected::before {
margin: -8px -2px;
}
.unit.marked::before,
.testlet.marked::before {
background: rgba(178, 200, 160, 0.5);
}
.unit.selected::before,
.testlet.selected::before {
background: rgba(178, 200, 160, 0.7);
}
.unit.marked.selected::before,
.testlet.marked.selected::before {
background: rgba(178, 200, 160, 0.9);
}
.locked .testlet {
border-color: #333333;
}
.featured-unit {
display: inline-flex;
vertical-align: middle;
align-items: center;
}
.vertical-align-middle {
display: inline-flex;
vertical-align: middle;
align-items: center;
white-space: nowrap;
}
.warning {
color: #821123;
font-weight: bold
}
.unit-badge.danger {
color: #821123;
}
.unit-badge.success {
color: #b2ff59
}
Legend
Html element with directive