Code owners
Assign users and groups as approvers for specific file changes. Learn more.
drop-list.component.ts 7.07 KiB
import {
Component, Input, OnInit,
ComponentRef, ElementRef,
ChangeDetectorRef, Renderer2,
ViewChild, ViewChildren, QueryList
} from '@angular/core';
import { Overlay } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { DropListElement } from 'common/models/elements/input-elements/drop-list';
import { DragNDropValueObject } from 'common/models/elements/label-interfaces';
import { DragEvent, DragStartEvent } from 'common/components/input-elements/drop-list/draggable.directive';
import { FormElementComponent } from 'common/directives/form-element-component.directive';
import { DragImageComponent } from './drag-image.component';
import { DragOperatorService } from './drag-operator.service';
@Component({
selector: 'aspect-drop-list',
template: `
<span *ngIf="clozeContext" [style.width.px]="0"> </span>
<!-- TODO testen, ob touchstart keine Probleme macht -->
<div class="drop-list" id="{{elementModel.id}}" #droplist
[class.cloze-context]="clozeContext"
[class.hovered]="!elementModel.isSortList && isHovered"
[class.isHighlighted]="isHighlighted"
[class.column]="elementModel.orientation === 'vertical'"
[class.row]="elementModel.orientation === 'horizontal'"
[class.float]="elementModel.orientation === 'flex'"
[style.outline-color]="elementModel.highlightReceivingDropListColor"
[style.border-color]="elementModel.highlightReceivingDropListColor"
[style.color]="elementModel.styling.fontColor"
[style.font-size.px]="elementModel.styling.fontSize"
[style.font-weight]="elementModel.styling.bold ? 'bold' : ''"
[style.font-style]="elementModel.styling.italic ? 'italic' : ''"
[style.text-decoration]="elementModel.styling.underline ? 'underline' : ''"
[style.background-color]="elementModel.styling.backgroundColor"
(touchstart)="elementFormControl.markAsTouched()"
(click)="elementFormControl.markAsTouched()">
<div *ngFor="let item of viewModel; let i = index;"
class="drop-list-item" [class.image-item]="item.imgSrc"
#listItem
aspect-draggable data-aspect-draggable="true"
(dragStart)="dragStart($event, item, i, this)"
(dragMove)="dragMove($event)"
(dragEnd)="dragEnd()"
[style.color]="elementModel.styling.fontColor"
[style.font-size.px]="elementModel.styling.fontSize"
[style.font-weight]="elementModel.styling.bold ? 'bold' : ''"
[style.font-style]="elementModel.styling.italic ? 'italic' : ''"
[style.text-decoration]="elementModel.styling.underline ? 'underline' : ''"
[style.background-color]="elementModel.styling.itemBackgroundColor"
[class.read-only]="elementModel.readOnly">
<aspect-text-image-panel [label]="item"></aspect-text-image-panel>
</div>
<mat-error *ngIf="elementFormControl.errors && elementFormControl.touched"
class="error-message">
{{elementFormControl.errors | errorTransform: elementModel}}
</mat-error>
</div>
`,
styleUrls: ['./drop-list.component.css']
})
export class DropListComponent extends FormElementComponent implements OnInit {
@Input() elementModel!: DropListElement;
@Input() clozeContext: boolean = false;
@ViewChild('droplist') droplistRef: ElementRef | undefined;
@ViewChildren('listItem') droplistItems: QueryList<ElementRef> | undefined;
viewModel!: DragNDropValueObject[];
dragImageRef: ComponentRef<DragImageComponent> | undefined;
isHovered = false;
isHighlighted = false;
private unlistenMouseEnter: (() => void) | undefined;
private unlistenMouseLeave: (() => void) | undefined;
private unlistenItemMouseEnter: (() => void)[] = [];
constructor(public dragOpService: DragOperatorService, public elementRef: ElementRef,
private renderer2: Renderer2, public cdr: ChangeDetectorRef, private overlay: Overlay) {
super(elementRef);
}
ngOnInit() {
super.ngOnInit();
this.viewModel = this.parentForm ? [...this.elementFormControl.value] : this.elementModel.value;
this.dragOpService.registerComponent(this);
this.initDragImageOverlay();
}
dragStart(event: DragStartEvent, item: DragNDropValueObject, index: number, sourceListComponent: DropListComponent) {
this.dragOpService.startDrag(event.sourceElement, sourceListComponent, index, item, event.dragType);
this.dragImageRef?.instance.initDragPreview(item, event.sourceElement, event.x, event.y);
document.body.classList.add('dragging-active'); // add class for cursor while dragging
this.cdr.detectChanges(); // needed for placeholder
}
dragMove(e: DragEvent) {
this.dragImageRef?.instance.setDragPreviewPosition(e.x, e.y);
if (this.dragOpService.dragOperation?.dragType === 'touch') this.dragOpService.checkHoveredListOrElement(e.x, e.y);
}
dragEnter(): void {
this.isHovered = true;
this.dragOpService.setTargetList(this.elementModel.id);
this.cdr.detectChanges();
}
dragLeave(): void {
this.isHovered = false;
this.dragOpService.unSetTargetList();
if (this.elementModel.isSortList) {
this.refreshViewModel();
}
this.cdr.detectChanges();
}
dragEnd() {
this.dragOpService.handleDrop();
this.dragImageRef?.instance.unsetDragPreview();
this.dragOpService.endDrag();
document.body.classList.remove('dragging-active'); // remove class for cursor while dragging
}
listenForHover() {
this.unlistenMouseEnter = this.renderer2.listen(this.droplistRef?.nativeElement, 'mouseenter', () => {
// Workaround for Windows+Firefox, which creates the event after reordering, thus triggering a source index reset.
if (this.dragOpService.isListHovered) return;
this.dragEnter();
});
this.unlistenMouseLeave = this.renderer2.listen(this.droplistRef?.nativeElement, 'mouseleave', () => {
this.dragLeave();
});
if (this.elementModel.isSortList) {
this.droplistItems?.toArray()
.forEach(listItem => {
this.unlistenItemMouseEnter.push(
this.renderer2.listen(listItem.nativeElement, 'mouseenter', (event: MouseEvent) => {
const targetIndex = Array.from(this.droplistRef?.nativeElement.children).indexOf(event.target);
this.dragOpService.positionSortPlaceholder(targetIndex);
this.cdr.detectChanges();
})
);
});
}
}
stopListenForHover() {
this.unlistenMouseEnter?.();
this.unlistenMouseLeave?.();
this.unlistenItemMouseEnter.forEach(listener => listener());
}
initDragImageOverlay() {
const overlayRef = this.overlay.create();
const componentPortal = new ComponentPortal(DragImageComponent);
this.dragImageRef = overlayRef.attach(componentPortal);
this.dragImageRef.instance.clozeContext = this.clozeContext;
this.dragImageRef.instance.styling = this.elementModel.styling;
}
refreshViewModel() {
this.viewModel = [...this.elementFormControl.value];
}
updateFormvalue(): void {
this.elementFormControl.setValue(this.elementFormControl.value);
}
}