Skip to content

Commit

Permalink
feat(gantt): support quick time focus #TINFR-1044 (#494)
Browse files Browse the repository at this point in the history
  • Loading branch information
smile1016 authored Nov 27, 2024
1 parent 4c0bdbb commit df0f813
Show file tree
Hide file tree
Showing 11 changed files with 151 additions and 9 deletions.
1 change: 1 addition & 0 deletions example/src/app/gantt-virtual-scroll/gantt.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
[virtualScrollEnabled]="true"
(virtualScrolledIndexChange)="virtualScrolledIndexChange($event)"
[loading]="loading"
[quickTimeFocus]="true"
>
<ngx-gantt-table
[draggable]="true"
Expand Down
1 change: 1 addition & 0 deletions example/src/app/gantt/gantt.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
(dragEnded)="dragEnded($event)"
(selectedChange)="selectedChange($event)"
(linkDragEnded)="linkDragEnded($event)"
[quickTimeFocus]="true"
>
<ngx-gantt-table
[draggable]="true"
Expand Down
6 changes: 5 additions & 1 deletion packages/gantt/src/components/icon/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,16 @@ xmlns:xlink="http://www.w3.org/1999/xlink"
</svg>`;

const dragIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fit="" preserveAspectRatio="xMidYMid meet" focusable="false"><g id="aijaction/drag--" stroke-width="1" fill-rule="evenodd"><g id="aij拖动" transform="translate(5 1)" fill-rule="nonzero"><path d="M1 2a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM1 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm-4 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm-4 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z" id="aij形状结合"></path></g></g></svg>`;
const arrowLeftIcon = `<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fit="" height="1em" width="1em" preserveAspectRatio="xMidYMid meet" focusable="false"><g id="adinavigation/arrow-left" stroke-width="1" fill-rule="evenodd"><path d="M7.4 4.15L4.438 7.315a.6.6 0 0 1-.876-.82l3.97-4.243a.598.598 0 0 1 .93-.057l3.97 4.323a.6.6 0 1 1-.885.812L8.6 4.118v9.149c0 .404-.269.733-.6.733-.332 0-.6-.329-.6-.733V4.15z" id="adi形状结合" transform="rotate(-90 7.995 8)"></path></g></svg>`;
const arrowRightIcon = `<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fit="" height="1em" width="1em" preserveAspectRatio="xMidYMid meet" focusable="false"><g id="adlnavigation/arrow-right" stroke-width="1" fill-rule="evenodd"><path d="M7.4 4.15L4.438 7.315a.6.6 0 0 1-.876-.82l3.97-4.243a.598.598 0 0 1 .93-.057l3.97 4.323a.6.6 0 1 1-.885.812L8.6 4.118v9.149c0 .404-.269.733-.6.733-.332 0-.6-.329-.6-.733V4.15z" id="adl形状结合" transform="rotate(90 7.995 8)"></path></g></svg>`;
export const icons = {
'angle-right': angleRight,
'angle-down': angleDown,
'plus-square': plusSquare,
'minus-square': minusSquare,
loading: loadingIcon,
empty: emptyIcon,
drag: dragIcon
drag: dragIcon,
'arrow-left': arrowLeftIcon,
'arrow-right': arrowRightIcon
};
26 changes: 26 additions & 0 deletions packages/gantt/src/components/main/gantt-main.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,29 @@
</div>
</ng-container>
</div>

@if (quickTimeFocus) {
<div class="gantt-quick-time-focus-container" [style.width.px]="ganttUpper.view.width">
<div class="gantt-quick-time-focus" [style.width.px]="dom.visibleRangeX().max - dom.visibleRangeX().min">
<ng-container *ngFor="let data of viewportItems; let i = index; trackBy: trackBy">
<div class="gantt-quick-time-focus-item" [style.height.px]="ganttUpper.styles.lineHeight">
<span class="ml-2">
@if ((data.refs.x < dom.visibleRangeX().min ) && data.refs.width ) {
<a class="gantt-quick-time-focus-item-arrow link-secondary" href="javascript:;" (click)="quickTime(data.origin, 'left')">
<gantt-icon iconName="arrow-left"></gantt-icon>
</a>
}
</span>

<span class="mr-2">
@if((data.refs.x + data.refs.width > dom.visibleRangeX().max) && data.refs.width) {
<a class="gantt-quick-time-focus-item-arrow link-secondary" href="javascript:;" (click)="quickTime(data.origin, 'right')">
<gantt-icon iconName="arrow-right"></gantt-icon>
</a>
}
</span>
</div>
</ng-container>
</div>
</div>
}
41 changes: 37 additions & 4 deletions packages/gantt/src/components/main/gantt-main.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, HostBinding, Inject, Input, TemplateRef, Output, EventEmitter } from '@angular/core';
import { Component, HostBinding, Inject, Input, TemplateRef, Output, EventEmitter, OnInit, AfterViewInit, NgZone } from '@angular/core';
import { GanttGroupInternal, GanttItemInternal, GanttBarClickEvent, GanttLineClickEvent } from '../../class';
import { GANTT_UPPER_TOKEN, GanttUpper } from '../../gantt-upper';
import { IsGanttRangeItemPipe, IsGanttBarItemPipe, IsGanttCustomItemPipe } from '../../gantt.pipe';
Expand All @@ -7,6 +7,10 @@ import { NgxGanttBarComponent } from '../bar/bar.component';
import { NgxGanttRangeComponent } from '../range/range.component';
import { NgFor, NgIf, NgClass, NgTemplateOutlet } from '@angular/common';
import { GanttLinksComponent } from '../links/links.component';
import { NgxGanttRootComponent } from 'ngx-gantt';
import { GanttIconComponent } from '../icon/icon.component';
import { GanttDomService } from '../../gantt-dom.service';
import { combineLatest, from, Subject, take, takeUntil } from 'rxjs';

@Component({
selector: 'gantt-main',
Expand All @@ -23,10 +27,11 @@ import { GanttLinksComponent } from '../links/links.component';
NgxGanttBaselineComponent,
IsGanttRangeItemPipe,
IsGanttBarItemPipe,
IsGanttCustomItemPipe
IsGanttCustomItemPipe,
GanttIconComponent
]
})
export class GanttMainComponent {
export class GanttMainComponent implements OnInit {
@Input() viewportItems: (GanttGroupInternal | GanttItemInternal)[];

@Input() flatItems: (GanttGroupInternal | GanttItemInternal)[];
Expand All @@ -41,15 +46,43 @@ export class GanttMainComponent {

@Input() baselineTemplate: TemplateRef<any>;

@Input() ganttRoot: NgxGanttRootComponent;

@Input() quickTimeFocus: boolean;

@Output() barClick = new EventEmitter<GanttBarClickEvent>();

@Output() lineClick = new EventEmitter<GanttLineClickEvent>();

@HostBinding('class.gantt-main-container') ganttMainClass = true;

constructor(@Inject(GANTT_UPPER_TOKEN) public ganttUpper: GanttUpper) {}
private unsubscribe$ = new Subject<void>();

constructor(@Inject(GANTT_UPPER_TOKEN) public ganttUpper: GanttUpper, public dom: GanttDomService, protected ngZone: NgZone) {}

ngOnInit(): void {
const onStable$ = this.ngZone.isStable ? from(Promise.resolve()) : this.ngZone.onStable.pipe(take(1));
this.ngZone.runOutsideAngular(() => {
onStable$.pipe(takeUntil(this.unsubscribe$)).subscribe(() => {
this.setupResize();
});
});
}

trackBy(index: number, item: GanttGroupInternal | GanttItemInternal) {
return item.id || index;
}

private setupResize() {
combineLatest([this.dom.getResize(), this.dom.getResizeByElement(this.dom.mainContainer)])
.pipe(takeUntil(this.unsubscribe$))
.subscribe(() => {
this.dom.setVisibleRangeX();
});
}

quickTime(item: GanttItemInternal, type: 'left' | 'right') {
const date = type === 'left' ? item.start || item.end : item.end || item.start;
this.ganttRoot.scrollToDate(date);
}
}
21 changes: 20 additions & 1 deletion packages/gantt/src/gantt-dom.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isPlatformServer } from '@angular/common';
import { Injectable, ElementRef, OnDestroy, Inject, PLATFORM_ID, NgZone } from '@angular/core';
import { Injectable, ElementRef, OnDestroy, Inject, PLATFORM_ID, NgZone, WritableSignal, signal } from '@angular/core';
import { fromEvent, Subject, merge, EMPTY, Observable } from 'rxjs';
import { pairwise, map, auditTime, takeUntil } from 'rxjs/operators';
import { isNumber } from './utils/helpers';
Expand Down Expand Up @@ -40,6 +40,8 @@ export class GanttDomService implements OnDestroy {

public linksOverlay: Element;

public visibleRangeX: WritableSignal<{ min: number; max: number }> = signal({ min: 0, max: 0 });

private mainFooter: Element;

private mainScrollbar: Element;
Expand Down Expand Up @@ -141,6 +143,7 @@ export class GanttDomService implements OnDestroy {
map(() => this.mainContainer.scrollLeft),
pairwise(),
map(([previous, current]) => {
this.setVisibleRangeX();
const event: ScrollEvent = {
target: this.mainContainer,
direction: ScrollDirection.NONE
Expand Down Expand Up @@ -170,6 +173,15 @@ export class GanttDomService implements OnDestroy {
return isPlatformServer(this.platformId) ? EMPTY : fromEvent(window, 'resize').pipe(auditTime(150));
}

getResizeByElement(element: Element) {
return new Observable((observer) => {
const resizeObserver = new ResizeObserver(() => {
observer.next();
});
resizeObserver.observe(element);
});
}

scrollMainContainer(left: number) {
if (isNumber(left)) {
const scrollLeft = left - this.mainContainer.clientWidth / 2;
Expand All @@ -181,6 +193,13 @@ export class GanttDomService implements OnDestroy {
}
}

setVisibleRangeX() {
this.visibleRangeX.set({
min: this.mainContainer.scrollLeft,
max: this.mainContainer.scrollLeft + this.mainContainer.clientWidth
});
}

ngOnDestroy() {
this.unsubscribe$.next();
this.unsubscribe$.complete();
Expand Down
2 changes: 2 additions & 0 deletions packages/gantt/src/gantt-upper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ export abstract class GanttUpper implements OnChanges, OnInit, OnDestroy {
return this._multiple;
}

@Input() quickTimeFocus = false;

@Output() loadOnScroll = new EventEmitter<GanttLoadOnScrollEvent>();

@Output() dragStarted = new EventEmitter<GanttDragEvent>();
Expand Down
2 changes: 2 additions & 0 deletions packages/gantt/src/gantt.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,15 @@
></gantt-calendar-grid>
<div class="gantt-main">
<gantt-main
[ganttRoot]="ganttRoot"
[flatItems]="flatItems"
[viewportItems]="viewportItems"
[groupHeaderTemplate]="groupHeaderTemplate"
[itemTemplate]="itemTemplate"
[barTemplate]="barTemplate"
[rangeTemplate]="rangeTemplate"
[baselineTemplate]="baselineTemplate"
[quickTimeFocus]="quickTimeFocus"
(barClick)="barClick.emit($event)"
(lineClick)="lineClick.emit($event)"
>
Expand Down
53 changes: 53 additions & 0 deletions packages/gantt/src/gantt.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,59 @@
background-color: rgba($color: variables.$gantt-table-header-drag-line-color, $alpha: 0.1);
}
}

.gantt-quick-time-focus-container {
position: absolute;
left: 0;
top: 0;
.gantt-quick-time-focus {
position: sticky;
left: 0;
width: 0px;
z-index: 3;
pointer-events: none;

&-item {
display: flex;
justify-content: space-between;
align-items: center;
span {
width: 24px;
height: 24px;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 2px;
pointer-events: all;
&:hover {
.gantt-quick-time-focus-item-arrow {
border: 1px solid rgba(variables.$gantt-primary-color, 1);
}
}
}

&-arrow {
width: 20px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
background-color: variables.$gantt-bar-bg;
border: 1px solid variables.$gantt-border-color;
border-radius: 4px;
box-shadow: 0 4px 7px 1px rgba(0, 0, 0, 0.05);
.gantt-icon {
display: inline-block;
svg {
width: 14px;
height: 14px;
}
}
}
}
}
}
}

.gantt-normal-viewport {
Expand Down
5 changes: 3 additions & 2 deletions packages/gantt/src/gantt.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport, ViewportRuler } from '@angular/cdk/scrolling';
import { NgClass, NgIf, NgTemplateOutlet } from '@angular/common';
import { NgClass, NgFor, NgIf, NgTemplateOutlet } from '@angular/common';
import {
AfterViewChecked,
AfterViewInit,
Expand Down Expand Up @@ -82,7 +82,8 @@ import { GanttScrollbarComponent } from './components/scrollbar/scrollbar.compon
GanttMainComponent,
GanttDragBackdropComponent,
GanttScrollbarComponent,
NgTemplateOutlet
NgTemplateOutlet,
NgFor
]
})
export class NgxGanttComponent extends GanttUpper implements OnInit, OnChanges, AfterViewInit, AfterViewChecked {
Expand Down
2 changes: 1 addition & 1 deletion packages/gantt/src/root.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export class NgxGanttRootComponent implements OnInit, OnDestroy {
}

private setupViewScroll() {
if (this.ganttUpper.disabledLoadOnScroll) {
if (this.ganttUpper.disabledLoadOnScroll && !this.ganttUpper.quickTimeFocus) {
return;
}
this.dom
Expand Down

0 comments on commit df0f813

Please sign in to comment.