diff --git a/packages/gantt/src/class/event.ts b/packages/gantt/src/class/event.ts index 2f973466..f8b7e099 100644 --- a/packages/gantt/src/class/event.ts +++ b/packages/gantt/src/class/event.ts @@ -1,6 +1,7 @@ import { QueryList } from '@angular/core'; import { NgxGanttTableColumnComponent } from '../table/gantt-column.component'; import { GanttItem } from './item'; +import { GanttLinkType } from './link'; export class GanttDragEvent { item: GanttItem; @@ -13,6 +14,7 @@ export class GanttTableEvent { export class GanttLinkDragEvent { source: GanttItem; target?: GanttItem; + type?: GanttLinkType; } export class GanttLoadOnScrollEvent { diff --git a/packages/gantt/src/class/item.ts b/packages/gantt/src/class/item.ts index 4361246a..212c143f 100644 --- a/packages/gantt/src/class/item.ts +++ b/packages/gantt/src/class/item.ts @@ -132,8 +132,9 @@ export class GanttItemInternal { this.origin.expanded = expanded; } - addLink(linkId: string) { - this.links = [...this.links, { type: GanttLinkType.fs, link: linkId }]; + addLink(link: GanttLink) { + console.log(link); + this.links = [...this.links, link]; this.origin.links = this.links; } } diff --git a/packages/gantt/src/class/test/item.spec.ts b/packages/gantt/src/class/test/item.spec.ts index 60df4ed9..22e75b21 100644 --- a/packages/gantt/src/class/test/item.spec.ts +++ b/packages/gantt/src/class/test/item.spec.ts @@ -1,3 +1,4 @@ +import { GanttLinkType } from 'ngx-gantt'; import { GanttDate } from '../../utils/date'; import { GanttItem, GanttItemInternal } from '../item'; import { GanttViewType } from '../view-type'; @@ -96,7 +97,10 @@ describe('GanttItemInternal', () => { }); it(`should add link`, () => { - ganttItemInternal.addLink('0102'); + ganttItemInternal.addLink({ + link: '0102', + type: GanttLinkType.fs + }); // expect(ganttItemInternal.links).toContain('0102'); }); }); diff --git a/packages/gantt/src/components/bar/bar-drag.ts b/packages/gantt/src/components/bar/bar-drag.ts index c77fc186..61a70aad 100644 --- a/packages/gantt/src/components/bar/bar-drag.ts +++ b/packages/gantt/src/components/bar/bar-drag.ts @@ -1,16 +1,18 @@ -import { Injectable, ElementRef, OnDestroy, NgZone } from '@angular/core'; +import { Injectable, ElementRef, OnDestroy } from '@angular/core'; import { DragRef, DragDrop } from '@angular/cdk/drag-drop'; import { GanttDomService } from '../../gantt-dom.service'; -import { GanttDragContainer } from '../../gantt-drag-container'; +import { GanttDragContainer, InBarPosition } from '../../gantt-drag-container'; import { GanttItemInternal } from '../../class/item'; import { GanttDate, differenceInCalendarDays } from '../../utils/date'; import { fromEvent, Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { GanttUpper } from '../../gantt-upper'; +import { GanttLinkType } from '../../class/link'; const dragMinWidth = 10; const activeClass = 'gantt-bar-active'; -const linkDropClass = 'gantt-bar-link-drop'; +const dropActiveClass = 'gantt-bar-drop-active'; +const singleDropActiveClass = 'gantt-bar-single-drop-active'; function createSvgElement(qualifiedName: string, className: string) { const element = document.createElementNS('http://www.w3.org/2000/svg', qualifiedName); @@ -45,13 +47,22 @@ export class GanttBarDrag implements OnDestroy { constructor(private dragDrop: DragDrop, private dom: GanttDomService, private dragContainer: GanttDragContainer) {} private createMouseEvents() { + const dropClass = + this.ganttUpper.config.linkOptions?.dependencyTypes?.length === 0 && + this.ganttUpper.config.linkOptions?.dependencyTypes[0] === GanttLinkType.fs + ? singleDropActiveClass + : dropActiveClass; + fromEvent(this.barElement, 'mouseenter') .pipe(takeUntil(this.destroy$)) - .subscribe(() => { + .subscribe((event: MouseEvent) => { if (this.dragContainer.linkDraggingId && this.dragContainer.linkDraggingId !== this.item.id) { if (this.item.linkable) { - this.barElement.classList.add(linkDropClass); - this.dragContainer.emitLinkDragEntered(this.item); + this.barElement.classList.add(dropClass); + this.dragContainer.emitLinkDragEntered({ + item: this.item, + element: this.barElement + }); } } else { this.barElement.classList.add(activeClass); @@ -64,9 +75,11 @@ export class GanttBarDrag implements OnDestroy { if (!this.dragContainer.linkDraggingId) { this.barElement.classList.remove(activeClass); } else { + if (this.dragContainer.linkDraggingId !== this.item.id) { + this.barElement.classList.remove(dropClass); + } this.dragContainer.emitLinkDragLeaved(); } - this.barElement.classList.remove(linkDropClass); }); } @@ -179,7 +192,7 @@ export class GanttBarDrag implements OnDestroy { const dragRefs = []; const handles = this.barElement.querySelectorAll('.link-handles .handle'); handles.forEach((handle, index) => { - const isBefore = index === 0; + const isBegin = index === 0; const dragRef = this.dragDrop.createDrag(handle); dragRef.withBoundaryElement(this.dom.root as HTMLElement); dragRef.beforeStarted.subscribe(() => { @@ -188,11 +201,15 @@ export class GanttBarDrag implements OnDestroy { this.barDragRef.disabled = true; } this.createLinkDraggingLine(); - this.dragContainer.emitLinkDragStarted(isBefore ? 'target' : 'source', this.item); + this.dragContainer.emitLinkDragStarted({ + element: this.barElement, + item: this.item, + pos: isBegin ? InBarPosition.start : InBarPosition.finish + }); }); dragRef.moved.subscribe(() => { - const positions = this.calcLinkLinePositions(handle, isBefore); + const positions = this.calcLinkLinePositions(handle, isBegin); this.linkDraggingLine.setAttribute('x1', positions.x1.toString()); this.linkDraggingLine.setAttribute('y1', positions.y1.toString()); this.linkDraggingLine.setAttribute('x2', positions.x2.toString()); @@ -200,14 +217,27 @@ export class GanttBarDrag implements OnDestroy { }); dragRef.ended.subscribe((event) => { - event.source.reset(); handle.style.pointerEvents = ''; if (this.barDragRef) { this.barDragRef.disabled = false; } + // 计算line拖动的落点位于目标Bar的值,如果值大于Bar宽度的一半,说明是拖动到Begin位置,否则则为拖动到End位置 + if (this.dragContainer.linkDragPath.to) { + const placePointX = + event.source.getRootElement().getBoundingClientRect().x - + this.dragContainer.linkDragPath.to.element.getBoundingClientRect().x; + + this.dragContainer.emitLinkDragEnded({ + ...this.dragContainer.linkDragPath.to, + pos: + placePointX < this.dragContainer.linkDragPath.to.item.refs.width / 2 + ? InBarPosition.start + : InBarPosition.finish + }); + } + event.source.reset(); this.barElement.classList.remove(activeClass); this.destroyLinkDraggingLine(); - this.dragContainer.emitLinkDragEnded(); }); dragRefs.push(dragRef); diff --git a/packages/gantt/src/components/bar/bar.component.scss b/packages/gantt/src/components/bar/bar.component.scss index 24ee1bca..e6dbcfd5 100644 --- a/packages/gantt/src/components/bar/bar.component.scss +++ b/packages/gantt/src/components/bar/bar.component.scss @@ -144,6 +144,7 @@ $gantt-bar-link-drop-border: 5px; background: $gantt-bar-background-color; overflow: hidden; box-sizing: border-box; + .gantt-bar-content-progress { position: absolute; left: 0; @@ -157,7 +158,7 @@ $gantt-bar-link-drop-border: 5px; @include active-bar(); } - &-link-drop { + &-single-drop-active- { .gantt-bar-border { display: block; } @@ -166,4 +167,26 @@ $gantt-bar-link-drop-border: 5px; box-shadow: none; } } + + &-drop-active { + @include active-bar(); + + .gantt-bar-layer { + .link-handles { + .handle { + &:first-child { + left: -$gantt-bar-link-height; + top: 50%; + padding-bottom: $gantt-bar-link-height; + } + + &:last-child { + right: -$gantt-bar-link-height; + top: 50%; + padding-bottom: $gantt-bar-link-height; + } + } + } + } + } } diff --git a/packages/gantt/src/components/bar/bar.component.ts b/packages/gantt/src/components/bar/bar.component.ts index 4df5629c..a531c58c 100644 --- a/packages/gantt/src/components/bar/bar.component.ts +++ b/packages/gantt/src/components/bar/bar.component.ts @@ -1,8 +1,6 @@ import { Component, OnInit, - Input, - TemplateRef, HostBinding, ElementRef, OnChanges, diff --git a/packages/gantt/src/gantt-drag-container.ts b/packages/gantt/src/gantt-drag-container.ts index 99aabbde..a8eb1dce 100644 --- a/packages/gantt/src/gantt-drag-container.ts +++ b/packages/gantt/src/gantt-drag-container.ts @@ -1,8 +1,37 @@ -import { Injectable, EventEmitter } from '@angular/core'; +import { Injectable, EventEmitter, Inject } from '@angular/core'; +import { GanttLinkType } from './class'; import { GanttDragEvent, GanttLinkDragEvent } from './class/event'; import { GanttItemInternal } from './class/item'; +import { GanttUpper, GANTT_UPPER_TOKEN } from './gantt-upper'; -export type LinkDragFrom = 'source' | 'target'; +function getDependencyType(path: LinkDragPath, dependencyTypes: GanttLinkType[]): GanttLinkType { + if (dependencyTypes.includes(GanttLinkType.ss) && path.from.pos === InBarPosition.start && path.to.pos === InBarPosition.start) { + return GanttLinkType.ss; + } + if (dependencyTypes.includes(GanttLinkType.ff) && path.from.pos === InBarPosition.finish && path.to.pos === InBarPosition.finish) { + return GanttLinkType.ff; + } + if (dependencyTypes.includes(GanttLinkType.sf) && path.from.pos === InBarPosition.start && path.to.pos === InBarPosition.finish) { + return GanttLinkType.sf; + } + return GanttLinkType.fs; +} + +export enum InBarPosition { + start = 'start', + finish = 'finish' +} + +export type LinkDragPosition = { + element: HTMLElement; + item: GanttItemInternal; + pos?: InBarPosition; +}; + +export interface LinkDragPath { + from?: LinkDragPosition; + to?: LinkDragPosition; +} @Injectable() export class GanttDragContainer { @@ -20,56 +49,44 @@ export class GanttDragContainer { linkDraggingId: string; - private linkDragSource: GanttItemInternal; - - private linkDragTarget: GanttItemInternal; + linkDragPath: LinkDragPath = { from: null, to: null }; - private linkDragFrom: LinkDragFrom; + constructor(@Inject(GANTT_UPPER_TOKEN) public ganttUpper: GanttUpper) {} - constructor() {} - - emitLinkDragStarted(from: LinkDragFrom, item: GanttItemInternal) { - this.linkDraggingId = item.id; - this.linkDragFrom = from; - this.linkDragSource = this.linkDragFrom === 'source' ? item : null; - this.linkDragTarget = this.linkDragFrom === 'target' ? item : null; + emitLinkDragStarted(from: LinkDragPosition) { + this.linkDraggingId = from.item.id; + this.linkDragPath.from = from; this.linkDragStarted.emit({ - source: this.linkDragSource && this.linkDragSource.origin, - target: this.linkDragTarget && this.linkDragTarget.origin + source: from.item.origin, + target: null }); } - emitLinkDragEntered(item: GanttItemInternal) { - if (this.linkDragFrom === 'source') { - this.linkDragTarget = item; - } else { - this.linkDragSource = item; - } + emitLinkDragEntered(to: LinkDragPosition) { + this.linkDragPath.to = to; this.linkDragEntered.emit({ - source: this.linkDragSource.origin, - target: this.linkDragTarget.origin + source: this.linkDragPath.from.item.origin, + target: to.item.origin }); } emitLinkDragLeaved() { - if (this.linkDragFrom === 'source') { - this.linkDragTarget = null; - } else { - this.linkDragSource = null; - } + this.linkDragPath.to = null; } - emitLinkDragEnded() { + emitLinkDragEnded(to: LinkDragPosition) { + this.linkDragPath.to = to; + const dependencyType = getDependencyType(this.linkDragPath, this.ganttUpper.linkOptions?.dependencyTypes); + this.linkDragPath.from.item.addLink({ + link: this.linkDragPath.to.item.id, + type: dependencyType + }); + this.linkDragEnded.emit({ + source: this.linkDragPath.from.item.origin, + target: this.linkDragPath.to.item.origin, + type: dependencyType + }); this.linkDraggingId = null; - if (this.linkDragSource && this.linkDragTarget) { - this.linkDragSource.addLink(this.linkDragTarget.id); - - this.linkDragEnded.emit({ - source: this.linkDragSource.origin, - target: this.linkDragTarget.origin - }); - } - this.linkDragSource = null; - this.linkDragTarget = null; + this.linkDragPath = { from: null, to: null }; } } diff --git a/packages/gantt/src/gantt-upper.ts b/packages/gantt/src/gantt-upper.ts index 8af03ac2..70b4544f 100644 --- a/packages/gantt/src/gantt-upper.ts +++ b/packages/gantt/src/gantt-upper.ts @@ -57,6 +57,14 @@ export abstract class GanttUpper { @Input() viewOptions: GanttViewOptions = {}; + @Input() set linkOptions(options: GanttLinkOptions) { + this._linkOptions = Object.assign(options, this.config.linkOptions); + } + + get linkOptions() { + return this._linkOptions; + } + @Input() disabledLoadOnScroll: boolean; @Input() @@ -107,8 +115,6 @@ export abstract class GanttUpper { public linkable: boolean; - public linkOptions: GanttLinkOptions; - public linkDragEnded = new EventEmitter(); public view: GanttView; @@ -139,13 +145,15 @@ export abstract class GanttUpper { private _multiple = false; + private _linkOptions: GanttLinkOptions; + @HostBinding('class.gantt') ganttClass = true; constructor( protected elementRef: ElementRef, protected cdr: ChangeDetectorRef, protected ngZone: NgZone, - @Inject(GANTT_GLOBAL_CONFIG) protected config: GanttGlobalConfig + @Inject(GANTT_GLOBAL_CONFIG) public config: GanttGlobalConfig ) {} private createView() { diff --git a/packages/gantt/src/gantt.component.ts b/packages/gantt/src/gantt.component.ts index 1903d6d4..7ccef54d 100644 --- a/packages/gantt/src/gantt.component.ts +++ b/packages/gantt/src/gantt.component.ts @@ -55,8 +55,6 @@ export class NgxGanttComponent extends GanttUpper implements OnInit, AfterViewIn @Input() linkable: boolean; - @Input() linkOptions: GanttLinkOptions; - @Output() linkDragStarted = new EventEmitter(); @Output() linkDragEnded = new EventEmitter(); @@ -149,11 +147,11 @@ export class NgxGanttComponent extends GanttUpper implements OnInit, AfterViewIn const selectedIds = this.selectionModel.selected; if (this.multiple) { - const selectedValue = this.getGanttItems(selectedIds).map((item) => item.origin); - this.selectedChange.emit({ event, selectedValue }); + const _selectedValue = this.getGanttItems(selectedIds).map((item) => item.origin); + this.selectedChange.emit({ event, selectedValue: _selectedValue }); } else { - const selectedValue = this.getGanttItem(selectedIds[0])?.origin; - this.selectedChange.emit({ event, selectedValue }); + const _selectedValue = this.getGanttItem(selectedIds[0])?.origin; + this.selectedChange.emit({ event, selectedValue: _selectedValue }); } }