File

src/app/group-monitor/test-session/test-session.component.ts

Metadata

selector tc-test-session
styleUrls ./test-session.component.css
templateUrl ./test-session.component.html

Index

Properties
Methods
Inputs
Outputs

Inputs

checked
Type : boolean
displayOptions
Type : TestViewDisplayOptions
marked
Type : Selected
selected
Type : Selected
testSession
Type : TestSession

Outputs

checked$
Type : EventEmitter
markedElement$
Type : EventEmitter
selectedElement$
Type : EventEmitter

Methods

Private applySelection
applySelection(testletOrNull: Testlet | null, inversion)
Parameters :
Name Type Optional Default value
testletOrNull Testlet | null No null
inversion No false
Returns : void
Private asSelectionObject
asSelectionObject(testletOrNull: Testlet | null, inversion)
Parameters :
Name Type Optional Default value
testletOrNull Testlet | null No null
inversion No false
Returns : Selected
check
check($event: MatCheckboxChange)
Parameters :
Name Type Optional
$event MatCheckboxChange No
Returns : void
deselect
deselect($event: MouseEvent | null)
Parameters :
Name Type Optional
$event MouseEvent | null No
Returns : void
deselectForce
deselectForce($event: Event)
Parameters :
Name Type Optional
$event Event No
Returns : boolean
invertSelection
invertSelection()
Returns : boolean
isMarked
isMarked(testletOrNull: Testlet | null)
Parameters :
Name Type Optional Default value
testletOrNull Testlet | null No null
Returns : boolean
isSelected
isSelected(testletOrNull: Testlet | null)
Parameters :
Name Type Optional Default value
testletOrNull Testlet | null No null
Returns : boolean
isSelectedHere
isSelectedHere(testletOrNull: Testlet | null)
Parameters :
Name Type Optional Default value
testletOrNull Testlet | null No null
Returns : boolean
mark
mark(testletOrNull: Testlet | null)
Parameters :
Name Type Optional Default value
testletOrNull Testlet | null No null
Returns : void
select
select($event: Event, testletOrNull: Testlet | null)
Parameters :
Name Type Optional
$event Event No
testletOrNull Testlet | null No
Returns : void

Properties

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 || "&nbsp;"}}
    </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>

./test-session.component.css

: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;
}

.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
Component
Html element with directive

results matching ""

    No results matching ""