From 94395fb8f8c31198d733521660c115737f016c04 Mon Sep 17 00:00:00 2001
From: rhenck <richard.henck@iqb.hu-berlin.de>
Date: Mon, 20 Sep 2021 14:30:35 +0200
Subject: [PATCH] [editor] Refactor section properties / remove edit mode

The section menu (containing all former properties options and
reordering) is now shown on section hover.
- With the removal of the section edit mode the view-only elements
are no longer needed, as sections can no longer be dragged.
- The unit service now has a unified logic for moving items in an array,
being used for pages and sections. Although for pages there is an
additional regarding the always shown page.
The reorder function silently ignores nonsense reorders!
- The prompt dialog now takes the text as parameter
---
 projects/editor/src/app/app.module.ts         |  4 --
 ...mic-view-only-element-overlay.component.ts | 12 -----
 .../canvas/page-canvas.component.html         | 45 ++++++----------
 .../page-view/canvas/page-canvas.component.ts | 11 ++--
 .../canvas/section-menu.component.ts          | 36 +++++++++++--
 .../page-view/canvas/section.component.ts     | 51 +------------------
 ...tic-view-only-element-overlay.component.ts | 19 -------
 projects/editor/src/app/dialog.service.ts     | 14 +++--
 projects/editor/src/app/unit.service.ts       | 42 ++++++++++-----
 9 files changed, 89 insertions(+), 145 deletions(-)
 delete mode 100644 projects/editor/src/app/components/unit-view/page-view/canvas/dynamic-view-only-element-overlay.component.ts
 delete mode 100644 projects/editor/src/app/components/unit-view/page-view/canvas/static-view-only-element-overlay.component.ts

diff --git a/projects/editor/src/app/app.module.ts b/projects/editor/src/app/app.module.ts
index 85d091859..101598cd1 100644
--- a/projects/editor/src/app/app.module.ts
+++ b/projects/editor/src/app/app.module.ts
@@ -26,8 +26,6 @@ import {
 } from './dialog.service';
 import { EditorTranslateLoader } from './editor-translate-loader';
 import { ElementPropertiesComponent } from './components/unit-view/page-view/properties/element-properties.component';
-import { StaticViewOnlyElementOverlayComponent } from './components/unit-view/page-view/canvas/static-view-only-element-overlay.component';
-import { DynamicViewOnlyElementOverlayComponent } from './components/unit-view/page-view/canvas/dynamic-view-only-element-overlay.component';
 import { SectionMenuComponent } from './components/unit-view/page-view/canvas/section-menu.component';
 
 @NgModule({
@@ -46,8 +44,6 @@ import { SectionMenuComponent } from './components/unit-view/page-view/canvas/se
     MultilineTextEditDialog,
     RichTextEditDialogTinyMCE,
     ElementPropertiesComponent,
-    StaticViewOnlyElementOverlayComponent,
-    DynamicViewOnlyElementOverlayComponent,
     SectionMenuComponent
   ],
   imports: [
diff --git a/projects/editor/src/app/components/unit-view/page-view/canvas/dynamic-view-only-element-overlay.component.ts b/projects/editor/src/app/components/unit-view/page-view/canvas/dynamic-view-only-element-overlay.component.ts
deleted file mode 100644
index bca827160..000000000
--- a/projects/editor/src/app/components/unit-view/page-view/canvas/dynamic-view-only-element-overlay.component.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Component } from '@angular/core';
-import { CanvasElementOverlay } from './canvas-element-overlay';
-
-@Component({
-  selector: 'app-dynamic-view-only-element-overlay',
-  template: `
-    <div #draggableElement [style.height.%]="100">
-      <ng-template #elementContainer></ng-template>
-    </div>
-  `
-})
-export class DynamicViewOnlyElementOverlayComponent extends CanvasElementOverlay { }
diff --git a/projects/editor/src/app/components/unit-view/page-view/canvas/page-canvas.component.html b/projects/editor/src/app/components/unit-view/page-view/canvas/page-canvas.component.html
index 8157d92fe..724bc0dbf 100644
--- a/projects/editor/src/app/components/unit-view/page-view/canvas/page-canvas.component.html
+++ b/projects/editor/src/app/components/unit-view/page-view/canvas/page-canvas.component.html
@@ -1,15 +1,19 @@
-<mat-slide-toggle (change)="toggleSectionEditMode($event)">
-  Abschnitte verwalten
-</mat-slide-toggle>
-
 <div class="canvasBackground" fxFlex>
-  <div *ngIf="!sectionEditMode"
-       class="canvasFrame"
+  <div class="canvasFrame"
        [style.width.px]="page.maxWidth"
        [style.padding.px]="page.margin"
        [style.background-color]="page.backgroundColor">
     <div cdkDropListGroup>
-      <ng-container *ngFor="let section of page.sections; let i = index">
+      <div *ngFor="let section of page.sections; let i = index" [style.position]="'relative'">
+        <app-section-menu [class.open]="hoveredSection === i" class="section-menu" fxLayout="column"
+                          [style.left.px]="-45" [style.z-index]="1" [style.position]="'absolute'"
+                          [section]="section"
+                          [allowMoveUp]="i != 0"
+                          [allowMoveDown]="i < page.sections.length - 1"
+                          [allowDelete]="page.sections.length > 1"
+                          (moveSection)="unitService.moveSection(section, page, $event)"
+                          (mouseover)="hoveredSection = i" (mouseleave)="hoveredSection = -1"> <!-- keep menu open-->
+        </app-section-menu>
         <!-- TODO split section into static and dynamic-->
         <div *ngIf="!section.dynamicPositioning"
              app-canvas-section class="section drop-list" id="section-{{i}}"
@@ -17,7 +21,8 @@
              cdkDropList cdkDropListSortingDisabled
              [cdkDropListData]="{ sectionIndex: i }"
              (cdkDropListDropped)="elementDropped($event)"
-             [cdkDropListConnectedTo]="dropListList">
+             [cdkDropListConnectedTo]="dropListList"
+             (mouseover)="hoveredSection = i" (mouseleave)="hoveredSection = -1">
         </div>
         <div *ngIf="section.dynamicPositioning"
              app-canvas-section class="section drop-list"
@@ -25,28 +30,8 @@
              [dropListList]="dropListList"
              (transferElement)="moveElementBetweenSections($event.element,
                                                            $event.previousSectionIndex,
-                                                           $event.newSectionIndex)">
-        </div>
-      </ng-container>
-    </div>
-  </div>
-
-  <div *ngIf="sectionEditMode" class="drop-list"
-       [style.width.px]="page.maxWidth" [style.padding.px]="page.margin"
-       cdkDropList (cdkDropListDropped)="sectionDrop($event)" [cdkDropListData]="page.sections">
-    <div *ngFor="let section of page.sections; let i = index" [style.position]="'relative'">
-      <button mat-fab class="add-section-button"
-              (click)="addSection(i)">
-        <mat-icon>add</mat-icon>
-      </button>
-      <div>
-        <app-section-menu class="button-panel" fxLayout="column"
-                          [style.left.px]="-42" [style.z-index]="1" [style.position]="'absolute'"
-                          [section]="section" [allowDelete]="page.sections.length > 1">
-        </app-section-menu>
-        <div app-canvas-section class="section"
-             [section]="section" [sectionEditMode]="sectionEditMode"
-             cdkDrag cdkDragLockAxis="y">
+                                                           $event.newSectionIndex)"
+             (mouseover)="hoveredSection = i" (mouseleave)="hoveredSection = -1">
         </div>
       </div>
     </div>
diff --git a/projects/editor/src/app/components/unit-view/page-view/canvas/page-canvas.component.ts b/projects/editor/src/app/components/unit-view/page-view/canvas/page-canvas.component.ts
index 5d5a8af05..0c285290a 100644
--- a/projects/editor/src/app/components/unit-view/page-view/canvas/page-canvas.component.ts
+++ b/projects/editor/src/app/components/unit-view/page-view/canvas/page-canvas.component.ts
@@ -4,7 +4,6 @@ import {
 import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
 import { Subject } from 'rxjs';
 import { takeUntil } from 'rxjs/operators';
-import { MatSlideToggleChange } from '@angular/material/slide-toggle';
 import { UnitPage, UnitPageSection, UnitUIElement } from '../../../../../../../common/unit';
 import { UnitService } from '../../../../unit.service';
 import { SelectionService } from '../../../../selection.service';
@@ -17,13 +16,15 @@ import { SelectionService } from '../../../../selection.service';
     '.canvasBackground {background-color: lightgrey; padding: 20px 50px; height: 100%; overflow: auto;}',
     '.add-section-button {width: 100%; height: 25px; background-color: #BABABA; margin: 15px 0; border-radius: 10%}',
     '::ng-deep .add-section-button span.mat-button-wrapper {padding: 0}',
-    '::ng-deep .add-section-button span.mat-button-wrapper mat-icon {vertical-align: unset}'
+    '::ng-deep .add-section-button span.mat-button-wrapper mat-icon {vertical-align: unset}',
+    '.section-menu {opacity:0; transition: opacity 0.5s linear; transition-delay:1s;}',
+    '.section-menu.open {opacity:1; transition-delay:0s;}'
   ]
 })
 export class PageCanvasComponent implements OnInit, OnDestroy {
   @Input() page!: UnitPage;
-  sectionEditMode: boolean = false;
   dropListList: string[] = [];
+  hoveredSection: number = -1;
   private ngUnsubscribe = new Subject<void>();
 
   constructor(private selectionService: SelectionService, public unitService: UnitService) { }
@@ -104,10 +105,6 @@ export class PageCanvasComponent implements OnInit, OnDestroy {
     return this.page.sections.reduce(reduceFct, 0);
   }
 
-  toggleSectionEditMode(event: MatSlideToggleChange): void {
-    this.sectionEditMode = event.checked;
-  }
-
   addSection(index: number | null = null): void {
     this.unitService.addSection(this.page, index);
   }
diff --git a/projects/editor/src/app/components/unit-view/page-view/canvas/section-menu.component.ts b/projects/editor/src/app/components/unit-view/page-view/canvas/section-menu.component.ts
index cac9bd499..45d4b92d9 100644
--- a/projects/editor/src/app/components/unit-view/page-view/canvas/section-menu.component.ts
+++ b/projects/editor/src/app/components/unit-view/page-view/canvas/section-menu.component.ts
@@ -1,9 +1,12 @@
 import {
-  Component, OnInit, Input,
+  Component, OnInit, OnDestroy, Input, Output, EventEmitter,
   ViewChild, ElementRef
 } from '@angular/core';
+import { Subject } from 'rxjs';
+import { takeUntil } from 'rxjs/operators';
 import { UnitPageSection } from '../../../../../../../common/unit';
 import { UnitService } from '../../../../unit.service';
+import { DialogService } from '../../../../dialog.service';
 
 @Component({
   selector: 'app-section-menu',
@@ -91,6 +94,14 @@ import { UnitService } from '../../../../unit.service';
       </div>
     </mat-menu>
 
+    <button *ngIf="allowMoveUp" mat-mini-fab
+            (click)="this.moveSection.emit('up')">
+      <mat-icon>north</mat-icon>
+    </button>
+    <button *ngIf="allowMoveDown" mat-mini-fab
+            (click)="this.moveSection.emit('down')">
+      <mat-icon>south</mat-icon>
+    </button>
     <button *ngIf="allowDelete" mat-mini-fab
             (click)="deleteSection()">
       <mat-icon>clear</mat-icon>
@@ -103,16 +114,20 @@ import { UnitService } from '../../../../unit.service';
     '::ng-deep .layoutMenu {width: 200px; padding: 0 15px}'
   ]
 })
-export class SectionMenuComponent implements OnInit {
+export class SectionMenuComponent implements OnInit, OnDestroy {
   @Input() section!: UnitPageSection;
+  @Input() allowMoveUp!: boolean;
+  @Input() allowMoveDown!: boolean;
   @Input() allowDelete!: boolean;
+  @Output() moveSection = new EventEmitter<'up' | 'down'>();
 
   @ViewChild('colorPicker') colorPicker!: ElementRef;
-
   columnSizes: { value: string, unit: string }[] = [];
   rowSizes: { value: string, unit: string }[] = [];
+  private ngUnsubscribe = new Subject<void>();
 
-  constructor(private unitService: UnitService) { }
+  constructor(public unitService: UnitService,
+              private dialogService: DialogService) { }
 
   ngOnInit(): void {
     this.updateGridSizes();
@@ -123,7 +138,13 @@ export class SectionMenuComponent implements OnInit {
   }
 
   deleteSection(): void {
-    this.unitService.deleteSection(this.section);
+    this.dialogService.showConfirmDialog('Abschnitt löschen?')
+      .pipe(takeUntil(this.ngUnsubscribe))
+      .subscribe((result: boolean) => {
+        if (result) {
+          this.unitService.deleteSection(this.section);
+        }
+      });
   }
 
   /* Initialize internal array of grid sizes. Where value and unit are put, split up, in an array. */
@@ -176,4 +197,9 @@ export class SectionMenuComponent implements OnInit {
   openColorPicker() {
     this.colorPicker.nativeElement.click();
   }
+
+  ngOnDestroy(): void {
+    this.ngUnsubscribe.next();
+    this.ngUnsubscribe.complete();
+  }
 }
diff --git a/projects/editor/src/app/components/unit-view/page-view/canvas/section.component.ts b/projects/editor/src/app/components/unit-view/page-view/canvas/section.component.ts
index 2b85712f1..fbb5c42f7 100644
--- a/projects/editor/src/app/components/unit-view/page-view/canvas/section.component.ts
+++ b/projects/editor/src/app/components/unit-view/page-view/canvas/section.component.ts
@@ -10,8 +10,7 @@ import { DragItemData, DropListData } from './page-canvas.component';
 @Component({
   selector: '[app-canvas-section]',
   template: `
-    <div *ngIf="!sectionEditMode"
-         #sectionElement class="section-wrapper"
+    <div #sectionElement class="section-wrapper"
          [style.border]="selected ? '1px solid': '1px dotted'"
          [style.height.px]="section.height"
          [style.background-color]="section.backgroundColor"
@@ -71,53 +70,6 @@ import { DragItemData, DropListData } from './page-canvas.component';
         </app-dynamic-canvas-overlay>
       </div>
     </div>
-
-    <div *ngIf="sectionEditMode"
-         #sectionElement class="section-wrapper"
-         [style.border]="selected ? '1px solid': '1px dotted'"
-         [style.height.px]="section.height"
-         [style.background-color]="section.backgroundColor"
-         (click)="selectionService.selectSection(this)">
-      <div *ngIf="!section.dynamicPositioning">
-        <app-view-only-element-overlay *ngFor="let element of section.elements" [element]="$any(element)">
-        </app-view-only-element-overlay>
-      </div>
-
-      <div *ngIf="section.dynamicPositioning"
-           [style.display]="'grid'"
-           [style.grid-template-columns]="section.gridColumnSizes"
-           [style.grid-template-rows]="section.gridRowSizes"
-           [style.height.%]="100">
-        <!-- Dynamic sections have the droplists for the grid cells next to the actual elements. Elements can not
-             be children of the grid cells because they can span over multiple cells. -->
-        <ng-container *ngFor="let column of this.section.gridColumnSizes.split(' '); let x = index">
-          <ng-container *ngFor="let row of this.section.gridRowSizes.split(' '); let y = index">
-            <div class="grid-placeholder"
-                 [style.grid-column-start]="x + 1"
-                 [style.grid-column-end]="x + 1"
-                 [style.grid-row-start]="y + 1"
-                 [style.grid-row-end]="y + 1">
-              {{x + 1}} / {{y + 1}}
-            </div>
-          </ng-container>
-        </ng-container>
-
-        <app-dynamic-view-only-element-overlay *ngFor="let element of section.elements"
-                                               [element]="$any(element)"
-                                               [style.min-width.px]="element.width"
-                                               [style.min-height.px]="element.height"
-                                               [style.margin-left.px]="element.marginLeft"
-                                               [style.margin-right.px]="element.marginRight"
-                                               [style.margin-top.px]="element.marginTop"
-                                               [style.margin-bottom.px]="element.marginBottom"
-                                               [style.grid-column-start]="element.gridColumnStart"
-                                               [style.grid-column-end]="element.gridColumnEnd"
-                                               [style.grid-row-start]="element.gridRowStart"
-                                               [style.grid-row-end]="element.gridRowEnd">
-        </app-dynamic-view-only-element-overlay>
-      </div>
-
-    </div>
   `,
   styles: [
     '.section-wrapper {width: 100%}',
@@ -126,7 +78,6 @@ import { DragItemData, DropListData } from './page-canvas.component';
 })
 export class SectionComponent {
   @Input() section!: UnitPageSection;
-  @Input() sectionEditMode: boolean = false;
   @Input() sectionIndex!: number;
   @Input() dropListList!: string[];
   @Output() transferElement = new EventEmitter<{ element: UnitUIElement,
diff --git a/projects/editor/src/app/components/unit-view/page-view/canvas/static-view-only-element-overlay.component.ts b/projects/editor/src/app/components/unit-view/page-view/canvas/static-view-only-element-overlay.component.ts
deleted file mode 100644
index 08a4b89b4..000000000
--- a/projects/editor/src/app/components/unit-view/page-view/canvas/static-view-only-element-overlay.component.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Component } from '@angular/core';
-import { CanvasElementOverlay } from './canvas-element-overlay';
-
-@Component({
-  selector: 'app-view-only-element-overlay',
-  template: `
-    <div [style.position]="'absolute'"
-         [style.width.px]="element.width"
-         [style.height.px]="element.height"
-         [style.left.px]="element.xPosition"
-         [style.top.px]="element.yPosition"
-         [style.z-index]="element.zIndex">
-      <ng-template #elementContainer></ng-template>
-    </div>
-  `
-})
-export class StaticViewOnlyElementOverlayComponent extends CanvasElementOverlay {
-
-}
diff --git a/projects/editor/src/app/dialog.service.ts b/projects/editor/src/app/dialog.service.ts
index 51593f429..60931f306 100644
--- a/projects/editor/src/app/dialog.service.ts
+++ b/projects/editor/src/app/dialog.service.ts
@@ -9,8 +9,12 @@ import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
 export class DialogService {
   constructor(private dialog: MatDialog) { }
 
-  showConfirmDialog(): Observable<boolean> {
-    const dialogRef = this.dialog.open(ConfirmationDialog);
+  showConfirmDialog(text: string): Observable<boolean> {
+    const dialogRef = this.dialog.open(ConfirmationDialog, {
+      data: {
+        text: text
+      }
+    });
     return dialogRef.afterClosed();
   }
 
@@ -49,7 +53,7 @@ export class DialogService {
   selector: 'app-confirmation-dialog',
   template: `
     <mat-dialog-content>
-        Seite wirklich löschen?
+        {{data.text}}
     </mat-dialog-content>
     <mat-dialog-actions>
       <button mat-button [mat-dialog-close]="true">Okay</button>
@@ -57,7 +61,9 @@ export class DialogService {
     </mat-dialog-actions>
     `
 })
-export class ConfirmationDialog {}
+export class ConfirmationDialog {
+  constructor(@Inject(MAT_DIALOG_DATA) public data: { text: string }) { }
+}
 
 @Component({
   selector: 'app-text-edit-dialog',
diff --git a/projects/editor/src/app/unit.service.ts b/projects/editor/src/app/unit.service.ts
index 001d9a8ef..e7551a4b6 100644
--- a/projects/editor/src/app/unit.service.ts
+++ b/projects/editor/src/app/unit.service.ts
@@ -58,22 +58,18 @@ export class UnitService {
     this.veronaApiService.sendVoeDefinitionChangedNotification();
   }
 
-  /* reorder page in page array */
+  /* Reorder page in page array.
+  * Checks first that the page can not be moved in front of the always visible page. */
   movePage(selectedPage: UnitPage, direction: 'up' | 'down'): void {
-    const oldPageIndex = this._unit.value.pages.indexOf(selectedPage);
-    if ((this._unit.value.pages.length > 1) &&
-        !(direction === 'down' && oldPageIndex + 1 === this._unit.value.pages.length) && // dont allow last page down
-        !(direction === 'up' && oldPageIndex === 0) && // dotn allow first page up
-        // dont allow second page to go before always shown page
-        !(direction === 'up' && oldPageIndex === 1 && this._unit.value.pages[0].alwaysVisible) &&
-        !(selectedPage.alwaysVisible)) {
-      const newPageIndex = direction === 'up' ? oldPageIndex - 1 : oldPageIndex + 1;
-      const page = this._unit.value.pages.splice(oldPageIndex, 1);
-      this._unit.value.pages.splice(newPageIndex, 0, page[0]);
-      this._unit.next(this._unit.value);
-      this.pageMoved.next();
-      this.veronaApiService.sendVoeDefinitionChangedNotification();
+    if (direction === 'up' &&
+        this._unit.value.pages.indexOf(selectedPage) === 1 &&
+        this._unit.value.pages[0].alwaysVisible) {
+      return;
     }
+    UnitService.moveArrayItem(selectedPage, this._unit.value.pages, direction);
+    this._unit.next(this._unit.value);
+    this.pageMoved.next();
+    this.veronaApiService.sendVoeDefinitionChangedNotification();
   }
 
   updatePageProperty(page: UnitPage, property: string, value: number | boolean): void {
@@ -119,6 +115,12 @@ export class UnitService {
     }
   }
 
+  moveSection(section: UnitPageSection, page: UnitPage, direction: 'up' | 'down'): void {
+    UnitService.moveArrayItem(section, page.sections, direction);
+    this._unit.next(this._unit.value);
+    this.veronaApiService.sendVoeDefinitionChangedNotification();
+  }
+
   setPageSections(page: UnitPage, sections: UnitPageSection[]): void {
     this._unit.value.pages[this.selectedPageIndex].sections = sections;
     this.veronaApiService.sendVoeDefinitionChangedNotification();
@@ -325,4 +327,16 @@ export class UnitService {
       // no default
     }
   }
+
+  private static moveArrayItem(item: unknown, array: unknown[], direction: 'up' | 'down'): void {
+    const oldIndex = array.indexOf(item);
+
+    if ((array.length > 1) &&
+      !(direction === 'down' && oldIndex + 1 === array.length) && // dont allow last element down
+      !(direction === 'up' && oldIndex === 0)) { // dont allow first element up
+      const newIndex = direction === 'up' ? oldIndex - 1 : oldIndex + 1;
+      const elements = array.splice(oldIndex, 1);
+      array.splice(newIndex, 0, elements[0]);
+    }
+  }
 }
-- 
GitLab