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