Skip to content
Snippets Groups Projects
canvas-element-overlay.ts 4.5 KiB
Newer Older
  Directive, Input, ComponentRef,
  ViewChild, ViewContainerRef, OnInit, OnDestroy, ChangeDetectorRef, Output, EventEmitter
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ElementComponent } from 'common/directives/element-component.directive';
import { CompoundElementComponent } from 'common/directives/compound-element.directive';
import { ClozeComponent } from 'common/components/compound-elements/cloze/cloze.component';
import { CompoundChildOverlayComponent } from
  'common/components/compound-elements/cloze/compound-child-overlay.component';
import { UIElement } from 'common/models/elements/element';
import { GeometryComponent } from 'common/components/geometry/geometry.component';
import { GeometryElement } from 'common/models/elements/geometry/geometry';
rhenck's avatar
rhenck committed
import { UnitService } from '../../../services/unit.service';
import { SelectionService } from '../../../services/selection.service';
export abstract class CanvasElementOverlay implements OnInit, OnDestroy {
  @Input() element!: UIElement;
  @Output() elementSelected = new EventEmitter();
  @ViewChild('elementContainer', { read: ViewContainerRef, static: true }) private elementContainer!: ViewContainerRef;
  isSelected = false;
  protected childComponent!: ComponentRef<ElementComponent | CompoundElementComponent>;
  private ngUnsubscribe = new Subject<void>();

  temporaryHighlight: boolean = false;

  constructor(public selectionService: SelectionService,
              protected unitService: UnitService,
              private changeDetectorRef: ChangeDetectorRef) { }
    this.childComponent = this.elementContainer.createComponent(this.element.getElementComponent());
    this.childComponent.instance.elementModel = this.element;

    // Make children not clickable. This way the only relevant events are managed by the overlay.
    this.childComponent.location.nativeElement.style.pointerEvents = 'none';
    if (this.childComponent.instance instanceof GeometryComponent) {
      this.childComponent.instance.appDefinition = (this.element as GeometryElement).appDefinition;
    }
    if (this.childComponent.instance instanceof ClozeComponent) {
      // make cloze element children clickable to access child elements
      this.childComponent.instance.editorMode = true;
      this.childComponent.location.nativeElement.style.pointerEvents = 'unset';
      this.childComponent.instance.childElementSelected
        .pipe(takeUntil(this.ngUnsubscribe))
        .subscribe((elementSelectionEvent: CompoundChildOverlayComponent) => {
          this.selectionService.selectElement({ elementComponent: elementSelectionEvent, multiSelect: false });

    this.selectionService.selectElement({ elementComponent: this, multiSelect: false });

    // Geogebra element needs to know when its props are updated, re-init itself
    this.unitService.geometryElementPropertyUpdated
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(
        (elementID: string) => {
          if (this.element.type === 'geometry' && this.element.id === elementID) {
            (this.childComponent.instance as GeometryComponent).refresh();
          }
        }
      );
  }

  setSelected(newValue: boolean): void {
    this.temporaryHighlight = true;
    setTimeout(() => {
      this.temporaryHighlight = false;
    }, 2000);
    this.isSelected = newValue;
    // This avoids: "NG0100: Expression has changed after it was checked"
    // The selection service may change the "selected" variable after onInit has run.
    // Therefore we need to run it again after this.
    this.changeDetectorRef.detectChanges();
  selectElement(event?: MouseEvent): void {
    if (!this.isSelected) {
      // this.selectElement(event.shiftKey);
      if (event?.shiftKey) {
        this.selectionService.selectElement({ elementComponent: this, multiSelect: true });
      } else {
        this.selectionService.selectElement({ elementComponent: this, multiSelect: false });
      }
    event?.stopPropagation();
    this.elementSelected.emit();
  }

  openEditDialog(): void {
    this.unitService.showDefaultEditDialog(this.element);
  }

  setInteractionEnabled(isEnabled: boolean): void {
    this.childComponent.location.nativeElement.style.pointerEvents = isEnabled ? 'unset' : 'none';
  }

  isInteractionEnabled(): boolean {
    return this.childComponent.location.nativeElement.style.pointerEvents !== 'none';
  }

  ngOnDestroy(): void {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }
}