From 1ad10ae17f718c321debe0c8017397d9fdd1c581 Mon Sep 17 00:00:00 2001
From: rhenck <richard.henck@iqb.hu-berlin.de>
Date: Tue, 29 Jun 2021 16:33:32 +0200
Subject: [PATCH] Fefactor canvas elements to use a wrapper-div-overlay for
 drag and drop

This makes the actual component elements agnostic to any drag and drop
and selection logic, which is handled completely via the
wrapper-overlay. Now the component elements can be easily used by the
player.
---
 .../canvas-element-component.directive.ts     |  28 +----
 .../element-components/audio.component.ts     |  12 +-
 .../element-components/button.component.ts    |  14 +--
 .../element-components/checkbox.component.ts  |  17 +--
 .../element-components/dropdown.component.ts  |  22 +---
 .../element-components/image.component.ts     |  15 +--
 .../element-components/label.component.ts     |  15 +--
 .../radio-button-group.component.ts           |  20 +---
 .../text-field.component.ts                   |  33 ++----
 .../element-components/video.component.ts     |  12 +-
 projects/editor/src/app/app.module.ts         |   4 +-
 .../canvas/canvas-drag-overlay.component.ts   | 105 ++++++++++++++++++
 .../canvas/canvas-section.component.ts        |  64 +++--------
 .../page-view/canvas/page-canvas.component.ts |  34 +++---
 14 files changed, 166 insertions(+), 229 deletions(-)
 create mode 100644 projects/editor/src/app/components/unit-view/page-view/canvas/canvas-drag-overlay.component.ts

diff --git a/projects/common/canvas-element-component.directive.ts b/projects/common/canvas-element-component.directive.ts
index 5561ed729..e89cfd75c 100644
--- a/projects/common/canvas-element-component.directive.ts
+++ b/projects/common/canvas-element-component.directive.ts
@@ -1,48 +1,28 @@
 import {
-  Directive, Output, EventEmitter, Input, OnInit
+  Directive, Input, OnInit
 } from '@angular/core';
 import { UnitUIElement } from './unit';
 
 @Directive()
 export abstract class CanvasElementComponent implements OnInit {
   @Input() elementModel: UnitUIElement = {} as UnitUIElement;
-  @Input() draggable: boolean = false;
-  @Output() elementSelected = new EventEmitter<{ componentElement: CanvasElementComponent, multiSelect: boolean }>();
-
-  _selected = false;
   style: Record<string, string> = {};
 
   ngOnInit(): void {
     this.updateStyle();
   }
 
-  set selected(newValue: boolean) {
-    this._selected = newValue;
-    this.updateStyle();
-  }
-
-  click(event: MouseEvent): void {
-    if (event.shiftKey) {
-      this.elementSelected.emit({ componentElement: this, multiSelect: true });
-    } else {
-      this.elementSelected.emit({ componentElement: this, multiSelect: false });
-    }
-  }
-
   updateStyle(): void {
     this.style = {
-      border: this._selected ? '5px solid' : '',
-      width: `${this.elementModel.width}px`,
-      height: `${this.elementModel.height}px`,
+      width: '100%',
+      height: '100%',
       'background-color': this.elementModel.backgroundColor,
       color: this.elementModel.fontColor,
       'font-family': this.elementModel.font,
       'font-size': `${this.elementModel.fontSize}px`,
       'font-weight': this.elementModel.bold ? 'bold' : '',
       'font-style': this.elementModel.italic ? 'italic' : '',
-      'text-decoration': this.elementModel.underline ? 'underline' : '',
-      left: `${this.elementModel.xPosition.toString()}px`,
-      top: `${this.elementModel.yPosition.toString()}px`
+      'text-decoration': this.elementModel.underline ? 'underline' : ''
     };
   }
 }
diff --git a/projects/common/element-components/audio.component.ts b/projects/common/element-components/audio.component.ts
index 8d211652b..fb387d461 100644
--- a/projects/common/element-components/audio.component.ts
+++ b/projects/common/element-components/audio.component.ts
@@ -4,20 +4,12 @@ import { CanvasElementComponent } from '../canvas-element-component.directive';
 @Component({
   selector: 'app-audio',
   template: `
-      <div *ngIf="draggable"
-           cdkDrag [cdkDragData]="this"
-           (click)="click($event)"
-           [ngStyle]="style">
-          <audio controls src="{{$any(elementModel).src}}"></audio>
-      </div>
-      <div *ngIf="!draggable"
-           (click)="click($event)"
-           [ngStyle]="style">
+      <div [ngStyle]="style">
           <audio controls src="{{$any(elementModel).src}}"></audio>
       </div>
   `,
   styles: [
-    'div {position: absolute; display: inline-block; border: 5px solid; padding: 12px 9px 9px 9px;}'
+    'div {display: inline-block; border: 5px solid; padding: 12px 9px 9px 9px;}'
   ]
 })
 export class AudioComponent extends CanvasElementComponent {
diff --git a/projects/common/element-components/button.component.ts b/projects/common/element-components/button.component.ts
index e991dd5cd..098245399 100644
--- a/projects/common/element-components/button.component.ts
+++ b/projects/common/element-components/button.component.ts
@@ -4,20 +4,10 @@ import { CanvasElementComponent } from '../canvas-element-component.directive';
 @Component({
   selector: 'app-button',
   template: `
-    <button *ngIf="draggable"
-            cdkDrag [cdkDragData]="this"
-            mat-button (click)="click($event)"
+    <button mat-button
             [ngStyle]="style">
       {{$any(elementModel).label}}
     </button>
-    <button *ngIf="!draggable"
-            mat-button (click)="click($event)"
-            [ngStyle]="style">
-      {{$any(elementModel).label}}
-    </button>
-  `,
-  styles: [
-    'button {position: absolute}'
-  ]
+  `
 })
 export class ButtonComponent extends CanvasElementComponent { }
diff --git a/projects/common/element-components/checkbox.component.ts b/projects/common/element-components/checkbox.component.ts
index 735dd4b93..6b5b7cc51 100644
--- a/projects/common/element-components/checkbox.component.ts
+++ b/projects/common/element-components/checkbox.component.ts
@@ -4,22 +4,9 @@ import { CanvasElementComponent } from '../canvas-element-component.directive';
 @Component({
   selector: 'app-checkbox',
   template: `
-      <mat-checkbox *ngIf="draggable"
-                    cdkDrag [cdkDragData]="this"
-                    class="example-margin"
-                    (click)="click($event)"
-                    [ngStyle]="style">
+      <mat-checkbox class="example-margin" [ngStyle]="style">
           {{$any(elementModel).label}}
       </mat-checkbox>
-      <mat-checkbox *ngIf="!draggable"
-                    class="example-margin"
-                    (click)="click($event)"
-                    [ngStyle]="style">
-          {{$any(elementModel).label}}
-      </mat-checkbox>
-  `,
-  styles: [
-    'button {position: absolute}'
-  ]
+  `
 })
 export class CheckboxComponent extends CanvasElementComponent { }
diff --git a/projects/common/element-components/dropdown.component.ts b/projects/common/element-components/dropdown.component.ts
index 6951bfe97..486c4a5de 100644
--- a/projects/common/element-components/dropdown.component.ts
+++ b/projects/common/element-components/dropdown.component.ts
@@ -4,11 +4,7 @@ import { CanvasElementComponent } from '../canvas-element-component.directive';
 @Component({
   selector: 'app-dropdown',
   template: `
-      <mat-form-field *ngIf="draggable"
-                      cdkDrag [cdkDragData]="this"
-                      appearance="fill"
-                      (click)="click($event)"
-                      [ngStyle]="style">
+      <mat-form-field appearance="fill" [ngStyle]="style">
           <mat-label>{{$any(elementModel).label}}</mat-label>
           <mat-select>
               <mat-option *ngFor="let option of $any(elementModel).options" [value]="option">
@@ -16,20 +12,6 @@ import { CanvasElementComponent } from '../canvas-element-component.directive';
               </mat-option>
           </mat-select>
       </mat-form-field>
-      <mat-form-field *ngIf="!draggable"
-                      appearance="fill"
-                      (click)="click($event)"
-                      [ngStyle]="style">
-          <mat-label>{{$any(elementModel).label}}</mat-label>
-          <mat-select>
-              <mat-option *ngFor="let option of $any(elementModel).options" [value]="option">
-                  {{option}}
-              </mat-option>
-          </mat-select>
-      </mat-form-field>
-  `,
-  styles: [
-    'mat-form-field {position: absolute}'
-  ]
+  `
 })
 export class DropdownComponent extends CanvasElementComponent { }
diff --git a/projects/common/element-components/image.component.ts b/projects/common/element-components/image.component.ts
index 241c7f5e1..d9e3299e6 100644
--- a/projects/common/element-components/image.component.ts
+++ b/projects/common/element-components/image.component.ts
@@ -4,18 +4,7 @@ import { CanvasElementComponent } from '../canvas-element-component.directive';
 @Component({
   selector: 'app-image',
   template: `
-      <img *ngIf="draggable"
-           cdkDrag [cdkDragData]="this"
-           src="{{$any(elementModel).src}}" alt="Image Placeholder"
-           (click)="click($event)"
-           [ngStyle]="style">
-      <img *ngIf="!draggable"
-           src="{{$any(elementModel).src}}" alt="Image Placeholder"
-           (click)="click($event)"
-           [ngStyle]="style">
-  `,
-  styles: [
-    'img {position: absolute}'
-  ]
+      <img src="{{$any(elementModel).src}}" alt="Image Placeholder" [ngStyle]="style">
+  `
 })
 export class ImageComponent extends CanvasElementComponent { }
diff --git a/projects/common/element-components/label.component.ts b/projects/common/element-components/label.component.ts
index 160ef20e3..a5f010f84 100644
--- a/projects/common/element-components/label.component.ts
+++ b/projects/common/element-components/label.component.ts
@@ -4,20 +4,9 @@ import { CanvasElementComponent } from '../canvas-element-component.directive';
 @Component({
   selector: 'app-label',
   template: `
-      <div *ngIf="draggable"
-           cdkDrag [cdkDragData]="this"
-           (click)="click($event)"
-           [ngStyle]="style">
+      <div [ngStyle]="style">
           {{$any(elementModel).label}}
       </div>
-      <div *ngIf="!draggable"
-           (click)="click($event)"
-           [ngStyle]="style">
-          {{$any(elementModel).label}}
-      </div>
-  `,
-  styles: [
-    'div {position: absolute}'
-  ]
+  `
 })
 export class LabelComponent extends CanvasElementComponent { }
diff --git a/projects/common/element-components/radio-button-group.component.ts b/projects/common/element-components/radio-button-group.component.ts
index b7d7061b2..a00974400 100644
--- a/projects/common/element-components/radio-button-group.component.ts
+++ b/projects/common/element-components/radio-button-group.component.ts
@@ -4,10 +4,7 @@ import { CanvasElementComponent } from '../canvas-element-component.directive';
 @Component({
   selector: 'app-radio-button-group',
   template: `
-      <div *ngIf="draggable"
-           cdkDrag [cdkDragData]="this"
-           (click)="click($event)"
-           [ngStyle]="style">
+      <div [ngStyle]="style">
           <label id="radio-group-label">{{$any(elementModel).text}}</label>
           <mat-radio-group aria-labelledby="radio-group-label" fxLayout="{{elementModel.alignment}}">
               <mat-radio-button *ngFor="let option of $any(elementModel).options" [value]="option">
@@ -15,19 +12,6 @@ import { CanvasElementComponent } from '../canvas-element-component.directive';
               </mat-radio-button>
           </mat-radio-group>
       </div>
-      <div *ngIf="!draggable"
-           (click)="click($event)"
-           [ngStyle]="style">
-          <label id="radio-group-label">{{$any(elementModel).text}}</label>
-          <mat-radio-group aria-labelledby="radio-group-label" fxLayout="{{elementModel.alignment}}">
-              <mat-radio-button *ngFor="let option of $any(elementModel).options" [value]="option">
-                  {{option}}
-              </mat-radio-button>
-          </mat-radio-group>
-      </div>
-  `,
-  styles: [
-    'div {position: absolute}'
-  ]
+  `
 })
 export class RadioButtonGroupComponent extends CanvasElementComponent { }
diff --git a/projects/common/element-components/text-field.component.ts b/projects/common/element-components/text-field.component.ts
index b6c0807f0..5f92fdb19 100644
--- a/projects/common/element-components/text-field.component.ts
+++ b/projects/common/element-components/text-field.component.ts
@@ -4,30 +4,13 @@ import { CanvasElementComponent } from '../canvas-element-component.directive';
 @Component({
   selector: 'app-text-field',
   template: `
-      <div *ngIf="draggable"
-           cdkDrag [cdkDragData]="this">
-          <input *ngIf="$any(elementModel).multiline === false" matInput (click)="click($event)"
-                 [ngStyle]="style"
-                 placeholder="{{$any(elementModel).placeholder}}">
-          <textarea *ngIf="$any(elementModel).multiline === true" matInput
-                    (click)="click($event)"
-                    [ngStyle]="style"
-                    placeholder="{{$any(elementModel).placeholder}}">
-      </textarea>
-      </div>
-      <div *ngIf="!draggable">
-          <input *ngIf="$any(elementModel).multiline === false" matInput (click)="click($event)"
-                 [ngStyle]="style"
-                 placeholder="{{$any(elementModel).placeholder}}">
-          <textarea *ngIf="$any(elementModel).multiline === true" matInput
-                    (click)="click($event)"
-                    [ngStyle]="style"
-                    placeholder="{{$any(elementModel).placeholder}}">
-      </textarea>
-      </div>
-  `,
-  styles: [
-    'div {position: absolute}'
-  ]
+    <input *ngIf="$any(elementModel).multiline === false" matInput
+           [ngStyle]="style"
+           placeholder="{{$any(elementModel).placeholder}}">
+    <textarea *ngIf="$any(elementModel).multiline === true" matInput
+              [ngStyle]="style"
+              placeholder="{{$any(elementModel).placeholder}}">
+    </textarea>
+  `
 })
 export class TextFieldComponent extends CanvasElementComponent { }
diff --git a/projects/common/element-components/video.component.ts b/projects/common/element-components/video.component.ts
index d192e51f9..f70a50223 100644
--- a/projects/common/element-components/video.component.ts
+++ b/projects/common/element-components/video.component.ts
@@ -4,20 +4,12 @@ import { CanvasElementComponent } from '../canvas-element-component.directive';
 @Component({
   selector: 'app-video',
   template: `
-      <div *ngIf="draggable"
-           cdkDrag [cdkDragData]="this"
-           (click)="click($event)"
-           [ngStyle]="style">
-          <video controls src="{{$any(elementModel).src}}"></video>
-      </div>
-      <div *ngIf="!draggable"
-           (click)="click($event)"
-           [ngStyle]="style">
+      <div [ngStyle]="style">
           <video controls src="{{$any(elementModel).src}}"></video>
       </div>
   `,
   styles: [
-    'div {position: absolute; display: inline-block;border: 5px solid; padding: 12px 9px 9px 9px}'
+    'div {display: inline-block;border: 5px solid; padding: 12px 9px 9px 9px}'
   ]
 })
 export class VideoComponent extends CanvasElementComponent { }
diff --git a/projects/editor/src/app/app.module.ts b/projects/editor/src/app/app.module.ts
index a5b58c92e..9408b537a 100644
--- a/projects/editor/src/app/app.module.ts
+++ b/projects/editor/src/app/app.module.ts
@@ -16,6 +16,7 @@ import { CanvasSectionComponent } from './components/unit-view/page-view/canvas/
 import { CanvasSectionToolbarComponent } from './components/unit-view/page-view/canvas/canvas-section-toolbar.component';
 
 import { SharedModule } from '../../../common/app.module';
+import { CanvasDragOverlayComponent } from './components/unit-view/page-view/canvas/canvas-drag-overlay.component';
 
 @NgModule({
   declarations: [
@@ -29,7 +30,8 @@ import { SharedModule } from '../../../common/app.module';
     CanvasToolbarComponent,
     CanvasSectionComponent,
     CanvasSectionToolbarComponent,
-    ConfirmationDialog
+    ConfirmationDialog,
+    CanvasDragOverlayComponent
   ],
   imports: [
     BrowserModule,
diff --git a/projects/editor/src/app/components/unit-view/page-view/canvas/canvas-drag-overlay.component.ts b/projects/editor/src/app/components/unit-view/page-view/canvas/canvas-drag-overlay.component.ts
new file mode 100644
index 000000000..ee83ff6cf
--- /dev/null
+++ b/projects/editor/src/app/components/unit-view/page-view/canvas/canvas-drag-overlay.component.ts
@@ -0,0 +1,105 @@
+import {
+  Component, OnInit, Input, Output,
+  EventEmitter,
+  ComponentFactory, ComponentFactoryResolver,
+  ViewChild, ViewContainerRef
+} from '@angular/core';
+import { UnitUIElement } from '../../../../../../../common/unit';
+import { LabelComponent } from '../../../../../../../common/element-components/label.component';
+import { ButtonComponent } from '../../../../../../../common/element-components/button.component';
+import { TextFieldComponent } from '../../../../../../../common/element-components/text-field.component';
+import { CheckboxComponent } from '../../../../../../../common/element-components/checkbox.component';
+import { DropdownComponent } from '../../../../../../../common/element-components/dropdown.component';
+import { RadioButtonGroupComponent } from '../../../../../../../common/element-components/radio-button-group.component';
+import { ImageComponent } from '../../../../../../../common/element-components/image.component';
+import { AudioComponent } from '../../../../../../../common/element-components/audio.component';
+import { VideoComponent } from '../../../../../../../common/element-components/video.component';
+import { CorrectionComponent } from '../../../../../../../common/element-components/compound-components/correction.component';
+import { CanvasElementComponent } from '../../../../../../../common/canvas-element-component.directive';
+
+@Component({
+  selector: 'app-canvas-drag-overlay',
+  template: `
+    <div cdkDrag [cdkDragData]="this.element" (click)="click($event)"
+         [ngStyle]="style">
+      <ng-template #elementContainer></ng-template>
+    </div>
+    `,
+  styles: [
+    'div {position: absolute}'
+  ]
+})
+export class CanvasDragOverlayComponent implements OnInit {
+  @Input() element!: UnitUIElement;
+  @Output() elementSelected = new EventEmitter<{
+    componentElement: CanvasDragOverlayComponent,
+    multiSelect: boolean }>();
+  @ViewChild('elementContainer', { read: ViewContainerRef, static: true }) private elementContainer!: ViewContainerRef;
+  private childComponent!: CanvasElementComponent;
+  _selected = false;
+  style: Record<string, string> = {};
+
+  constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
+
+  ngOnInit(): void {
+    this.updateStyle();
+    const componentFactory = this.getComponentFactory(this.element.type);
+    this.childComponent = this.elementContainer.createComponent(componentFactory).instance;
+    this.childComponent.elementModel = this.element;
+  }
+
+  set selected(newValue: boolean) {
+    this._selected = newValue;
+    this.updateStyle();
+  }
+
+  updateStyle(): void {
+    this.style = {
+      border: this._selected ? '5px solid' : '',
+      width: `${this.element.width}px`,
+      height: `${this.element.height}px`,
+      left: `${this.element.xPosition.toString()}px`,
+      top: `${this.element.yPosition.toString()}px`
+    };
+    this.childComponent?.updateStyle();
+  }
+
+  click(event: MouseEvent): void {
+    if (event.shiftKey) {
+      this.elementSelected.emit({
+        componentElement: this, multiSelect: true
+      });
+    } else {
+      this.elementSelected.emit({
+        componentElement: this, multiSelect: false
+      });
+    }
+  }
+
+  private getComponentFactory(elementType: string): ComponentFactory<CanvasElementComponent> {
+    switch (elementType) {
+      case 'label':
+        return this.componentFactoryResolver.resolveComponentFactory(LabelComponent);
+      case 'button':
+        return this.componentFactoryResolver.resolveComponentFactory(ButtonComponent);
+      case 'text-field':
+        return this.componentFactoryResolver.resolveComponentFactory(TextFieldComponent);
+      case 'checkbox':
+        return this.componentFactoryResolver.resolveComponentFactory(CheckboxComponent);
+      case 'dropdown':
+        return this.componentFactoryResolver.resolveComponentFactory(DropdownComponent);
+      case 'radio':
+        return this.componentFactoryResolver.resolveComponentFactory(RadioButtonGroupComponent);
+      case 'image':
+        return this.componentFactoryResolver.resolveComponentFactory(ImageComponent);
+      case 'audio':
+        return this.componentFactoryResolver.resolveComponentFactory(AudioComponent);
+      case 'video':
+        return this.componentFactoryResolver.resolveComponentFactory(VideoComponent);
+      case 'correction':
+        return this.componentFactoryResolver.resolveComponentFactory(CorrectionComponent);
+      default:
+        throw new Error('unknown element');
+    }
+  }
+}
diff --git a/projects/editor/src/app/components/unit-view/page-view/canvas/canvas-section.component.ts b/projects/editor/src/app/components/unit-view/page-view/canvas/canvas-section.component.ts
index 3d1ffde40..ae6d5f6e0 100644
--- a/projects/editor/src/app/components/unit-view/page-view/canvas/canvas-section.component.ts
+++ b/projects/editor/src/app/components/unit-view/page-view/canvas/canvas-section.component.ts
@@ -5,17 +5,7 @@ import {
   ViewChild, ViewContainerRef
 } from '@angular/core';
 import { UnitPageSection, UnitUIElement } from '../../../../../../../common/unit';
-import { CanvasElementComponent } from '../../../../../../../common/canvas-element-component.directive';
-import { LabelComponent } from '../../../../../../../common/element-components/label.component';
-import { ButtonComponent } from '../../../../../../../common/element-components/button.component';
-import { TextFieldComponent } from '../../../../../../../common/element-components/text-field.component';
-import { CheckboxComponent } from '../../../../../../../common/element-components/checkbox.component';
-import { DropdownComponent } from '../../../../../../../common/element-components/dropdown.component';
-import { RadioButtonGroupComponent } from '../../../../../../../common/element-components/radio-button-group.component';
-import { ImageComponent } from '../../../../../../../common/element-components/image.component';
-import { AudioComponent } from '../../../../../../../common/element-components/audio.component';
-import { VideoComponent } from '../../../../../../../common/element-components/video.component';
-import { CorrectionComponent } from '../../../../../../../common/element-components/compound-components/correction.component';
+import { CanvasDragOverlayComponent } from './canvas-drag-overlay.component';
 
 @Component({
   selector: '[app-canvas-section]',
@@ -26,9 +16,9 @@ import { CorrectionComponent } from '../../../../../../../common/element-compone
 export class CanvasSectionComponent implements OnInit {
   @Input() section!: UnitPageSection;
   @Input() childrenDraggable: boolean = false;
-  @Output() elementSelected = new EventEmitter<{ componentElement: CanvasElementComponent, multiSelect: boolean }>();
+  @Output() elementSelected = new EventEmitter<{ componentElement: CanvasDragOverlayComponent, multiSelect: boolean }>();
   @ViewChild('elementContainer', { read: ViewContainerRef, static: true }) private elementContainer!: ViewContainerRef;
-  private canvasComponents: CanvasElementComponent[] = [];
+  private canvasComponents: CanvasDragOverlayComponent[] = [];
 
   constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
 
@@ -45,15 +35,15 @@ export class CanvasSectionComponent implements OnInit {
   }
 
   updateElementStyles(): void {
-    this.canvasComponents.forEach((component: CanvasElementComponent) => {
+    this.canvasComponents.forEach((component: CanvasDragOverlayComponent) => {
       component.updateStyle();
     });
   }
 
   updateSelection(selectedElements: UnitUIElement[]): void {
-    this.canvasComponents.forEach((component: CanvasElementComponent) => {
+    this.canvasComponents.forEach((component: CanvasDragOverlayComponent) => {
       selectedElements.forEach((selectedElement: UnitUIElement) => {
-        if (component.elementModel === selectedElement) {
+        if (component.element === selectedElement) {
           component.selected = true;
         }
       });
@@ -61,49 +51,21 @@ export class CanvasSectionComponent implements OnInit {
   }
 
   clearSelection(): void {
-    this.canvasComponents.forEach((canvasComponent: CanvasElementComponent) => {
+    this.canvasComponents.forEach((canvasComponent: CanvasDragOverlayComponent) => {
       canvasComponent.selected = false;
     });
   }
 
   private createCanvasElement(element: UnitUIElement): void {
-    const componentFactory = this.getComponentFactory(element);
-    const componentRef = this.elementContainer.createComponent(componentFactory);
-    componentRef.instance.elementModel = element;
-    componentRef.instance.draggable = this.childrenDraggable;
+    const overlayFactory = this.componentFactoryResolver.resolveComponentFactory(CanvasDragOverlayComponent);
+    const overlayRef = this.elementContainer.createComponent(overlayFactory);
+    overlayRef.instance.element = element;
 
-    componentRef.instance.elementSelected.subscribe(
-      (event: { componentElement: CanvasElementComponent, multiSelect: boolean }) => {
+    overlayRef.instance.elementSelected.subscribe(
+      (event: { componentElement: CanvasDragOverlayComponent, multiSelect: boolean }) => {
         this.elementSelected.emit(event);
       }
     );
-    this.canvasComponents.push(componentRef.instance);
-  }
-
-  private getComponentFactory(element: UnitUIElement) {
-    switch (element.type) {
-      case 'label':
-        return this.componentFactoryResolver.resolveComponentFactory(LabelComponent);
-      case 'button':
-        return this.componentFactoryResolver.resolveComponentFactory(ButtonComponent);
-      case 'text-field':
-        return this.componentFactoryResolver.resolveComponentFactory(TextFieldComponent);
-      case 'checkbox':
-        return this.componentFactoryResolver.resolveComponentFactory(CheckboxComponent);
-      case 'dropdown':
-        return this.componentFactoryResolver.resolveComponentFactory(DropdownComponent);
-      case 'radio':
-        return this.componentFactoryResolver.resolveComponentFactory(RadioButtonGroupComponent);
-      case 'image':
-        return this.componentFactoryResolver.resolveComponentFactory(ImageComponent);
-      case 'audio':
-        return this.componentFactoryResolver.resolveComponentFactory(AudioComponent);
-      case 'video':
-        return this.componentFactoryResolver.resolveComponentFactory(VideoComponent);
-      case 'correction':
-        return this.componentFactoryResolver.resolveComponentFactory(CorrectionComponent);
-      default:
-        throw new Error('unknown element');
-    }
+    this.canvasComponents.push(overlayRef.instance);
   }
 }
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 7aa4258fe..92cb83d0d 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
@@ -8,7 +8,7 @@ import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/dr
 import { UnitPage, UnitPageSection } from '../../../../../../../common/unit';
 import { UnitService } from '../../../../unit.service';
 import { CanvasSectionComponent } from './canvas-section.component';
-import { CanvasElementComponent } from '../../../../../../../common/canvas-element-component.directive';
+import { CanvasDragOverlayComponent } from './canvas-drag-overlay.component';
 
 @Component({
   selector: 'app-page-canvas',
@@ -63,7 +63,7 @@ export class PageCanvasComponent implements OnInit, OnDestroy {
   page!: UnitPage;
   sectionEditMode: boolean = false;
   selectedSectionIndex = 0;
-  selectedComponentElements: CanvasElementComponent[] = [];
+  selectedComponentElements: CanvasDragOverlayComponent[] = [];
 
   @HostListener('window:keydown', ['$event'])
   handleKeyDown(event: KeyboardEvent): void {
@@ -107,12 +107,12 @@ export class PageCanvasComponent implements OnInit, OnDestroy {
     });
   }
 
-  elementSelected(event: { componentElement: CanvasElementComponent; multiSelect: boolean }): void {
+  elementSelected(event: { componentElement: CanvasDragOverlayComponent; multiSelect: boolean }): void {
     if (!event.multiSelect) {
       this.clearSelection();
     }
     this.selectedComponentElements.push(event.componentElement);
-    this.unitService.selectElement(event.componentElement.elementModel);
+    this.unitService.selectElement(event.componentElement.element);
     event.componentElement.selected = true;
   }
 
@@ -126,7 +126,7 @@ export class PageCanvasComponent implements OnInit, OnDestroy {
 
   // TODO use updateSelectedElementProperty
   elementDropped(event: CdkDragDrop<UnitPageSection>): void {
-    const sourceItemModel = event.item.data.elementModel;
+    const sourceItemModel = event.item.data;
 
     if (event.previousContainer !== event.container) {
       transferArrayItem(event.previousContainer.data.elements,
@@ -170,31 +170,31 @@ export class PageCanvasComponent implements OnInit, OnDestroy {
     let newValue: number;
     switch (event) {
       case 'left':
-        newValue = Math.min(...this.selectedComponentElements.map(el => el.elementModel.xPosition));
-        this.selectedComponentElements.forEach((element: CanvasElementComponent) => {
-          element.elementModel.xPosition = newValue;
+        newValue = Math.min(...this.selectedComponentElements.map(el => el.element.xPosition));
+        this.selectedComponentElements.forEach((element: CanvasDragOverlayComponent) => {
+          element.element.xPosition = newValue;
         });
         break;
       case 'right':
         newValue = Math.max(...this.selectedComponentElements.map(
-          el => el.elementModel.xPosition + el.elementModel.width
+          el => el.element.xPosition + el.element.width
         ));
-        this.selectedComponentElements.forEach((element: CanvasElementComponent) => {
-          element.elementModel.xPosition = newValue - element.elementModel.width;
+        this.selectedComponentElements.forEach((element: CanvasDragOverlayComponent) => {
+          element.element.xPosition = newValue - element.element.width;
         });
         break;
       case 'top':
-        newValue = Math.min(...this.selectedComponentElements.map(el => el.elementModel.yPosition));
-        this.selectedComponentElements.forEach((element: CanvasElementComponent) => {
-          element.elementModel.yPosition = newValue;
+        newValue = Math.min(...this.selectedComponentElements.map(el => el.element.yPosition));
+        this.selectedComponentElements.forEach((element: CanvasDragOverlayComponent) => {
+          element.element.yPosition = newValue;
         });
         break;
       case 'bottom':
         newValue = Math.max(...this.selectedComponentElements.map(
-          el => el.elementModel.yPosition + el.elementModel.height
+          el => el.element.yPosition + el.element.height
         ));
-        this.selectedComponentElements.forEach((element: CanvasElementComponent) => {
-          element.elementModel.yPosition = newValue - element.elementModel.height;
+        this.selectedComponentElements.forEach((element: CanvasDragOverlayComponent) => {
+          element.element.yPosition = newValue - element.element.height;
         });
         break;
       // no default
-- 
GitLab