From 8070921b27ba565aba3ad0f458ec5cd72738dadf Mon Sep 17 00:00:00 2001 From: jojohoch <joachim.hoch@iqb.hu-berlin.de> Date: Thu, 7 Oct 2021 10:58:46 +0200 Subject: [PATCH] [player] Set `TOUCHED` status for interactive elements * When elements receive the focusin event, their status is changed to `TOUCHED` * Replace `this.form.valueChanges` subscription with `this.unitStateService.unitStateElementCodeChanged` subscription --- .../element-components/button.component.ts | 4 ++- .../element-components/checkbox.component.ts | 4 ++- .../element-components/dropdown.component.ts | 6 ++-- .../radio-button-group.component.ts | 7 ++-- .../element-components/text-area.component.ts | 2 ++ .../text-field.component.ts | 2 ++ projects/player/src/app/app.component.ts | 2 +- .../components/element/element.component.ts | 6 ++++ .../unit-state/unit-state.component.ts | 8 ++--- projects/player/src/app/models/verona.ts | 7 ++++ .../src/app/services/unit-state.service.ts | 34 +++++++++++++++---- 11 files changed, 65 insertions(+), 17 deletions(-) diff --git a/projects/common/element-components/button.component.ts b/projects/common/element-components/button.component.ts index 5570d72c3..ca4c613c1 100644 --- a/projects/common/element-components/button.component.ts +++ b/projects/common/element-components/button.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, EventEmitter, Output } from '@angular/core'; import { ElementComponent } from '../element-component.directive'; import { ButtonElement } from '../unit'; @@ -6,6 +6,7 @@ import { ButtonElement } from '../unit'; selector: 'app-button', template: ` <button mat-button + (focusin)="onFocusin.emit()" [style.width.%]="100" [style.height.%]="100" [style.background-color]="elementModel.backgroundColor" @@ -20,5 +21,6 @@ import { ButtonElement } from '../unit'; ` }) export class ButtonComponent extends ElementComponent { + @Output() onFocusin = new EventEmitter(); elementModel!: ButtonElement; } diff --git a/projects/common/element-components/checkbox.component.ts b/projects/common/element-components/checkbox.component.ts index dd417ab5f..aac74faab 100644 --- a/projects/common/element-components/checkbox.component.ts +++ b/projects/common/element-components/checkbox.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, EventEmitter, Output } from '@angular/core'; import { ValidatorFn, Validators } from '@angular/forms'; import { CheckboxElement } from '../unit'; import { FormElementComponent } from '../form-element-component.directive'; @@ -11,6 +11,7 @@ import { FormElementComponent } from '../form-element-component.directive'; [style.height.%]="100" [style.background-color]="elementModel.backgroundColor"> <mat-checkbox #checkbox class="example-margin" + (focusin)="onFocusin.emit()" [formControl]="elementFormControl" [style.color]="elementModel.fontColor" [style.font-family]="elementModel.font" @@ -31,6 +32,7 @@ import { FormElementComponent } from '../form-element-component.directive'; ` }) export class CheckboxComponent extends FormElementComponent { + @Output() onFocusin = new EventEmitter(); elementModel!: CheckboxElement; get validators(): ValidatorFn[] { diff --git a/projects/common/element-components/dropdown.component.ts b/projects/common/element-components/dropdown.component.ts index 626e98205..51dee9a03 100644 --- a/projects/common/element-components/dropdown.component.ts +++ b/projects/common/element-components/dropdown.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, EventEmitter, Output } from '@angular/core'; import { DropdownElement } from '../unit'; import { FormElementComponent } from '../form-element-component.directive'; @@ -17,7 +17,8 @@ import { FormElementComponent } from '../form-element-component.directive'; [style.text-decoration]="elementModel.underline ? 'underline' : ''"> {{$any(elementModel).label}} </mat-label> - <mat-select [formControl]="elementFormControl"> + <mat-select (focusin)="onFocusin.emit()" + [formControl]="elementFormControl"> <mat-option *ngIf="elementModel.allowUnset" value=""></mat-option> <mat-option *ngFor="let option of elementModel.options" [value]="option"> {{option}} @@ -30,5 +31,6 @@ import { FormElementComponent } from '../form-element-component.directive'; ` }) export class DropdownComponent extends FormElementComponent { + @Output() onFocusin = new EventEmitter(); elementModel!: DropdownElement; } diff --git a/projects/common/element-components/radio-button-group.component.ts b/projects/common/element-components/radio-button-group.component.ts index d375ceed8..950566e2f 100644 --- a/projects/common/element-components/radio-button-group.component.ts +++ b/projects/common/element-components/radio-button-group.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, EventEmitter, Output } from '@angular/core'; import { RadioButtonGroupElement } from '../unit'; import { FormElementComponent } from '../form-element-component.directive'; @@ -17,10 +17,12 @@ import { FormElementComponent } from '../form-element-component.directive'; [style.text-decoration]="elementModel.underline ? 'underline' : ''"> <label [innerHTML]="elementModel.label" id="radio-group-label"></label> <mat-radio-group aria-labelledby="radio-group-label" + (focusin)="onFocusin.emit()" [style.margin-bottom.px]="25" [fxLayout]="elementModel.alignment" [formControl]="elementFormControl"> - <mat-radio-button *ngFor="let option of elementModel.options" [value]="option"> + <mat-radio-button *ngFor="let option of elementModel.options" + [value]="option"> {{option}} </mat-radio-button> <mat-error *ngIf="elementFormControl.errors && elementFormControl.touched" @@ -34,5 +36,6 @@ import { FormElementComponent } from '../form-element-component.directive'; ` }) export class RadioButtonGroupComponent extends FormElementComponent { + @Output() onFocusin = new EventEmitter(); elementModel!: RadioButtonGroupElement; } diff --git a/projects/common/element-components/text-area.component.ts b/projects/common/element-components/text-area.component.ts index 7dd9ad5fc..2ba391656 100644 --- a/projects/common/element-components/text-area.component.ts +++ b/projects/common/element-components/text-area.component.ts @@ -16,6 +16,7 @@ import { FormElementComponent } from '../form-element-component.directive'; [style.text-decoration]="elementModel.underline ? 'underline' : ''" [appearance]="$any(elementModel.appearance)"> <textarea matInput [formControl]="elementFormControl" #input + (focusin)="onFocusin.emit()" (focus)="onFocus.emit(input)" (blur)="onBlur.emit(input)" placeholder="{{elementModel.label}}" @@ -29,6 +30,7 @@ import { FormElementComponent } from '../form-element-component.directive'; ` }) export class TextAreaComponent extends FormElementComponent { + @Output() onFocusin = new EventEmitter(); @Output() onFocus = new EventEmitter<HTMLElement>(); @Output() onBlur = new EventEmitter<HTMLElement>(); elementModel!: TextAreaElement; diff --git a/projects/common/element-components/text-field.component.ts b/projects/common/element-components/text-field.component.ts index e2c95bf6e..4150a1594 100644 --- a/projects/common/element-components/text-field.component.ts +++ b/projects/common/element-components/text-field.component.ts @@ -17,6 +17,7 @@ import { FormElementComponent } from '../form-element-component.directive'; appInputBackgroundColor [backgroundColor]="elementModel.backgroundColor" [appearance]="$any(elementModel.appearance)"> <input matInput type="text" [pattern]="elementModel.pattern" #input + (focusin)="onFocusin.emit()" (focus)="onFocus.emit(input)" (blur)="onBlur.emit(input)" [formControl]="elementFormControl" @@ -28,6 +29,7 @@ import { FormElementComponent } from '../form-element-component.directive'; ` }) export class TextFieldComponent extends FormElementComponent { + @Output() onFocusin = new EventEmitter(); @Output() onFocus = new EventEmitter<HTMLElement>(); @Output() onBlur = new EventEmitter<HTMLElement>(); elementModel!: TextFieldElement; diff --git a/projects/player/src/app/app.component.ts b/projects/player/src/app/app.component.ts index 8c57ec8fa..c36032356 100644 --- a/projects/player/src/app/app.component.ts +++ b/projects/player/src/app/app.component.ts @@ -63,7 +63,7 @@ export class AppComponent implements OnInit { this.pages = unitDefinition.pages; this.unitStateService.unitStateElementCodes = message.unitState?.dataParts?.elementCodes ? JSON.parse(message.unitState.dataParts.elementCodes) : []; - this.keyboardService.useKeyboard(false, 'mini'); + this.keyboardService.useKeyboard(true, 'full'); } else { this.dialog.open(AlertDialogComponent, { data: { diff --git a/projects/player/src/app/components/element/element.component.ts b/projects/player/src/app/components/element/element.component.ts index 65de9664b..dcef23aef 100644 --- a/projects/player/src/app/components/element/element.component.ts +++ b/projects/player/src/app/components/element/element.component.ts @@ -51,6 +51,12 @@ export class ElementComponent implements OnInit { this.unitStateService.registerElement(elementComponent.elementModel); + elementComponent.onFocusin + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe(() => { + this.unitStateService.changeElementStatus({ id: this.elementModel.id, status: 'TOUCHED' }); + }); + if (Object.prototype.hasOwnProperty.call(this.elementModel, 'required')) { const elementForm = this.formBuilder.group({}); elementComponent.parentForm = elementForm; diff --git a/projects/player/src/app/components/unit-state/unit-state.component.ts b/projects/player/src/app/components/unit-state/unit-state.component.ts index 9181495c3..f4a7d8690 100644 --- a/projects/player/src/app/components/unit-state/unit-state.component.ts +++ b/projects/player/src/app/components/unit-state/unit-state.component.ts @@ -66,9 +66,9 @@ export class UnitStateComponent implements OnInit, OnDestroy { this.unitStateService.presentedPageAdded .pipe(takeUntil(this.ngUnsubscribe)) .subscribe((presentedPage: number): void => this.onPresentedPageAdded(presentedPage)); - this.form.valueChanges + this.unitStateService.unitStateElementCodeChanged .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe((): void => this.onFormChanges()); + .subscribe((): void => this.onUnitStateElementCodeChanged()); this.veronaSubscriptionService.vopNavigationDeniedNotification .pipe(takeUntil(this.ngUnsubscribe)) .subscribe((message: VopNavigationDeniedNotification): void => this.onNavigationDenied(message)); @@ -116,9 +116,9 @@ export class UnitStateComponent implements OnInit, OnDestroy { } }; - private onFormChanges(): void { + private onUnitStateElementCodeChanged(): void { // eslint-disable-next-line no-console - console.log('player: onFormChanges', this.unitStateService.unitStateElementCodes); + console.log('player: onUnitStateElementCodeChanged', this.unitStateService.unitStateElementCodes); this.sendVopStateChangedNotification(); } diff --git a/projects/player/src/app/models/verona.ts b/projects/player/src/app/models/verona.ts index fdd656a6a..3cdcb988b 100644 --- a/projects/player/src/app/models/verona.ts +++ b/projects/player/src/app/models/verona.ts @@ -8,6 +8,13 @@ export type StateReportPolicy = 'none' | 'eager' | 'on-demand'; export type UnitStateElementCodeStatus = 'NOT_REACHED' | 'DISPLAYED' | 'TOUCHED' | 'VALUE_CHANGED'; +export enum UnitStateElementCodeStatusValue { NOT_REACHED = 0, DISPLAYED = 1, TOUCHED = 2, VALUE_CHANGED = 3} + +export interface StatusChangeElement { + id: string; + status: UnitStateElementCodeStatus; +} + export interface PlayerConfig { unitNumber?: number; unitTitle?: number; diff --git a/projects/player/src/app/services/unit-state.service.ts b/projects/player/src/app/services/unit-state.service.ts index 7abe8dab8..fa1b7310d 100644 --- a/projects/player/src/app/services/unit-state.service.ts +++ b/projects/player/src/app/services/unit-state.service.ts @@ -1,7 +1,12 @@ import { Injectable } from '@angular/core'; import { Observable, Subject } from 'rxjs'; import { UnitUIElement } from '../../../../common/unit'; -import { UnitStateElementCode, UnitStateElementCodeStatus } from '../models/verona'; +import { + StatusChangeElement, + UnitStateElementCode, + UnitStateElementCodeStatus, + UnitStateElementCodeStatusValue +} from '../models/verona'; import { ValueChangeElement } from '../../../../common/form'; @Injectable({ @@ -9,6 +14,7 @@ import { ValueChangeElement } from '../../../../common/form'; }) export class UnitStateService { private _presentedPageAdded = new Subject<number>(); + private _unitStateElementCodeChanged = new Subject<UnitStateElementCode>(); unitStateElementCodes!: UnitStateElementCode[]; getUnitStateElement(id: string): UnitStateElementCode | undefined { @@ -20,16 +26,25 @@ export class UnitStateService { const unitStateElementCode = this.getUnitStateElement(id); if (unitStateElementCode) { unitStateElementCode.value = value; + this._unitStateElementCodeChanged.next(unitStateElementCode); } } setUnitStateElementCodeStatus(id: string, status: UnitStateElementCodeStatus): void { const unitStateElementCode = this.getUnitStateElement(id); if (unitStateElementCode) { - unitStateElementCode.status = status; + // Set status only if it is higher than the old status + if (UnitStateElementCodeStatusValue[status] > UnitStateElementCodeStatusValue[unitStateElementCode.status]) { + unitStateElementCode.status = status; + this._unitStateElementCodeChanged.next(unitStateElementCode); + } } } + get unitStateElementCodeChanged(): Observable<UnitStateElementCode> { + return this._unitStateElementCodeChanged.asObservable(); + } + get presentedPageAdded(): Observable<number> { return this._presentedPageAdded.asObservable(); } @@ -44,16 +59,23 @@ export class UnitStateService { changeElementValue(elementValues: ValueChangeElement): void { // eslint-disable-next-line no-console - console.log(`player: onElementValueChanges ${elementValues.id}: + console.log(`player: changeElementValue ${elementValues.id}: old: ${elementValues.values[0]}, new: ${elementValues.values[1]}`); this.setUnitStateElementCodeStatus(elementValues.id, 'VALUE_CHANGED'); this.setUnitStateElementCodeValue(elementValues.id, elementValues.values[1]); } + changeElementStatus(elementStatus: StatusChangeElement): void { + // eslint-disable-next-line no-console + console.log(`player: changeElementStatus ${elementStatus.id}: ${elementStatus.status}`); + this.setUnitStateElementCodeStatus(elementStatus.id, elementStatus.status); + } + private addUnitStateElementCode(id: string, value: string | number | boolean | string[] | undefined): void { - const unitStateElementCode = this.getUnitStateElement(id); - if (!unitStateElementCode) { - this.unitStateElementCodes.push({ id: id, value: value, status: 'NOT_REACHED' }); + if (!this.getUnitStateElement(id)) { + const unitStateElementCode: UnitStateElementCode = { id: id, value: value, status: 'NOT_REACHED' }; + this.unitStateElementCodes.push(unitStateElementCode); + this._unitStateElementCodeChanged.next(unitStateElementCode); } } } -- GitLab