diff --git a/projects/common/element-component.directive.ts b/projects/common/element-component.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..1d723608453c62578d43f1f4977511563f2ad5dd --- /dev/null +++ b/projects/common/element-component.directive.ts @@ -0,0 +1,9 @@ +import { + Directive +} from '@angular/core'; +import { UnitUIElement } from './unit'; + +@Directive() +export abstract class ElementComponent { + abstract elementModel: UnitUIElement; +} diff --git a/projects/common/element-components/audio.component.ts b/projects/common/element-components/audio.component.ts index 40673b54ad5286167e11b6395aff75c011d6e399..fa1127a920b95c5c84392ca1af2050940b488cbd 100644 --- a/projects/common/element-components/audio.component.ts +++ b/projects/common/element-components/audio.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { AudioElement } from '../unit'; +import { ElementComponent } from '../element-component.directive'; @Component({ selector: 'app-audio', @@ -12,6 +13,6 @@ import { AudioElement } from '../unit'; 'div {display: inline-block; border: 5px solid; padding: 12px 9px 9px 9px;}' ] }) -export class AudioComponent { +export class AudioComponent extends ElementComponent { elementModel!: AudioElement; } diff --git a/projects/common/element-components/button.component.ts b/projects/common/element-components/button.component.ts index 197fdd0ad6838598886475d60dea0cf4af20d025..b02d161c30243eae711da91c734ff69bf962212a 100644 --- a/projects/common/element-components/button.component.ts +++ b/projects/common/element-components/button.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { ElementComponent } from '../element-component.directive'; import { ButtonElement } from '../unit'; @Component({ @@ -18,6 +19,6 @@ import { ButtonElement } from '../unit'; </button> ` }) -export class ButtonComponent { +export class ButtonComponent extends ElementComponent { elementModel!: ButtonElement; } diff --git a/projects/common/element-components/checkbox.component.ts b/projects/common/element-components/checkbox.component.ts index 38aee9c55ce68a6f5542d45a1cec1f0648b60618..4354ce87689d72870bef9948cc1f59c29630b3a9 100644 --- a/projects/common/element-components/checkbox.component.ts +++ b/projects/common/element-components/checkbox.component.ts @@ -5,23 +5,21 @@ import { FormElementComponent } from '../form-element-component.directive'; @Component({ selector: 'app-checkbox', template: ` - <mat-checkbox #checkbox class="example-margin" - [formControl]="formElementControl" - [style.width.px]="elementModel.width" - [style.height.px]="elementModel.height" - [style.background-color]="elementModel.backgroundColor" - [style.color]="elementModel.fontColor" - [style.font-family]="elementModel.font" - [style.font-size.px]="elementModel.fontSize" - [style.font-weight]="elementModel.bold ? 'bold' : ''" - [style.font-style]="elementModel.italic ? 'italic' : ''" - [style.text-decoration]="elementModel.underline ? 'underline' : ''"> - {{elementModel.label}} - </mat-checkbox> + <mat-checkbox #checkbox class="example-margin" + [formControl]="elementFormControl" + [style.width.px]="elementModel.width" + [style.height.px]="elementModel.height" + [style.background-color]="elementModel.backgroundColor" + [style.color]="elementModel.fontColor" + [style.font-family]="elementModel.font" + [style.font-size.px]="elementModel.fontSize" + [style.font-weight]="elementModel.bold ? 'bold' : ''" + [style.font-style]="elementModel.italic ? 'italic' : ''" + [style.text-decoration]="elementModel.underline ? 'underline' : ''"> + {{elementModel.label}} + </mat-checkbox> ` }) export class CheckboxComponent extends FormElementComponent { elementModel!: CheckboxElement; - // TODO: get from elementModel - defaultValue: boolean = true; } diff --git a/projects/common/element-components/compound-components/correction.component.ts b/projects/common/element-components/compound-components/correction.component.ts index 390d7c5c687d292ea98549c2c0f39fbd1346b539..743d763237b8a0c12f66e33d02efd91d28fd8b44 100644 --- a/projects/common/element-components/compound-components/correction.component.ts +++ b/projects/common/element-components/compound-components/correction.component.ts @@ -16,7 +16,7 @@ import { FormElementComponent } from '../../form-element-component.directive'; fxLayout="column"> <mat-form-field> <input matInput type="text" - [formControl]="formElementControl"> + [formControl]="elementFormControl"> </mat-form-field> <div> {{word}} diff --git a/projects/common/element-components/dropdown.component.ts b/projects/common/element-components/dropdown.component.ts index f6f19b4e56bc9918700c3ef8a5cbff3d09977284..349db5e9ecd12b3cc112fe9dac428b74b1b6902c 100644 --- a/projects/common/element-components/dropdown.component.ts +++ b/projects/common/element-components/dropdown.component.ts @@ -17,7 +17,7 @@ import { FormElementComponent } from '../form-element-component.directive'; [style.text-decoration]="elementModel.underline ? 'underline' : ''"> {{$any(elementModel).label}} </mat-label> - <mat-select [formControl]="formElementControl"> + <mat-select [formControl]="elementFormControl"> <mat-option *ngFor="let option of elementModel.options" [value]="option"> {{option}} </mat-option> diff --git a/projects/common/element-components/image.component.ts b/projects/common/element-components/image.component.ts index ead36c4709019549e20b7df0a104967de68a86d7..76e594df1ddeb181acfa60fb81a77582fbd0cf76 100644 --- a/projects/common/element-components/image.component.ts +++ b/projects/common/element-components/image.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { ImageElement } from '../unit'; +import { ElementComponent } from '../element-component.directive'; @Component({ selector: 'app-image', @@ -7,6 +8,6 @@ import { ImageElement } from '../unit'; <img src="{{elementModel.src}}" alt="Image Placeholder"> ` }) -export class ImageComponent { +export class ImageComponent extends ElementComponent { elementModel!: ImageElement; } diff --git a/projects/common/element-components/radio-button-group.component.ts b/projects/common/element-components/radio-button-group.component.ts index ca9be5510b9d6f6ca4ddac274d1419b1d10c6705..43c9ef789a3aa80a53d701d1f53c5da0b78a609d 100644 --- a/projects/common/element-components/radio-button-group.component.ts +++ b/projects/common/element-components/radio-button-group.component.ts @@ -16,7 +16,7 @@ import { FormElementComponent } from '../form-element-component.directive'; [style.text-decoration]="elementModel.underline ? 'underline' : ''"> <label id="radio-group-label">{{elementModel.label}}</label> <mat-radio-group aria-labelledby="radio-group-label" fxLayout="{{elementModel.alignment}}" - [formControl]="formElementControl"> + [formControl]="elementFormControl"> <mat-radio-button *ngFor="let option of elementModel.options" [value]="option"> {{option}} </mat-radio-button> diff --git a/projects/common/element-components/text-area.component.ts b/projects/common/element-components/text-area.component.ts index 99c030b62e33686b3a4a7f91d70396d41d7d8e31..95b7b9e54e2f0b35d86834f3abdd501088b6075a 100644 --- a/projects/common/element-components/text-area.component.ts +++ b/projects/common/element-components/text-area.component.ts @@ -5,21 +5,20 @@ import { FormElementComponent } from '../form-element-component.directive'; @Component({ selector: 'app-text-area', template: ` - <mat-form-field [style.width.px]="elementModel.width" - [style.height.px]="elementModel.height" - [style.background-color]="elementModel.backgroundColor" - [style.color]="elementModel.fontColor" - [style.font-family]="elementModel.font" - [style.font-size.px]="elementModel.fontSize" - [style.font-weight]="elementModel.bold ? 'bold' : ''" - [style.font-style]="elementModel.italic ? 'italic' : ''" - [style.text-decoration]="elementModel.underline ? 'underline' : ''"> - <textarea matInput [formControl]="formElementControl" + <mat-form-field [style.width.px]="elementModel.width" + [style.height.px]="elementModel.height" + [style.background-color]="elementModel.backgroundColor" + [style.color]="elementModel.fontColor" + [style.font-family]="elementModel.font" + [style.font-size.px]="elementModel.fontSize" + [style.font-weight]="elementModel.bold ? 'bold' : ''" + [style.font-style]="elementModel.italic ? 'italic' : ''" + [style.text-decoration]="elementModel.underline ? 'underline' : ''"> + <textarea matInput [formControl]="elementFormControl" placeholder="{{elementModel.label}}" - [(ngModel)]="elementModel.text" [style.resize]="elementModel.resizeEnabled ? 'both' : 'none'"> </textarea> - </mat-form-field> + </mat-form-field> ` }) export class TextAreaComponent extends FormElementComponent { diff --git a/projects/common/element-components/text-field.component.ts b/projects/common/element-components/text-field.component.ts index dafcb4456b90a358d037a39c6e5ac2551d3cb71b..3d7e1116f8420a85d52077d0611d8b6b950d7074 100644 --- a/projects/common/element-components/text-field.component.ts +++ b/projects/common/element-components/text-field.component.ts @@ -14,9 +14,8 @@ import { FormElementComponent } from '../form-element-component.directive'; [style.font-weight]="elementModel.bold ? 'bold' : ''" [style.font-style]="elementModel.italic ? 'italic' : ''" [style.text-decoration]="elementModel.underline ? 'underline' : ''"> - <input matInput [formControl]="formElementControl" - placeholder="{{elementModel.label}}" - [(ngModel)]="elementModel.text"> + <input matInput [formControl]="elementFormControl" + placeholder="{{elementModel.label}}"> </mat-form-field> ` }) diff --git a/projects/common/element-components/text.component.ts b/projects/common/element-components/text.component.ts index 6f292e034632931cfe40e3125f9b5756fa8b2626..d39283edd4ba781895c76d3412ea829b00661543 100644 --- a/projects/common/element-components/text.component.ts +++ b/projects/common/element-components/text.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { TextElement } from '../unit'; +import { ElementComponent } from '../element-component.directive'; @Component({ selector: 'app-text', @@ -18,6 +19,6 @@ import { TextElement } from '../unit'; </div> ` }) -export class TextComponent { +export class TextComponent extends ElementComponent { elementModel!: TextElement; } diff --git a/projects/common/element-components/video.component.ts b/projects/common/element-components/video.component.ts index 4d41b60a5deed78802a49cb7e0de13870fa9b001..f9ca43d7e3274cace5ece7256ed7042a72164231 100644 --- a/projects/common/element-components/video.component.ts +++ b/projects/common/element-components/video.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { VideoElement } from '../unit'; +import { ElementComponent } from '../element-component.directive'; @Component({ selector: 'app-video', @@ -12,6 +13,6 @@ import { VideoElement } from '../unit'; 'div {display: inline-block;border: 5px solid; padding: 12px 9px 9px 9px}' ] }) -export class VideoComponent { +export class VideoComponent extends ElementComponent { elementModel!: VideoElement; } diff --git a/projects/common/form-element-component.directive.ts b/projects/common/form-element-component.directive.ts index 4b38ebc9d2576c3545420a30fcfd4701f6b9bdb2..d6c661d0a130009c7fd526d211225a03c5ff8aeb 100644 --- a/projects/common/form-element-component.directive.ts +++ b/projects/common/form-element-component.directive.ts @@ -1,30 +1,41 @@ -import { Directive, OnInit } from '@angular/core'; +import { + Directive, EventEmitter, OnDestroy, OnInit, Output +} from '@angular/core'; import { FormControl, FormGroup, ValidatorFn } from '@angular/forms'; -import { pairwise } from 'rxjs/operators'; -import { UnitUIElement } from './unit'; +import { Subject } from 'rxjs'; +import { pairwise, startWith, takeUntil } from 'rxjs/operators'; import { FormService } from './form.service'; +import { ValueChangeElement } from './form'; +import { ElementComponent } from './element-component.directive'; @Directive() -export abstract class FormElementComponent implements OnInit { - abstract elementModel: UnitUIElement; +export abstract class FormElementComponent extends ElementComponent implements OnInit, OnDestroy { + @Output() formValueChanged = new EventEmitter<ValueChangeElement>(); parentForm!: FormGroup; - defaultValue!: unknown; - formElementControl!: FormControl; + defaultValue!: string | number | boolean | undefined; + elementFormControl!: FormControl; + private ngUnsubscribe = new Subject<void>(); - constructor(private formService: FormService) {} + constructor(private formService: FormService) { + super(); + } ngOnInit(): void { - const formControl = new FormControl(this.defaultValue, this.getValidations()); + const formControl = new FormControl(this.elementModel.value, this.getValidations()); const id = this.elementModel.id; this.formService.registerFormControl({ id, formControl }); - this.formElementControl = this.getFormControl(id); - this.formElementControl.valueChanges - .pipe(pairwise()) - .subscribe( - ([prevValue, nextValue] : [unknown, unknown]) => this.onValueChange([prevValue, nextValue]) - ); + this.elementFormControl = this.getFormControl(id); + this.elementFormControl.valueChanges + .pipe( + takeUntil(this.ngUnsubscribe), + startWith([this.elementModel.value, this.elementModel.value]), + pairwise() + ) + .subscribe(([prevValue, nextValue] : [string | number | boolean | undefined, string | number | boolean]) => { + this.formValueChanged.emit({ id: this.elementModel.id, values: [prevValue, nextValue] }); + }); } // TODO: get from elementModel examples, example: [Validators.requiredTrue, Validators.required] @@ -35,8 +46,12 @@ export abstract class FormElementComponent implements OnInit { return (this.parentForm) ? this.parentForm.controls[id] as FormControl : new FormControl(); } - private onValueChange(values: [unknown, unknown]): void { - const element = this.elementModel.id; - this.formService.changeElementValue({ id: element, values }); + updateFormValue(newValue: string | number | boolean): void { + this.elementFormControl?.setValue(newValue, { emitEvent: false }); + } + + ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); } } diff --git a/projects/common/form.ts b/projects/common/form.ts index 0648b3bf204be02f20bbda2adac14ee2653c7365..31a950618e2b6ffb50add3719a48924ae0c7a614 100644 --- a/projects/common/form.ts +++ b/projects/common/form.ts @@ -2,7 +2,7 @@ import { FormControl } from '@angular/forms'; export interface ValueChangeElement { id: string; - values: [unknown, unknown]; + values: [string | number | boolean | undefined, string | number | boolean]; } export interface FormControlElement { diff --git a/projects/common/unit.ts b/projects/common/unit.ts index 8795ee35183115c70084c293ca631bbd4f32fb80..7c958e92f55320f0ee33e5ebd1d33102e7ebf51e 100644 --- a/projects/common/unit.ts +++ b/projects/common/unit.ts @@ -18,7 +18,7 @@ export interface UnitPageSection { } export interface UnitUIElement { - [index: string]: string | number | boolean | string[], + [index: string]: string | number | boolean | string[] | undefined, type: string; // TODO maybe use enum or manual enumeration, because possible values are known id: string; xPosition: number; @@ -56,28 +56,31 @@ export interface ButtonElement extends TextUIElement, SurfaceUIElement { export interface TextFieldElement extends TextUIElement, SurfaceUIElement { label: string; - text: string; + value: string; } export interface TextAreaElement extends TextUIElement, SurfaceUIElement { label: string; - text: string; + value: string; resizeEnabled: boolean; } export interface CheckboxElement extends TextUIElement, SurfaceUIElement { label: string; + value: boolean | undefined; } export interface DropdownElement extends TextUIElement, SurfaceUIElement { label: string; options: string[]; + value: number | undefined; } export interface RadioButtonGroupElement extends UnitUIElement, SurfaceUIElement { label: string; options: string[]; alignment: 'row' | 'column'; + value: number | undefined; } export interface ImageElement extends UnitUIElement { diff --git a/projects/editor/src/app/app.module.ts b/projects/editor/src/app/app.module.ts index 91008b27603c0e19b86f1ea8de663528f20771a0..7e7e2ef1c2a9677c23cb3b267abbc162892aad32 100644 --- a/projects/editor/src/app/app.module.ts +++ b/projects/editor/src/app/app.module.ts @@ -5,6 +5,7 @@ import { CommonModule } from '@angular/common'; import { createCustomElement } from '@angular/elements'; import { MatDialogModule } from '@angular/material/dialog'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { AppComponent } from './app.component'; import { ToolbarComponent } from './components/toolbar/toolbar.component'; @@ -42,7 +43,8 @@ import { ConfirmationDialog, MultilineTextEditDialog, TextEditDialog } from './d BrowserAnimationsModule, CommonModule, SharedModule, - MatDialogModule + MatDialogModule, + MatButtonToggleModule ], providers: [] }) diff --git a/projects/editor/src/app/components/unit-view/page-view/canvas/canvas-drag-overlay.component.ts b/projects/editor/src/app/components/unit-view/page-view/canvas/canvas-drag-overlay.component.ts index c2dbe9e1d84ae08b9077a5faf3f8736e9189447b..0a578035308d057693afed945011eff3e2ba2230 100644 --- a/projects/editor/src/app/components/unit-view/page-view/canvas/canvas-drag-overlay.component.ts +++ b/projects/editor/src/app/components/unit-view/page-view/canvas/canvas-drag-overlay.component.ts @@ -1,10 +1,17 @@ import { - Component, OnInit, Input, Output, EventEmitter, ComponentFactoryResolver, ViewChild, ViewContainerRef + Component, OnInit, OnDestroy, Input, Output, + EventEmitter, + ComponentFactoryResolver, ViewChild, ViewContainerRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; import { CdkDragMove } from '@angular/cdk/drag-drop'; import { UnitUIElement } from '../../../../../../../common/unit'; import * as ComponentUtils from '../../../../../../../common/component-utils'; import { UnitService } from '../../../../unit.service'; +import { ValueChangeElement } from '../../../../../../../common/form'; +import { ElementComponent } from '../../../../../../../common/element-component.directive'; +import { FormElementComponent } from '../../../../../../../common/form-element-component.directive'; @Component({ selector: 'app-canvas-drag-overlay', @@ -46,7 +53,7 @@ import { UnitService } from '../../../../unit.service'; '.test.cdk-drop-list-dragging {cursor: nwse-resize}' ] }) -export class CanvasDragOverlayComponent implements OnInit { +export class CanvasDragOverlayComponent implements OnInit, OnDestroy { @Input() element!: UnitUIElement; @Output() elementSelected = new EventEmitter<{ componentElement: CanvasDragOverlayComponent, @@ -57,14 +64,31 @@ export class CanvasDragOverlayComponent implements OnInit { selected = false; private oldX: number = 0; private oldY: number = 0; + private childComponent!: ElementComponent; + private ngUnsubscribe = new Subject<void>(); constructor(private unitService: UnitService, private componentFactoryResolver: ComponentFactoryResolver) { } ngOnInit(): void { const componentFactory = ComponentUtils.getComponentFactory(this.element.type, this.componentFactoryResolver); - const childComponent = this.elementContainer.createComponent(componentFactory); - childComponent.instance.elementModel = this.element; + this.childComponent = this.elementContainer.createComponent(componentFactory).instance; + this.childComponent.elementModel = this.element; + if (this.childComponent instanceof FormElementComponent) { + this.childComponent.formValueChanged + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe((changeElement: ValueChangeElement) => { + this.unitService.updateSelectedElementProperty('value', changeElement.values[1]); + }); + + this.unitService.selectedElements + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe(() => { + (this.childComponent as FormElementComponent).updateFormValue( + this.element.value as string | number | boolean + ); + }); + } } setSelected(newValue: boolean): void { @@ -96,4 +120,9 @@ export class CanvasDragOverlayComponent implements OnInit { openEditDialog(): void { this.unitService.showDefaultEditDialog(this.element); } + + ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } } diff --git a/projects/editor/src/app/components/unit-view/page-view/properties/properties.component.html b/projects/editor/src/app/components/unit-view/page-view/properties/properties.component.html index 6742000f5c0a7d188273085f93e606c17a4a4009..6bd4e6f1fbca716c8cb158413a3fdf9315098404 100644 --- a/projects/editor/src/app/components/unit-view/page-view/properties/properties.component.html +++ b/projects/editor/src/app/components/unit-view/page-view/properties/properties.component.html @@ -27,9 +27,35 @@ </textarea> </mat-form-field> + <mat-form-field *ngIf="combinedProperties.type === 'text-field'"> + <mat-label>Vorbelegung</mat-label> + <input matInput type="text" + [value]="combinedProperties.value" + (input)="updateModel('value', $any($event.target).value)"> + </mat-form-field> + <section *ngIf="combinedProperties.type === 'checkbox'"> + Vorbelegung + <mat-button-toggle-group (change)="transformToBoolAndUpdateModel('value', $event.value)"> + <mat-button-toggle value="true">wahr</mat-button-toggle> + <mat-button-toggle value="false">falsch</mat-button-toggle> + <mat-button-toggle value="undefined">undefiniert</mat-button-toggle> + </mat-button-toggle-group> + </section> + <mat-form-field *ngIf="combinedProperties.type === 'dropdown' || combinedProperties.type === 'radio'" + appearance="fill"> + <mat-label>Vorbelegung</mat-label> + <mat-select (selectionChange)="updateModel('value', $event.value, $event)"> + <mat-option>undefiniert</mat-option> + <mat-option *ngFor="let option of $any(combinedProperties).options; let i = index" [value]="i"> + {{option}} + </mat-option> + </mat-select> + </mat-form-field> + <mat-form-field disabled="true" *ngIf="combinedProperties.hasOwnProperty('options')"> <div *ngIf="combinedProperties.options !== undefined"> <mat-label>Optionen</mat-label> +<!-- TODO reorder via droplist--> <mat-list *ngFor="let option of $any(combinedProperties.options)"> <mat-list-item>{{option}}</mat-list-item> <mat-divider></mat-divider> diff --git a/projects/editor/src/app/components/unit-view/page-view/properties/properties.component.ts b/projects/editor/src/app/components/unit-view/page-view/properties/properties.component.ts index 217f9706077d249b3869a7b4b4dcbfbe45bac6c6..126edc8accc28302ed2735865f7737e6235cb214 100644 --- a/projects/editor/src/app/components/unit-view/page-view/properties/properties.component.ts +++ b/projects/editor/src/app/components/unit-view/page-view/properties/properties.component.ts @@ -52,7 +52,7 @@ export class PropertiesComponent { // event as optional parameter in case the input is invalid and the old value needs // to be restored. This is for now only relevant for IDs. Might need rework for other properties. - updateModel(property: string, value: string | number | boolean, event?: Event): void { + updateModel(property: string, value: string | number | boolean | undefined, event?: any): void { if (!this.unitService.updateSelectedElementProperty(property, value)) { if (event) { (event.target as HTMLInputElement).value = <string> this.combinedProperties[property]; @@ -60,6 +60,24 @@ export class PropertiesComponent { } } + /* button group always handles values as string and since we also want to handle undefined + we need to transform the value before passing it on. */ + transformToBoolAndUpdateModel(property: string, value: string): void { + switch (value) { + case 'true': { + this.updateModel(property, true); + break; + } + case 'false': { + this.updateModel(property, false); + break; + } + default: { + this.updateModel(property, undefined); + } + } + } + deleteElement(): void { this.unitService.deleteSelectedElements(); } diff --git a/projects/editor/src/app/model/UnitFactory.ts b/projects/editor/src/app/model/UnitFactory.ts index af3f41c272ebf2babdc3b62e9ff1eaf33bdb69ff..3fb1399e5008c2fa5b2eb3d6d9468e9a7d0800ec 100644 --- a/projects/editor/src/app/model/UnitFactory.ts +++ b/projects/editor/src/app/model/UnitFactory.ts @@ -81,7 +81,7 @@ export function createButtonElement(): ButtonElement { export function createTextfieldElement(): TextFieldElement { return { label: 'Example Label', - text: '', + value: '', ...createTextUIElement('text-field'), ...createSurfaceUIElement('text-field') }; @@ -90,7 +90,7 @@ export function createTextfieldElement(): TextFieldElement { export function createTextareaElement(): TextFieldElement { return { label: 'Example Label', - text: '', + value: '', resizeEnabled: false, ...createTextUIElement('text-area'), ...createSurfaceUIElement('text-area'), @@ -101,6 +101,7 @@ export function createTextareaElement(): TextFieldElement { export function createCheckboxElement(): CheckboxElement { return { label: 'Label Checkbox', + value: undefined, ...createTextUIElement('checkbox'), ...createSurfaceUIElement('checkbox') }; @@ -110,6 +111,7 @@ export function createDropdownElement(): DropdownElement { return { label: 'Label Dropdown', options: [], + value: undefined, ...createTextUIElement('dropdown'), ...createSurfaceUIElement('dropdown') }; @@ -120,6 +122,7 @@ export function createRadioButtonGroupElement(): RadioButtonGroupElement { label: 'Label Optionsfeld', options: [], alignment: 'row', + value: undefined, ...createTextUIElement('radio'), ...createSurfaceUIElement('radio'), height: 75 diff --git a/projects/editor/src/app/unit.service.ts b/projects/editor/src/app/unit.service.ts index 93ed941b8e393ef7a258881240c1431fff0b049d..40c1207422d3be947884669b0441834e1a1c5a3a 100644 --- a/projects/editor/src/app/unit.service.ts +++ b/projects/editor/src/app/unit.service.ts @@ -197,10 +197,10 @@ export class UnitService { this._selectedPageSectionIndex.next(newIndex); } - updateSelectedElementProperty(property: string, value: string | number | boolean): boolean { + updateSelectedElementProperty(property: string, value: string | number | boolean | undefined): boolean { // eslint-disable-next-line no-restricted-syntax for (const element of this._selectedElements.value) { - if (['string', 'number', 'boolean'].indexOf(typeof element[property]) > -1) { + if (['string', 'number', 'boolean', 'undefined'].indexOf(typeof element[property]) > -1) { if (property === 'id') { if (!this.idService.isIdAvailable((value as string))) { // prohibit existing IDs this.messageService.showError('ID ist bereits vergeben'); @@ -212,6 +212,8 @@ export class UnitService { element[property] = value; } else if (Array.isArray(element[property])) { (element[property] as string[]).push(value as string); + } else { + console.error('ElementProperty not found!', element[property]); } } this._selectedElements.next(this._selectedElements.value); // hack to notify properties panel about change @@ -244,18 +246,38 @@ export class UnitService { } showDefaultEditDialog(element: UnitUIElement): void { - if (Object.prototype.hasOwnProperty.call(element, 'label')) { - this.dialogService.showTextEditDialog((element as any).label).subscribe((result: string) => { - if (result) { - this.updateSelectedElementProperty('label', result); - } - }); - } else if (Object.prototype.hasOwnProperty.call(element, 'text')) { - this.dialogService.showTextEditDialog((element as any).text, true).subscribe((result: string) => { - if (result) { - this.updateSelectedElementProperty('text', result); - } - }); + switch (element.type) { + case 'button': + case 'checkbox': + case 'dropdown': + case 'radio': + this.dialogService.showTextEditDialog((element as any).label, false).subscribe((result: string) => { + if (result) { + this.updateSelectedElementProperty('label', result); + } + }); + break; + case 'text': + this.dialogService.showTextEditDialog((element as any).text, true).subscribe((result: string) => { + if (result) { + this.updateSelectedElementProperty('text', result); + } + }); + break; + case 'text-field': + this.dialogService.showTextEditDialog((element as any).value).subscribe((result: string) => { + if (result) { + this.updateSelectedElementProperty('value', result); + } + }); + break; + case 'text-area': + this.dialogService.showTextEditDialog((element as any).value, true).subscribe((result: string) => { + if (result) { + this.updateSelectedElementProperty('value', result); + } + }); + // no default } } } diff --git a/projects/player/src/app/components/element-overlay.component.ts b/projects/player/src/app/components/element-overlay.component.ts index 2e6bbba73d9696c0bee4fd3e55f18b87ba88dab5..d3482c4c1601837cca5a5195ec974fa3af48bf17 100644 --- a/projects/player/src/app/components/element-overlay.component.ts +++ b/projects/player/src/app/components/element-overlay.component.ts @@ -1,11 +1,17 @@ import { - Component, ComponentFactory, ComponentFactoryResolver, ComponentRef, Input, OnInit, ViewChild, ViewContainerRef + Component, OnInit, OnDestroy, Input, + ComponentFactory, ComponentFactoryResolver, ComponentRef, + ViewChild, ViewContainerRef } from '@angular/core'; import { FormGroup } from '@angular/forms'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; import { UnitUIElement } from '../../../../common/unit'; import * as ComponentUtils from '../../../../common/component-utils'; import { FormElementComponent } from '../../../../common/form-element-component.directive'; import { ValidationMessageComponent } from './validation-message.component'; +import { ValueChangeElement } from '../../../../common/form'; +import { FormService } from '../../../../common/form.service'; @Component({ selector: 'app-element-overlay', @@ -18,8 +24,7 @@ import { ValidationMessageComponent } from './validation-message.component'; </div> ` }) - -export class ElementOverlayComponent implements OnInit { +export class ElementOverlayComponent implements OnInit, OnDestroy { @Input() elementModel!: UnitUIElement; @ViewChild('elementComponentContainer', { read: ViewContainerRef, static: true }) private elementComponentContainer!: ViewContainerRef; @@ -28,11 +33,12 @@ export class ElementOverlayComponent implements OnInit { { read: ViewContainerRef, static: true }) private validationMessageComponentContainer!: ViewContainerRef; parentForm!: FormGroup; + private ngUnsubscribe = new Subject<void>(); - constructor(private componentFactoryResolver: ComponentFactoryResolver) { } + constructor(private formService: FormService, + private componentFactoryResolver: ComponentFactoryResolver) { } ngOnInit(): void { - // eslint-disable-next-line max-len const elementComponentFactory = ComponentUtils.getComponentFactory(this.elementModel.type, this.componentFactoryResolver); const elementComponent = this.elementComponentContainer.createComponent(elementComponentFactory).instance; @@ -40,6 +46,11 @@ export class ElementOverlayComponent implements OnInit { if (elementComponent instanceof FormElementComponent) { elementComponent.parentForm = this.parentForm; + elementComponent.formValueChanged + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe((changeElement: ValueChangeElement) => { + this.formService.changeElementValue(changeElement); + }); const validationMessageComponentFactory: ComponentFactory<ValidationMessageComponent> = this.componentFactoryResolver.resolveComponentFactory(ValidationMessageComponent); @@ -50,4 +61,9 @@ export class ElementOverlayComponent implements OnInit { validationMessageComponentRef.instance.elementModel = this.elementModel; } } + + ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } }