kanbanworkflowstimelinescrumrubyroadmapproject-planningproject-managementopenprojectangularissue-trackerifcgantt-chartganttbug-trackerboardsbcf
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
196 lines
6.6 KiB
196 lines
6.6 KiB
import {Injector} from '@angular/core';
|
|
import {I18nToken} from 'core-app/angular4-transition-utils';
|
|
import {timeOutput} from '../../../helpers/debug_output';
|
|
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
|
|
import {States} from '../../states.service';
|
|
import {WorkPackageEditingService} from '../../wp-edit-form/work-package-editing-service';
|
|
import {WorkPackageTable} from '../wp-fast-table';
|
|
import {RelationRenderInfo, RelationsRenderPass} from './relations/relations-render-pass';
|
|
import {SingleRowBuilder} from './rows/single-row-builder';
|
|
import {TimelineRenderPass} from './timeline/timeline-render-pass';
|
|
|
|
export type RenderedRowType = 'primary' | 'relations';
|
|
|
|
export interface RowRenderInfo {
|
|
// Unique class name as an identifier to uniquely identify the row in both table and timeline
|
|
classIdentifier:string;
|
|
// Additional classes to be added by any secondary render passes
|
|
additionalClasses:string[];
|
|
// If this row is a work package, contains a reference to the rendered WP
|
|
workPackage:WorkPackageResourceInterface | null;
|
|
// If this is an additional row not present, this contains a reference to the WP
|
|
// it originated from
|
|
belongsTo?:WorkPackageResourceInterface;
|
|
// The type of row this was rendered from
|
|
renderType:RenderedRowType;
|
|
// Marks if the row is currently hidden to the user
|
|
hidden:boolean;
|
|
// Additional data by the render passes
|
|
data?:any;
|
|
}
|
|
|
|
export type RenderedRow = { classIdentifier:string, workPackageId:string | null, hidden:boolean };
|
|
|
|
export abstract class PrimaryRenderPass {
|
|
|
|
public wpEditing:WorkPackageEditingService = this.injector.get(WorkPackageEditingService);
|
|
public states:States = this.injector.get(States);
|
|
public I18n:op.I18n = this.injector.get(I18nToken);
|
|
|
|
/** The rendered order of rows of work package IDs or <null>, if not a work package row */
|
|
public renderedOrder:RowRenderInfo[];
|
|
|
|
/** Resulting table body */
|
|
public tableBody:DocumentFragment;
|
|
|
|
/** Additional render pass that handles timeline rendering */
|
|
public timeline:TimelineRenderPass;
|
|
|
|
/** Additional render pass that handles table relation rendering */
|
|
public relations:RelationsRenderPass;
|
|
|
|
constructor(public readonly injector:Injector,
|
|
public workPackageTable:WorkPackageTable,
|
|
public rowBuilder:SingleRowBuilder) {
|
|
}
|
|
|
|
/**
|
|
* Execute the entire render pass, executing this pass and all subsequent registered passes
|
|
* for timeline and relations.
|
|
* @return {PrimaryRenderPass}
|
|
*/
|
|
public render():this {
|
|
|
|
timeOutput('Primary render pass', () => {
|
|
|
|
// Prepare and reset the render pass
|
|
this.prepare();
|
|
|
|
// Render into the table fragment
|
|
this.doRender();
|
|
});
|
|
|
|
// Render subsequent passes
|
|
// that may modify the structure of the table
|
|
timeOutput('Relations render pass', () => {
|
|
this.relations.render();
|
|
});
|
|
|
|
// Synchronize the rows to timeline
|
|
timeOutput('Timelines render pass', () => {
|
|
this.timeline.render();
|
|
});
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Refresh a single row using the render pass it was originally created from.
|
|
* @param row
|
|
*/
|
|
public refresh(row:RowRenderInfo, workPackage:WorkPackageResourceInterface, body:HTMLElement) {
|
|
let oldRow = jQuery(body).find(`.${row.classIdentifier}`);
|
|
let replacement:JQuery | null = null;
|
|
let editing = this.wpEditing.changesetFor(workPackage);
|
|
|
|
switch (row.renderType) {
|
|
case 'primary':
|
|
replacement = this.rowBuilder.refreshRow(workPackage, editing, oldRow);
|
|
break;
|
|
case 'relations':
|
|
replacement = this.relations.refreshRelationRow(row as RelationRenderInfo, workPackage, editing, oldRow);
|
|
}
|
|
|
|
if (replacement !== null && oldRow.length) {
|
|
oldRow.replaceWith(replacement);
|
|
}
|
|
}
|
|
|
|
public get result():RenderedRow[] {
|
|
return this.renderedOrder.map((row) => {
|
|
return {
|
|
classIdentifier: row.classIdentifier,
|
|
workPackageId: row.workPackage ? row.workPackage.id : null,
|
|
hidden: row.hidden
|
|
} as RenderedRow;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Splice a row into a specific location of the current render pass through the given selector.
|
|
*
|
|
* 1. Insert into the document fragment after the last match of the selector
|
|
* 2. Splice into the renderedOrder array.
|
|
*/
|
|
public spliceRow(row:HTMLElement, selector:string, renderedInfo:RowRenderInfo) {
|
|
// Insert into table using the selector
|
|
// If it matches multiple, select the last element
|
|
const target = jQuery(this.tableBody)
|
|
.find(selector)
|
|
.last();
|
|
|
|
target.after(row);
|
|
|
|
// Splice the renderedOrder at this exact location
|
|
const index = target.index();
|
|
this.renderedOrder.splice(index + 1, 0, renderedInfo);
|
|
}
|
|
|
|
|
|
protected prepare() {
|
|
this.timeline = new TimelineRenderPass(this.injector, this.workPackageTable, this);
|
|
this.relations = new RelationsRenderPass(this.injector, this.workPackageTable, this);
|
|
this.tableBody = document.createDocumentFragment();
|
|
this.renderedOrder = [];
|
|
}
|
|
|
|
/**
|
|
* The actual render function of this renderer.
|
|
*/
|
|
protected abstract doRender():void;
|
|
|
|
/**
|
|
* Append a work package row to both containers
|
|
* @param workPackage The work package, if the row belongs to one
|
|
* @param row HTMLElement to append
|
|
* @param rowClasses Additional classes to apply to the timeline row for mirroring purposes
|
|
* @param hidden whether the row was rendered hidden
|
|
*/
|
|
protected appendRow(workPackage:WorkPackageResourceInterface,
|
|
row:HTMLElement,
|
|
additionalClasses:string[] = [],
|
|
hidden:boolean = false) {
|
|
|
|
this.tableBody.appendChild(row);
|
|
|
|
this.renderedOrder.push({
|
|
classIdentifier: this.rowBuilder.classIdentifier(workPackage),
|
|
additionalClasses: additionalClasses,
|
|
workPackage: workPackage,
|
|
renderType: 'primary',
|
|
hidden: hidden
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Append a non-work package row to both containers
|
|
* @param row HTMLElement to append
|
|
* @param classIdentifer a unique identifier for the two rows (one each in table/timeline).
|
|
* @param hidden whether the row was rendered hidden
|
|
*/
|
|
protected appendNonWorkPackageRow(row:HTMLElement,
|
|
classIdentifer:string,
|
|
additionalClasses:string[] = [],
|
|
hidden:boolean = false) {
|
|
row.classList.add(classIdentifer);
|
|
this.tableBody.appendChild(row);
|
|
|
|
this.renderedOrder.push({
|
|
classIdentifier: classIdentifer,
|
|
additionalClasses: additionalClasses,
|
|
workPackage: null,
|
|
renderType: 'primary',
|
|
hidden: hidden
|
|
});
|
|
}
|
|
}
|
|
|