From 090cd7c7d43c8416d3ae7fb9fff6ff0d6f54df29 Mon Sep 17 00:00:00 2001 From: jojohoch <joachim.hoch@iqb.hu-berlin.de> Date: Mon, 29 Jan 2024 14:39:51 +0100 Subject: [PATCH] Add new trigger element #608 --- .../components/trigger/trigger.component.ts | 41 ++++++++++++++ projects/common/models/elements/element.ts | 2 +- .../common/models/elements/trigger/trigger.ts | 55 +++++++++++++++++++ projects/common/shared.module.ts | 3 + projects/common/util/element.factory.ts | 2 + .../ui-element-toolbox.component.html | 5 ++ .../element-model-properties.component.html | 8 +++ .../action-properties.component.ts | 3 +- .../button-properties.component.ts | 7 +-- .../editor/src/app/services/id.service.ts | 3 +- projects/editor/src/assets/i18n/de.json | 3 +- .../modules/logging/services/log.service.ts | 2 +- .../element-group-selection.component.ts | 2 +- .../interactive-group-element.component.html | 9 +++ .../interactive-group-element.component.ts | 13 ++++- .../directives/in-view-detection.directive.ts | 2 +- 16 files changed, 145 insertions(+), 15 deletions(-) create mode 100644 projects/common/components/trigger/trigger.component.ts create mode 100644 projects/common/models/elements/trigger/trigger.ts diff --git a/projects/common/components/trigger/trigger.component.ts b/projects/common/components/trigger/trigger.component.ts new file mode 100644 index 000000000..23e490f6e --- /dev/null +++ b/projects/common/components/trigger/trigger.component.ts @@ -0,0 +1,41 @@ +import { ElementComponent } from 'common/directives/element-component.directive'; +import { + Component, EventEmitter, Input, Output +} from '@angular/core'; +import { TriggerElement, TriggerActionEvent } from 'common/models/elements/trigger/trigger'; + +@Component({ + selector: 'aspect-trigger', + template: ` + <div> + <div *ngIf="project === 'editor'" + class="hidden-trigger"> + </div> + </div> + `, + styles: [` + .hidden-trigger { + height: 20px; + background-image: linear-gradient( + 135deg, #fff 45%, #999 45%, #999 50%, #fff 50%, #fff 95%, #999 95%, #999 100% + ); + background-size: 10px 10px; + border: 1px solid #999; + } + `] +}) + +export class TriggerComponent extends ElementComponent { + @Input() elementModel!: TriggerElement; + @Output() triggerActionEvent = new EventEmitter<TriggerActionEvent>(); + + emitEvent(): void { + if (this.elementModel.action && this.elementModel.actionParam) { + this.triggerActionEvent + .emit({ + action: this.elementModel.action, + param: this.elementModel.actionParam + }); + } + } +} diff --git a/projects/common/models/elements/element.ts b/projects/common/models/elements/element.ts index 8ce263e02..b7bd7d6cc 100644 --- a/projects/common/models/elements/element.ts +++ b/projects/common/models/elements/element.ts @@ -25,7 +25,7 @@ import { VariableInfo } from '@iqb/responses'; export type UIElementType = 'text' | 'button' | 'text-field' | 'text-field-simple' | 'text-area' | 'checkbox' | 'dropdown' | 'radio' | 'image' | 'audio' | 'video' | 'likert' | 'likert-row' | 'radio-group-images' | 'hotspot-image' | 'drop-list' | 'cloze' | 'spell-correct' | 'slider' | 'frame' | 'toggle-button' | 'geometry' -| 'math-field' | 'math-table' | 'text-area-math'; +| 'math-field' | 'math-table' | 'text-area-math' | 'trigger'; export interface OptionElement extends UIElement { getNewOptionLabel(optionText: string): Label; diff --git a/projects/common/models/elements/trigger/trigger.ts b/projects/common/models/elements/trigger/trigger.ts new file mode 100644 index 000000000..e79f0800b --- /dev/null +++ b/projects/common/models/elements/trigger/trigger.ts @@ -0,0 +1,55 @@ +import { Type } from '@angular/core'; +import { + UIElement, UIElementProperties, UIElementType +} from 'common/models/elements/element'; +import { ElementComponent } from 'common/directives/element-component.directive'; +import { StateVariable } from 'common/models/state-variable'; +import { environment } from 'common/environment'; +import { InstantiationEror } from 'common/util/errors'; +import { TriggerComponent } from 'common/components/trigger/trigger.component'; + +export class TriggerElement extends UIElement implements TriggerProperties { + type: UIElementType = 'trigger'; + action: null | TriggerAction = null; + actionParam: null | string | StateVariable = null; + + constructor(element?: TriggerProperties) { + super(element); + if (element && isValid(element)) { + this.action = element.action; + this.actionParam = element.actionParam; + } else { + if (environment.strictInstantiation) { + throw new InstantiationEror('Error at Trigger instantiation', element); + } + if (element?.action !== undefined) this.action = element.action; + if (element?.actionParam !== undefined) this.actionParam = element.actionParam; + } + } + + getDuplicate(): TriggerElement { + return new TriggerElement(this); + } + + getElementComponent(): Type<ElementComponent> { + return TriggerComponent; + } +} + +export interface TriggerProperties extends UIElementProperties { + action: null | TriggerAction; + actionParam: null | string | StateVariable ; +} + +function isValid(blueprint?: TriggerProperties): boolean { + if (!blueprint) return false; + return blueprint.action !== undefined && + blueprint.actionParam !== undefined; +} + +export interface TriggerActionEvent { + action: TriggerAction; + param: string | StateVariable; +} + +export type TriggerAction = 'highlightText' | 'stateVariableChange'; diff --git a/projects/common/shared.module.ts b/projects/common/shared.module.ts index 14a5cc7bb..531ff26c4 100644 --- a/projects/common/shared.module.ts +++ b/projects/common/shared.module.ts @@ -30,6 +30,7 @@ import { TooltipEventTooltipDirective } from 'common/components/tooltip/tooltip- import { TooltipComponent } from 'common/components/tooltip/tooltip.component'; import { PointerEventTooltipDirective } from 'common/components/tooltip/pointer-event-tooltip.directive'; import { ClozeChildErrorMessage } from 'common/components/compound-elements/cloze/cloze-child-error-message'; +import { TriggerComponent } from 'common/components/trigger/trigger.component'; import { TextComponent } from './components/text/text.component'; import { ButtonComponent } from './components/button/button.component'; import { TextFieldComponent } from './components/input-elements/text-field.component'; @@ -116,6 +117,7 @@ import { TextAreaMathComponent } from './components/input-elements/text-area-mat ], declarations: [ ButtonComponent, + TriggerComponent, TextComponent, TextFieldComponent, TextFieldSimpleComponent, @@ -207,6 +209,7 @@ import { TextAreaMathComponent } from './components/input-elements/text-area-mat HotspotImageComponent, LikertComponent, ButtonComponent, + TriggerComponent, FrameComponent, ImageComponent, GeometryComponent, diff --git a/projects/common/util/element.factory.ts b/projects/common/util/element.factory.ts index acc7f5885..b0c4f9aef 100644 --- a/projects/common/util/element.factory.ts +++ b/projects/common/util/element.factory.ts @@ -26,11 +26,13 @@ import { HotspotImageElement } from 'common/models/elements/input-elements/hotsp import { MathFieldElement } from 'common/models/elements/input-elements/math-field'; import { MathTableElement } from 'common/models/elements/input-elements/math-table'; import { TextAreaMathElement } from 'common/models/elements/input-elements/text-area-math'; +import { TriggerElement } from 'common/models/elements/trigger/trigger'; export abstract class ElementFactory { static ELEMENT_CLASSES: Record<string, Type<UIElement>> = { text: TextElement, button: ButtonElement, + trigger: TriggerElement, 'text-field': TextFieldElement, 'text-field-simple': TextFieldSimpleElement, 'text-area': TextAreaElement, diff --git a/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.html b/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.html index 4a326ba64..f117b4da0 100644 --- a/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.html +++ b/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.html @@ -153,6 +153,11 @@ <mat-icon>architecture</mat-icon> {{'toolbox.geometry' | translate }} </button> + <button mat-stroked-button (click)="addUIElement('trigger')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','trigger')"> + <mat-icon>bolt</mat-icon> + {{'toolbox.trigger' | translate }} + </button> </mat-expansion-panel> </mat-accordion> </div> diff --git a/projects/editor/src/app/components/properties-panel/model-properties-tab/element-model-properties.component.html b/projects/editor/src/app/components/properties-panel/model-properties-tab/element-model-properties.component.html index 601c91b4f..367cdcf4c 100644 --- a/projects/editor/src/app/components/properties-panel/model-properties-tab/element-model-properties.component.html +++ b/projects/editor/src/app/components/properties-panel/model-properties-tab/element-model-properties.component.html @@ -134,6 +134,14 @@ (updateModel)="updateModel.emit($event)"> </aspect-button-properties> + <aspect-action-properties *ngIf="combinedProperties.action !== undefined" + [actions]="combinedProperties.type === 'button' ? ['unitNav', 'pageNav', 'highlightText', 'stateVariableChange'] : + ['highlightText', 'stateVariableChange']" + [combinedProperties]="combinedProperties" + (updateModel)="updateModel.emit($event)"> + </aspect-action-properties> + + <aspect-slider-properties [combinedProperties]="combinedProperties" (updateModel)="updateModel.emit($event)"> </aspect-slider-properties> diff --git a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/action-properties.component.ts b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/action-properties.component.ts index 0402f5e5a..d7c3dc9b1 100644 --- a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/action-properties.component.ts +++ b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/action-properties.component.ts @@ -22,7 +22,7 @@ import { Page } from 'common/models/page'; <mat-option [value]="null"> {{ 'propertiesPanel.none' | translate }} </mat-option> - <mat-option *ngFor="let option of ['unitNav', 'pageNav', 'highlightText', 'stateVariableChange']" + <mat-option *ngFor="let option of actions" [value]="option"> {{ 'propertiesPanel.' + option | translate }} </mat-option> @@ -88,6 +88,7 @@ import { Page } from 'common/models/page'; export class ActionPropertiesComponent { @Input() combinedProperties!: UIElement; + @Input() actions!: string[]; @Output() updateModel = new EventEmitter<{ property: string; value: string | number | boolean | StateVariable | null, isInputValid?: boolean | null diff --git a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties.component.ts b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties.component.ts index 0e33cfc27..3720bff26 100644 --- a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties.component.ts +++ b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties.component.ts @@ -3,7 +3,6 @@ import { } from '@angular/core'; import { FileService } from 'common/services/file.service'; import { UIElement } from 'common/models/elements/element'; -import { StateVariable } from 'common/models/state-variable'; @Component({ selector: 'aspect-button-properties', @@ -84,10 +83,6 @@ import { StateVariable } from 'common/models/state-variable'; </mat-form-field> </div> </fieldset> - <aspect-action-properties - [combinedProperties]="combinedProperties" - (updateModel)="updateModel.emit($event)"> - </aspect-action-properties> </ng-container> `, styles: [` @@ -125,7 +120,7 @@ export class ButtonPropertiesComponent { @Input() combinedProperties!: UIElement; @Output() updateModel = new EventEmitter<{ - property: string; value: string | number | boolean | StateVariable | null, isInputValid?: boolean | null + property: string; value: string | number | boolean | null, isInputValid?: boolean | null }>(); checked = false; diff --git a/projects/editor/src/app/services/id.service.ts b/projects/editor/src/app/services/id.service.ts index bbb03c3b5..a5c5edd51 100644 --- a/projects/editor/src/app/services/id.service.ts +++ b/projects/editor/src/app/services/id.service.ts @@ -39,7 +39,8 @@ export class IDService { 'math-table': 0, value: 0, 'state-variable': 0, - 'text-area-math': 0 + 'text-area-math': 0, + trigger: 0 }; constructor(private messageService: MessageService, private translateService: TranslateService) { } diff --git a/projects/editor/src/assets/i18n/de.json b/projects/editor/src/assets/i18n/de.json index 9caee642c..d20552966 100644 --- a/projects/editor/src/assets/i18n/de.json +++ b/projects/editor/src/assets/i18n/de.json @@ -327,7 +327,8 @@ "formula": "Formel", "math-field": "Feld", "math-area": "Bereich", - "math-table": "Rechenkästchen" + "math-table": "Rechenkästchen", + "trigger": "Auslöser" }, "section-menu": { "height": "Höhe", diff --git a/projects/player/modules/logging/services/log.service.ts b/projects/player/modules/logging/services/log.service.ts index 651ce17b3..47ad6e147 100644 --- a/projects/player/modules/logging/services/log.service.ts +++ b/projects/player/modules/logging/services/log.service.ts @@ -6,7 +6,7 @@ export enum LogLevel { NONE = 0, ERROR = 1, WARN = 2, INFO = 3, DEBUG = 4 } providedIn: 'root' }) export class LogService { - static level: LogLevel = 0; + static level: LogLevel = 4; static error(...args: unknown[]): void { if (LogService.level >= LogLevel.ERROR) { diff --git a/projects/player/src/app/components/elements/element-group-selection/element-group-selection.component.ts b/projects/player/src/app/components/elements/element-group-selection/element-group-selection.component.ts index 90fcaf5bb..45f615daf 100644 --- a/projects/player/src/app/components/elements/element-group-selection/element-group-selection.component.ts +++ b/projects/player/src/app/components/elements/element-group-selection/element-group-selection.component.ts @@ -23,7 +23,7 @@ export class ElementGroupSelectionComponent implements OnInit { }, { name: 'compoundGroup', types: ['cloze', 'likert'] }, { name: 'textGroup', types: ['text'] }, - { name: 'interactiveGroup', types: ['button', 'image', 'math-table'] }, + { name: 'interactiveGroup', types: ['button', 'image', 'math-table', 'trigger'] }, { name: 'externalAppGroup', types: ['geometry'] } ]; diff --git a/projects/player/src/app/components/elements/interactive-group-element/interactive-group-element.component.html b/projects/player/src/app/components/elements/interactive-group-element/interactive-group-element.component.html index a76e6c846..e082902ee 100644 --- a/projects/player/src/app/components/elements/interactive-group-element/interactive-group-element.component.html +++ b/projects/player/src/app/components/elements/interactive-group-element/interactive-group-element.component.html @@ -19,6 +19,15 @@ (focusChanged)="toggleKeyInput($event)" (elementValueChanged)="changeElementCodeValue($event)"> </aspect-math-table> +<aspect-trigger + *ngIf="elementModel.type === 'trigger'" + #elementComponent + [elementModel]="elementModel | cast: TriggerElement" + (triggerActionEvent)="applyTriggerAction($event)" + aspectInViewDetection + detectionType="top" + (intersecting)="elementComponent.emitEvent()">> +</aspect-trigger> <aspect-floating-keypad [isKeypadOpen]="isKeypadOpen && keypadService.position === 'floating'"> diff --git a/projects/player/src/app/components/elements/interactive-group-element/interactive-group-element.component.ts b/projects/player/src/app/components/elements/interactive-group-element/interactive-group-element.component.ts index 2342c9d8e..1f9fc5396 100644 --- a/projects/player/src/app/components/elements/interactive-group-element/interactive-group-element.component.ts +++ b/projects/player/src/app/components/elements/interactive-group-element/interactive-group-element.component.ts @@ -3,6 +3,7 @@ import { } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { ButtonElement, ButtonEvent, UnitNavParam } from 'common/models/elements/button/button'; +import { TriggerActionEvent, TriggerElement } from 'common/models/elements/trigger/trigger'; import { ImageElement } from 'common/models/elements/media-elements/image'; import { UIElement, ValueChangeElement } from 'common/models/elements/element'; import { VeronaPostService } from 'player/modules/verona/services/verona-post.service'; @@ -29,6 +30,7 @@ export class InteractiveGroupElementComponent extends ElementGroupDirective impl ButtonElement!: ButtonElement; ImageElement!: ImageElement; MathTableElement!: MathTableElement; + TriggerElement!: TriggerElement; tableModel: MathTableRow[] = []; @@ -92,12 +94,19 @@ export class InteractiveGroupElementComponent extends ElementGroupDirective impl case 'pageNav': this.navigationService.setPage(buttonEvent.param as number); break; + default: + this.applyTriggerAction(buttonEvent as TriggerActionEvent); + } + } + + applyTriggerAction(triggerActionEvent: TriggerActionEvent): void { + switch (triggerActionEvent.action) { case 'highlightText': - this.anchorService.toggleAnchor(buttonEvent.param as string); + this.anchorService.toggleAnchor(triggerActionEvent.param as string); break; case 'stateVariableChange': this.stateVariableStateService.changeElementCodeValue( - buttonEvent.param as { id: string, value: string } + triggerActionEvent.param as { id: string, value: string } ); break; default: diff --git a/projects/player/src/app/directives/in-view-detection.directive.ts b/projects/player/src/app/directives/in-view-detection.directive.ts index 8b3d81113..eb35957e3 100644 --- a/projects/player/src/app/directives/in-view-detection.directive.ts +++ b/projects/player/src/app/directives/in-view-detection.directive.ts @@ -21,7 +21,7 @@ export class InViewDetectionDirective implements AfterViewInit, OnDestroy { constructor(private elementRef: ElementRef) {} ngAfterViewInit(): void { - const intersectionContainer = this.elementRef.nativeElement.closest('aspect-page-scroll-button'); + const intersectionContainer = this.elementRef.nativeElement.closest('aspect-page-scroll-button') || document; if (intersectionContainer) { const constraint = this.detectionType === 'top' ? '0px' : '0px'; this.intersectionDetector = new IntersectionDetector(intersectionContainer, constraint); -- GitLab