From f853737200d85af4de4f82acca1aafdb3f8d990f Mon Sep 17 00:00:00 2001 From: jojohoch <joachim.hoch@iqb.hu-berlin.de> Date: Wed, 25 May 2022 15:25:53 +0200 Subject: [PATCH] [editor] Add prototype implementation for schemer --- projects/common/models/elements/element.ts | 20 +++++++ .../elements/input-elements/checkbox.ts | 27 ++++++++- .../elements/input-elements/drop-list.ts | 36 +++++++++++- .../elements/input-elements/dropdown.ts | 24 +++++++- .../radio-button-group-complex.ts | 18 +++++- .../input-elements/radio-button-group.ts | 25 ++++++++- .../editor/src/app/services/unit.service.ts | 56 ++++++++++++++----- 7 files changed, 187 insertions(+), 19 deletions(-) diff --git a/projects/common/models/elements/element.ts b/projects/common/models/elements/element.ts index ee5be5565..998bd64ee 100644 --- a/projects/common/models/elements/element.ts +++ b/projects/common/models/elements/element.ts @@ -62,8 +62,11 @@ export abstract class InputElement extends UIElement { super(element); Object.assign(this, element); } + + //abstract getSchemerData(options: any): SchemerData; } + export abstract class CompoundElement extends UIElement { abstract getChildElements(): UIElement[]; } @@ -75,6 +78,23 @@ export abstract class PlayerElement extends UIElement { super(element); this.player = ElementFactory.initPlayerProps(element.player); } + + // abstract getSchemerData(options: any): SchemerData; +} + +export interface SchemerValue { + value: string; + label: string; +} + +export interface SchemerData { + id: string; + type: string; + format?: string; + multiple?: boolean; + nullable?: boolean; + values?: SchemerValue[]; + valuesComplete?: boolean; } export interface PositionedUIElement extends UIElement { diff --git a/projects/common/models/elements/input-elements/checkbox.ts b/projects/common/models/elements/input-elements/checkbox.ts index 90d03cf23..758f9dd7e 100644 --- a/projects/common/models/elements/input-elements/checkbox.ts +++ b/projects/common/models/elements/input-elements/checkbox.ts @@ -1,5 +1,11 @@ import { ElementFactory } from 'common/util/element.factory'; -import { BasicStyles, InputElement, PositionedUIElement, PositionProperties } from 'common/models/elements/element'; +import { + BasicStyles, + InputElement, + PositionedUIElement, + PositionProperties, + SchemerData, SchemerValue +} from 'common/models/elements/element'; import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { CheckboxComponent } from 'common/components/input-elements/checkbox.component'; @@ -14,6 +20,25 @@ export class CheckboxElement extends InputElement implements PositionedUIElement this.styling = ElementFactory.initStylingProps(element.styling); } + getSchemerData(options: any = undefined): SchemerData { + return { + id: this.id, + type: 'boolean', + format: '', + multiple: false, + nullable: false, + values: this.getSchemerValues(), + valuesComplete: true + }; + } + + private getSchemerValues(): SchemerValue[] { + return [ + { value: 'true', label: `Angekreuzt: ${this.label}` }, + { value: 'false', label: `Nicht Angekreuzt: ${this.label}` } + ]; + } + getComponentFactory(): Type<ElementComponent> { return CheckboxComponent; } diff --git a/projects/common/models/elements/input-elements/drop-list.ts b/projects/common/models/elements/input-elements/drop-list.ts index fad5cbccc..0b8927ba5 100644 --- a/projects/common/models/elements/input-elements/drop-list.ts +++ b/projects/common/models/elements/input-elements/drop-list.ts @@ -1,8 +1,17 @@ import { ElementFactory } from 'common/util/element.factory'; -import { BasicStyles, InputElement, PositionedUIElement, PositionProperties } from 'common/models/elements/element'; +import { + BasicStyles, DragNDropValueObject, + InputElement, + PositionedUIElement, + PositionProperties, + SchemerData, SchemerValue +} from 'common/models/elements/element'; import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { DropListComponent } from 'common/components/input-elements/drop-list.component'; +import { + DropListSimpleElement +} from 'common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple'; export class DropListElement extends InputElement implements PositionedUIElement { onlyOneItem: boolean = false; @@ -30,6 +39,31 @@ export class DropListElement extends InputElement implements PositionedUIElement }; } + getSchemerData(options: any): SchemerData { + return { + id: this.id, + type: 'string', + format: '', + multiple: true, + nullable: false, + values: this.getSchemerValues(options), + valuesComplete: true + }; + } + + getSchemerValues( dropLists: DropListElement[] | DropListSimpleElement[]): SchemerValue[] { + const valueDropLists = dropLists.filter( dropList => dropList.connectedTo.includes(this.id) ); + if (valueDropLists.length) { // TODO: or Sorting List + return [this, ...valueDropLists] + .map(dropList => dropList.value as DragNDropValueObject[]) + .flat() + .map(option => ({ value: option.id, label: option.stringValue as string })); // TODO: imageValueSrc + } else { + // only drag list - no drop list + return []; + } + } + getComponentFactory(): Type<ElementComponent> { return DropListComponent; } diff --git a/projects/common/models/elements/input-elements/dropdown.ts b/projects/common/models/elements/input-elements/dropdown.ts index ce3cbd71a..7502ae5e1 100644 --- a/projects/common/models/elements/input-elements/dropdown.ts +++ b/projects/common/models/elements/input-elements/dropdown.ts @@ -1,5 +1,11 @@ import { ElementFactory } from 'common/util/element.factory'; -import { BasicStyles, InputElement, PositionedUIElement, PositionProperties } from 'common/models/elements/element'; +import { + BasicStyles, + InputElement, + PositionedUIElement, + PositionProperties, + SchemerData, SchemerValue +} from 'common/models/elements/element'; import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { DropdownComponent } from 'common/components/input-elements/dropdown.component'; @@ -19,6 +25,22 @@ export class DropdownElement extends InputElement implements PositionedUIElement }; } + getSchemerData(options: any): SchemerData { + return { + id: this.id, + type: 'integer', + format: '', + multiple: false, + nullable: this.allowUnset, + values: this.getSchemerValues(), + valuesComplete: true + }; + } + + private getSchemerValues(): SchemerValue[] { + return this.options.map((option, index) => ({ value: (index + 1).toString(), label: option })); + } + getComponentFactory(): Type<ElementComponent> { return DropdownComponent; } diff --git a/projects/common/models/elements/input-elements/radio-button-group-complex.ts b/projects/common/models/elements/input-elements/radio-button-group-complex.ts index 48ab3d9fd..a41cf3118 100644 --- a/projects/common/models/elements/input-elements/radio-button-group-complex.ts +++ b/projects/common/models/elements/input-elements/radio-button-group-complex.ts @@ -3,7 +3,7 @@ import { BasicStyles, InputElement, PositionedUIElement, - PositionProperties, + PositionProperties, SchemerData, SchemerValue, TextImageLabel } from 'common/models/elements/element'; import { Type } from '@angular/core'; @@ -24,6 +24,22 @@ export class RadioButtonGroupComplexElement extends InputElement implements Posi }; } + getSchemerData(): SchemerData { + return { + id: this.id, + type: 'integer', + format: '', + multiple: false, + nullable: true, + values: [], //? + valuesComplete: true + }; + } + + private getSchemerValues(): SchemerValue[] { + return [];// this.columns.map((option, index) => ({ value: (index + 1).toString(), label: option })); + } + getComponentFactory(): Type<ElementComponent> { return RadioGroupImagesComponent; } diff --git a/projects/common/models/elements/input-elements/radio-button-group.ts b/projects/common/models/elements/input-elements/radio-button-group.ts index 66ba96a08..a62d74a3c 100644 --- a/projects/common/models/elements/input-elements/radio-button-group.ts +++ b/projects/common/models/elements/input-elements/radio-button-group.ts @@ -1,5 +1,11 @@ import { ElementFactory } from 'common/util/element.factory'; -import { BasicStyles, InputElement, PositionedUIElement, PositionProperties } from 'common/models/elements/element'; +import { + BasicStyles, + InputElement, + PositionedUIElement, + PositionProperties, + SchemerData, SchemerValue +} from 'common/models/elements/element'; import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { RadioButtonGroupComponent } from 'common/components/input-elements/radio-button-group.component'; @@ -26,6 +32,23 @@ export class RadioButtonGroupElement extends InputElement implements PositionedU }; } + getSchemerData(options: any): SchemerData { + return { + id: this.id, + type: 'integer', + format: 'integer', + multiple: false, + nullable: true, + values: this.getSchemerValues(), + valuesComplete: true + }; + } + + private getSchemerValues(): SchemerValue[] { + return this.richTextOptions + .map((option, index) => ({ value: (index + 1).toString(), label: option })); + } + getComponentFactory(): Type<ElementComponent> { return RadioButtonGroupComponent; } diff --git a/projects/editor/src/app/services/unit.service.ts b/projects/editor/src/app/services/unit.service.ts index 2350d9c4e..6dfa01b18 100644 --- a/projects/editor/src/app/services/unit.service.ts +++ b/projects/editor/src/app/services/unit.service.ts @@ -74,12 +74,12 @@ export class UnitService { } unitUpdated(): void { - this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); + this.sendChangedNotifications(); } addSection(page: Page): void { page.sections.push(new Section()); - this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); + this.sendChangedNotifications(); } deleteSection(section: Section): void { @@ -87,7 +87,7 @@ export class UnitService { this.unit.pages[this.selectionService.selectedPageIndex].sections.indexOf(section), 1 ); - this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); + this.sendChangedNotifications(); } duplicateSection(section: Section, page: Page, sectionIndex: number): void { @@ -96,7 +96,7 @@ export class UnitService { elements: section.elements.map(element => this.duplicateElement(element) as PositionedUIElement) }); page.sections.splice(sectionIndex + 1, 0, newSection); - this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); + this.sendChangedNotifications(); } moveSection(section: Section, page: Page, direction: 'up' | 'down'): void { @@ -106,7 +106,7 @@ export class UnitService { } else if (direction === 'down') { this.selectionService.selectedPageSectionIndex += 1; } - this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); + this.sendChangedNotifications(); } addElementToSectionByIndex(elementType: UIElementType, @@ -120,7 +120,9 @@ export class UnitService { coordinates?: { x: number, y: number }): Promise<void> { console.log('addElementToSection', elementType); let newElement: PositionedUIElement; + // TODO: Remove switch use parameter for loadFile if (['audio', 'video', 'image'].includes(elementType)) { + // TODO: loadFile before addElementToSection let mediaSrc = ''; switch (elementType) { case 'image': @@ -134,6 +136,7 @@ export class UnitService { break; // no default } + // TODO: ElementFactory.createElement is used 2 times newElement = ElementFactory.createElement( elementType, { id: this.idService.getNewID(elementType), @@ -161,7 +164,7 @@ export class UnitService { newElement.position.yPosition = coordinates.y; } section.elements.push(newElement); - this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); + this.sendChangedNotifications(); } deleteElements(elements: UIElement[]): void { @@ -169,7 +172,7 @@ export class UnitService { this.unit.pages[this.selectionService.selectedPageIndex].sections.forEach(section => { section.elements = section.elements.filter(element => !elements.includes(element)); }); - this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); + this.sendChangedNotifications(); } private freeUpIds(elements: UIElement[]): void { @@ -190,7 +193,7 @@ export class UnitService { newSection.elements.push(element as PositionedUIElement); (element as PositionedUIElement).position.dynamicPositioning = newSection.dynamicPositioning; }); - this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); + this.sendChangedNotifications(); } duplicateElementsInSection(elements: UIElement[], pageIndex: number, sectionIndex: number): void { @@ -198,7 +201,7 @@ export class UnitService { elements.forEach((element: UIElement) => { section.elements.push(this.duplicateElement(element) as PositionedUIElement); }); - this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); + this.sendChangedNotifications(); } private duplicateElement(element: UIElement): UIElement { @@ -245,7 +248,7 @@ export class UnitService { section.setProperty(property, value); } this.elementPropertyUpdated.next(); - this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); + this.sendChangedNotifications(); } updateElementsProperty(elements: UIElement[], @@ -277,7 +280,7 @@ export class UnitService { } }); this.elementPropertyUpdated.next(); - this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); + this.sendChangedNotifications(); } updateSelectedElementsPositionProperty(property: string, value: any): void { @@ -289,7 +292,7 @@ export class UnitService { element.setPositionProperty(property, value); }); this.elementPropertyUpdated.next(); - this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); + this.sendChangedNotifications(); } updateSelectedElementsStyleProperty(property: string, value: any): void { @@ -298,7 +301,7 @@ export class UnitService { element.setStyleProperty(property, value); }); this.elementPropertyUpdated.next(); - this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); + this.sendChangedNotifications(); } updateElementsPlayerProperty(elements: UIElement[], property: string, value: any): void { @@ -306,7 +309,7 @@ export class UnitService { element.setPlayerProperty(property, value); }); this.elementPropertyUpdated.next(); - this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); + this.sendChangedNotifications(); } createLikertRowElement(rowLabelText: string, columnCount: number): LikertRowElement { @@ -354,9 +357,32 @@ export class UnitService { // no default } this.elementPropertyUpdated.next(); + this.sendChangedNotifications(); + } + + sendChangedNotifications(): void { + // stattdessen event emitten? this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); + + // relevante schemer Data ermitteln + const schemerData = UnitUtils.findUIElements(this.unit.pages) + .filter(element => element.getSchemerData) + .map( element => element.getSchemerData(this.getSchemerOption(element.type))) + .filter(data => data.values.length > 0); // schemerData mit leeren values sind nicht von interesse + console.log(schemerData); } + // Values für Schemer elemente setzen? Sind nur Droplists dynamisch? + private getSchemerOption(type: UIElementType): any { + if (type === 'drop-list-simple' || type === 'drop-list') { + return UnitUtils + .findUIElements(this.unit.pages, 'drop-list') + .concat(UnitUtils.findUIElements(this.unit.pages, 'drop-list-simple')); + } + return null; + } + + saveUnit(): void { FileService.saveUnitToFile(JSON.stringify(this.unit)); } @@ -365,6 +391,7 @@ export class UnitService { this.loadUnitDefinition(await FileService.loadFile(['.json'])); } + // TODO: showDefaultEditDialog is method in unitService? showDefaultEditDialog(element: UIElement): void { switch (element.type) { case 'button': @@ -440,6 +467,7 @@ export class UnitService { /* Used by props panel to show available dropLists to connect */ getDropListElementIDs(): string[] { + // TODO: DropListSinple? return this.unit.pages .map(page => page.sections .map(section => section.elements -- GitLab