Skip to content

Commit

Permalink
fix: children did not move together after dragging #INFR-7103 (#344)
Browse files Browse the repository at this point in the history
  • Loading branch information
walkerkay authored Mar 30, 2023
1 parent 2e78378 commit 9697d2e
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 48 deletions.
2 changes: 0 additions & 2 deletions example/src/app/gantt/gantt.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@
[items]="items"
[baselineItems]="baselineItems"
[viewType]="viewType"
[async]="true"
[childrenResolve]="childrenResolve"
[draggable]="true"
[linkable]="true"
[selectable]="true"
Expand Down
45 changes: 31 additions & 14 deletions example/src/app/gantt/gantt.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
GanttBaselineItem,
GanttView,
GanttToolbarOptions,
GanttTableDragEnterPredicateContext
GanttTableDragEnterPredicateContext,
GanttTableDragDroppedEvent
} from 'ngx-gantt';
import { finalize, of } from 'rxjs';
import { delay } from 'rxjs/operators';
Expand Down Expand Up @@ -59,12 +60,12 @@ export class AppGanttExampleComponent implements OnInit, AfterViewInit {
loading = false;

items: GanttItem[] = [
{ id: '000000', title: 'Task 0', start: 1627729997, end: 1628421197, expandable: true },
// { id: '000001', title: 'Task 1', start: 1617361997, end: 1625483597, links: ['000003', '000004', '000000'], expandable: true },
{ id: '000001', title: 'Task 1', start: 1617361997, end: 1625483597, links: ['000003', '000004', '0000029'], expandable: true },
{ id: '000000', title: 'Task 0', start: 1627729997, end: 1628421197 },
// { id: '000001', title: 'Task 1', start: 1617361997, end: 1625483597, links: ['000003', '000004', '000000'], },
{ id: '000001', title: 'Task 1', start: 1617361997, end: 1625483597, links: ['000003', '000004', '0000029'] },
{ id: '000002', title: 'Task 2', start: 1610536397, end: 1610622797, progress: 0.5 },
{ id: '000003', title: 'Task 3 (不可拖动)', start: 1628507597, end: 1633345997, expandable: true, itemDraggable: false },
{ id: '000004', title: 'Task 4', start: 1624705997, expandable: true },
{ id: '000003', title: 'Task 3 (不可拖动)', start: 1628507597, end: 1633345997, itemDraggable: false },
{ id: '000004', title: 'Task 4', start: 1624705997 },
{ id: '000005', title: 'Task 5', start: 1628075597, end: 1629544397, color: '#709dc1' },
{ id: '000006', title: 'Task 6', start: 1641121997, end: 1645528397 },
{ id: '000007', title: 'Task 7', start: 1639393997, end: 1640862797 },
Expand Down Expand Up @@ -112,18 +113,22 @@ export class AppGanttExampleComponent implements OnInit, AfterViewInit {

@ViewChild('gantt') ganttComponent: NgxGanttComponent;

childrenResolve = (item: GanttItem) => {
const children = randomItems(random(1, 5), item);
return of(children).pipe(delay(1000));
};

dropEnterPredicate = (event: GanttTableDragEnterPredicateContext) => {
return true;
};

constructor(private printService: GanttPrintService, private thyNotify: ThyNotifyService) {}

ngOnInit(): void {}
ngOnInit(): void {
// init items children
this.items.forEach((item, index) => {
if (index % 5 === 0) {
item.children = randomItems(random(1, 5), item);
}
});

console.log(this.items);
}

ngAfterViewInit() {
setTimeout(() => this.ganttComponent.scrollToDate(1627729997), 200);
Expand Down Expand Up @@ -202,7 +207,19 @@ export class AppGanttExampleComponent implements OnInit, AfterViewInit {
});
}

onDragDropped(event) {
console.log(event);
onDragDropped(event: GanttTableDragDroppedEvent) {
const sourceItems = event.sourceParent?.children || this.items;
sourceItems.splice(sourceItems.indexOf(event.source), 1);
if (event.dropPosition === 'inside') {
event.target.children = [...(event.target.children || []), event.source];
} else {
const targetItems = event.targetParent?.children || this.items;
if (event.dropPosition === 'before') {
targetItems.splice(targetItems.indexOf(event.target), 0, event.source);
} else {
targetItems.splice(targetItems.indexOf(event.target) + 1, 0, event.source);
}
}
this.items = [...this.items];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
class="gantt-expand-icon"
[style.marginLeft.px]="item.level * 20"
>
<ng-container *ngIf="item.level < gantt.maxLevel - 1 && item.expandable">
<ng-container *ngIf="item.level < gantt.maxLevel - 1 && ((gantt.async && item.expandable) || item.children?.length > 0)">
<gantt-icon
*ngIf="!item.loading"
class="expand-icon"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { debounceTime, filter, startWith, Subject, takeUntil } from 'rxjs';
import { auditTime, debounceTime, filter, startWith, Subject, takeUntil } from 'rxjs';
import {
Component,
HostBinding,
Expand Down Expand Up @@ -27,7 +27,7 @@ import { coerceCssPixelValue } from '@angular/cdk/coercion';
import { GanttAbstractComponent, GANTT_ABSTRACT_TOKEN } from '../../../gantt-abstract';
import { defaultColumnWidth } from '../header/gantt-table-header.component';
import { GanttUpper, GANTT_UPPER_TOKEN } from '../../../gantt-upper';
import { CdkDrag, CdkDragDrop, CdkDragEnd, CdkDragMove, CdkDragStart } from '@angular/cdk/drag-drop';
import { CdkDrag, CdkDragDrop, CdkDragEnd, CdkDragMove, CdkDragStart, DragRef } from '@angular/cdk/drag-drop';
import { DOCUMENT } from '@angular/common';
@Component({
selector: 'gantt-table-body',
Expand Down Expand Up @@ -83,7 +83,7 @@ export class GanttTableBodyComponent implements OnInit, OnDestroy, AfterViewInit
public hasExpandIcon = false;

// 缓存 Element 和 DragRef 的关系,方便在 Item 拖动时查找
private itemDragRefMap = new Map<HTMLElement, CdkDrag<GanttItemInternal>>();
private itemDragsMap = new Map<HTMLElement, CdkDrag<GanttItemInternal>>();

private itemDragMoved = new Subject<CdkDragMove>();

Expand Down Expand Up @@ -121,19 +121,19 @@ export class GanttTableBodyComponent implements OnInit, OnDestroy, AfterViewInit
this.cdkDrags.changes
.pipe(startWith(this.cdkDrags), takeUntil(this.destroy$))
.subscribe((drags: QueryList<CdkDrag<GanttItemInternal>>) => {
this.itemDragRefMap.clear();
this.itemDragsMap.clear();
drags.forEach((drag) => {
if (drag.data) {
// cdkDrag 变化时,缓存 Element 与 DragRef 的关系,方便 Drag Move 时查找
this.itemDragRefMap.set(drag.element.nativeElement, drag);
this.itemDragsMap.set(drag.element.nativeElement, drag);
}
});
});

this.itemDragMoved
.pipe(
debounceTime(30),
// debounce 可能会导致拖动结束后仍然执行 moved ,所以通过判断 dragging 状态来过滤无效 moved
auditTime(30),
// auditTime 可能会导致拖动结束后仍然执行 moved ,所以通过判断 dragging 状态来过滤无效 moved
filter((event: CdkDragMove) => event.source._dragRef.isDragging()),
takeUntil(this.destroy$)
)
Expand Down Expand Up @@ -181,13 +181,13 @@ export class GanttTableBodyComponent implements OnInit, OnDestroy, AfterViewInit

// 缓存放置目标Id 并计算鼠标相对应的位置
this.itemDropTarget = {
id: this.itemDragRefMap.get(targetElement)?.data.id,
id: this.itemDragsMap.get(targetElement)?.data.id,
position: this.getTargetPosition(targetElement, event)
};

// 执行外部传入的 dropEnterPredicate 判断是否允许拖入目标项
if (this.dropEnterPredicate) {
const targetDragRef = this.itemDragRefMap.get(targetElement);
const targetDragRef = this.itemDragsMap.get(targetElement);
if (
this.dropEnterPredicate({
source: event.source.data.origin,
Expand All @@ -212,29 +212,34 @@ export class GanttTableBodyComponent implements OnInit, OnDestroy, AfterViewInit
if (!this.itemDropTarget) {
return;
}
const targetDragRef = this.cdkDrags.find((item) => item.data?.id === this.itemDropTarget.id);

const sourceItem = event.item.data;
const sourceParent = this.getParentByItem(sourceItem);
const sourceChildren = this.getExpandChildrenByDrag(event.item);

const targetDragRef = this.cdkDrags.find((item) => item.data?.id === this.itemDropTarget.id);
const targetItem = targetDragRef?.data;
const targetParent = this.getParentByItem(targetItem);

this.removeItem(sourceItem);
this.removeItem(sourceItem, sourceChildren);

switch (this.itemDropTarget.position) {
case 'before':
case 'after':
this.insertItem(targetItem, sourceItem, this.itemDropTarget.position);
this.insertItem(targetItem, sourceItem, sourceChildren, this.itemDropTarget.position);
sourceItem.updateLevel(targetItem.level);
break;
case 'inside':
this.insertChildrenItem(targetItem, sourceItem);
this.insertChildrenItem(targetItem, sourceItem, sourceChildren);
sourceItem.updateLevel(targetItem.level + 1);
break;
}

this.dragDropped.emit({
source: sourceItem.origin,
sourceParent: this.getParentByItem(sourceItem)?.origin,
sourceParent: sourceParent?.origin,
target: targetItem.origin,
targetParent: this.getParentByItem(targetItem)?.origin,
targetParent: targetParent?.origin,
dropPosition: this.itemDropTarget.position
});

Expand All @@ -250,31 +255,36 @@ export class GanttTableBodyComponent implements OnInit, OnDestroy, AfterViewInit
this.destroy$.complete();
}

private removeItem(item: GanttItemInternal) {
this.renderData.splice(this.renderData.indexOf(item), 1);
this.flatData.splice(this.flatData.indexOf(item), 1);
private removeItem(item: GanttItemInternal, children: GanttItemInternal[]) {
this.renderData.splice(this.renderData.indexOf(item), 1 + children.length);
this.flatData.splice(this.flatData.indexOf(item), 1 + children.length);
}

private insertItem(target: GanttItemInternal, inserted: GanttItemInternal, position: 'before' | 'after') {
private insertItem(
target: GanttItemInternal,
inserted: GanttItemInternal,
children: GanttItemInternal[],
position: 'before' | 'after'
) {
if (position === 'before') {
this.renderData.splice(this.renderData.indexOf(target), 0, inserted);
this.flatData.splice(this.flatData.indexOf(target), 0, inserted);
this.renderData.splice(this.renderData.indexOf(target), 0, inserted, ...children);
this.flatData.splice(this.flatData.indexOf(target), 0, inserted, ...children);
} else {
const dragRef = this.cdkDrags.find((drag) => drag.data === target);
// 如果目标项是展开的,插入的 index 位置需要考虑子项的数量
let childrenCount = 0;
if (target.expanded) {
childrenCount = this.getChildrenElementsByElement(dragRef.element.nativeElement)?.length || 0;
}
this.renderData.splice(this.renderData.indexOf(target) + 1 + childrenCount, 0, inserted);
this.flatData.splice(this.flatData.indexOf(target) + 1 + childrenCount, 0, inserted);
this.renderData.splice(this.renderData.indexOf(target) + 1 + childrenCount, 0, inserted, ...children);
this.flatData.splice(this.flatData.indexOf(target) + 1 + childrenCount, 0, inserted, ...children);
}
}

private insertChildrenItem(target: GanttItemInternal, inserted: GanttItemInternal) {
private insertChildrenItem(target: GanttItemInternal, inserted: GanttItemInternal, children: GanttItemInternal[]) {
if (target.expanded) {
this.renderData.splice(this.renderData.indexOf(target) + target.children.length + 1, 0, inserted);
this.flatData.splice(this.flatData.indexOf(target) + target.children.length + 1, 0, inserted);
this.renderData.splice(this.renderData.indexOf(target) + target.children.length + 1, 0, inserted, ...children);
this.flatData.splice(this.flatData.indexOf(target) + target.children.length + 1, 0, inserted, ...children);
}
target.children.push(inserted);
}
Expand All @@ -285,19 +295,28 @@ export class GanttTableBodyComponent implements OnInit, OnDestroy, AfterViewInit
});
}

private getExpandChildrenByDrag(dragRef: CdkDrag<GanttItemInternal>) {
if (!dragRef.data.expanded) {
return [];
} else {
const childrenElements = this.getChildrenElementsByElement(dragRef.element.nativeElement);
return childrenElements.map((element) => this.itemDragsMap.get(element).data);
}
}

private getChildrenElementsByElement(dragElement: HTMLElement) {
// 通过循环持续查找 next element,如果 element 的 level 小于当前 item 的 level,则为它的 children
const children: HTMLElement[] = [];
const dragRef = this.itemDragRefMap.get(dragElement);
const dragRef = this.itemDragsMap.get(dragElement);

// 如果当前的 Drag 正在拖拽,会创建 PlaceholderElement 占位,所以以 PlaceholderElement 向下查找
let nextElement = (dragRef.getPlaceholderElement() || dragElement).nextElementSibling as HTMLElement;
let nextDragRef = this.itemDragRefMap.get(nextElement);
let nextDragRef = this.itemDragsMap.get(nextElement);

while (nextDragRef && nextDragRef.data.level > dragRef.data.level) {
children.push(nextElement);
nextElement = nextElement.nextElementSibling as HTMLElement;
nextDragRef = this.itemDragRefMap.get(nextElement);
nextDragRef = this.itemDragsMap.get(nextElement);
}

return children;
Expand Down Expand Up @@ -326,10 +345,10 @@ export class GanttTableBodyComponent implements OnInit, OnDestroy, AfterViewInit
private cleanupDragArtifacts(dropped = false) {
if (dropped) {
this.itemDropTarget = null;
this.document.querySelectorAll('.drag-item-hide').forEach((element) => element.classList.remove('drag-item-hide'));
}
this.document.querySelectorAll('.drop-position-before').forEach((element) => element.classList.remove('drop-position-before'));
this.document.querySelectorAll('.drop-position-after').forEach((element) => element.classList.remove('drop-position-after'));
this.document.querySelectorAll('.drop-position-inside').forEach((element) => element.classList.remove('drop-position-inside'));
this.document.querySelectorAll('.drag-item-hide').forEach((element) => element.classList.remove('drop-item-hide'));
}
}
1 change: 1 addition & 0 deletions packages/gantt/src/gantt-abstract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface GanttAbstractComponent {
table: NgxGanttTableComponent;
styles: GanttStyles;
maxLevel: number;
async: boolean;
cdr: ChangeDetectorRef;
expandGroup(group: GanttGroupInternal): void;
expandChildren(item: GanttItemInternal): void;
Expand Down
2 changes: 1 addition & 1 deletion packages/gantt/src/gantt.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
OnChanges,
SimpleChanges
} from '@angular/core';
import { startWith, takeUntil, take, finalize, skip } from 'rxjs/operators';
import { takeUntil, take, finalize, skip } from 'rxjs/operators';
import { Observable, from } from 'rxjs';
import { GanttUpper, GANTT_UPPER_TOKEN } from './gantt-upper';
import { GanttLinkDragEvent, GanttLineClickEvent, GanttItemInternal, GanttItem, GanttSelectedEvent, GanttGroupInternal } from './class';
Expand Down

0 comments on commit 9697d2e

Please sign in to comment.