From bc4fa40abd582f929089a9dd22a7d1dd62f8815e Mon Sep 17 00:00:00 2001 From: jojohoch <joachim.hoch@iqb.hu-berlin.de> Date: Fri, 4 Mar 2022 14:19:29 +0100 Subject: [PATCH] [player] Implement virtual keyboard for text inputs of cloze component --- .../text-field-simple.component.ts | 11 +++-- .../directives/element-component.directive.ts | 2 +- projects/common/interfaces/elements.ts | 2 + projects/common/util/element.factory.ts | 3 ++ .../element-compound-group.component.html | 15 ++++++- .../element-compound-group.component.ts | 26 +++++++++++- .../element-text-input-group.component.html | 40 +++++++++---------- 7 files changed, 72 insertions(+), 27 deletions(-) diff --git a/projects/common/components/ui-elements/text-field-simple.component.ts b/projects/common/components/ui-elements/text-field-simple.component.ts index 8651ec386..6cd951dbb 100644 --- a/projects/common/components/ui-elements/text-field-simple.component.ts +++ b/projects/common/components/ui-elements/text-field-simple.component.ts @@ -1,11 +1,13 @@ -import { Component, Input } from '@angular/core'; +import { + Component, EventEmitter, Input, Output +} from '@angular/core'; import { FormElementComponent } from '../../directives/form-element-component.directive'; import { TextFieldSimpleElement } from '../../interfaces/elements'; @Component({ selector: 'aspect-text-field-simple', template: ` - <input type="text" form="parentForm" + <input #input type="text" autocomplete="off" autocapitalize="none" autocorrect="off" @@ -21,7 +23,9 @@ import { TextFieldSimpleElement } from '../../interfaces/elements'; [style.text-decoration]="elementModel.styling.underline ? 'underline' : ''" [readonly]="elementModel.readOnly" [formControl]="elementFormControl" - [value]="elementModel.value"> + [value]="elementModel.value" + (focus)="elementModel.inputAssistancePreset !== 'none' ? onFocusChanged.emit(input) : null" + (blur)="elementModel.inputAssistancePreset !== 'none' ? onFocusChanged.emit(null): null"> `, styles: [ 'input {border: 1px solid rgba(0,0,0,.12); border-radius: 5px}' @@ -29,4 +33,5 @@ import { TextFieldSimpleElement } from '../../interfaces/elements'; }) export class TextFieldSimpleComponent extends FormElementComponent { @Input() elementModel!: TextFieldSimpleElement; + @Output() onFocusChanged = new EventEmitter<HTMLElement | null>(); } diff --git a/projects/common/directives/element-component.directive.ts b/projects/common/directives/element-component.directive.ts index 08d18bed0..0993eaf11 100644 --- a/projects/common/directives/element-component.directive.ts +++ b/projects/common/directives/element-component.directive.ts @@ -9,7 +9,7 @@ export abstract class ElementComponent implements AfterContentChecked { abstract elementModel: UIElement; project!: 'player' | 'editor'; - constructor(private elementRef: ElementRef) {} + constructor(public elementRef: ElementRef) {} get domElement(): Element { return this.elementRef.nativeElement; diff --git a/projects/common/interfaces/elements.ts b/projects/common/interfaces/elements.ts index c5260db34..f706e3364 100644 --- a/projects/common/interfaces/elements.ts +++ b/projects/common/interfaces/elements.ts @@ -284,6 +284,8 @@ export interface TextAreaElement extends InputElement { export interface TextFieldSimpleElement extends InputElement { type: 'text-field'; + inputAssistancePreset: InputAssistancePreset; + inputAssistancePosition: 'floating' | 'right'; styling: BasicStyles; // TODO okay? bg-color? } diff --git a/projects/common/util/element.factory.ts b/projects/common/util/element.factory.ts index e220dd501..762ca1cd8 100644 --- a/projects/common/util/element.factory.ts +++ b/projects/common/util/element.factory.ts @@ -467,6 +467,9 @@ export abstract class ElementFactory { ...ElementFactory.initInputElement({ height: 25, ...element }), type: 'text-field', label: element.label !== undefined ? element.label : undefined, + inputAssistancePreset: element.inputAssistancePreset !== undefined ? element.inputAssistancePreset : 'none', + inputAssistancePosition: element.inputAssistancePosition !== undefined ? + element.inputAssistancePosition : 'floating', styling: ElementFactory.initBasicStyles(element.styling) }; } diff --git a/projects/player/src/app/components/element-compound-group/element-compound-group.component.html b/projects/player/src/app/components/element-compound-group/element-compound-group.component.html index 3431f8717..7c8b896eb 100644 --- a/projects/player/src/app/components/element-compound-group/element-compound-group.component.html +++ b/projects/player/src/app/components/element-compound-group/element-compound-group.component.html @@ -1,4 +1,5 @@ -<form [formGroup]="form"> +<form class="inline-container" + [formGroup]="form"> <aspect-cloze *ngIf="elementModel.type === 'cloze'" #elementComponent @@ -14,3 +15,15 @@ (childrenAdded)="onChildrenAdded($event)"> </aspect-likert> </form> + +<aspect-floating-keyboard + *ngIf="keyboardService.preset !== 'none'" + [isKeyboardOpen]="isKeyboardOpen && keyboardService.position === 'floating'" + [overlayOrigin]="keyboardService.elementComponent" + [inputElement]="keyboardService.inputElement" + [position]="keyboardService.position" + [preset]="keyboardService.preset" + [positionOffset]="0" + (deleteCharacter)="keyboardService.deleterCharacters()" + (enterKey)="keyboardService.enterKey($event)"> +</aspect-floating-keyboard> diff --git a/projects/player/src/app/components/element-compound-group/element-compound-group.component.ts b/projects/player/src/app/components/element-compound-group/element-compound-group.component.ts index b5f07265c..8a24bbb02 100644 --- a/projects/player/src/app/components/element-compound-group/element-compound-group.component.ts +++ b/projects/player/src/app/components/element-compound-group/element-compound-group.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { - ClozeElement, InputElement, LikertElement + ClozeElement, InputElement, LikertElement, TextFieldElement } from '../../../../../common/interfaces/elements'; import { ClozeUtils } from '../../../../../common/util/cloze'; import { UnitStateService } from '../../services/unit-state.service'; @@ -11,6 +11,10 @@ import { ElementFormGroupDirective } from '../../directives/element-form-group.d import { MessageService } from '../../../../../common/services/message.service'; import { VeronaSubscriptionService } from '../../services/verona-subscription.service'; import { ValidatorService } from '../../services/validator.service'; +import { KeyboardService } from '../../services/keyboard.service'; +import { TextAreaComponent } from '../../../../../common/components/ui-elements/text-area.component'; +import { TextFieldComponent } from '../../../../../common/components/ui-elements/text-field.component'; +import { TextFieldSimpleComponent } from '../../../../../common/components/ui-elements/text-field-simple.component'; @Component({ selector: 'aspect-element-compound-group', @@ -19,10 +23,12 @@ import { ValidatorService } from '../../services/validator.service'; }) export class ElementCompoundGroupComponent extends ElementFormGroupDirective implements OnInit { @ViewChild('elementComponent') elementComponent!: ElementComponent; + isKeyboardOpen!: boolean; ClozeElement!: ClozeElement; LikertElement!: LikertElement; constructor( + public keyboardService: KeyboardService, public unitStateService: UnitStateService, public unitStateElementMapperService: UnitStateElementMapperService, public translateService: TranslateService, @@ -44,6 +50,24 @@ export class ElementCompoundGroupComponent extends ElementFormGroupDirective imp children.forEach(child => { const childModel = child.elementModel as InputElement; this.registerAtUnitStateService(childModel.id, childModel.value, child, this.pageIndex); + if (childModel.type === 'text-field') { + (child as TextFieldSimpleComponent) + .onFocusChanged.subscribe(element => this.onFocusChanged(element, child as TextFieldComponent)); + } }); } + + onFocusChanged(focussedElement: HTMLElement | null, elementComponent: TextAreaComponent | TextFieldComponent): void { + if (focussedElement) { + const focussedInputElement = this.elementModel.type === 'text-area' ? + focussedElement as HTMLTextAreaElement : + focussedElement as HTMLInputElement; + const preset = (elementComponent.elementModel as TextFieldElement).inputAssistancePreset; + const position = (elementComponent.elementModel as TextFieldElement).inputAssistancePosition; + this.isKeyboardOpen = this.keyboardService + .openKeyboard(focussedInputElement, preset, position, elementComponent); + } else { + this.isKeyboardOpen = this.keyboardService.closeKeyboard(); + } + } } diff --git a/projects/player/src/app/components/element-text-input-group/element-text-input-group.component.html b/projects/player/src/app/components/element-text-input-group/element-text-input-group.component.html index 8e451a2b5..f79fb974f 100644 --- a/projects/player/src/app/components/element-text-input-group/element-text-input-group.component.html +++ b/projects/player/src/app/components/element-text-input-group/element-text-input-group.component.html @@ -1,27 +1,25 @@ -<div class="inline-container" cdkOverlayOrigin #overlayOrigin="cdkOverlayOrigin"> - <form [formGroup]="form"> - <aspect-text-area - *ngIf="elementModel.type === 'text-area'" - #elementComponent - [parentForm]="form" - [elementModel]="elementModel | cast: TextAreaElement" - (onFocusChanged)="onFocusChanged($event, elementComponent)"> - </aspect-text-area> - <aspect-text-field - *ngIf="elementModel.type === 'text-field'" - #elementComponent - [parentForm]="form" - [elementModel]="elementModel | cast: TextFieldElement" - (onFocusChanged)="onFocusChanged($event, elementComponent)"> - </aspect-text-field> - </form> -</div> +<form class="inline-container" + [formGroup]="form"> + <aspect-text-area + *ngIf="elementModel.type === 'text-area'" + #elementComponent + [parentForm]="form" + [elementModel]="elementModel | cast: TextAreaElement" + (onFocusChanged)="onFocusChanged($event, elementComponent)"> + </aspect-text-area> + <aspect-text-field + *ngIf="elementModel.type === 'text-field'" + #elementComponent + [parentForm]="form" + [elementModel]="elementModel | cast: TextFieldElement" + (onFocusChanged)="onFocusChanged($event, elementComponent)"> + </aspect-text-field> +</form> <aspect-floating-keyboard - *ngIf="keyboardService.preset !== 'none' && - (elementModel.type === 'text-area' || elementModel.type === 'text-field')" + *ngIf="keyboardService.preset !== 'none'" [isKeyboardOpen]="isKeyboardOpen && keyboardService.position === 'floating'" - [overlayOrigin]="overlayOrigin" + [overlayOrigin]="elementComponent" [inputElement]="keyboardService.inputElement" [position]="keyboardService.position" [preset]="keyboardService.preset" -- GitLab