Newer
Older
ViewChild, ViewContainerRef, OnInit, OnDestroy, ChangeDetectorRef, Output, EventEmitter
} from '@angular/core';
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';
import { UnitService } from '../../../services/unit.service';
import { SelectionService } from '../../../services/selection.service';
@Directive()
export abstract class CanvasElementOverlay implements OnInit, OnDestroy {
@Input() element!: UIElement;
@Output() elementSelected = new EventEmitter();
@ViewChild('elementContainer', { read: ViewContainerRef, static: true }) private elementContainer!: ViewContainerRef;
protected childComponent!: ComponentRef<ElementComponent | CompoundElementComponent>;
private ngUnsubscribe = new Subject<void>();
constructor(public selectionService: SelectionService,
protected unitService: UnitService,
private changeDetectorRef: ChangeDetectorRef) { }
ngOnInit(): void {
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 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 {
// this.selectElement(event.shiftKey);
if (event?.shiftKey) {
this.selectionService.selectElement({ elementComponent: this, multiSelect: true });
} else {
this.selectionService.selectElement({ elementComponent: this, multiSelect: false });
}
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';
}