[24880] Synchronize relations between table and single view

pull/5276/head
Oliver Günther 8 years ago
parent b38e398de1
commit 9a549d3521
No known key found for this signature in database
GPG Key ID: 88872239EB414F99
  1. 10
      frontend/app/components/api/api-v3/hal-resources/relation-resource.service.ts
  2. 25
      frontend/app/components/work-package-states.service.ts
  3. 60
      frontend/app/components/wp-relations/wp-relation-row/wp-relation-row.directive.ts
  4. 7
      frontend/app/components/wp-relations/wp-relations-create/wp-relations-create.directive.ts
  5. 28
      frontend/app/components/wp-relations/wp-relations.directive.ts
  6. 146
      frontend/app/components/wp-relations/wp-relations.service.ts
  7. 22
      frontend/app/components/wp-table/timeline/global-elements/timeline-relation-element.ts
  8. 1
      frontend/app/components/wp-table/timeline/wp-timeline-cell.ts
  9. 20
      frontend/app/components/wp-table/timeline/wp-timeline-container.directive.ts
  10. 260
      frontend/app/components/wp-table/timeline/wp-timeline-global.directive.ts
  11. 2
      frontend/app/helpers/reactive-fassade.ts

@ -80,6 +80,16 @@ export class RelationResource extends HalResource {
return this.type;
}
/**
* Get the involved IDs, returning an object to the ids.
*/
public get ids() {
return {
from: WorkPackageResource.idFromLink(this.from.href!),
to: WorkPackageResource.idFromLink(this.to.href!)
};
}
public updateDescription(description:string) {
return this.$links.updateImmediately({ description: description });
}

@ -0,0 +1,25 @@
import { MultiState, initStates } from '../helpers/reactive-fassade';
import {RelationResource} from './api/api-v3/hal-resources/relation-resource.service';
import {whenDebugging} from '../helpers/debug_output';
import { opServicesModule } from '../angular-modules';
import { RelationsStateValue } from "./wp-relations/wp-relations.service";
/* /api/v3/work_packages */
export class WorkPackageStates {
/* /:id/relations */
relations = new MultiState<RelationsStateValue>();
constructor() {
initStates(this, function (msg: any) {
whenDebugging(() => {
console.debug(msg);
});
});
}
}
opServicesModule.service('wpStates', WorkPackageStates);

@ -42,7 +42,7 @@ class WpRelationRowDirectiveController {
protected $http:ng.IHttpService,
protected wpCacheService: WorkPackageCacheService,
protected wpNotificationsService: WorkPackageNotificationService,
protected wpRelationsService: WorkPackageRelationsService,
protected wpRelations: WorkPackageRelationsService,
protected I18n:op.I18n,
protected PathHelper: op.PathHelper) {
@ -58,7 +58,7 @@ class WpRelationRowDirectiveController {
};
this.userInputs.newRelationText = this.relation.description || '';
this.availableRelationTypes = wpRelationsService.getRelationTypes(true);
this.availableRelationTypes = wpRelations.getRelationTypes(true);
this.selectedRelationType = _.find(this.availableRelationTypes, {'name': this.relation.type}) as RelationResourceInterface;
};
@ -100,15 +100,16 @@ class WpRelationRowDirectiveController {
}
public saveDescription() {
this.relation.updateImmediately({
description: this.userInputs.newRelationText
}).then((savedRelation:RelationResourceInterface) => {
this.$scope.$emit('wp-relations.changed', this.relation);
this.relation = savedRelation;
this.relatedWorkPackage.relatedBy = savedRelation;
this.userInputs.showDescriptionEditForm = false;
this.wpNotificationsService.showSave(this.relatedWorkPackage);
});
this.wpRelations.updateRelation(
this.workPackage.id,
this.relation,
{ description: this.userInputs.newRelationText })
.then((savedRelation:RelationResourceInterface) => {
this.relation = savedRelation;
this.relatedWorkPackage.relatedBy = savedRelation;
this.userInputs.showDescriptionEditForm = false;
this.wpNotificationsService.showSave(this.relatedWorkPackage);
});
}
public get showDescriptionInfo() {
@ -127,16 +128,17 @@ class WpRelationRowDirectiveController {
}
public saveRelationType() {
this.relation.updateImmediately({
type: this.selectedRelationType.name
}).then((savedRelation:RelationResourceInterface) => {
this.wpNotificationsService.showSave(this.relatedWorkPackage);
this.$scope.$emit('wp-relations.changed', this.relation);
this.relatedWorkPackage.relatedBy = savedRelation;
this.relation = savedRelation;
this.userInputs.showRelationTypesForm = false;
});
this.wpRelations.updateRelation(
this.workPackage.id,
this.relation,
{ type: this.selectedRelationType.name })
.then((savedRelation:RelationResourceInterface) => {
this.wpNotificationsService.showSave(this.relatedWorkPackage);
this.relatedWorkPackage.relatedBy = savedRelation;
this.relation = savedRelation;
this.userInputs.showRelationTypesForm = false;
});
}
public toggleUserDescriptionForm() {
@ -144,14 +146,14 @@ class WpRelationRowDirectiveController {
}
public removeRelation() {
this.relation.delete().then(() => {
this.$scope.$emit('wp-relations.changed', this.relation);
this.wpCacheService.updateWorkPackage(this.relatedWorkPackage);
this.wpNotificationsService.showSave(this.relatedWorkPackage);
this.$timeout(() => {
angular.element('#relation--add-relation').focus();
});
})
this.wpRelations.removeRelation(this.relation)
.then(() => {
this.wpCacheService.updateWorkPackage(this.relatedWorkPackage);
this.wpNotificationsService.showSave(this.relatedWorkPackage);
this.$timeout(() => {
angular.element('#relation--add-relation').focus();
});
})
.catch((err:any) => this.wpNotificationsService.handleErrorResponse(err, this.relatedWorkPackage));
}
}

@ -14,7 +14,7 @@ export class WorkPackageRelationsCreateController {
public selectedWpId:string;
public externalFormToggle: boolean;
public fixedRelationType:string;
public relationTypes = this.wpRelationsService.getRelationTypes(true);
public relationTypes = this.wpRelations.getRelationTypes(true);
public canAddChildren = !!this.workPackage.addChild;
public canLinkChildren = !!this.workPackage.changeParent;
@ -25,7 +25,7 @@ export class WorkPackageRelationsCreateController {
protected $rootScope:ng.IRootScopeService,
protected $element:ng.IAugmentedJQuery,
protected $timeout:ng.ITimeoutService,
protected wpRelationsService:WorkPackageRelationsService,
protected wpRelations:WorkPackageRelationsService,
protected wpRelationsHierarchyService:WorkPackageRelationsHierarchyService,
protected wpNotificationsService:WorkPackageNotificationService,
protected wpCacheService:WorkPackageCacheService) {
@ -97,9 +97,8 @@ export class WorkPackageRelationsCreateController {
}
protected createCommonRelation() {
return this.wpRelationsService.addCommonRelation(this.workPackage, this.selectedRelationType, this.selectedWpId)
return this.wpRelations.addCommonRelation(this.workPackage, this.selectedRelationType, this.selectedWpId)
.then(relation => {
this.$scope.$emit('wp-relations.changed', relation);
this.wpNotificationsService.showSave(this.workPackage);
})
.catch(err => this.wpNotificationsService.handleErrorResponse(err, this.workPackage))

@ -34,7 +34,8 @@ import {
WorkPackageResource
} from '../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageCacheService} from '../work-packages/work-package-cache.service';
import {Observable} from "rxjs";
import { Observable } from "rxjs";
import { WorkPackageRelationsService, RelationsStateValue } from "./wp-relations.service";
export class WorkPackageRelationsController {
public relationGroups:RelatedWorkPackagesGroup;
@ -49,12 +50,15 @@ export class WorkPackageRelationsController {
protected $q:ng.IQService,
protected $state:ng.ui.IState,
protected I18n:op.I18n,
protected wpRelations:WorkPackageRelationsService,
protected wpCacheService:WorkPackageCacheService) {
// Reload the current relations after a change, causing loadRelations to re-run
this.$scope.$on('wp-relations.changed', () => {
this.workPackage.updateLinkedResources('relations');
});
this.wpRelations
.relationState(this.workPackage.id)
.observeOnScope(this.$scope)
.subscribe((relations:RelationsStateValue) => {
this.loadedRelations(relations);
});
// Listen for changes to this WP.
this.wpCacheService
@ -62,7 +66,7 @@ export class WorkPackageRelationsController {
.observeOnScope(this.$scope)
.subscribe((wp:WorkPackageResourceInterface) => {
this.workPackage = wp;
this.workPackage.relations.$load().then(this.loadRelations.bind(this));
this.wpRelations.require(wp);
});
}
@ -80,9 +84,9 @@ export class WorkPackageRelationsController {
return observablesToGetZipped[0];
}
protected getRelatedWorkPackageId(relation:any):string {
let direction = (relation.to.href === this.workPackage.href) ? 'from' : 'to';
return relation[direction].href.split('/').pop();
protected getRelatedWorkPackageId(relation:RelationResourceInterface):string {
const involved = relation.ids;
return (relation.to.href === this.workPackage.href) ? involved.from : involved.to;
}
public toggleGroupBy() {
@ -105,16 +109,16 @@ export class WorkPackageRelationsController {
});
}
protected loadRelations():void {
protected loadedRelations(stateValues:RelationsStateValue):void {
var relatedWpIds:string[] = [];
var relations:{[wpId:string]: any} = [];
if (this.workPackage.relations.elements.length === 0) {
if (_.size(stateValues) === 0) {
this.currentRelations = [];
return this.buildRelationGroups();
}
this.workPackage.relations.elements.forEach(relation => {
_.each(stateValues, (relation:RelationResourceInterface) => {
const relatedWpId = this.getRelatedWorkPackageId(relation);
relatedWpIds.push(relatedWpId);
relations[relatedWpId] = relation;

@ -30,12 +30,20 @@
import {wpDirectivesModule} from '../../angular-modules';
import {WorkPackageCacheService} from '../work-packages/work-package-cache.service';
import {WorkPackageNotificationService} from '../wp-edit/wp-notification.service';
import {RelationResource} from '../api/api-v3/hal-resources/relation-resource.service';
import {WorkPackageResourceInterface} from '../api/api-v3/hal-resources/work-package-resource.service';
import { RelationResource, RelationResourceInterface } from '../api/api-v3/hal-resources/relation-resource.service';
import { WorkPackageResourceInterface } from '../api/api-v3/hal-resources/work-package-resource.service';
import { HalRequestService } from "../api/api-v3/hal-request/hal-request.service";
import { CollectionResource } from "../api/api-v3/hal-resources/collection-resource.service";
import { State } from "../../helpers/reactive-fassade";
import { WorkPackageStates } from "../work-package-states.service";
export type RelationsStateValue = {[id:number]:RelationResource};
export class WorkPackageRelationsService {
constructor(protected $rootScope:ng.IRootScopeService,
protected $q:ng.IQService,
protected wpStates:WorkPackageStates,
protected halRequest:HalRequestService,
protected wpCacheService:WorkPackageCacheService,
protected wpNotificationsService:WorkPackageNotificationService,
protected I18n:op.I18n,
@ -43,6 +51,43 @@ export class WorkPackageRelationsService {
protected NotificationsService:any) {
}
/**
* Return the relation state for the given work package ID.
*/
public relationState(workPackageId:string):State<RelationsStateValue> {
return this.wpStates.relations.get(workPackageId);
}
/**
* Require the relations of the given singular work package to be loaded into its state.
*/
public require(workPackage:WorkPackageResourceInterface, force:boolean = false) {
const state = this.relationState(workPackage.id);
if (force) {
state.clear();
}
if (state.isPristine()) {
workPackage.relations.$load(true).then((collection:CollectionResource) => {
if (collection.elements.length > 0) {
this.mergeIntoStates(collection.elements as RelationResource[]);
} else {
this.relationState(workPackage.id).put({}, 'Received empty response from singular relations');
}
});
}
}
/**
* Require the relations of a set of involved work packages loaded into the states.
*/
public requireInvolved(workPackageIds:string[]) {
this.relationsRequest(workPackageIds).then((elements:RelationResource[]) => {
this.mergeIntoStates(elements);
});
}
public addCommonRelation(workPackage:WorkPackageResourceInterface, relationType:string, relatedWpId:string) {
const params = {
_links: {
@ -52,7 +97,10 @@ export class WorkPackageRelationsService {
type: relationType
};
return workPackage.addRelation(params);
return workPackage.addRelation(params).then((relation:RelationResourceInterface) => {
this.merge(workPackage.id, [relation]);
return relation;
});
}
public getRelationTypes(rejectParentChild?:boolean):any[] {
@ -66,6 +114,96 @@ export class WorkPackageRelationsService {
return { name: key, label: this.I18n.t('js.relation_labels.' + key) };
});
}
/**
* Update the given relation
*/
public updateRelation(workPackageId:string, relation:RelationResourceInterface, params:any) {
return relation.updateImmediately(params)
.then((savedRelation:RelationResourceInterface) => {
this.merge(workPackageId, [savedRelation]);
return savedRelation;
});
}
/**
* Remove the given relation.
*/
public removeRelation(relation:RelationResourceInterface) {
return relation.delete().then(() => {
_.each(relation.ids, (member:string) => {
const state = this.relationState(member);
const currentValue = state.getCurrentValue();
if (currentValue !== null) {
delete currentValue[relation.id];
state.put(currentValue);
}
});
});
}
/**
* Merge a set of relations into the associated states
*/
private mergeIntoStates(elements:RelationResource[]) {
const stateValues = this.accumulateRelationsFromCollection(elements);
_.each(stateValues, (relations:RelationResource[], workPackageId:string) => {
this.merge(workPackageId, relations);
});
}
/**
*
* We don't know how many values we're getting for a single work package
* So accumlate the state values before pushing them once.
*/
private accumulateRelationsFromCollection(relations:RelationResource[]) {
const stateValues:{[workPackageId:string]:RelationResource[]} = {};
relations.forEach((relation:RelationResource) => {
const involved = relation.ids;
if (!stateValues[involved.from]) {
stateValues[involved.from] = [];
}
if (!stateValues[involved.to]) {
stateValues[involved.to] = [];
}
stateValues[involved.from].push(relation);
stateValues[involved.to].push(relation);
});
return stateValues;
}
/**
* Merge an object of relations into the associated state or create it, if empty.
*/
private merge(workPackageId:string, newRelations:RelationResource[]) {
const state = this.relationState(workPackageId);
let relationsToInsert = _.keyBy(newRelations, r => r.id);
let current = state.getCurrentValue()!;
if (current !== null) {
relationsToInsert = _.assign(current, relationsToInsert);
}
state.put(relationsToInsert, 'Initializing relations state.');
}
private relationsRequest(workPackageIds:string[]):ng.IPromise<RelationResource[]> {
return this.halRequest.get(
'/api/v3/relations',
{
filters: JSON.stringify([{ involved: {operator: '=', values: workPackageIds } }])
}).then((collection:CollectionResource) => {
return collection.elements;
});
}
}
wpDirectivesModule.service('wpRelationsService', WorkPackageRelationsService);
wpDirectivesModule.service('wpRelations', WorkPackageRelationsService);

@ -0,0 +1,22 @@
import { RelationResource } from "../../../api/api-v3/hal-resources/relation-resource.service";
export class TimelineRelationElement {
constructor(public belongsToId:string, public relation:RelationResource) {
}
public static workPackagePrefix(workPackageId:string) {
return `__tl-relation-${workPackageId}`;
}
public get prefix():string {
return TimelineRelationElement.workPackagePrefix(this.belongsToId);
}
public get identifier():string {
return `${this.prefix}-${this.relation.id}`;
}
public get classNames():string[] {
return [this.prefix, this.identifier];
}
}

@ -67,7 +67,6 @@ export class WorkPackageTimelineCell {
.filter(([renderInfo, visible]) => visible)
.map(([renderInfo, visible]) => renderInfo)
.subscribe(renderInfo => {
// console.error("Timeline Cell drawing", this.workPackageId);
this.updateView(renderInfo);
this.workPackageTimeline.globalService.updateWorkPackageInfo(this);
});

@ -27,15 +27,15 @@
// ++
import {openprojectModule} from "../../../angular-modules";
import {TimelineViewParameters, RenderInfo, timelineElementCssClass} from "./wp-timeline";
import {WorkPackageResourceInterface} from "../../api/api-v3/hal-resources/work-package-resource.service";
import {HalRequestService} from "../../api/api-v3/hal-request/hal-request.service";
import {WorkPackageResourceInterface} from "./../../api/api-v3/hal-resources/work-package-resource.service";
import {WpTimelineHeader} from "./wp-timeline.header";
import {States} from "../../states.service";
import {BehaviorSubject, Observable} from "rxjs";
import * as moment from "moment";
import {WpTimelineGlobalService} from "./wp-timeline-global.directive";
import {opDimensionEventName} from "../../common/ui/detect-dimension-changes.directive";
import {scopeDestroyed$} from "../../../helpers/angular-rx-utils";
import { scopeDestroyed$ } from "../../../helpers/angular-rx-utils";
import { debugLog } from "../../../helpers/debug_output";
import Moment = moment.Moment;
import IDirective = angular.IDirective;
import IScope = angular.IScope;
@ -48,7 +48,7 @@ export class WorkPackageTimelineTableController {
public wpTimelineHeader: WpTimelineHeader;
public readonly globalService = new WpTimelineGlobalService(this, this.$scope, this.states, this.halRequest);
public readonly globalService = new WpTimelineGlobalService(this.$scope);
private updateAllWorkPackagesSubject = new BehaviorSubject<boolean>(true);
@ -61,8 +61,7 @@ export class WorkPackageTimelineTableController {
constructor(private $scope: IScope,
private $element: ng.IAugmentedJQuery,
private TypeResource:any,
private states: States,
private halRequest: HalRequestService) {
private states: States) {
"ngInject";
@ -111,7 +110,7 @@ export class WorkPackageTimelineTableController {
refreshView() {
if (!this.refreshViewRequested) {
console.error("refreshView()");
debugLog('refreshView() in timeline container');
setTimeout(() => {
this.calculateViewParams(this._viewParameters);
this.updateAllWorkPackagesSubject.next(true);
@ -128,17 +127,12 @@ export class WorkPackageTimelineTableController {
}
addWorkPackage(wpId: string): Observable<RenderInfo> {
// console.error("addWorkPackage()", wpId);
const wpObs = this.states.workPackages.get(wpId)
.observeUntil(scopeDestroyed$(this.$scope))
.map((wp: any) => {
// console.error("Timeline work package stream: WorkPackage", wpId, "incoming...");
this.workPackagesInView[wp.id] = wp;
const viewParamsChanged = this.calculateViewParams(this._viewParameters);
if (viewParamsChanged) {
// console.log(" view params changed");
// view params have changed, notify all cells
this.globalService.updateViewParameter(this._viewParameters);
this.refreshView();
@ -181,8 +175,6 @@ export class WorkPackageTimelineTableController {
return false;
}
// console.error("calculateViewParams()");
const newParams = new TimelineViewParameters();
let changed = false;

@ -28,22 +28,26 @@
// ++
import IDirective = angular.IDirective;
import IComponentOptions = angular.IComponentOptions;
import {timelineElementCssClass, TimelineViewParameters} from "./wp-timeline";
import {WorkPackageTimelineCell} from "./wp-timeline-cell";
import {States} from "../../states.service";
import {HalRequestService} from "../../api/api-v3/hal-request/hal-request.service";
import {RelationResource} from "../../api/api-v3/hal-resources/relation-resource.service";
import {CollectionResource} from "../../api/api-v3/hal-resources/collection-resource.service";
import {debugLog} from "../../../helpers/debug_output";
import {WorkPackageResource} from "../../api/api-v3/hal-resources/work-package-resource.service";
import {WorkPackageTimelineTableController} from "./wp-timeline-container.directive";
import {timelineElementCssClass, TimelineViewParameters} from './wp-timeline';
import {WorkPackageTimelineCell} from './wp-timeline-cell';
import {States} from '../../states.service';
import {HalRequestService} from '../../api/api-v3/hal-request/hal-request.service';
import {RelationResource} from '../../api/api-v3/hal-resources/relation-resource.service';
import {CollectionResource} from '../../api/api-v3/hal-resources/collection-resource.service';
import {debugLog} from '../../../helpers/debug_output';
import {WorkPackageResource} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageStates} from '../../work-package-states.service';
import {injectorBridge} from '../../angular/angular-injector-bridge.functions';
import {WorkPackageRelationsService, RelationsStateValue} from "../../wp-relations/wp-relations.service";
import {Observable} from "rxjs/Rx";
import { TimelineRelationElement } from "./global-elements/timeline-relation-element";
import { scopeDestroyed$ } from "../../../helpers/angular-rx-utils";
import IScope = angular.IScope;
export const timelineGlobalElementCssClassname = 'timeline-global-element';
function newSegment(vp: TimelineViewParameters,
classId: string,
classNames: string[],
color: string,
top: number,
left: number,
@ -51,7 +55,7 @@ function newSegment(vp: TimelineViewParameters,
height: number): HTMLElement {
const segment = document.createElement('div');
segment.classList.add(timelineElementCssClass, timelineGlobalElementCssClassname, classId);
segment.classList.add(timelineElementCssClass, timelineGlobalElementCssClassname, ...classNames);
segment.style.position = 'absolute';
segment.style.cssFloat = 'left';
segment.style.backgroundColor = 'blue';
@ -64,56 +68,25 @@ function newSegment(vp: TimelineViewParameters,
return segment;
}
export class TimelineGlobalElement {
static readonly timelineGlobalElementIdCssClass = "timeline-global-element-id-";
private static nextId = 0;
readonly id = TimelineGlobalElement.nextId++;
readonly classId = TimelineGlobalElement.timelineGlobalElementIdCssClass + this.id;
from: string;
to: string;
}
export class WpTimelineGlobalService {
private workPackageIdOrder: string[] = [];
// Injected arguments
public states:States;
public wpStates:WorkPackageStates;
public wpRelations:WorkPackageRelationsService;
private viewParameters: TimelineViewParameters;
private workPackageIdOrder:string[] = [];
private cells: {[id: string]: WorkPackageTimelineCell} = {};
private viewParameters:TimelineViewParameters;
private elements: TimelineGlobalElement[] = [];
private cells:{[id: string]:WorkPackageTimelineCell} = {};
constructor(private timelineController: WorkPackageTimelineTableController,
scope: IScope,
states: States,
halRequest: HalRequestService) {
private elements:TimelineRelationElement[] = [];
states.table.rows.observeOnScope(scope)
.subscribe(rows => {
this.workPackageIdOrder = rows.map(wp => wp.id.toString());
halRequest.get(
'/api/v3/relations',
{
filters: JSON.stringify([{ involved: {operator: '=', values: this.workPackageIdOrder } }])
}).then((collection: CollectionResource) => {
this.removeAllElements();
collection.elements.forEach((relation: RelationResource) => {
const fromId = WorkPackageResource.idFromLink(relation.from.href!);
const toId = WorkPackageResource.idFromLink(relation.to.href!);
this.displayRelation(fromId, toId);
});
this.renderElements();
});
});
constructor(private scope:IScope) {
injectorBridge(this);
this.requireVisibleRelations();
this.setupRelationSubscription();
}
updateViewParameter(viewParams: TimelineViewParameters) {
@ -131,22 +104,62 @@ export class WpTimelineGlobalService {
this.update();
}
displayRelation(from: string, to: string): number {
const elem = new TimelineGlobalElement();
elem.from = from;
elem.to = to;
this.elements.push(elem);
this.update();
return elem.id;
/**
* Ensure visible relations (through table.rows) are loaded automatically.
*/
private requireVisibleRelations() {
// Observe the rows and request relations if changed
// AND timeline is visible.
Observable.combineLatest(
this.states.table.timelineVisible.observeUntil(scopeDestroyed$(this.scope)),
this.states.table.rows.observeUntil(scopeDestroyed$(this.scope))
)
.filter(([visible, rows]) => visible)
.map(([visible, rows]) => rows)
.subscribe((rows:WorkPackageResource[]) => {
this.workPackageIdOrder = rows.map(wp => wp.id.toString());
this.wpRelations.requireInvolved(this.workPackageIdOrder);
});
}
/**
* Refresh relations of visible rows.
*/
private setupRelationSubscription() {
this.wpStates.relations
.observeUntil(scopeDestroyed$(this.scope))
.withLatestFrom(
this.states.table.timelineVisible.observeUntil(scopeDestroyed$(this.scope))
)
.filter(([nextVal, visible]) => nextVal && visible)
.map(([nextVal, visible]) => nextVal)
.subscribe((nextVal:[string, RelationsStateValue]) => {
const [workPackageId, relations] = nextVal;
if (workPackageId && this.cells[workPackageId]) {
this.refreshRelations(workPackageId, relations);
}
});
}
removeElement(id: number) {
jQuery("." + TimelineGlobalElement.timelineGlobalElementIdCssClass + id).remove();
_.remove(this.elements, elem => elem.id == id);
private refreshRelations(workPackageId:string, relations:RelationsStateValue) {
// Remove all previous relations for the work package
const prefix = TimelineRelationElement.workPackagePrefix(workPackageId);
jQuery(`.${prefix}`).remove();
_.remove(this.elements, (element) => element.belongsToId === workPackageId);
_.each(relations, (relation:RelationResource) => {
const elem = new TimelineRelationElement(workPackageId, relation);
this.elements.push(elem);
if (this.viewParameters !== undefined) {
this.renderElement(elem);
}
});
}
private update() {
console.error("global update()");
this.removeAllVisibleElements();
this.renderElements();
}
@ -166,80 +179,81 @@ export class WpTimelineGlobalService {
return;
}
for (const e of this.elements) {
this.renderElement(e);
}
}
private renderElement(e:TimelineRelationElement) {
const vp = this.viewParameters;
const involved = e.relation.ids;
const idxFrom = this.workPackageIdOrder.indexOf(involved.from);
const idxTo = this.workPackageIdOrder.indexOf(involved.to);
for (let e of this.elements) {
jQuery('.' + e.classId).remove();
const startCell = this.cells[involved.from];
const endCell = this.cells[involved.to];
const idxFrom = this.workPackageIdOrder.indexOf(e.from);
const idxTo = this.workPackageIdOrder.indexOf(e.to);
if (idxFrom === -1 || idxTo === -1 || _.isNil(startCell) || _.isNil(endCell)) {
return;
}
const startCell = this.cells[e.from];
const endCell = this.cells[e.to];
if (!startCell.canConnectRelations() || !endCell.canConnectRelations()) {
return;
}
if (idxFrom === -1 || idxTo === -1 || _.isNil(startCell) || _.isNil(endCell)) {
continue;
}
const directionY = idxFrom < idxTo ? 1 : -1;
let lastX = startCell.getRightmostPosition();
let targetX = endCell.getLeftmostPosition();
const directionX = targetX >= lastX ? 1 : -1;
if (!startCell.canConnectRelations() || !endCell.canConnectRelations()) {
continue;
}
// start
if (!startCell) {
return;
}
const startLength = 13;
startCell.timelineCell.appendChild(newSegment(vp, e.classNames, 'green', 19, lastX, startLength, 1));
lastX += startLength;
const directionY = idxFrom < idxTo ? 1 : -1;
let lastX = startCell.getRightmostPosition();
let targetX = endCell.getLeftmostPosition();
const directionX = targetX >= lastX ? 1 : -1;
if (directionY === 1) {
startCell.timelineCell.appendChild(newSegment(vp, e.classNames, 'red', 19, lastX, 1, 22));
} else {
startCell.timelineCell.appendChild(newSegment(vp, e.classNames, 'red', -1, lastX, 1, 22));
}
// start
if (!startCell) {
// vert segment
for (let index = idxFrom + directionY; index !== idxTo; index += directionY) {
const id = this.workPackageIdOrder[index];
const cell = this.cells[id];
if (_.isNil(cell)) {
continue;
}
cell.timelineCell.appendChild(newSegment(vp, e.classNames, 'blue', 0, lastX, 1, 42));
}
const startLength = 13;
startCell.timelineCell.appendChild(newSegment(vp, e.classId, 'green', 19, lastX, startLength, 1));
lastX += startLength;
// end
if (directionX === 1) {
if (directionY === 1) {
startCell.timelineCell.appendChild(newSegment(vp, e.classId, 'red', 19, lastX, 1, 22));
endCell.timelineCell.appendChild(newSegment(vp, e.classNames, 'green', 0, lastX, 1, 19));
endCell.timelineCell.appendChild(newSegment(vp, e.classNames, 'blue', 19, lastX, targetX - lastX, 1));
} else {
startCell.timelineCell.appendChild(newSegment(vp, e.classId, 'red', -1, lastX, 1, 22));
}
// vert segment
for (let index = idxFrom + directionY; index !== idxTo; index += directionY) {
const id = this.workPackageIdOrder[index];
const cell = this.cells[id];
if (_.isNil(cell)) {
continue;
}
cell.timelineCell.appendChild(newSegment(vp, e.classId, 'blue', 0, lastX, 1, 42));
endCell.timelineCell.appendChild(newSegment(vp, e.classNames, 'green', 19, lastX, 1, 22));
endCell.timelineCell.appendChild(newSegment(vp, e.classNames, 'blue', 19, lastX, targetX - lastX, 1));
}
// end
if (directionX === 1) {
if (directionY === 1) {
endCell.timelineCell.appendChild(newSegment(vp, e.classId, 'green', 0, lastX, 1, 19));
endCell.timelineCell.appendChild(newSegment(vp, e.classId, 'blue', 19, lastX, targetX - lastX, 1));
} else {
endCell.timelineCell.appendChild(newSegment(vp, e.classId, 'green', 19, lastX, 1, 22));
endCell.timelineCell.appendChild(newSegment(vp, e.classId, 'blue', 19, lastX, targetX - lastX, 1));
}
} else {
if (directionY === 1) {
endCell.timelineCell.appendChild(newSegment(vp, e.classNames, 'green', 0, lastX, 1, 8));
endCell.timelineCell.appendChild(newSegment(vp, e.classNames, 'blue', 8, targetX - 10, lastX - targetX + 11, 1));
endCell.timelineCell.appendChild(newSegment(vp, e.classNames, 'green', 8, targetX - 10, 1, 11));
endCell.timelineCell.appendChild(newSegment(vp, e.classNames, 'red', 19, targetX - 10, 10, 1));
} else {
if (directionY === 1) {
endCell.timelineCell.appendChild(newSegment(vp, e.classId, 'green', 0, lastX, 1, 8));
endCell.timelineCell.appendChild(newSegment(vp, e.classId, 'blue', 8, targetX - 10, lastX - targetX + 11, 1));
endCell.timelineCell.appendChild(newSegment(vp, e.classId, 'green', 8, targetX - 10, 1, 11));
endCell.timelineCell.appendChild(newSegment(vp, e.classId, 'red', 19, targetX - 10, 10, 1));
} else {
endCell.timelineCell.appendChild(newSegment(vp, e.classId, 'green', 32, lastX, 1, 8));
endCell.timelineCell.appendChild(newSegment(vp, e.classId, 'blue', 32, targetX - 10, lastX - targetX + 11, 1));
endCell.timelineCell.appendChild(newSegment(vp, e.classId, 'green', 19, targetX - 10, 1, 13));
endCell.timelineCell.appendChild(newSegment(vp, e.classId, 'red', 19, targetX - 10, 10, 1));
}
endCell.timelineCell.appendChild(newSegment(vp, e.classNames, 'green', 32, lastX, 1, 8));
endCell.timelineCell.appendChild(newSegment(vp, e.classNames, 'blue', 32, targetX - 10, lastX - targetX + 11, 1));
endCell.timelineCell.appendChild(newSegment(vp, e.classNames, 'green', 19, targetX - 10, 1, 13));
endCell.timelineCell.appendChild(newSegment(vp, e.classNames, 'red', 19, targetX - 10, 10, 1));
}
}
}
}
WpTimelineGlobalService.$inject = ['states', 'wpStates', 'wpRelations'];

@ -193,7 +193,7 @@ export class MultiState<T> extends StoreElement {
* @param scope An optional scope
* @returns {Observable<string>} Observable on the changed ids
*/
public observeUntil(subject: Subject<any>): Observable<[string, T]> {
public observeUntil(subject: Observable<any>): Observable<[string, T]> {
return this.memberSubject.takeUntil(subject);
}

Loading…
Cancel
Save