Newer
Older
AfterViewInit, ApplicationRef, Component, OnInit, Renderer2, ViewChild
} from '@angular/core';
import { takeUntil } from 'rxjs/operators';
import { ElementComponent } from 'common/directives/element-component.directive';
import { VeronaSubscriptionService } from 'player/modules/verona/services/verona-subscription.service';
import {
TextFieldSimpleComponent
} from 'common/components/compound-elements/cloze/cloze-child-elements/text-field-simple.component';
import { ClozeElement } from 'common/models/elements/compound-elements/cloze/cloze';
import { LikertElement } from 'common/models/elements/compound-elements/likert/likert';
CompoundElement, InputElement, UIElement
} from 'common/models/elements/element';
import { ButtonComponent } from 'common/components/button/button.component';
import { VeronaPostService } from 'player/modules/verona/services/verona-post.service';
import { NavigationService } from 'player/src/app/services/navigation.service';
import { AnchorService } from 'player/src/app/services/anchor.service';
import { UnitNavParam } from 'common/models/elements/button/button';
import { StateVariableStateService } from 'player/src/app/services/state-variable-state.service';
import { Subscription } from 'rxjs';
import { TextInputGroupDirective } from 'player/src/app/directives/text-input-group.directive';
import { ResponseValueType } from '@iqb/responses';
import { TableElement } from 'common/models/elements/compound-elements/table/table';
import { ImageElement } from 'common/models/elements/media-elements/image';
import { TextFieldComponent } from 'common/components/input-elements/text-field.component';
import { ImageComponent } from 'common/components/media-elements/image.component';
import { AudioComponent } from 'common/components/media-elements/audio.component';
import { AudioElement } from 'common/models/elements/media-elements/audio';
import { MediaPlayerService } from 'player/src/app/services/media-player.service';
import { NativeEventService } from 'player/src/app/services/native-event.service';
import { TextComponent } from 'common/components/text/text.component';
import { TextMarkingSupport } from 'player/src/app/classes/text-marking-support';
import { TextElement } from 'common/models/elements/text/text';
import { TextMarkingUtils } from 'player/src/app/classes/text-marking-utils';
import { MarkableSupport } from 'player/src/app/classes/markable-support';
import { UnitStateService } from '../../../services/unit-state.service';
import { ElementModelElementCodeMappingService } from '../../../services/element-model-element-code-mapping.service';
import { ValidationService } from '../../../services/validation.service';
import { KeypadService } from '../../../services/keypad.service';
import { KeyboardService } from '../../../services/keyboard.service';
import { DeviceService } from '../../../services/device.service';
selector: 'aspect-compound-group-element',
templateUrl: './compound-group-element.component.html',
styleUrls: ['./compound-group-element.component.scss']
export class CompoundGroupElementComponent extends TextInputGroupDirective implements OnInit, AfterViewInit {
@ViewChild('elementComponent') elementComponent!: ElementComponent;
ClozeElement!: ClozeElement;
LikertElement!: LikertElement;
TableElement!: TableElement;
ImageElement!: ImageElement;
inputElement!: HTMLTextAreaElement | HTMLInputElement;
keypadEnterKeySubscription!: Subscription;
keypadDeleteCharactersSubscription!: Subscription;
keypadSelectSubscription!: Subscription;
savedPlaybackTimes: { [key: string]: number } = {};
savedTexts: { [key: string]: string } = {};
savedMarks: { [key: string]: string[] } = {};
textMarkingSupports: { [key: string]: TextMarkingSupport } = {};
markableSupports: { [key: string]: MarkableSupport } = {};
public keyboardService: KeyboardService,
public deviceService: DeviceService,
public keypadService: KeypadService,
public unitStateService: UnitStateService,
public elementModelElementCodeMappingService: ElementModelElementCodeMappingService,
public veronaSubscriptionService: VeronaSubscriptionService,
private veronaPostService: VeronaPostService,
private navigationService: NavigationService,
private nativeEventService: NativeEventService,
private anchorService: AnchorService,
public validationService: ValidationService,
private stateVariableStateService: StateVariableStateService,
public mediaPlayerService: MediaPlayerService,
private renderer: Renderer2,
private applicationRef: ApplicationRef
) {
super();
}
this.createForm((this.elementModel as CompoundElement).getChildElements() as InputElement[]);
if (this.elementModel.type === 'table') {
this.initAudioTableChildren();
private initTextChildren(): void {
(this.elementModel as TableElement).elements
.filter(child => child.type === 'text')
.forEach(element => {
this.setTextMarkingSupportForText(element as TextElement);
private setTextMarkingSupportForText(element: TextElement): void {
this.textMarkingSupports[element.id] = new TextMarkingSupport(this.nativeEventService, this.anchorService);
this.markableSupports[element.id] = new MarkableSupport(this.renderer, this.applicationRef);
this.savedMarks[element.id] = this.elementModelElementCodeMappingService
.mapToElementModelValue(this.unitStateService
.getElementCodeById(element.id)?.value, element) as string[];
this.savedTexts[element.id] = (element.markingMode === 'default') ?
TextMarkingUtils
.restoreMarkedTextIndices(
this.savedMarks[element.id],
ElementModelElementCodeMappingService.modifyAnchors(element.text)) :
ElementModelElementCodeMappingService.modifyAnchors(element.text);
private initAudioTableChildren(): void {
(this.elementModel as TableElement).elements
.filter(child => child.type === 'audio')
.forEach(element => {
this.setSavedPlaybackTimeForAudio(element);
this.registerAtMediaPlayerService(element as AudioElement);
});
}
private registerAtMediaPlayerService(audioElement: AudioElement): void {
this.mediaPlayerService.registerMediaElement(
audioElement.id,
audioElement.player?.minRuns as number === 0
);
}
private setSavedPlaybackTimeForAudio(element: UIElement): void {
this.savedPlaybackTimes[element.id] = this.elementModelElementCodeMappingService.mapToElementModelValue(
this.unitStateService.getElementCodeById(element.id)?.value, element) as number;
ngAfterViewInit(): void {
this.registerAtUnitStateService(
this.elementModel.id,
null,
this.elementComponent,
this.pageIndex
);
}
registerCompoundChildren(children: ElementComponent[]): void {
children.forEach(child => {
const childModel = child.elementModel;
let initialValue: ResponseValueType;
switch (childModel.type) {
case 'image':
initialValue = ElementModelElementCodeMappingService.mapToElementCodeValue(
(this.elementModel as ImageElement).magnifierUsed, this.elementModel.type);
break;
case 'audio':
initialValue = this.elementModelElementCodeMappingService.mapToElementModelValue(
this.unitStateService.getElementCodeById(childModel.id)?.value, childModel) as number;
break;
case 'button':
initialValue = null;
break;
case 'text':
if (childModel.markingMode === 'word' || childModel.markingMode === 'range') {
this.markableSupports[childModel.id]
.createMarkables(this.savedMarks[childModel.id], child as TextComponent);
}
initialValue = ElementModelElementCodeMappingService.mapToElementCodeValue(
this.getTextChildModelValue(childModel as TextElement),
childModel.type,
{
markingMode: (childModel as TextElement).markingMode,
color: 'yellow'
});
initialValue = ElementModelElementCodeMappingService
.mapToElementCodeValue((childModel as InputElement).value, childModel.type);
this.registerAtUnitStateService(childModel.id, initialValue, child, this.pageIndex);
this.registerChildEvents(child, childModel);
private getTextChildModelValue(childModel: TextElement): string | string[] {
return childModel.markingMode === 'word' || childModel.markingMode === 'range' ?
this.savedMarks[childModel.id] :
this.savedTexts[childModel.id];
}
private registerChildEvents(child: ElementComponent, childModel: UIElement): void {
if (childModel.type === 'text-field-simple') {
this.manageKeyInputToggling(child as TextFieldSimpleComponent);
this.manageOnKeyDown(child as TextFieldSimpleComponent, childModel as InputElement);
}
if (childModel.type === 'text-field') {
this.manageKeyInputToggling(child as TextFieldComponent);
this.manageOnKeyDown(child as TextFieldComponent, childModel as InputElement);
}
if (childModel.type === 'button') {
this.addButtonActionEventListener(child as ButtonComponent);
}
if (childModel.type === 'text') {
this.addMarkingDataChangedSubscription(child as TextComponent, childModel);
this.addTextSelectionStartSubscription(child as TextComponent, childModel);
this.addElementCodeValueSubscription(
child as TextComponent,
childModel,
{ markingMode: 'word', color: 'yellow' }
);
if (childModel.type === 'image') {
this.addElementCodeValueSubscription(child as ImageComponent, childModel);
if (childModel.type === 'audio') {
this.addElementCodeValueSubscription(child as AudioComponent, childModel);
this.addMediaSubscriptions(child as AudioComponent);
}
}
private addTextSelectionStartSubscription(child: TextComponent, childModel: UIElement): void {
child.textSelectionStart
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(data => {
this.textMarkingSupports[childModel.id]
.startTextSelection(data, child);
});
}
private addMarkingDataChangedSubscription(child: TextComponent, childModel: UIElement): void {
child.markingDataChanged
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(data => this.textMarkingSupports[childModel.id]
.applyMarkingData(data, child));
}
private addMediaSubscriptions(child: AudioComponent): void {
child.mediaValidStatusChanged
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(mediaId => {
this.mediaPlayerService.setValidStatusChanged(mediaId);
});
child.mediaPlayStatusChanged
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(mediaId => {
this.mediaPlayerService.setActualPlayingId(mediaId);
});
}
private addElementCodeValueSubscription(
child: ImageComponent | AudioComponent | TextComponent,
childModel: UIElement,
options?: Record<string, string>
child.elementValueChanged
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(value => {
this.unitStateService.changeElementCodeValue({
id: value.id,
value: ElementModelElementCodeMappingService
.mapToElementCodeValue(value.value, childModel.type, options)
}
private manageOnKeyDown(
textInputComponent: TextFieldSimpleComponent | TextFieldComponent,
elementModel: InputElement): void {
textInputComponent
.onKeyDown
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(event => {
this.onKeyDown(event, elementModel);
});
}
private manageKeyInputToggling(textInputComponent: TextFieldSimpleComponent | TextFieldComponent): void {
textInputComponent
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(focusedTextInput => {
this.toggleKeyInput(focusedTextInput, textInputComponent);
});
}
private onKeyDown(event: {
keyboardEvent: KeyboardEvent;
inputElement: HTMLInputElement | HTMLTextAreaElement
}, elementModel: InputElement): void {
this.detectHardwareKeyboard(elementModel);
this.checkInputLimitation(event, elementModel);
private addButtonActionEventListener(button: ButtonComponent) {
button.buttonActionEvent
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(buttonEvent => {
switch (buttonEvent.action) {
case 'unitNav':
this.veronaPostService.sendVopUnitNavigationRequestedNotification(
(buttonEvent.param as UnitNavParam)
);
break;
case 'pageNav':
this.navigationService.setPage(buttonEvent.param as number);
break;
this.anchorService.toggleAnchor(buttonEvent.param as string);
break;
case 'stateVariableChange':
this.stateVariableStateService.changeElementCodeValue(
buttonEvent.param as { id: string, value: string }
);
default: