Restore relations display

[ci skip]
pull/5456/head
Oliver Günther 8 years ago
parent 1ee7653c11
commit fd4b5b03c7
No known key found for this signature in database
GPG Key ID: 88872239EB414F99
  1. 39
      frontend/app/components/wp-table/timeline/container/wp-timeline-container.directive.ts
  2. 1
      frontend/app/components/wp-table/timeline/container/wp-timeline-container.html
  3. 145
      frontend/app/components/wp-table/timeline/global-elements/wp-timeline-relations.directive.ts
  4. 2
      frontend/app/components/wp-table/timeline/global-elements/wp-timeline.today-line.ts
  5. 1
      frontend/app/components/wp-table/timeline/grid/wp-timeline-grid.directive.ts
  6. 1
      frontend/app/components/wp-table/timeline/header/wp-timeline-header.directive.ts
  7. 4
      frontend/app/components/wp-table/timeline/wp-timeline-cell.ts
  8. 5
      frontend/app/components/wp-table/wp-table.directive.html

@ -34,7 +34,6 @@ import {
TimelineViewParameters TimelineViewParameters
} from "../wp-timeline"; } from "../wp-timeline";
import {WorkPackageResourceInterface} from "../../../api/api-v3/hal-resources/work-package-resource.service"; import {WorkPackageResourceInterface} from "../../../api/api-v3/hal-resources/work-package-resource.service";
import {WpTimelineGlobalService} from "../wp-timeline-global.directive";
import {States} from "../../../states.service"; import {States} from "../../../states.service";
import {WorkPackageTableTimelineService} from "../../../wp-fast-table/state/wp-table-timeline.service"; import {WorkPackageTableTimelineService} from "../../../wp-fast-table/state/wp-table-timeline.service";
import {WorkPackageNotificationService} from "../../../wp-edit/wp-notification.service"; import {WorkPackageNotificationService} from "../../../wp-edit/wp-notification.service";
@ -45,6 +44,7 @@ import {debugLog} from "../../../../helpers/debug_output";
import {openprojectModule} from "../../../../angular-modules"; import {openprojectModule} from "../../../../angular-modules";
import {WorkPackageTimelineHeaderController} from "../header/wp-timeline-header.directive"; import {WorkPackageTimelineHeaderController} from "../header/wp-timeline-header.directive";
import {TypeResource} from "../../../api/api-v3/hal-resources/type-resource.service"; import {TypeResource} from "../../../api/api-v3/hal-resources/type-resource.service";
import {WorkPackageTimelineCell} from "../wp-timeline-cell";
export class WorkPackageTimelineTableController { export class WorkPackageTimelineTableController {
@ -54,8 +54,6 @@ export class WorkPackageTimelineTableController {
private workPackagesInView: {[id: string]: WorkPackageResourceInterface} = {}; private workPackagesInView: {[id: string]: WorkPackageResourceInterface} = {};
public readonly globalService = new WpTimelineGlobalService(this.$scope);
private updateAllWorkPackagesSubject = new BehaviorSubject<boolean>(true); private updateAllWorkPackagesSubject = new BehaviorSubject<boolean>(true);
private refreshViewRequested = false; private refreshViewRequested = false;
@ -64,7 +62,9 @@ export class WorkPackageTimelineTableController {
public header:WorkPackageTimelineHeaderController; public header:WorkPackageTimelineHeaderController;
private members:{ [name:string]: (vp:TimelineViewParameters) => void } = {}; public cells:{[id: string]:WorkPackageTimelineCell} = {};
private renderers:{ [name:string]: (vp:TimelineViewParameters) => void } = {};
constructor(private $scope:IScope, constructor(private $scope:IScope,
private $element:ng.IAugmentedJQuery, private $element:ng.IAugmentedJQuery,
@ -108,15 +108,25 @@ export class WorkPackageTimelineTableController {
} }
onRefreshRequested(name:string, callback:(vp:TimelineViewParameters) => void) { onRefreshRequested(name:string, callback:(vp:TimelineViewParameters) => void) {
this.members[name] = callback; this.renderers[name] = callback;
} }
refreshScrollOnly() {
jQuery("." + timelineElementCssClass).css("margin-left", this._viewParameters.scrollOffsetInPx + "px");
}
/** public updateWorkPackageInfo(cell: WorkPackageTimelineCell) {
* Returns a defensive copy of the currently used view parameters. this.cells[cell.latestRenderInfo.workPackage.id] = cell;
*/ this.refreshView();
getViewParametersCopy(): TimelineViewParameters { }
return _.cloneDeep(this._viewParameters);
public removeWorkPackageInfo(id: string) {
delete this.cells[id];
this.refreshView();
}
get viewParameters(): TimelineViewParameters {
return this._viewParameters;
} }
get viewParameterSettings() { get viewParameterSettings() {
@ -140,22 +150,17 @@ export class WorkPackageTimelineTableController {
this.updateAllWorkPackagesSubject.next(true); this.updateAllWorkPackagesSubject.next(true);
this.header.refreshView(this._viewParameters); this.header.refreshView(this._viewParameters);
_.each(this.members, (cb, key) => { _.each(this.renderers, (cb, key) => {
debugLog(`Refreshing timeline member ${key}`); debugLog(`Refreshing timeline member ${key}`);
cb(this._viewParameters); cb(this._viewParameters);
}); });
this.refreshScrollOnly();
this.refreshViewRequested = false; this.refreshViewRequested = false;
}, 30); }, 30);
} }
this.refreshViewRequested = true; this.refreshViewRequested = true;
} }
refreshScrollOnly() {
jQuery("." + timelineElementCssClass).css("margin-left", this._viewParameters.scrollOffsetInPx + "px");
}
addWorkPackage(wpId: string): Observable<RenderInfo> { addWorkPackage(wpId: string): Observable<RenderInfo> {
const wpObs = this.states.workPackages.get(wpId).values$() const wpObs = this.states.workPackages.get(wpId).values$()
.takeUntil(scopeDestroyed$(this.$scope)) .takeUntil(scopeDestroyed$(this.$scope))
@ -163,8 +168,6 @@ export class WorkPackageTimelineTableController {
this.workPackagesInView[wp.id] = wp; this.workPackagesInView[wp.id] = wp;
const viewParamsChanged = this.calculateViewParams(this._viewParameters); const viewParamsChanged = this.calculateViewParams(this._viewParameters);
if (viewParamsChanged) { if (viewParamsChanged) {
// view params have changed, notify all cells
this.globalService.updateViewParameter(this._viewParameters);
this.refreshView(); this.refreshView();
} }

@ -3,6 +3,7 @@
<div class="wp-table-timeline--container"> <div class="wp-table-timeline--container">
<wp-timeline-header></wp-timeline-header> <wp-timeline-header></wp-timeline-header>
<wp-timeline-grid></wp-timeline-grid> <wp-timeline-grid></wp-timeline-grid>
<wp-timeline-relations></wp-timeline-relations>
<div class="wp-table-timeline--body"></div> <div class="wp-table-timeline--body"></div>
</div> </div>
</div> </div>

@ -1,7 +1,6 @@
// -- copyright // -- copyright
// OpenProject is a project management system. // OpenProject is a project management system.
// Copyright (C) 2012-2017 the OpenProject Foundation (OPF) // Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
// //
// This program is free software; you can redistribute it and/or // This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3. // modify it under the terms of the GNU General Public License version 3.
@ -26,23 +25,25 @@
// //
// See doc/COPYRIGHT.rdoc for more details. // See doc/COPYRIGHT.rdoc for more details.
// ++ // ++
import {
import IDirective = angular.IDirective; timelineElementCssClass,
import IComponentOptions = angular.IComponentOptions; TimelineViewParameters,
import {Observable} from "rxjs/Rx"; } from "../wp-timeline";
import {scopeDestroyed$} from "../../../helpers/angular-rx-utils"; import {WorkPackageTimelineTableController} from "../container/wp-timeline-container.directive";
import {debugLog} from "../../../helpers/debug_output";
import {injectorBridge} from "../../angular/angular-injector-bridge.functions"; import {Observable} from 'rxjs';
import {RelationResource} from "../../api/api-v3/hal-resources/relation-resource.service"; import * as moment from 'moment';
import {WorkPackageResource} from "../../api/api-v3/hal-resources/work-package-resource.service"; import Moment = moment.Moment;
import {States} from "../../states.service"; import {openprojectModule} from "../../../../angular-modules";
import {WorkPackageStates} from "../../work-package-states.service"; import {States} from "../../../states.service";
import {RelationsStateValue, WorkPackageRelationsService} from "../../wp-relations/wp-relations.service"; import {WorkPackageStates} from "../../../work-package-states.service";
import {TimelineRelationElement} from "./global-elements/timeline-relation-element"; import {
import {timelineElementCssClass, TimelineViewParameters} from "./wp-timeline"; RelationsStateValue,
import {WorkPackageTimelineCell} from "./wp-timeline-cell"; WorkPackageRelationsService
import IScope = angular.IScope; } from "../../../wp-relations/wp-relations.service";
import {scopeDestroyed$} from "../../../../helpers/angular-rx-utils";
import {TimelineRelationElement} from "./timeline-relation-element";
import {RelationResource} from "../../../api/api-v3/hal-resources/relation-resource.service";
export const timelineGlobalElementCssClassname = "relation-line"; export const timelineGlobalElementCssClassname = "relation-line";
@ -70,40 +71,33 @@ function newSegment(vp: TimelineViewParameters,
return segment; return segment;
} }
export class WpTimelineGlobalService { export class WorkPackageTableTimelineRelations {
// Injected arguments public wpTimeline:WorkPackageTimelineTableController;
public states:States;
public wpStates:WorkPackageStates;
public wpRelations:WorkPackageRelationsService;
private workPackageIdOrder:string[] = []; private container:ng.IAugmentedJQuery;
private viewParameters:TimelineViewParameters; private workPackageIdOrder:string[] = [];
private cells:{[id: string]:WorkPackageTimelineCell} = {};
private elements:TimelineRelationElement[] = []; private elements:TimelineRelationElement[] = [];
constructor(private scope:IScope) { constructor(public $element:ng.IAugmentedJQuery,
injectorBridge(this); public $scope:ng.IScope,
this.requireVisibleRelations(); public states:States,
this.setupRelationSubscription(); public wpStates:WorkPackageStates,
public wpRelations:WorkPackageRelationsService) {
} }
updateViewParameter(viewParams: TimelineViewParameters) { $onInit() {
this.viewParameters = viewParams; this.container = this.$element.find('.wp-table-timeline--relations');
this.update(); this.wpTimeline.onRefreshRequested('relations', (vp:TimelineViewParameters) => this.refreshView(vp));
}
updateWorkPackageInfo(cell: WorkPackageTimelineCell) { this.requireVisibleRelations();
this.cells[cell.latestRenderInfo.workPackage.id] = cell; this.setupRelationSubscription();
this.update();
} }
removeWorkPackageInfo(id: string) { private refreshView(vp:TimelineViewParameters) {
delete this.cells[id]; this.update(vp);
this.update();
} }
/** /**
@ -114,31 +108,40 @@ export class WpTimelineGlobalService {
// Observe the rows and request relations if changed // Observe the rows and request relations if changed
// AND timeline is visible. // AND timeline is visible.
Observable.combineLatest( Observable.combineLatest(
this.states.table.timelineVisible.values$().takeUntil(scopeDestroyed$(this.scope)), this.states.table.timelineVisible.values$().takeUntil(scopeDestroyed$(this.$scope)),
this.states.table.rows.values$().takeUntil(scopeDestroyed$(this.scope)) this.states.table.rendered.values$().takeUntil(scopeDestroyed$(this.$scope))
) )
.filter(([timelineState, rows]) => timelineState.isVisible) .filter(([timelineState, rendered]) => timelineState.isVisible)
.map(([_timelineState, rows]) => rows) .subscribe(() => {
.subscribe((rows:WorkPackageResource[]) => { this.workPackageIdOrder = this.getVisibleWorkPackageOrder();
this.workPackageIdOrder = rows.map(wp => wp.id.toString());
this.wpRelations.requireInvolved(this.workPackageIdOrder); this.wpRelations.requireInvolved(this.workPackageIdOrder);
}); });
} }
private getVisibleWorkPackageOrder():string[] {
const ids:string[] = [];
jQuery('.wp-table--row').each((i, el) => {
ids.push(el.getAttribute('data-work-package-id')!);
});
return ids;
}
/** /**
* Refresh relations of visible rows. * Refresh relations of visible rows.
*/ */
private setupRelationSubscription() { private setupRelationSubscription() {
this.wpStates.relations.observeChange() this.wpStates.relations.observeChange()
.takeUntil(scopeDestroyed$(this.scope)) .takeUntil(scopeDestroyed$(this.$scope))
.withLatestFrom( .withLatestFrom(
this.states.table.timelineVisible.values$().takeUntil(scopeDestroyed$(this.scope))) this.states.table.timelineVisible.values$().takeUntil(scopeDestroyed$(this.$scope)))
.filter(([relations, timelineVisible]) => relations && timelineVisible.isVisible) .filter(([relations, timelineVisible]) => relations && timelineVisible.isVisible)
.map(([relations]) => relations) .map(([relations]) => relations)
.subscribe((nextVal) => { .subscribe((nextVal) => {
const [workPackageId, relations] = nextVal; const [workPackageId, relations] = nextVal;
if (workPackageId && this.cells[workPackageId]) { if (workPackageId && this.wpTimeline.cells[workPackageId]) {
this.refreshRelations(workPackageId, relations!); this.refreshRelations(workPackageId, relations!);
} }
}); });
@ -154,45 +157,32 @@ export class WpTimelineGlobalService {
const elem = new TimelineRelationElement(workPackageId, relation); const elem = new TimelineRelationElement(workPackageId, relation);
this.elements.push(elem); this.elements.push(elem);
if (this.viewParameters !== undefined) { this.renderElement(this.wpTimeline.viewParameters, elem);
this.renderElement(elem);
}
}); });
} }
private update() { private update(vp:TimelineViewParameters) {
this.removeAllVisibleElements();
this.renderElements();
}
private removeAllElements() {
this.removeAllVisibleElements(); this.removeAllVisibleElements();
this.elements = []; this.renderElements(vp);
} }
private removeAllVisibleElements() { private removeAllVisibleElements() {
jQuery('.' + timelineGlobalElementCssClassname).remove(); jQuery('.' + timelineGlobalElementCssClassname).remove();
} }
private renderElements() { private renderElements(vp:TimelineViewParameters) {
if (this.viewParameters === undefined) {
debugLog('renderElements() aborted - no viewParameters');
return;
}
for (const e of this.elements) { for (const e of this.elements) {
this.renderElement(e); this.renderElement(vp, e);
} }
} }
private renderElement(e:TimelineRelationElement) { private renderElement(vp:TimelineViewParameters, e:TimelineRelationElement) {
const vp = this.viewParameters;
const involved = e.relation.ids; const involved = e.relation.ids;
const idxFrom = this.workPackageIdOrder.indexOf(involved.from); const idxFrom = this.workPackageIdOrder.indexOf(involved.from);
const idxTo = this.workPackageIdOrder.indexOf(involved.to); const idxTo = this.workPackageIdOrder.indexOf(involved.to);
const startCell = this.cells[involved.from]; const startCell = this.wpTimeline.cells[involved.from];
const endCell = this.cells[involved.to]; const endCell = this.wpTimeline.cells[involved.to];
if (idxFrom === -1 || idxTo === -1 || _.isNil(startCell) || _.isNil(endCell)) { if (idxFrom === -1 || idxTo === -1 || _.isNil(startCell) || _.isNil(endCell)) {
return; return;
@ -225,7 +215,7 @@ export class WpTimelineGlobalService {
// vert segment // vert segment
for (let index = idxFrom + directionY; index !== idxTo; index += directionY) { for (let index = idxFrom + directionY; index !== idxTo; index += directionY) {
const id = this.workPackageIdOrder[index]; const id = this.workPackageIdOrder[index];
const cell = this.cells[id]; const cell = this.wpTimeline.cells[id];
if (_.isNil(cell)) { if (_.isNil(cell)) {
continue; continue;
} }
@ -255,6 +245,13 @@ export class WpTimelineGlobalService {
} }
} }
} }
} }
WpTimelineGlobalService.$inject = ['states', 'wpStates', 'wpRelations']; openprojectModule.component("wpTimelineRelations", {
template: '<div class="wp-table-timeline--relations"></div>',
controller: WorkPackageTableTimelineRelations,
require: {
wpTimeline: '^wpTimelineContainer'
}
});

@ -26,7 +26,7 @@
// See doc/COPYRIGHT.rdoc for more details. // See doc/COPYRIGHT.rdoc for more details.
// ++ // ++
import {calculatePositionValueForDayCount, TimelineViewParameters} from "./wp-timeline"; import {calculatePositionValueForDayCount, TimelineViewParameters} from "../wp-timeline";
import * as moment from 'moment'; import * as moment from 'moment';

@ -31,7 +31,6 @@ import {
ZoomLevel, ZoomLevel,
calculatePositionValueForDayCount, timelineGridElementCssClass calculatePositionValueForDayCount, timelineGridElementCssClass
} from "../wp-timeline"; } from "../wp-timeline";
import {todayLine} from "../wp-timeline.today-line";
import {WorkPackageTimelineTableController} from "../container/wp-timeline-container.directive"; import {WorkPackageTimelineTableController} from "../container/wp-timeline-container.directive";
import * as moment from 'moment'; import * as moment from 'moment';
import Moment = moment.Moment; import Moment = moment.Moment;

@ -31,7 +31,6 @@ import {
ZoomLevel, ZoomLevel,
calculatePositionValueForDayCount calculatePositionValueForDayCount
} from "../wp-timeline"; } from "../wp-timeline";
import {todayLine} from "../wp-timeline.today-line";
import {WorkPackageTimelineTableController} from "../container/wp-timeline-container.directive"; import {WorkPackageTimelineTableController} from "../container/wp-timeline-container.directive";
import * as moment from 'moment'; import * as moment from 'moment';
import Moment = moment.Moment; import Moment = moment.Moment;

@ -74,13 +74,13 @@ export class WorkPackageTimelineCell {
.map(([renderInfo, _visible]) => renderInfo) .map(([renderInfo, _visible]) => renderInfo)
.subscribe(renderInfo => { .subscribe(renderInfo => {
this.updateView(renderInfo); this.updateView(renderInfo);
this.workPackageTimeline.globalService.updateWorkPackageInfo(this); this.workPackageTimeline.cells[this.workPackageId] = this;
}); });
} }
deactivate() { deactivate() {
this.clear(); this.clear();
this.workPackageTimeline.globalService.removeWorkPackageInfo(this.workPackageId); delete this.workPackageTimeline.cells[this.workPackageId];
this.subscription && this.subscription.unsubscribe(); this.subscription && this.subscription.unsubscribe();
} }

@ -1,6 +1,5 @@
<div class="work-packages-split-view--left"> <div class="work-packages-split-view--left loading-indicator--location" data-indicator-name="table">
<div class="work-packages-split-view--left-table loading-indicator--location" <div class="work-packages-split-view--left-table">
data-indicator-name="table">
<table class="keyboard-accessible-list generic-table work-package-table"> <table class="keyboard-accessible-list generic-table work-package-table">
<colgroup> <colgroup>
<col highlight-col/> <col highlight-col/>

Loading…
Cancel
Save