Skip to content
Snippets Groups Projects
element-text-group.component.ts 4.39 KiB
Newer Older
jojohoch's avatar
jojohoch committed
import {
  Component, Input, OnDestroy, OnInit
} from '@angular/core';
import { first, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { TextElement, UIElement } from '../../../../../common/interfaces/elements';
import { TextComponent } from '../../../../../common/components/ui-elements/text.component';
import { TextMarker } from '../../classes/text-marker';
import { NativeEventService } from '../../services/native-event.service';

@Component({
  selector: 'aspect-element-text-group',
  templateUrl: './element-text-group.component.html',
  styleUrls: ['./element-text-group.component.scss']
})
export class ElementTextGroupComponent implements OnInit, OnDestroy {
  @Input() elementModel!: UIElement;
  TextElement!: TextElement;

  isMarkingBarOpen!: boolean;
  selectedColor!: string | null;
  selectedMode!: 'mark' | 'delete' | null;
  markingBarPosition: { top: number, left: number } = { top: 0, left: 0 };
  textComponentRect!: DOMRect;
  textComponentContainerScrollTop!: number;

  private ngUnsubscribe = new Subject<void>();

  constructor(private nativeEventService: NativeEventService) { }

  ngOnInit(): void {
  }

  applySelection(
    selection: { active: boolean; mode: 'mark' | 'delete'; color: string; colorName: string | undefined },
    elementComponent: TextComponent
  ): void {
    if (selection.active) {
      this.selectedColor = selection.color;
      this.selectedMode = selection.mode;
      this.applySelectionToText(selection.mode, selection.color, elementComponent);
    } else {
      this.selectedColor = null;
      this.selectedMode = null;
    }
  }

  startSelection(pointerDown: MouseEvent | TouchEvent, elementComponent: TextComponent): void {
    this.isMarkingBarOpen = false;
    this.nativeEventService.pointerUp
      .pipe(takeUntil(this.ngUnsubscribe), first())
      .subscribe((pointerUp: TouchEvent | MouseEvent) => {
        if (pointerUp.cancelable) {
          pointerUp.preventDefault();
        }
        this.stopSelection(
          this.getClientPointFromEvent(pointerUp),
          pointerUp.ctrlKey,
          this.getClientPointFromEvent(pointerDown),
          elementComponent
        );
      });
  }

  applySelectionToText(mode: 'mark' | 'delete', color: string, elementComponent: TextComponent): void {
    TextMarker
      .applySelection(
        mode,
        color,
        elementComponent
      );
    this.isMarkingBarOpen = false;
  }

  private stopSelection(
    mouseUp: { clientX: number, clientY: number },
    ctrlKey: boolean,
    downPosition: { clientX: number, clientY: number },
    elementComponent: TextComponent
  ) {
    const selection = window.getSelection();
    if (selection && TextMarker.isSelectionValid(selection) && selection.rangeCount > 0) {
      if (!TextMarker.isRangeInside(selection.getRangeAt(0), elementComponent.textContainerRef.nativeElement) ||
        (ctrlKey)) {
        selection.removeAllRanges();
      } else if (this.selectedMode && this.selectedColor) {
        this.applySelectionToText(this.selectedMode, this.selectedColor, elementComponent);
      } else if (!this.isMarkingBarOpen) {
        this.openMarkingBar(mouseUp, downPosition, elementComponent);
      }
    }
  }

  private getClientPointFromEvent = (event: MouseEvent | TouchEvent): { clientX: number, clientY: number } => ({
    clientX: (event instanceof MouseEvent) ? event.clientX : event.changedTouches[0].clientX,
    clientY: (event instanceof MouseEvent) ? event.clientY : event.changedTouches[0].clientY
  });

  private openMarkingBar(
    mouseUp: { clientX: number, clientY: number },
    downPosition: { clientX: number, clientY: number },
    elementComponent: TextComponent
  ) {
    this.markingBarPosition.left = downPosition.clientY > mouseUp.clientY ? downPosition.clientX : mouseUp.clientX;
    this.markingBarPosition.top = downPosition.clientY > mouseUp.clientY ? downPosition.clientY : mouseUp.clientY;
    this.textComponentContainerScrollTop =
      elementComponent.domElement.closest('.fixed-size-content')?.scrollTop || 0;
    this.textComponentRect = elementComponent.domElement.getBoundingClientRect();
    this.isMarkingBarOpen = true;
    this.nativeEventService.pointerDown
      .pipe(takeUntil(this.ngUnsubscribe), first())
      .subscribe(() => this.closeMarkingBar());
  }

  private closeMarkingBar() {
    this.isMarkingBarOpen = false;
  }

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