From 0ab8fe0ee60b4f3a0018221cb1b269ac59b60c2d Mon Sep 17 00:00:00 2001 From: jojohoch <joachim.hoch@iqb.hu-berlin.de> Date: Mon, 17 Jul 2023 12:54:47 +0200 Subject: [PATCH] Add audioSrc to DragNDropValueObject --- docs/unit_definition_changelog.txt | 2 + .../input-elements/drop-list.component.ts | 41 +++++++++++++++---- .../models/elements/label-interfaces.ts | 1 + .../common/services/sanitization.service.ts | 2 + .../drop-list-option-edit-dialog.component.ts | 36 ++++++++++++---- .../drop-list-properties.component.ts | 3 +- projects/editor/src/assets/i18n/de.json | 3 ++ ...model-element-code-mapping.service.spec.ts | 14 ++++++- 8 files changed, 84 insertions(+), 18 deletions(-) diff --git a/docs/unit_definition_changelog.txt b/docs/unit_definition_changelog.txt index 691f25022..bebee0721 100644 --- a/docs/unit_definition_changelog.txt +++ b/docs/unit_definition_changelog.txt @@ -100,3 +100,5 @@ iqb-aspect-definition@1.0.0 - add "stateVariables: StateVariable[]" (StateVariable: {id: string; value: string;}) - UIElement: - add "isRelevantForPresentationComplete" +- DragNDropValueObject: + - add "audioSrc" diff --git a/projects/common/components/input-elements/drop-list.component.ts b/projects/common/components/input-elements/drop-list.component.ts index ff03769b0..eb36fcbab 100644 --- a/projects/common/components/input-elements/drop-list.component.ts +++ b/projects/common/components/input-elements/drop-list.component.ts @@ -9,9 +9,8 @@ import { copyArrayItem } from '@angular/cdk/drag-drop'; import { DropListElement } from 'common/models/elements/input-elements/drop-list'; -import { FormElementComponent } from '../../directives/form-element-component.directive'; - import { DragNDropValueObject } from 'common/models/elements/label-interfaces'; +import { FormElementComponent } from '../../directives/form-element-component.directive'; @Component({ selector: 'aspect-drop-list', @@ -54,13 +53,26 @@ import { DragNDropValueObject } from 'common/models/elements/label-interfaces'; class="list-item text-list-item" [class.hide-list-item]="parentForm && (elementModel.allowReplacement || elementModel.copyOnDrop) && elementFormControl.value.length === 1 && showsPlaceholder" + [class.audio-list-item]="dropListValueElement.audioSrc" cdkDrag [cdkDragData]="dropListValueElement" (cdkDragStarted)="dragStart($event)" (cdkDragEnded)="dragEnd()" [style.background-color]="elementModel.styling.itemBackgroundColor"> - <span>{{dropListValueElement.text}}</span> + <ng-container *ngIf="dropListValueElement.audioSrc"> + <audio #player + [src]="dropListValueElement.audioSrc | safeResourceUrl"> + </audio> + <div class="audio-button" + (click)="player.play()"> + <mat-icon>play_arrow</mat-icon> + </div> + </ng-container> + <div class="text-padding"> + {{dropListValueElement.text}} + </div> <ng-template cdkDragPreview matchSize> <div class="text-preview" + [class.audio-list-item]="dropListValueElement.audioSrc" [class.cloze-context-preview]="clozeContext" [style.color]="elementModel.styling.fontColor" [style.font-family]="elementModel.styling.font" @@ -69,7 +81,11 @@ import { DragNDropValueObject } from 'common/models/elements/label-interfaces'; [style.font-style]="elementModel.styling.italic ? 'italic' : ''" [style.text-decoration]="elementModel.styling.underline ? 'underline' : ''" [style.background-color]="elementModel.styling.itemBackgroundColor"> - <span>{{dropListValueElement.text}}</span> + <div *ngIf="dropListValueElement.audioSrc" + class="audio-button"> + <mat-icon>play_arrow</mat-icon> + </div> + <div class="text-padding">{{dropListValueElement.text}}</div> </div> </ng-template> </div> @@ -96,7 +112,7 @@ import { DragNDropValueObject } from 'common/models/elements/label-interfaces'; (!elementFormControl.value.length && !showsPlaceholder) || (elementFormControl.value.length === 1 && !showsPlaceholder && dragging) : !elementModel.value.length;"> - <span class="baseline-helper"> </span> + <div class="baseline-helper text-padding"> </div> <mat-error *ngIf="elementFormControl.errors && elementFormControl.touched && !classReference.highlightReceivingDropList" @@ -105,6 +121,11 @@ import { DragNDropValueObject } from 'common/models/elements/label-interfaces'; </div> `, styles: [ + '.audio-button {cursor: pointer;}', + '.audio-button:hover {color: #006064;}', + '.cloze-context .list-item.text-list-item .audio-button .mat-icon {height: 19px;}', + ':not(.cloze-context) .list-item.text-list-item .audio-button .mat-icon {margin-top: 5px; padding-left: 3px;}', + '.audio-list-item {display: flex; flex-direction: row; align-items: center; justify-content: flex-start;}', '.list {width: 100%; height: 100%; background-color: rgb(244, 244, 242); border-radius: 5px;}', '.list {display: flex; box-sizing: border-box; padding: 5px;}', '.list.vertical-orientation {flex-direction: column;}', @@ -116,13 +137,17 @@ import { DragNDropValueObject } from 'common/models/elements/label-interfaces'; '.error {padding: 3px; border: 2px solid #f44336 !important;}', '.list-item:active {cursor: grabbing;}', '.list-item {border-radius: 5px;}', - ':not(.cloze-context) .list-item.text-list-item {padding: 10px;}', - '.cloze-context .list-item.text-list-item {padding: 0 5px;}', + ':not(.cloze-context) .list-item.text-list-item:not(.audio-list-item) .text-padding {padding: 10px;}', + ':not(.cloze-context) .list-item.text-list-item.audio-list-item .text-padding {padding: 10px 10px 10px 5px;}', + '.cloze-context .list-item.text-list-item .text-padding {padding: 0 5px;}', '.cloze-context.only-one-item .list-item {height: 100%; display: flex; align-items: center; justify-content: center;}', '.image-list-item {align-self: flex-start;}', '.hide-list-item {display: none !important; transform: unset !important;}', '.cdk-drag-preview {border-radius: 5px; box-shadow: 2px 2px 5px black;}', - '.cdk-drag-preview.text-preview {padding: 10px; box-sizing: border-box;}', + '.cdk-drag-preview.text-preview {box-sizing: border-box;}', + '.cdk-drag-preview.text-preview:not(.audio-list-item) .text-padding{padding: 10px;}', + '.cdk-drag-preview.text-preview.audio-list-item .text-padding{padding: 10px 10px 10px 5px;}', + '.cdk-drag-preview.text-preview .audio-button .mat-icon {margin-top: 5px; padding-left: 3px;}', '.cdk-drag-preview.cloze-context-preview {padding: 0 2px; display: flex; align-items: center; justify-content: center;}', '.cdk-drop-list-dragging .cdk-drag {transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);}', '.cdk-drag-placeholder {background-color: #ccc !important;}', diff --git a/projects/common/models/elements/label-interfaces.ts b/projects/common/models/elements/label-interfaces.ts index 0cd9c256c..5d9560dca 100644 --- a/projects/common/models/elements/label-interfaces.ts +++ b/projects/common/models/elements/label-interfaces.ts @@ -11,6 +11,7 @@ export interface DragNDropValueObject extends TextImageLabel { id: string; originListID: string; originListIndex: number; + audioSrc: string | null; } export type Label = TextLabel | TextImageLabel | DragNDropValueObject; diff --git a/projects/common/services/sanitization.service.ts b/projects/common/services/sanitization.service.ts index 52807533f..d7948fd55 100644 --- a/projects/common/services/sanitization.service.ts +++ b/projects/common/services/sanitization.service.ts @@ -381,6 +381,7 @@ export class SanitizationService { id: 'id_placeholder', text: option, imgSrc: null, + audioSrc: null, imgPosition: 'above', originListID: newElement.id as string, originListIndex: index @@ -396,6 +397,7 @@ export class SanitizationService { id: 'id_placeholder', text: value, imgSrc: null, + audioSrc: null, imgPosition: 'above', originListID: newElement.id as string, originListIndex: index diff --git a/projects/editor/src/app/components/dialogs/drop-list-option-edit-dialog.component.ts b/projects/editor/src/app/components/dialogs/drop-list-option-edit-dialog.component.ts index ac62db753..8efd7c3cb 100644 --- a/projects/editor/src/app/components/dialogs/drop-list-option-edit-dialog.component.ts +++ b/projects/editor/src/app/components/dialogs/drop-list-option-edit-dialog.component.ts @@ -1,23 +1,31 @@ import { Component, Inject } from '@angular/core'; import { MAT_DIALOG_DATA } from '@angular/material/dialog'; import { FileService } from 'common/services/file.service'; - - import { DragNDropValueObject } from 'common/models/elements/label-interfaces'; @Component({ selector: 'aspect-drop-list-option-edit-dialog', template: ` - <mat-dialog-content class="fx-column-start-stretch"> + <mat-dialog-content class="fx-column-start-stretch fx-gap-20"> <mat-form-field> <mat-label>{{'text' | translate }}</mat-label> <input #textField matInput type="text" [value]="data.value.text"> </mat-form-field> - <button mat-raised-button (click)="loadImage()">{{ 'loadImage' | translate }}</button> - <button mat-raised-button (click)="imgSrc = null">{{ 'removeImage' | translate }}</button> - <img [src]="imgSrc" - [style.object-fit]="'scale-down'" - [width]="200"> + <div *ngIf="!audioSrc" class="fx-column-start-stretch fx-gap-3"> + <button mat-raised-button (click)="loadImage()">{{ 'loadImage' | translate }}</button> + <button mat-raised-button (click)="imgSrc = null">{{ 'removeImage' | translate }}</button> + <img [src]="imgSrc" + [style.object-fit]="'scale-down'" + [width]="200"> + </div> + <div *ngIf="!imgSrc" class="fx-column-start-stretch fx-gap-3"> + <button mat-raised-button (click)="loadAudio()"> + {{(audioSrc ? 'changeAudio' : 'loadAudio') | translate }} + </button> + <button mat-raised-button (click)="audioSrc = null"> + {{ 'removeAudio' | translate }} + </button> + </div> <mat-form-field> <mat-label>{{'id' | translate }}</mat-label> <input #idField matInput type="text" [value]="data.value.id"> @@ -27,6 +35,7 @@ import { DragNDropValueObject } from 'common/models/elements/label-interfaces'; <button mat-button [mat-dialog-close]="{ text: textField.value, imgSrc: imgSrc, + audioSrc: audioSrc, id: idField.value }"> {{'save' | translate }} @@ -35,6 +44,12 @@ import { DragNDropValueObject } from 'common/models/elements/label-interfaces'; </mat-dialog-actions> `, styles: [` + .fx-gap-3 { + gap: 3px; + } + .fx-gap-20 { + gap: 20px; + } .fx-column-start-stretch { box-sizing: border-box; display: flex; @@ -47,8 +62,13 @@ import { DragNDropValueObject } from 'common/models/elements/label-interfaces'; export class DropListOptionEditDialogComponent { constructor(@Inject(MAT_DIALOG_DATA) public data: { value: DragNDropValueObject }) { } imgSrc: string | null = this.data.value.imgSrc; + audioSrc: string | null = this.data.value.audioSrc; async loadImage(): Promise<void> { this.imgSrc = await FileService.loadImage(); } + + async loadAudio() { + this.audioSrc = await FileService.loadAudio(); + } } diff --git a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/drop-list-properties.component.ts b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/drop-list-properties.component.ts index 0812db203..88c17113b 100644 --- a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/drop-list-properties.component.ts +++ b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/drop-list-properties.component.ts @@ -7,11 +7,11 @@ import { moveItemInArray } from '@angular/cdk/drag-drop'; import { MessageService } from 'common/services/message.service'; import { CombinedProperties } from 'editor/src/app/components/properties-panel/element-properties-panel.component'; import { IDService } from 'editor/src/app/services/id.service'; +import { DragNDropValueObject, TextImageLabel } from 'common/models/elements/label-interfaces'; import { UnitService } from '../../../../services/unit.service'; import { SelectionService } from '../../../../services/selection.service'; import { DialogService } from '../../../../services/dialog.service'; -import { DragNDropValueObject, TextImageLabel } from 'common/models/elements/label-interfaces'; @Component({ selector: 'aspect-drop-list-properties', @@ -141,6 +141,7 @@ export class DropListPropertiesComponent { { text: value, imgSrc: null, + audioSrc: null, imgPosition: 'above', id: this.unitService.getNewValueID(), originListID: 'id_placeholder', diff --git a/projects/editor/src/assets/i18n/de.json b/projects/editor/src/assets/i18n/de.json index 7c3b9c21d..9342ed51e 100644 --- a/projects/editor/src/assets/i18n/de.json +++ b/projects/editor/src/assets/i18n/de.json @@ -22,6 +22,9 @@ "text": "Text", "loadImage": "Bild laden", "removeImage": "Bild entfernen", + "loadAudio": "Audio laden", + "changeAudio": "Audio ändern", + "removeAudio": "Audio entfernen", "above": "oberhalb", "below": "unterhalb", "preset": "Vorbelegung", diff --git a/projects/player/src/app/services/element-model-element-code-mapping.service.spec.ts b/projects/player/src/app/services/element-model-element-code-mapping.service.spec.ts index 168a3cafc..569f39cb2 100644 --- a/projects/player/src/app/services/element-model-element-code-mapping.service.spec.ts +++ b/projects/player/src/app/services/element-model-element-code-mapping.service.spec.ts @@ -29,8 +29,8 @@ import { RadioButtonGroupComplexElement } from 'common/models/elements/input-ele import { LikertRowElement } from 'common/models/elements/compound-elements/likert/likert-row'; import { ToggleButtonElement } from 'common/models/elements/compound-elements/cloze/cloze-child-elements/toggle-button'; import { Hotspot, HotspotImageElement } from 'common/models/elements/input-elements/hotspot-image'; -import { ElementModelElementCodeMappingService } from './element-model-element-code-mapping.service'; import { DragNDropValueObject } from 'common/models/elements/label-interfaces'; +import { ElementModelElementCodeMappingService } from './element-model-element-code-mapping.service'; describe('ElementModelElementCodeMappingService', () => { let service: ElementModelElementCodeMappingService; @@ -235,6 +235,7 @@ describe('ElementModelElementCodeMappingService', () => { text: 'a', id: 'value_1', imgSrc: null, + audioSrc: null, imgPosition: 'above', originListID: 'id', originListIndex: 0 @@ -243,6 +244,7 @@ describe('ElementModelElementCodeMappingService', () => { text: 'b', id: 'value_2', imgSrc: null, + audioSrc: null, imgPosition: 'above', originListID: 'id', originListIndex: 0 @@ -251,6 +253,7 @@ describe('ElementModelElementCodeMappingService', () => { text: 'c', id: 'value_3', imgSrc: null, + audioSrc: null, imgPosition: 'above', originListID: 'id', originListIndex: 0 @@ -259,6 +262,7 @@ describe('ElementModelElementCodeMappingService', () => { text: 'd', id: 'value_4', imgSrc: null, + audioSrc: null, imgPosition: 'above', originListID: 'id', originListIndex: 0 @@ -267,6 +271,7 @@ describe('ElementModelElementCodeMappingService', () => { text: 'e', id: 'value_5', imgSrc: null, + audioSrc: null, imgPosition: 'above', originListID: 'id', originListIndex: 0 @@ -278,6 +283,7 @@ describe('ElementModelElementCodeMappingService', () => { text: 'e', id: 'value_5', imgSrc: null, + audioSrc: null, imgPosition: 'above', originListID: 'id', originListIndex: 0 @@ -293,6 +299,7 @@ describe('ElementModelElementCodeMappingService', () => { text: 'a', id: 'value_1', imgSrc: null, + audioSrc: null, imgPosition: 'above', originListID: 'id', originListIndex: 0 @@ -301,6 +308,7 @@ describe('ElementModelElementCodeMappingService', () => { text: 'b', id: 'value_2', imgSrc: null, + audioSrc: null, imgPosition: 'above', originListID: 'id', originListIndex: 1 @@ -309,6 +317,7 @@ describe('ElementModelElementCodeMappingService', () => { text: 'c', id: 'value_3', imgSrc: null, + audioSrc: null, imgPosition: 'above', originListID: 'id', originListIndex: 2 @@ -317,6 +326,7 @@ describe('ElementModelElementCodeMappingService', () => { text: 'd', id: 'value_4', imgSrc: null, + audioSrc: null, imgPosition: 'above', originListID: 'id', originListIndex: 3 @@ -325,6 +335,7 @@ describe('ElementModelElementCodeMappingService', () => { text: 'e', id: 'value_5', imgSrc: null, + audioSrc: null, imgPosition: 'above', originListID: 'id', originListIndex: 4 @@ -336,6 +347,7 @@ describe('ElementModelElementCodeMappingService', () => { text: 'e', id: 'value_5', imgSrc: null, + audioSrc: null, imgPosition: 'above', originListID: 'id', originListIndex: 4 -- GitLab