diff --git a/frontend/app/components/context-menus/wp-context-menu/wp-context-menu.controller.ts b/frontend/app/components/context-menus/wp-context-menu/wp-context-menu.controller.ts index e3c6b904ab..1df8e5d6e5 100644 --- a/frontend/app/components/context-menus/wp-context-menu/wp-context-menu.controller.ts +++ b/frontend/app/components/context-menus/wp-context-menu/wp-context-menu.controller.ts @@ -34,10 +34,12 @@ import { WorkPackageResourceInterface } from "../../api/api-v3/hal-resources/work-package-resource.service"; import {WorkPackageRelationsHierarchyService} from "../../wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service"; +import {States} from '../../states.service'; function wpContextMenuController($scope:any, $rootScope:ng.IRootScopeService, $state:ng.ui.IStateService, + states:States, WorkPackageContextMenuHelper:any, WorkPackageService:any, wpRelationsHierarchyService:WorkPackageRelationsHierarchyService, @@ -49,8 +51,10 @@ function wpContextMenuController($scope:any, $scope.I18n = I18n; - if (!wpTableSelection.isSelected($scope.row.object.id)) { - wpTableSelection.setSelection($scope.row); + const wpId = $scope.workPackageId; + const workPackage = states.workPackages.get(wpId).value!; + if (!wpTableSelection.isSelected(wpId)) { + wpTableSelection.setSelection(wpId, $scope.rowIndex); } $scope.permittedActions = WorkPackageContextMenuHelper.getPermittedActions(getSelectedWorkPackages(), PERMITTED_CONTEXT_MENU_ACTIONS); @@ -60,9 +64,6 @@ function wpContextMenuController($scope:any, }; $scope.triggerContextMenuAction = function (action:any, link:any) { - let table:WorkPackageTable; - let wp:WorkPackageResourceInterface; - switch (action) { case 'delete': deleteSelectedWorkPackages(); @@ -77,20 +78,15 @@ function wpContextMenuController($scope:any, break; case 'relation-precedes': - table = $scope.table; - wp = $scope.row.object; - table.timelineController.startAddRelationPredecessor(wp); + $scope.table.timelineController.startAddRelationPredecessor(workPackage); break; case 'relation-follows': - table = $scope.table; - wp = $scope.row.object; - table.timelineController.startAddRelationFollower(wp); + $scope.table.timelineController.startAddRelationFollower(workPackage); break; case 'relation-new-child': - wp = $scope.row.object; - wpRelationsHierarchyService.addNewChildWp(wp); + wpRelationsHierarchyService.addNewChildWp(workPackage); break; default: @@ -145,15 +141,14 @@ function wpContextMenuController($scope:any, } function getSelectedWorkPackages() { - let workPackagefromContext = $scope.row.object; let selectedWorkPackages = wpTableSelection.getSelectedWorkPackages(); if (selectedWorkPackages.length === 0) { - return [workPackagefromContext]; + return [workPackage]; } - if (selectedWorkPackages.indexOf(workPackagefromContext) === -1) { - selectedWorkPackages.push(workPackagefromContext); + if (selectedWorkPackages.indexOf(workPackage) === -1) { + selectedWorkPackages.push(workPackage); } return selectedWorkPackages; diff --git a/frontend/app/components/context-menus/wp-context-menu/wp-context-menu.service.html b/frontend/app/components/context-menus/wp-context-menu/wp-context-menu.service.html index ddb1892383..78527cfbc6 100644 --- a/frontend/app/components/context-menus/wp-context-menu/wp-context-menu.service.html +++ b/frontend/app/components/context-menus/wp-context-menu/wp-context-menu.service.html @@ -7,13 +7,13 @@
  • - +
  • - + diff --git a/frontend/app/components/context-menus/wp-context-menu/wp-context-menu.service.test.ts b/frontend/app/components/context-menus/wp-context-menu/wp-context-menu.service.test.ts index 9d8697de29..54b23c637d 100644 --- a/frontend/app/components/context-menus/wp-context-menu/wp-context-menu.service.test.ts +++ b/frontend/app/components/context-menus/wp-context-menu/wp-context-menu.service.test.ts @@ -26,6 +26,7 @@ // See doc/COPYRIGHT.rdoc for more details. // ++ +import {States} from '../../states.service'; describe('workPackageContextMenu', () => { var container:any; var contextMenu; @@ -34,6 +35,12 @@ describe('workPackageContextMenu', () => { var setSelection:any; var ngContextMenu; var wpTableSelection; + var states:States; + var workPackage:any = { + id: 123, + update: '/work_packages/123/edit', + move: '/work_packages/move/new?ids%5B%5D=123', + }; beforeEach(angular.mock.module('ng-context-menu', 'openproject', @@ -60,11 +67,13 @@ describe('workPackageContextMenu', () => { container = angular.element('
    '); }); - beforeEach(angular.mock.inject((_$rootScope_:any, _ngContextMenu_:any, _wpTableSelection_:any, $templateCache:any) => { + beforeEach(angular.mock.inject((_$rootScope_:any, _states_:States, _ngContextMenu_:any, _wpTableSelection_:any, $templateCache:any) => { wpTableSelection = _wpTableSelection_; $rootScope = _$rootScope_; ngContextMenu = _ngContextMenu_; + states = _states_; + states.workPackages.get('123').putValue(workPackage); sinon.stub(wpTableSelection, 'getSelectedWorkPackages').returns([]); sinon.stub(wpTableSelection, 'isSelected').returns(false); @@ -81,17 +90,12 @@ describe('workPackageContextMenu', () => { templateUrl: 'work_package_context_menu.html' }); - contextMenu.open({x: 0, y: 0}); + contextMenu.open({x: 0, y: 0}, { workPackageId: '123', rowIndex: 1 }); })); describe('when the context menu context contains one work package', () => { var I18n:any; var actions = ['edit', 'move']; - var workPackage:any = { - id: 123, - update: '/work_packages/123/edit', - move: '/work_packages/move/new?ids%5B%5D=123', - } var directListElements:any; @@ -104,9 +108,6 @@ describe('workPackageContextMenu', () => { })); beforeEach(() => { - $rootScope.rows = []; - $rootScope.row = {object: workPackage}; - $rootScope.$digest(); directListElements = container.find('.dropdown-menu > li:not(.folder)'); @@ -125,15 +126,15 @@ describe('workPackageContextMenu', () => { }); it('sets the checked property of the row within the context to true', () => { - expect(setSelection).to.have.been.calledWith($rootScope.row); + expect(setSelection).to.have.been.calledWith('123', 1); }); describe('when delete is permitted on a work package', () => { workPackage['delete'] = '/work_packages/bulk'; beforeEach(() => { + $rootScope.wpId = '123'; $rootScope.rows = []; - $rootScope.row = {object: workPackage}; $rootScope.$digest(); directListElements = container.find('.dropdown-menu > li:not(.folder)'); diff --git a/frontend/app/components/wp-fast-table/builders/modes/grouped/grouped-render-pass.ts b/frontend/app/components/wp-fast-table/builders/modes/grouped/grouped-render-pass.ts index d7edf526e3..cd157f95ae 100644 --- a/frontend/app/components/wp-fast-table/builders/modes/grouped/grouped-render-pass.ts +++ b/frontend/app/components/wp-fast-table/builders/modes/grouped/grouped-render-pass.ts @@ -22,8 +22,8 @@ export class GroupedRenderPass extends PlainRenderPass { */ protected doRender() { let currentGroup:GroupObject | null = null; - this.workPackageTable.rows.forEach((wpId:string) => { - let row = this.workPackageTable.rowIndex[wpId]; + this.workPackageTable.originalRows.forEach((wpId:string) => { + let row = this.workPackageTable.originalRowIndex[wpId]; let nextGroup = this.matchingGroup(row.object); if (nextGroup && currentGroup !== nextGroup) { diff --git a/frontend/app/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts b/frontend/app/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts index fe2d726c4d..77bed5ffa3 100644 --- a/frontend/app/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts +++ b/frontend/app/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts @@ -52,8 +52,8 @@ export class HierarchyRenderPass extends PrimaryRenderPass { * Render the hierarchy table into the document fragment */ protected doRender() { - this.workPackageTable.rows.forEach((wpId:string) => { - const row:WorkPackageTableRow = this.workPackageTable.rowIndex[wpId]; + this.workPackageTable.originalRows.forEach((wpId:string) => { + const row:WorkPackageTableRow = this.workPackageTable.originalRowIndex[wpId]; const workPackage:WorkPackageResourceInterface = row.object; // If we need to defer this row, skip it for now @@ -94,7 +94,7 @@ export class HierarchyRenderPass extends PrimaryRenderPass { // Will only defer is parent is // 1. existent in the table results // 1. yet to be rendered - if (this.workPackageTable.rowIndex[parent.id] === undefined || this.rendered[parent.id]) { + if (this.workPackageTable.originalRowIndex[parent.id] === undefined || this.rendered[parent.id]) { return false; } @@ -117,7 +117,7 @@ export class HierarchyRenderPass extends PrimaryRenderPass { // run them through the callback deferredChildren.forEach((child:WorkPackageResourceInterface) => { // Callback on the child itself - const row:WorkPackageTableRow = this.workPackageTable.rowIndex[child.id]; + const row:WorkPackageTableRow = this.workPackageTable.originalRowIndex[child.id]; this.insertUnderParent(row, child.parent); // Descend into any children the child WP might have and callback diff --git a/frontend/app/components/wp-fast-table/builders/modes/plain/plain-render-pass.ts b/frontend/app/components/wp-fast-table/builders/modes/plain/plain-render-pass.ts index c7c3aa1fc8..8260f9f99e 100644 --- a/frontend/app/components/wp-fast-table/builders/modes/plain/plain-render-pass.ts +++ b/frontend/app/components/wp-fast-table/builders/modes/plain/plain-render-pass.ts @@ -13,9 +13,9 @@ export class PlainRenderPass extends PrimaryRenderPass { * The actual render function of this renderer. */ protected doRender():void { - this.workPackageTable.rows.forEach((wpId:string) => { - let row = this.workPackageTable.rowIndex[wpId]; - let [tr, _hidden] = this.rowBuilder.buildEmpty(row.object); + this.workPackageTable.originalRows.forEach((wpId:string) => { + let row = this.workPackageTable.originalRowIndex[wpId]; + let [tr,] = this.rowBuilder.buildEmpty(row.object); row.element = tr; this.appendRow(row.object, tr); this.tableBody.appendChild(tr); diff --git a/frontend/app/components/wp-fast-table/handlers/row/click-handler.ts b/frontend/app/components/wp-fast-table/handlers/row/click-handler.ts index 6396ba78c8..8927aeb40d 100644 --- a/frontend/app/components/wp-fast-table/handlers/row/click-handler.ts +++ b/frontend/app/components/wp-fast-table/handlers/row/click-handler.ts @@ -41,6 +41,7 @@ export class RowClickHandler implements TableEventHandler { // Locate the row from event let element = target.closest(this.SELECTOR); let wpId = element.data('workPackageId'); + let classIdentifier = element.data('classIdentifier'); if (!wpId) { return true; @@ -54,22 +55,22 @@ export class RowClickHandler implements TableEventHandler { // The current row is the last selected work package // not matter what other rows are (de-)selected below. // Thus save that row for the details view button. - let row = table.rowObject(wpId); + let [index, row] = table.findRenderedRow(classIdentifier); this.states.focusedWorkPackage.putValue(wpId); // Update single selection if no modifier present if (!(evt.ctrlKey || evt.metaKey || evt.shiftKey)) { - this.wpTableSelection.setSelection(row); + this.wpTableSelection.setSelection(wpId, index); } // Multiple selection if shift present if (evt.shiftKey) { - this.wpTableSelection.setMultiSelectionFrom(table.rows, row); + this.wpTableSelection.setMultiSelectionFrom(table.renderedRows, wpId, index); } // Single selection expansion if ctrl / cmd(mac) if (evt.ctrlKey || evt.metaKey) { - this.wpTableSelection.toggleRow(row.workPackageId); + this.wpTableSelection.toggleRow(wpId); } return false; diff --git a/frontend/app/components/wp-fast-table/handlers/row/context-menu-handler.ts b/frontend/app/components/wp-fast-table/handlers/row/context-menu-handler.ts index 0b2cd0ebaa..2e963b7410 100644 --- a/frontend/app/components/wp-fast-table/handlers/row/context-menu-handler.ts +++ b/frontend/app/components/wp-fast-table/handlers/row/context-menu-handler.ts @@ -47,8 +47,8 @@ export class ContextMenuHandler implements TableEventHandler { return false; } - let row = table.rowObject(element.data('workPackageId')); - this.contextMenu.activate('WorkPackageContextMenu', evt, {row: row, table: table}); + let [index,] = table.findRenderedRow(element.data('workPackageId')); + this.contextMenu.activate('WorkPackageContextMenu', evt, {workPackageId: wpId, rowIndex: index, table: table}); return false; } } diff --git a/frontend/app/components/wp-fast-table/handlers/row/context-menu-keyboard-handler.ts b/frontend/app/components/wp-fast-table/handlers/row/context-menu-keyboard-handler.ts index 976163a5ef..8ded4365cf 100644 --- a/frontend/app/components/wp-fast-table/handlers/row/context-menu-keyboard-handler.ts +++ b/frontend/app/components/wp-fast-table/handlers/row/context-menu-keyboard-handler.ts @@ -9,7 +9,7 @@ export class ContextMenuKeyboardHandler implements TableEventHandler { // Injections public contextMenu:ContextMenuService; - constructor(table: WorkPackageTable) { + constructor(private table:WorkPackageTable) { injectorBridge(this); } @@ -25,7 +25,7 @@ export class ContextMenuKeyboardHandler implements TableEventHandler { return jQuery(table.tbody); } - public handleEvent(table: WorkPackageTable, evt:JQueryEventObject):boolean { + public handleEvent(table:WorkPackageTable, evt:JQueryEventObject):boolean { let target = jQuery(evt.target); if (!(evt.keyCode === keyCodes.F10 && evt.shiftKey && evt.altKey)) { @@ -36,13 +36,14 @@ export class ContextMenuKeyboardHandler implements TableEventHandler { evt.stopPropagation(); // Locate the row from event - let element = target.closest(this.SELECTOR); - let row = table.rowObject(element.data('workPackageId')); + const element = target.closest(this.SELECTOR); + const wpId = element.data('workPackageId'); + const [index,] = table.findRenderedRow(element.data('workPackageId')); // Set position args to open at element let position = { of: target }; - this.contextMenu.activate('WorkPackageContextMenu', evt, { row: row }, position); + this.contextMenu.activate('WorkPackageContextMenu', evt, { workPackageId: wpId, rowIndex: index, table: this.table}, position); return false; } } diff --git a/frontend/app/components/wp-fast-table/handlers/row/double-click-handler.ts b/frontend/app/components/wp-fast-table/handlers/row/double-click-handler.ts index 2b35fcbdae..68228ea636 100644 --- a/frontend/app/components/wp-fast-table/handlers/row/double-click-handler.ts +++ b/frontend/app/components/wp-fast-table/handlers/row/double-click-handler.ts @@ -41,7 +41,7 @@ export class RowDoubleClickHandler implements TableEventHandler { // Locate the row from event let element = target.closest(this.SELECTOR); - let row = table.rowObject(element.data('workPackageId')); + let wpId = element.data('workPackageId'); // Ignore links if (target.is('a') || target.parent().is('a')) { @@ -49,11 +49,11 @@ export class RowDoubleClickHandler implements TableEventHandler { } // Save the currently focused work package - this.states.focusedWorkPackage.putValue(row.workPackageId); + this.states.focusedWorkPackage.putValue(wpId); this.$state.go( 'work-packages.show', - { workPackageId: row.workPackageId } + { workPackageId: wpId } ); return false; diff --git a/frontend/app/components/wp-fast-table/handlers/state/columns-transformer.ts b/frontend/app/components/wp-fast-table/handlers/state/columns-transformer.ts index f592b025b1..95bb85ded7 100644 --- a/frontend/app/components/wp-fast-table/handlers/state/columns-transformer.ts +++ b/frontend/app/components/wp-fast-table/handlers/state/columns-transformer.ts @@ -16,7 +16,7 @@ export class ColumnsTransformer { .filter(() => this.wpTableColumns.hasRelationColumns() === false) .takeUntil(this.states.table.stopAllSubscriptions) .subscribe(() => { - if (table.rows.length > 0) { + if (table.originalRows.length > 0) { var t0 = performance.now(); // Redraw the table section, ignore timeline diff --git a/frontend/app/components/wp-fast-table/handlers/state/selection-transformer.ts b/frontend/app/components/wp-fast-table/handlers/state/selection-transformer.ts index 0dab2624cd..abd99be932 100644 --- a/frontend/app/components/wp-fast-table/handlers/state/selection-transformer.ts +++ b/frontend/app/components/wp-fast-table/handlers/state/selection-transformer.ts @@ -22,7 +22,7 @@ export class SelectionTransformer { // Bind CTRL+A to select all work packages Mousetrap.bind(['command+a', 'ctrl+a'], (e) => { - this.wpTableSelection.selectAll(table.rows); + this.wpTableSelection.selectAll(table.renderedRows); e.preventDefault(); return false; diff --git a/frontend/app/components/wp-fast-table/helpers/wp-table-hierarchy-helpers.ts b/frontend/app/components/wp-fast-table/helpers/wp-table-hierarchy-helpers.ts index 16e4a41f22..5d89984a05 100644 --- a/frontend/app/components/wp-fast-table/helpers/wp-table-hierarchy-helpers.ts +++ b/frontend/app/components/wp-fast-table/helpers/wp-table-hierarchy-helpers.ts @@ -29,8 +29,8 @@ export function hasChildrenInTable(workPackage:WorkPackageResourceInterface, tab } // Return if this work package is in the ancestor chain of any of the work packages - return !!_.find(table.rows, (wpId:string) => { - const row = table.rowIndex[wpId].object; + return !!_.find(table.originalRows, (wpId:string) => { + const row = table.originalRowIndex[wpId].object; return row.ancestorIds.indexOf(workPackage.id.toString()) >= 0; }); diff --git a/frontend/app/components/wp-fast-table/state/wp-table-selection.service.ts b/frontend/app/components/wp-fast-table/state/wp-table-selection.service.ts index 059c514a37..be0d091e6a 100644 --- a/frontend/app/components/wp-fast-table/state/wp-table-selection.service.ts +++ b/frontend/app/components/wp-fast-table/state/wp-table-selection.service.ts @@ -3,6 +3,7 @@ import {opServicesModule} from '../../../angular-modules'; import {WPTableRowSelectionState, WorkPackageTableRow} from '../wp-table.interfaces'; import {WorkPackageResource} from '../../api/api-v3/hal-resources/work-package-resource.service'; import {InputState} from "reactivestates"; +import {RenderedRow} from '../builders/primary-render-pass'; export class WorkPackageTableSelection { @@ -23,11 +24,13 @@ export class WorkPackageTableSelection { /** * Select all work packages */ - public selectAll(rows: string[]) { + public selectAll(rows: RenderedRow[]) { const state:WPTableRowSelectionState = this._emptyState; - rows.forEach((workPackageId:string) => { - state.selected[workPackageId] = true; + rows.forEach((row) => { + if (row.workPackageId) { + state.selected[row.workPackageId] = true; + } }); this.selectionState.putValue(state); @@ -109,12 +112,12 @@ export class WorkPackageTableSelection { /** * Override current selection with the given work package id. */ - public setSelection(row:WorkPackageTableRow) { + public setSelection(wpId:string, position:number) { let state:WPTableRowSelectionState = { selected: {}, - activeRowIndex: row.position + activeRowIndex: position }; - state.selected[row.workPackageId] = true; + state.selected[wpId] = true; this.selectionState.putValue(state); } @@ -123,21 +126,21 @@ export class WorkPackageTableSelection { * Select a number of rows from the current `activeRowIndex` * to the selected target. * (aka shift click expansion) - * @param rows Current visible rows - * @param selected Selection target */ - public setMultiSelectionFrom(rows:string[], selected:WorkPackageTableRow) { + public setMultiSelectionFrom(rows:RenderedRow[], wpId:string, position:number) { let state = this.currentState; if (this.selectionCount === 0) { - state.selected[selected.workPackageId] = true; - state.activeRowIndex = selected.position; + state.selected[wpId] = true; + state.activeRowIndex = position; } else if (state.activeRowIndex !== null) { - let start = Math.min(selected.position, state.activeRowIndex); - let end = Math.max(selected.position, state.activeRowIndex); + let start = Math.min(position, state.activeRowIndex); + let end = Math.max(position, state.activeRowIndex); - rows.forEach((workPackageId, i) => { - state.selected[workPackageId] = i >= start && i <= end; + rows.forEach((row, i) => { + if (row.workPackageId) { + state.selected[row.workPackageId] = i >= start && i <= end; + } }); } diff --git a/frontend/app/components/wp-fast-table/wp-fast-table.ts b/frontend/app/components/wp-fast-table/wp-fast-table.ts index b77f09acfa..e082f2b660 100644 --- a/frontend/app/components/wp-fast-table/wp-fast-table.ts +++ b/frontend/app/components/wp-fast-table/wp-fast-table.ts @@ -15,7 +15,7 @@ import {GroupedRowsBuilder} from './builders/modes/grouped/grouped-rows-builder' import {HierarchyRowsBuilder} from './builders/modes/hierarchy/hierarchy-rows-builder'; import {RowsBuilder} from './builders/modes/rows-builder'; import {WorkPackageTimelineTableController} from '../wp-table/timeline/container/wp-timeline-container.directive'; -import {PrimaryRenderPass} from './builders/primary-render-pass'; +import {PrimaryRenderPass, RenderedRow} from './builders/primary-render-pass'; import {debugLog} from '../../helpers/debug_output'; export class WorkPackageTable { @@ -23,8 +23,8 @@ export class WorkPackageTable { public states:States; public I18n:op.I18n; - public rows: string[] = []; - public rowIndex:{[id: string]: WorkPackageTableRow} = {}; + public originalRows: string[] = []; + public originalRowIndex:{[id: string]: WorkPackageTableRow} = {}; // WP rows builder // Ordered by priority @@ -45,8 +45,14 @@ export class WorkPackageTable { TableHandlerRegistry.attachTo(this); } - public rowObject(workPackageId:string):WorkPackageTableRow { - return this.rowIndex[workPackageId]; + public get renderedRows() { + return this.states.table.rendered.getValueOr([]); + } + + public findRenderedRow(classIdentifier:string):[number, RenderedRow] { + const index = _.findIndex(this.renderedRows, (row) => row.classIdentifier === classIdentifier); + + return [index, this.renderedRows[index]]; } public get rowBuilder():RowsBuilder { @@ -58,10 +64,10 @@ export class WorkPackageTable { * @param rows */ private buildIndex(rows:WorkPackageResource[]) { - this.rowIndex = {}; - this.rows = rows.map((wp:WorkPackageResource, i:number) => { + this.originalRowIndex = {}; + this.originalRows = rows.map((wp:WorkPackageResource, i:number) => { let wpId = wp.id; - this.rowIndex[wpId] = { object: wp, workPackageId: wpId, position: i }; + this.originalRowIndex[wpId] = { object: wp, workPackageId: wpId, position: i }; return wpId; }); } @@ -77,8 +83,8 @@ export class WorkPackageTable { this.redrawTableAndTimeline(); // Preselect first work package as focused - if (this.rows.length && this.states.focusedWorkPackage.isPristine()) { - this.states.focusedWorkPackage.putValue(this.rows[0]); + if (this.originalRows.length && this.states.focusedWorkPackage.isPristine()) { + this.states.focusedWorkPackage.putValue(this.originalRows[0]); } } diff --git a/frontend/app/components/wp-table/context-menu-helper/wp-context-menu-helper.service.ts b/frontend/app/components/wp-table/context-menu-helper/wp-context-menu-helper.service.ts index 60e28b418a..809f042d55 100644 --- a/frontend/app/components/wp-table/context-menu-helper/wp-context-menu-helper.service.ts +++ b/frontend/app/components/wp-table/context-menu-helper/wp-context-menu-helper.service.ts @@ -26,7 +26,6 @@ // See doc/COPYRIGHT.rdoc for more details. //++ import {WorkPackageResourceInterface} from './../../api/api-v3/hal-resources/work-package-resource.service'; -import {States} from "../../states.service"; import {WorkPackageTableTimelineService} from "../../wp-fast-table/state/wp-table-timeline.service"; angular @@ -38,8 +37,7 @@ function WorkPackageContextMenuHelper( UrlParamsHelper:any, wpTableTimeline:WorkPackageTableTimelineService, PathHelper:any, - I18n: op.I18n, - states: States) { + I18n: op.I18n) { const BULK_ACTIONS = [ {