Newer
Older
import { Component, Input, OnInit } from '@angular/core';
CdkDrag,
CdkDragDrop,
CdkDragStart,
CdkDropList,
moveItemInArray,
transferArrayItem,
copyArrayItem
} from '@angular/cdk/drag-drop';
import { DropListElement } from 'common/models/elements/input-elements/drop-list';
import { DragNDropValueObject } from 'common/models/elements/element';
import { FormElementComponent } from '../../directives/form-element-component.directive';
selector: 'aspect-drop-list',
<div class="list" [id]="elementModel.id"
[class.only-one-item]="elementModel.onlyOneItem"
[class.vertical-orientation]="elementModel.orientation === 'vertical'"
[class.horizontal-orientation]="elementModel.orientation === 'horizontal'"
[class.static-placeholder]="parentForm && elementFormControl.value.length === 1 &&
(elementModel.allowReplacement || (elementModel.copyOnDrop && showsPlaceholder)) &&
!dragging"
[class.highlight-receiver]="classReference.highlightReceivingDropList"
[class.error]="elementFormControl.errors &&
elementFormControl.touched &&
!classReference.highlightReceivingDropList"
(cdkDropListEntered)="showsPlaceholder = true"
(cdkDropListExited)="showsPlaceholder = false"
[cdkDropListData]="this" [cdkDropListConnectedTo]="elementModel.connectedTo"
[cdkDropListOrientation]="$any(elementModel.orientation)"
[cdkDropListSortingDisabled]="elementModel.orientation === 'flex'"
(cdkDropListDropped)="drop($event);"
[style.gap.px]="elementModel.onlyOneItem ? 0 : 5"
[style.color]="elementModel.styling.fontColor"
[style.font-family]="elementModel.styling.font"
[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.backgroundColor]="elementModel.styling.backgroundColor"
[style.border-color]="elementModel.highlightReceivingDropListColor"
<ng-container *ngFor="let dropListValueElement of
parentForm ? elementFormControl.value : elementModel.value; let index = index;">
<div *ngIf="!dropListValueElement.imgSrc"
class="list-item text-list-item"
[class.hide-list-item]="parentForm && (elementModel.allowReplacement || elementModel.copyOnDrop) &&
elementFormControl.value.length === 1 && showsPlaceholder"
cdkDrag [cdkDragData]="dropListValueElement"
(cdkDragStarted)="dragStart($event)"
(cdkDragEnded)="dragEnd()"
[style.background-color]="elementModel.styling.itemBackgroundColor">
<span>{{dropListValueElement.text}}</span>
<ng-template cdkDragPreview matchSize>
<div class="text-preview"
[class.cloze-context-preview]="clozeContext"
[style.color]="elementModel.styling.fontColor"
[style.font-family]="elementModel.styling.font"
[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">
<span>{{dropListValueElement.text}}</span>
</div>
</ng-template>
<div *ngIf="dropListValueElement.imgSrc"
class="list-item image-list-item"
[class.hide-list-item]="parentForm && (elementModel.allowReplacement || elementModel.copyOnDrop) &&
elementFormControl.value.length === 1 && showsPlaceholder"
cdkDrag [cdkDragData]="dropListValueElement"
(cdkDragStarted)="dragStart($event)"
(cdkDragEnded)="dragEnd()">
<span class="baseline-helper"> </span>
<img [src]="dropListValueElement.imgSrc | safeResourceUrl" alt="Image Placeholder">
<ng-template cdkDragPreview matchSize>
<div>
<span class="baseline-helper"> </span>
<img *ngIf="dropListValueElement.imgSrc"
[src]="dropListValueElement.imgSrc | safeResourceUrl" alt="Image Placeholder">
</ng-template>
</div>
<div class="list-item text-list-item" *ngIf="parentForm ?
(!elementFormControl.value.length && !showsPlaceholder) ||
(elementFormControl.value.length === 1 && !showsPlaceholder && dragging) :
!elementModel.value.length;">
<span class="baseline-helper"> </span>
<mat-error *ngIf="elementFormControl.errors &&
elementFormControl.touched &&
!classReference.highlightReceivingDropList"
class="error-message">{{elementFormControl.errors | errorTransform: elementModel}}</mat-error>
</div>
'.list {width: 100%; height: 100%; background-color: rgb(244, 244, 242); border-radius: 5px;}',
'.list {display: flex; box-sizing: border-box; padding: 5px;}',
'.list.vertical-orientation {flex-direction: column;}',
'.list.horizontal-orientation {flex-direction: row;}',
'.list.floating-orientation {place-content: center space-around; align-items: center; flex-flow: row wrap;}',
'.cloze-context.list {padding: 0}',
'.highlight-receiver.cdk-drop-list-receiving {border: 2px solid;}',
'.highlight-receiver.cdk-drop-list-receiving:not(.cloze-context) {padding: 3px;}',
'.error {padding: 3px; border: 2px solid #f44336 !important;}',
'.list-item:active {cursor: grabbing;}',
':not(.cloze-context) .list-item.text-list-item {padding: 10px;}',
'.cloze-context .list-item.text-list-item {padding: 0 5px;}',
'.cloze-context.only-one-item .list-item {height: 100%; display: flex; align-items: center; justify-content: center;}',
'.image-list-item {align-self: flex-start;}',
'.hide-list-item {display: none !important; transform: unset !important;}',
'.cdk-drag-preview {border-radius: 5px; box-shadow: 2px 2px 5px black;}',
'.cdk-drag-preview.text-preview {padding: 10px; box-sizing: border-box;}',
'.cdk-drag-preview.cloze-context-preview {padding: 0 2px; display: flex; align-items: center; justify-content: center;}',
'.cdk-drop-list-dragging .cdk-drag {transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);}',
'.cdk-drag-placeholder {background-color: #ccc !important;}',
'.cdk-drag-placeholder {transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);}',
'.cdk-drag-placeholder * {visibility: hidden;}',
'.static-placeholder .cdk-drag-placeholder {transform: unset !important;}',
'.error-message {font-size: 75%;}',
'.baseline-helper {width: 0; display: inline-block;}'
export class DropListComponent extends FormElementComponent implements OnInit {
@Input() elementModel!: DropListElement;
dragging = false;
showsPlaceholder = false;
classReference = DropListComponent;
static highlightReceivingDropList = false;
static dragAndDropComponents: { [id: string]: DropListComponent } = {};
ngOnInit() {
super.ngOnInit();
DropListComponent.dragAndDropComponents[this.elementModel.id] = this;
}
dragStart(event: CdkDragStart) {
DropListComponent.setHighlighting(event.source.dropContainer.data.elementModel.highlightReceivingDropList);
// add class for cursor while dragging
this.showsPlaceholder = true;
}
dragEnd() {
DropListComponent.setHighlighting(false);
document.body.classList.remove('dragging-active');
Object.values(DropListComponent.dragAndDropComponents).forEach(d => { d.showsPlaceholder = false; });
}
static setHighlighting(showHighlight: boolean) {
DropListComponent.highlightReceivingDropList = showHighlight;
}
moveItemInArray(event.container.data.elementFormControl.value, event.previousIndex, event.currentIndex);
} else if (DropListComponent.isPutBack(event.item, event.container)) {
event.previousContainer.data.elementFormControl.value.splice(event.previousIndex, 1);
} else if (DropListComponent.isReplace(event)) {
const isToReplaceItemAlreadyInOrigin: boolean =
event.container.data.elementFormControl.value[0].originListID === event.container.data.elementModel.id;
return;
}
// splice first and hold the replaced item, then move. to prevent indix mixup
const replacedItem: DragNDropValueObject = event.container.data.elementFormControl.value.splice(0, 1)[0];
DropListComponent.moveItem(event);
const originComponent = DropListComponent.dragAndDropComponents[replacedItem.originListID];
const isIDAlreadyPresentInOrigin = DropListComponent.isItemIDAlreadyPresent(
replacedItem.id,
originComponent.elementFormControl.value);
if (!(originComponent.elementModel.copyOnDrop && isIDAlreadyPresentInOrigin)) {
DropListComponent.addElementToList(originComponent, replacedItem);
}
} else {
DropListComponent.moveItem(event);
static moveItem(event: CdkDragDrop<any>): void {
if (DropListComponent.isCopyDrop(event)) {
copyArrayItem(
event.previousContainer.data.elementFormControl.value,
event.container.data.elementFormControl.value,
event.previousIndex,
event.currentIndex);
DropListComponent.transferItem(event.previousContainer, event.container, event.previousIndex, event.currentIndex);
}
event.previousContainer.data.updateFormvalue();
event.container.data.updateFormvalue();
}
static transferItem(previousContainer: CdkDropList, newContainer: CdkDropList,
previousIndex: number, newIndex: number): void {
transferArrayItem(
previousContainer.data.elementFormControl.value,
newContainer.data.elementFormControl.value,
previousIndex,
newIndex
);
}
static addElementToList(listComponent: DropListComponent, element: DragNDropValueObject): void {
const targetIndex = Math.min(listComponent.elementFormControl.value.length, element.originListIndex || 0);
listComponent.elementFormControl.value.splice(targetIndex, 0, element);
listComponent.elementFormControl.setValue(listComponent.elementFormControl.value);
static isReorderDrop(event: CdkDragDrop<any>): boolean {
return event.previousContainer === event.container;
}
static isCopyDrop(event: CdkDragDrop<any>): boolean {
return event.previousContainer.data.elementModel.copyOnDrop;
}
static isPutBack(draggedItem: CdkDrag, list: CdkDropList): boolean {
return list.data.elementModel.copyOnDrop &&
DropListComponent.isItemIDAlreadyPresent(draggedItem.data.id, list.data.elementFormControl.value);
static isReplace(event: CdkDragDrop<any>): boolean {
return event.container.data.elementFormControl.value.length === 1 &&
event.container.data.elementModel.allowReplacement;
}
static isItemIDAlreadyPresent(itemID: string, valueList: DragNDropValueObject[]): boolean {
const listValueIDs = valueList.map((valueValue: DragNDropValueObject) => valueValue.id);
return listValueIDs.includes(itemID);
}
this.elementFormControl.setValue(this.elementFormControl.value);
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
validDropPredicate = (draggedItem: CdkDrag, targetList: CdkDropList): boolean => {
if (!DropListComponent.isItemIDAlreadyPresent(draggedItem.data.id, targetList.data.elementFormControl.value) &&
!targetList.data.elementModel.onlyOneItem) {
return true;
}
if (targetList.data.elementModel.onlyOneItem && targetList.data.elementFormControl.value.length < 1) {
return true;
}
if (targetList.data.elementModel.onlyOneItem &&
targetList.data.elementFormControl.value.length > 0 &&
(targetList.data.elementModel.allowReplacement && DropListComponent.containedItemIsReplacable(targetList))) {
return true;
}
if (DropListComponent.isItemIDAlreadyPresent(draggedItem.data.id, targetList.data.elementFormControl.value) &&
targetList.data.elementModel.onlyOneItem &&
(targetList.data.elementModel.allowReplacement && DropListComponent.containedItemIsReplacable(targetList))) {
return true;
}
if (DropListComponent.isPutBack(draggedItem, targetList)) {
return true;
}
return false;
};
/* To be replacable an item must not be in it's origin. Otherwise it has nowhere to go to. */
static containedItemIsReplacable(list: CdkDropList): boolean {
return list.data.elementFormControl.value[0].originListID !== list.data.elementModel.id;
}