From e786b2b1369f21bae7c1a6cd4279701329b17ce4 Mon Sep 17 00:00:00 2001
From: jojohoch <joachim.hoch@iqb.hu-berlin.de>
Date: Thu, 22 Dec 2022 15:18:15 +0100
Subject: [PATCH] [player] Set drop effects for drag items and document

#405
---
 docs/release-notes-player.md                  |  6 ++
 .../input-elements/drop-list.component.ts     | 70 ++++++++++++-------
 2 files changed, 49 insertions(+), 27 deletions(-)

diff --git a/docs/release-notes-player.md b/docs/release-notes-player.md
index e2b5ca4b2..88bd89e6a 100644
--- a/docs/release-notes-player.md
+++ b/docs/release-notes-player.md
@@ -4,6 +4,12 @@ Player
 ### Verbesserungen
 - Ändert die voreingestellte Hintergrundfarbe bei Ablegelisten für Ablegeankündigung
 - Ändert die Rahmenfarbe für Eingabefelder, Eingabebereiche, Formeln und Optionsfelder in Lückentexten
+- Überarbeitet Mauszeiger für Ablegelisten
+  - Wird in der Ursprungsliste "Elemente kopieren" benutzt, wird der "Kopieren"-Mauszeiger verwendet
+  - Ist ein Ablegen nicht erlaubt, wird der "Verboten"-Mauszeiger benutzt
+  - In allen anderen Fällen (das beinhaltet alle Flächen außerhalb von Ablegelisten) wird der
+    "Verschieben"-Mauszeiger verwendet
+  - **Die Darstellung der Mauszeiger ist abhängig vom Betriebssystem**
 
 ## Fehlerbehebungen
 - Speichert Änderungen an GeoGebra-Elementen, die durch Benutzung der 
diff --git a/projects/common/components/input-elements/drop-list.component.ts b/projects/common/components/input-elements/drop-list.component.ts
index c2832c712..15c390e1e 100644
--- a/projects/common/components/input-elements/drop-list.component.ts
+++ b/projects/common/components/input-elements/drop-list.component.ts
@@ -13,10 +13,10 @@ import { FormElementComponent } from '../../directives/form-element-component.di
     <div class="list" [id]="elementModel.id"
          [fxLayout]="elementModel.orientation | droplistLayout"
          [fxLayoutAlign]="elementModel.orientation |  droplistLayoutAlign"
-         [class.vertical-orientation] = "elementModel.orientation === 'vertical'"
-         [class.horizontal-orientation] = "elementModel.orientation === 'horizontal'"
-         [class.cloze-context] = "clozeContext"
-         [class.only-one-item]= "elementModel.onlyOneItem"
+         [class.vertical-orientation]="elementModel.orientation === 'vertical'"
+         [class.horizontal-orientation]="elementModel.orientation === 'horizontal'"
+         [class.cloze-context]="clozeContext"
+         [class.only-one-item]="elementModel.onlyOneItem"
          [style.min-height.px]="elementModel.position?.useMinHeight || clozeContext ? elementModel.height : undefined"
          [style.color]="elementModel.styling.fontColor"
          [style.font-family]="elementModel.styling.font"
@@ -32,15 +32,15 @@ import { FormElementComponent } from '../../directives/form-element-component.di
          tabindex="0"
          (focusout)="elementFormControl.markAsTouched()"
          (drop)="drop($event)" (dragenter)="dragEnterList($event)" (dragleave)="dragLeaveList($event)"
-         (dragover)="$event.preventDefault()">
-          <!--Add dummy div - otherwise the empty list in cloze context will not be in one line-->
-          <div *ngIf="viewModel.length === 0"
-               [style.min-height.px]="elementModel.height - 4"
-               [style.pointer-events]="'none'"
-               fxLayout="row"
-               [fxLayoutAlign]="'center center'">
-            <span>&nbsp;</span>
-          </div>
+         (dragover)="setDropEffect($event)">
+      <!--Add dummy div - otherwise the empty list in cloze context will not be in one line-->
+      <div *ngIf="viewModel.length === 0"
+           [style.min-height.px]="elementModel.height - 4"
+           [style.pointer-events]="'none'"
+           fxLayout="row"
+           [fxLayoutAlign]="'center center'">
+        <span>&nbsp;</span>
+      </div>
       <ng-container *ngFor="let dropListValueElement of viewModel let index = index;">
         <div *ngIf="!dropListValueElement.imgSrc"
              class="list-item"
@@ -88,7 +88,7 @@ import { FormElementComponent } from '../../directives/form-element-component.di
     '.error-message {font-size: 75%; margin-top: 10px; margin-left: 3px;}',
     '.error-message {position: absolute; bottom: 3px; pointer-events: none;}',
     '.list-item {cursor: grab;}',
-    '.list-item:active {cursor: grabbing}',
+    '.list-item:active {cursor: grabbing;}',
     '.show-as-placeholder {opacity: 0.5 !important; pointer-events: none;}',
     '.highlight-valid-drop {background-color: #ccc !important;}',
     '.highlight-as-receiver {padding: 0; border: 2px solid;}',
@@ -121,6 +121,8 @@ export class DropListComponent extends FormElementComponent implements OnInit, A
 
   ngAfterViewInit() {
     DropListComponent.dragAndDropComponents[this.elementModel.id] = this;
+    // Prevent 'forbidden' cursor outside of drop lists
+    document.addEventListener('dragover', (event => event.preventDefault()));
   }
 
   // TODO method names
@@ -173,7 +175,7 @@ export class DropListComponent extends FormElementComponent implements OnInit, A
     dragImage.style.maxWidth = `${(baseElement as HTMLElement).offsetWidth}px`;
     dragImage.style.fontSize = `${this.elementModel.styling.fontSize}px`;
     dragImage.style.borderRadius = '5px';
-    dragImage.style.padding = '10px';
+    dragImage.style.padding = '10px 20px'; // Leave space for cursor
     document.body.appendChild(dragImage);
     return dragImage;
   }
@@ -194,11 +196,23 @@ export class DropListComponent extends FormElementComponent implements OnInit, A
     this.placeHolderIndex = targetIndex;
   }
 
-  dragEnterList(event: DragEvent) {
+  setDropEffect(event: DragEvent) {
     event.preventDefault();
+    if (!event.dataTransfer) return;
+    if (this.isDropAllowed()) {
+      if ((DropListComponent.sourceList as DropListComponent).elementModel.copyOnDrop) {
+        event.dataTransfer.dropEffect = 'copy';
+      } else {
+        event.dataTransfer.dropEffect = 'move';
+      }
+    } else {
+      event.dataTransfer.dropEffect = 'none';
+    }
+  }
 
-    if (!DropListComponent.sourceList || !this.isDropAllowed((DropListComponent.sourceList as DropListComponent).elementModel.connectedTo)) return;
-
+  dragEnterList(event: DragEvent) {
+    event.preventDefault();
+    if (!this.isDropAllowed()) return;
     if (!this.elementModel.isSortList) {
       this.highlightValidDrop = true;
     } else if (DropListComponent.sourceList !== this) {
@@ -227,20 +241,20 @@ export class DropListComponent extends FormElementComponent implements OnInit, A
     }
 
     // if drop is allowed that means item transfer between non-sort lists
-    if (this.isDropAllowed((DropListComponent.sourceList as DropListComponent).elementModel.connectedTo)) {
+    if (this.isDropAllowed()) {
       if (!DropListComponent.isItemIDAlreadyPresent(DropListComponent.draggedElement?.id as string, this.elementFormControl.value) &&
-          !(this.elementModel.onlyOneItem && this.viewModel.length > 0)) { // normal drop
+        !(this.elementModel.onlyOneItem && this.viewModel.length > 0)) { // normal drop
         if (!DropListComponent.sourceList?.elementModel.copyOnDrop) { // remove source item if not copy
           DropListComponent.removeElementFromList(DropListComponent.sourceList as DropListComponent,
             DropListComponent.sourceList?.placeHolderIndex as number);
         }
         DropListComponent.addElementToList(this, DropListComponent.draggedElement as DragNDropValueObject);
       } else if (DropListComponent.isItemIDAlreadyPresent(DropListComponent.draggedElement?.id as string, this.elementFormControl.value) &&
-                 this.elementModel.deleteDroppedItemWithSameID) { // put back (return) item
+        this.elementModel.deleteDroppedItemWithSameID) { // put back (return) item
         DropListComponent.removeElementFromList(DropListComponent.sourceList as DropListComponent,
           DropListComponent.sourceList?.placeHolderIndex as number);
       } else if (this.elementModel.onlyOneItem && this.viewModel.length > 0 &&
-                 this.viewModel[0].returnToOriginOnReplacement) { // replace
+        this.viewModel[0].returnToOriginOnReplacement) { // replace
         const originListComponent = DropListComponent.dragAndDropComponents[this.viewModel[0].originListID as string];
         const isItemIDAlreadyPresent =
           DropListComponent.isItemIDAlreadyPresent(this.viewModel[0].id, originListComponent.elementFormControl.value);
@@ -267,12 +281,14 @@ export class DropListComponent extends FormElementComponent implements OnInit, A
   onlyOneItem && itemcount = 1 && this.viewModel[0].returnToOriginOnReplacement)  // verdraengen
   - (! id already present) || id already present && deleteDroppedItemWithSameID // zuruecklegen
    */
-  isDropAllowed(connectedDropLists: string[]): boolean {
+  isDropAllowed(): boolean {
+    if (!DropListComponent.sourceList) return false;
     const sameList = DropListComponent.sourceList === this;
+    const connectedDropLists = (DropListComponent.sourceList as DropListComponent).elementModel.connectedTo;
     const isConnectedList = (connectedDropLists as string[]).includes(this.elementModel.id);
     return (sameList) || (isConnectedList &&
-                             !this.isOnlyOneItemAndNoReplacingOrReturning() &&
-                             !this.isIDPresentAndNoReturning());
+      !this.isOnlyOneItemAndNoReplacingOrReturning() &&
+      !this.isIDPresentAndNoReturning());
   }
 
   isIDPresentAndNoReturning(): boolean {
@@ -287,8 +303,8 @@ export class DropListComponent extends FormElementComponent implements OnInit, A
   isOnlyOneItemAndNoReplacingOrReturning(): boolean {
     return this.elementModel.onlyOneItem && this.viewModel.length > 0 &&
       !((this.viewModel[0].returnToOriginOnReplacement && !this.elementModel.isSortList) ||
-       (this.elementModel.deleteDroppedItemWithSameID &&
-         DropListComponent.draggedElement?.id === this.viewModel[0].id));
+        (this.elementModel.deleteDroppedItemWithSameID &&
+          DropListComponent.draggedElement?.id === this.viewModel[0].id));
   }
 
   static isItemIDAlreadyPresent(itemID: string, valueList: DragNDropValueObject[]): boolean {
-- 
GitLab