From 1442163d2dbde68858a141a8abf964a2774e6b8c Mon Sep 17 00:00:00 2001
From: rhenck <richard.henck@iqb.hu-berlin.de>
Date: Mon, 29 Nov 2021 12:20:54 +0100
Subject: [PATCH] Add new element DropListSimple

It is meant to be used as inline positioned (sub) element. Therefore it
has not postioning information and less property options than the normal
drop list. Similar to the simple text field.
---
 projects/common/shared.module.ts              |  4 +-
 .../common/ui-elements/cloze/cloze-element.ts |  5 +-
 .../ui-elements/cloze/cloze.component.ts      |  4 +-
 .../drop-list-simple.component.ts             | 89 +++++++++++++++++++
 .../drop-list-simple/drop-list-simple.ts      | 33 +++++++
 5 files changed, 129 insertions(+), 6 deletions(-)
 create mode 100644 projects/common/ui-elements/drop-list-simple/drop-list-simple.component.ts
 create mode 100644 projects/common/ui-elements/drop-list-simple/drop-list-simple.ts

diff --git a/projects/common/shared.module.ts b/projects/common/shared.module.ts
index 6ff88e55b..7d1bd4732 100644
--- a/projects/common/shared.module.ts
+++ b/projects/common/shared.module.ts
@@ -43,6 +43,7 @@ import { RadioGroupImagesComponent } from './ui-elements/radio-with-images/radio
 import { DropListComponent } from './ui-elements/drop-list/drop-list.component';
 import { ClozeComponent } from './ui-elements/cloze/cloze.component';
 import { TextFieldSimpleComponent } from './ui-elements/textfield-simple/text-field-simple.component';
+import { DropListSimpleComponent } from './ui-elements/drop-list-simple/drop-list-simple.component';
 
 @NgModule({
   imports: [
@@ -86,7 +87,8 @@ import { TextFieldSimpleComponent } from './ui-elements/textfield-simple/text-fi
     RadioGroupImagesComponent,
     DropListComponent,
     ClozeComponent,
-    TextFieldSimpleComponent
+    TextFieldSimpleComponent,
+    DropListSimpleComponent
   ],
   exports: [
     CommonModule,
diff --git a/projects/common/ui-elements/cloze/cloze-element.ts b/projects/common/ui-elements/cloze/cloze-element.ts
index cd803cf02..0f18c437a 100644
--- a/projects/common/ui-elements/cloze/cloze-element.ts
+++ b/projects/common/ui-elements/cloze/cloze-element.ts
@@ -135,10 +135,9 @@ export class ClozeElement extends CompoundElement implements PositionedElement,
         newElement = new DropdownElement(elementModel);
         break;
       case 'drop-list':
-        newElement = new DropListElement(elementModel);
-        newElement.height = 30;
+        newElement = new DropListSimpleElement(elementModel);
+        newElement.height = 25;
         newElement.width = 100;
-        newElement.onlyOneItem = true;
         break;
       default:
         throw new Error(`ElementType ${elementModel.type} not found!`);
diff --git a/projects/common/ui-elements/cloze/cloze.component.ts b/projects/common/ui-elements/cloze/cloze.component.ts
index 3a7efb715..f2561f966 100644
--- a/projects/common/ui-elements/cloze/cloze.component.ts
+++ b/projects/common/ui-elements/cloze/cloze.component.ts
@@ -66,11 +66,11 @@ import { FormElementComponent } from '../../directives/form-element-component.di
                [style.vertical-align]="'middle'"
                [style.width.px]="$any(part.value).width"
                [style.height.px]="$any(part.value).height">
-            <app-drop-list #droplistComponent
+            <app-drop-list-simple #droplistComponent
                            [parentForm]="parentForm"
                            (elementValueChanged)="elementValueChanged.emit($event)"
                            [elementModel]="$any(part.value)">
-            </app-drop-list>
+            </app-drop-list-simple>
           </div>
         </span>
       </ng-container>
diff --git a/projects/common/ui-elements/drop-list-simple/drop-list-simple.component.ts b/projects/common/ui-elements/drop-list-simple/drop-list-simple.component.ts
new file mode 100644
index 000000000..73a96e88a
--- /dev/null
+++ b/projects/common/ui-elements/drop-list-simple/drop-list-simple.component.ts
@@ -0,0 +1,89 @@
+import { Component, Input } from '@angular/core';
+import { CdkDragDrop } from '@angular/cdk/drag-drop/drag-events';
+import {
+  CdkDrag, CdkDropList, moveItemInArray, transferArrayItem
+} from '@angular/cdk/drag-drop';
+import { DropListSimpleElement } from './drop-list-simple';
+import { FormElementComponent } from '../../directives/form-element-component.directive';
+
+@Component({
+  selector: 'app-drop-list-simple',
+  template: `
+    <div class="list-container">
+      <!-- Border width is a workaround to enable/disable the Material cdk-drop-list-receiving-->
+      <!-- class style.-->
+      <div class="list"
+           [class.dropList-highlight]="elementModel.highlightReceivingDropList"
+           [style.border-color]="elementModel.highlightReceivingDropListColor"
+           [style.border-width.px]="elementModel.highlightReceivingDropList ? 2 : 0"
+           [style.color]="elementModel.fontProps.fontColor"
+           [style.font-family]="elementModel.fontProps.font"
+           [style.font-size.px]="elementModel.fontProps.fontSize"
+           [style.font-weight]="elementModel.fontProps.bold ? 'bold' : ''"
+           [style.font-style]="elementModel.fontProps.italic ? 'italic' : ''"
+           [style.text-decoration]="elementModel.fontProps.underline ? 'underline' : ''"
+           [style.backgroundColor]="elementModel.surfaceProps.backgroundColor"
+           cdkDropList
+           [id]="elementModel.id"
+           [cdkDropListData]="this"
+           [cdkDropListConnectedTo]="elementModel.connectedTo"
+           [cdkDropListEnterPredicate]="onlyOneItemPredicate"
+           (cdkDropListDropped)="drop($event)">
+        <div class="item" *ngFor="let value of $any(elementModel.value)" cdkDrag
+             [style.line-height.px]="elementModel.height - 4"
+             [style.background-color]="elementModel.itemBackgroundColor">
+          <div *cdkDragPreview
+               [style.font-size.px]="elementModel.fontProps.fontSize"
+               [style.background-color]="elementModel.itemBackgroundColor">
+            {{value}}
+          </div>
+          <div class="drag-placeholder" *cdkDragPlaceholder [style.min-height.px]="elementModel.fontProps.fontSize">
+          </div>
+          {{value}}
+        </div>
+      </div>
+      <mat-error *ngIf="elementFormControl.errors && elementFormControl.touched"
+                 class="error-message">
+        {{elementFormControl.errors | errorTransform: elementModel}}
+      </mat-error>
+    </div>
+  `,
+  styles: [
+    '.list-container {display: flex; flex-direction: column; width: 100%; height: 100%;}',
+    '.list {width: 100%; height: 100%; border-radius: 5px}',
+    '.item {border-radius: 5px; padding: 0 5px; height: 100%}',
+    '.item:not(:last-child) {margin-bottom: 5px;}',
+    '.error-message {font-size: 75%; margin-top: 10px;}',
+    '.cdk-drag-preview {padding: 8px 20px; border-radius: 10px}',
+    '.drag-placeholder {background-color: lightgrey; border: dotted 3px #999; padding: 10px;}',
+    '.drag-placeholder {transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);}',
+    '.cdk-drag-animating {transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);}',
+
+    '.dropList-highlight.cdk-drop-list-receiving {border: solid;}',
+    '.dropList-highlight.cdk-drop-list-dragging {border: solid;}'
+  ]
+})
+export class DropListSimpleComponent extends FormElementComponent {
+  @Input() elementModel!: DropListSimpleElement;
+
+  drop(event: CdkDragDrop<DropListSimpleComponent>): void {
+    if (!this.elementModel.readOnly) {
+      if (event.previousContainer === event.container) {
+        moveItemInArray(event.container.data.elementModel.value as string[], event.previousIndex, event.currentIndex);
+      } else {
+        transferArrayItem(
+          event.previousContainer.data.elementModel.value as string[],
+          event.container.data.elementModel.value as string[],
+          event.previousIndex,
+          event.currentIndex
+        );
+        event.previousContainer.data.elementFormControl.setValue(event.previousContainer.data.elementModel.value);
+      }
+      this.elementFormControl.setValue(event.container.data.elementModel.value);
+    }
+  }
+
+  onlyOneItemPredicate = (drag: CdkDrag, drop: CdkDropList): boolean => (
+    !drop.data.elementModel.onlyOneItem || drop.data.elementModel.value.length < 1
+  );
+}
diff --git a/projects/common/ui-elements/drop-list-simple/drop-list-simple.ts b/projects/common/ui-elements/drop-list-simple/drop-list-simple.ts
new file mode 100644
index 000000000..0b0e0a10b
--- /dev/null
+++ b/projects/common/ui-elements/drop-list-simple/drop-list-simple.ts
@@ -0,0 +1,33 @@
+import {
+  FontElement,
+  FontProperties,
+  InputElement,
+  PositionedElement, PositionProperties,
+  SurfaceElement,
+  SurfaceProperties,
+  UIElement
+} from '../../models/uI-element';
+import { initFontElement, initPositionedElement, initSurfaceElement } from '../../util/unit-interface-initializer';
+
+export class DropListSimpleElement extends InputElement implements FontElement, SurfaceElement {
+  connectedTo: string[] = [];
+  itemBackgroundColor: string = '#add8e6';
+  highlightReceivingDropList: boolean = false;
+  highlightReceivingDropListColor: string = '#add8e6';
+
+  fontProps: FontProperties;
+  surfaceProps: SurfaceProperties;
+
+  constructor(serializedElement: UIElement) {
+    super(serializedElement);
+    Object.assign(this, serializedElement);
+    this.fontProps = initFontElement(serializedElement);
+    this.surfaceProps = initSurfaceElement(serializedElement);
+
+    this.value = serializedElement.value as string[] || [];
+    this.height = serializedElement.height || 100;
+    this.surfaceProps.backgroundColor =
+      serializedElement.surfaceProps?.backgroundColor as string ||
+      '#eeeeec';
+  }
+}
-- 
GitLab