parent
eff5075c72
commit
b33faeea7c
@ -0,0 +1,7 @@ |
|||||||
|
// Let board list span whole screen |
||||||
|
.router--boards-full-view |
||||||
|
@include extended-content--bottom |
||||||
|
@include extended-content--right |
||||||
|
|
||||||
|
#content |
||||||
|
height: 100% |
@ -0,0 +1,16 @@ |
|||||||
|
import {ApiV3Filter} from "core-components/api/api-v3/api-v3-filter-builder"; |
||||||
|
import {input} from "reactivestates"; |
||||||
|
|
||||||
|
export class BoardFiltersService { |
||||||
|
/** |
||||||
|
* We need to remember the current filter, that may either come |
||||||
|
* from the saved board, or were assigned by the user. |
||||||
|
* |
||||||
|
* This is due to the fact we do not work on an query object here. |
||||||
|
*/ |
||||||
|
filters = input<ApiV3Filter[]>([]); |
||||||
|
|
||||||
|
get current():ApiV3Filter[] { |
||||||
|
return this.filters.getValueOr([]); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
<ng-container *ngIf="(board$ | async) as board"> |
||||||
|
<div class="boards-list--container" |
||||||
|
#container |
||||||
|
*ngIf="showBoardListView()" |
||||||
|
cdkDropList |
||||||
|
[cdkDropListDisabled]="!board.editable" |
||||||
|
cdkDropListOrientation="horizontal" |
||||||
|
(cdkDropListDropped)="moveList(board, $event)" |
||||||
|
> |
||||||
|
<div *ngFor="let queryWidget of board.queries; trackBy:trackByQueryId" |
||||||
|
class="boards-list--item" |
||||||
|
wp-isolated-query-space |
||||||
|
cdkDrag |
||||||
|
vsDragScroll |
||||||
|
[cdkDragData]="queryWidget" |
||||||
|
[vsDragScrollContainer]="_container"> |
||||||
|
<span *ngIf="board.editable" |
||||||
|
class="boards-list-item-handle icon icon-drag-handle" |
||||||
|
cdkDragHandle></span> |
||||||
|
<board-list [resource]="queryWidget" |
||||||
|
[board]="board" |
||||||
|
(onRemove)="removeList(board, queryWidget)"></board-list> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="boards-list--add-item -no-text-select" |
||||||
|
*ngIf="board.editable" |
||||||
|
(click)="addList(board)"> |
||||||
|
<div class="boards-list--add-item-text"> |
||||||
|
<op-icon icon-classes="icon-add icon-context"></op-icon> |
||||||
|
<span [textContent]="text.addList"></span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<enterprise-banner *ngIf="!showBoardListView()" |
||||||
|
[leftMargin]="true" |
||||||
|
[linkMessage]="text.upsaleCheckOutLink" |
||||||
|
[textMessage]="text.upsaleBoards" |
||||||
|
[opReferrer]="opReferrer(board)"> |
||||||
|
</enterprise-banner> |
||||||
|
</ng-container> |
@ -0,0 +1,221 @@ |
|||||||
|
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector} from "@angular/core"; |
||||||
|
import { |
||||||
|
DynamicComponentDefinition, |
||||||
|
ToolbarButtonComponentDefinition, |
||||||
|
ViewPartitionState |
||||||
|
} from "core-app/modules/work_packages/routing/partitioned-query-space-page/partitioned-query-space-page.component"; |
||||||
|
import {StateService, TransitionService} from "@uirouter/core"; |
||||||
|
import {BoardFilterComponent} from "core-app/modules/boards/board/board-filter/board-filter.component"; |
||||||
|
import {Board} from "core-app/modules/boards/board/board"; |
||||||
|
import {NotificationsService} from "core-app/modules/common/notifications/notifications.service"; |
||||||
|
import {HalResourceNotificationService} from "core-app/modules/hal/services/hal-resource-notification.service"; |
||||||
|
import {BoardCacheService} from "core-app/modules/boards/board/board-cache.service"; |
||||||
|
import {BoardService} from "core-app/modules/boards/board/board.service"; |
||||||
|
import {DragAndDropService} from "core-app/modules/common/drag-and-drop/drag-and-drop.service"; |
||||||
|
import {WorkPackageFilterButtonComponent} from "core-components/wp-buttons/wp-filter-button/wp-filter-button.component"; |
||||||
|
import {ZenModeButtonComponent} from "core-components/wp-buttons/zen-mode-toggle-button/zen-mode-toggle-button.component"; |
||||||
|
import {BoardsMenuButtonComponent} from "core-app/modules/boards/board/toolbar-menu/boards-menu-button.component"; |
||||||
|
import {RequestSwitchmap} from "core-app/helpers/rxjs/request-switchmap"; |
||||||
|
import {from} from "rxjs"; |
||||||
|
import {componentDestroyed} from "@w11k/ngx-componentdestroyed"; |
||||||
|
import {take} from "rxjs/operators"; |
||||||
|
import {I18nService} from "core-app/modules/common/i18n/i18n.service"; |
||||||
|
import {UntilDestroyedMixin} from "core-app/helpers/angular/until-destroyed.mixin"; |
||||||
|
import {QueryResource} from "core-app/modules/hal/resources/query-resource"; |
||||||
|
import {Ng2StateDeclaration} from "@uirouter/angular"; |
||||||
|
import {BoardFiltersService} from "core-app/modules/boards/board/board-filter/board-filters.service"; |
||||||
|
|
||||||
|
@Component({ |
||||||
|
templateUrl: '/app/modules/work_packages/routing/partitioned-query-space-page/partitioned-query-space-page.component.html', |
||||||
|
styleUrls: [ |
||||||
|
'/app/modules/work_packages/routing/partitioned-query-space-page/partitioned-query-space-page.component.sass' |
||||||
|
], |
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush, |
||||||
|
providers: [ |
||||||
|
DragAndDropService, |
||||||
|
BoardFiltersService, |
||||||
|
] |
||||||
|
}) |
||||||
|
export class BoardPartitionedPageComponent extends UntilDestroyedMixin { |
||||||
|
|
||||||
|
text = { |
||||||
|
button_more: this.I18n.t('js.button_more'), |
||||||
|
delete: this.I18n.t('js.button_delete'), |
||||||
|
areYouSure: this.I18n.t('js.text_are_you_sure'), |
||||||
|
deleteSuccessful: this.I18n.t('js.notice_successful_delete'), |
||||||
|
updateSuccessful: this.I18n.t('js.notice_successful_update'), |
||||||
|
unnamedBoard: this.I18n.t('js.boards.label_unnamed_board'), |
||||||
|
loadingError: 'No such board found', |
||||||
|
addList: this.I18n.t('js.boards.add_list'), |
||||||
|
upsaleBoards: this.I18n.t('js.boards.upsale.teaser_text'), |
||||||
|
upsaleCheckOutLink: this.I18n.t('js.work_packages.table_configuration.upsale.check_out_link'), |
||||||
|
unnamed_list: this.I18n.t('js.boards.label_unnamed_list'), |
||||||
|
}; |
||||||
|
|
||||||
|
/** Board observable */ |
||||||
|
board$ = this.BoardCache.observe(this.state.params.board_id.toString()); |
||||||
|
|
||||||
|
/** Whether this is a new board just created */ |
||||||
|
isNew:boolean = !!this.state.params.isNew; |
||||||
|
|
||||||
|
/** Whether the board is editable */ |
||||||
|
editable:boolean; |
||||||
|
|
||||||
|
/** Go back to boards using back-button */ |
||||||
|
backButtonCallback = () => this.state.go('boards'); |
||||||
|
|
||||||
|
/** Current query title to render */ |
||||||
|
selectedTitle?:string; |
||||||
|
currentQuery:QueryResource|undefined; |
||||||
|
|
||||||
|
/** Whether we're saving the board */ |
||||||
|
toolbarDisabled:boolean = false; |
||||||
|
|
||||||
|
/** Do we currently have query props ? */ |
||||||
|
showToolbarSaveButton:boolean; |
||||||
|
|
||||||
|
/** Listener callbacks */ |
||||||
|
removeTransitionSubscription:Function; |
||||||
|
|
||||||
|
showToolbar = true; |
||||||
|
|
||||||
|
/** Whether filtering is allowed */ |
||||||
|
filterAllowed:boolean = true; |
||||||
|
|
||||||
|
/** We need to pass the correct partition state to the view to manage the grid */ |
||||||
|
currentPartition:ViewPartitionState = '-split'; |
||||||
|
|
||||||
|
/** We need to apply our own board filter component */ |
||||||
|
/** Which filter container component to mount */ |
||||||
|
filterContainerDefinition:DynamicComponentDefinition = { |
||||||
|
component: BoardFilterComponent, |
||||||
|
inputs: { |
||||||
|
board$: this.board$ |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
// We remember when we want to update the board
|
||||||
|
boardSaver = new RequestSwitchmap( |
||||||
|
(board:Board) => { |
||||||
|
this.toolbarDisabled = true; |
||||||
|
const promise = this.Boards |
||||||
|
.save(board) |
||||||
|
.then(board => { |
||||||
|
this.toolbarDisabled = false; |
||||||
|
return board; |
||||||
|
}) |
||||||
|
.catch((error) => { |
||||||
|
this.toolbarDisabled = false; |
||||||
|
throw error; |
||||||
|
}); |
||||||
|
|
||||||
|
return from(promise); |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
toolbarButtonComponents:ToolbarButtonComponentDefinition[] = [ |
||||||
|
{ |
||||||
|
component: WorkPackageFilterButtonComponent, |
||||||
|
containerClasses: 'hidden-for-mobile' |
||||||
|
}, |
||||||
|
{ |
||||||
|
component: ZenModeButtonComponent, |
||||||
|
containerClasses: 'hidden-for-mobile' |
||||||
|
}, |
||||||
|
{ |
||||||
|
component: BoardsMenuButtonComponent, |
||||||
|
containerClasses: 'hidden-for-mobile', |
||||||
|
show: () => this.editable, |
||||||
|
inputs: { |
||||||
|
board$: this.board$ |
||||||
|
} |
||||||
|
} |
||||||
|
]; |
||||||
|
|
||||||
|
constructor(readonly I18n:I18nService, |
||||||
|
readonly cdRef:ChangeDetectorRef, |
||||||
|
readonly $transitions:TransitionService, |
||||||
|
readonly state:StateService, |
||||||
|
readonly notifications:NotificationsService, |
||||||
|
readonly halNotification:HalResourceNotificationService, |
||||||
|
readonly injector:Injector, |
||||||
|
readonly BoardCache:BoardCacheService, |
||||||
|
readonly boardFilters:BoardFiltersService, |
||||||
|
readonly Boards:BoardService) { |
||||||
|
super(); |
||||||
|
} |
||||||
|
|
||||||
|
ngOnInit():void { |
||||||
|
// Ensure board is being loaded
|
||||||
|
this.Boards.loadAllBoards(); |
||||||
|
|
||||||
|
this.boardSaver |
||||||
|
.observe(componentDestroyed(this)) |
||||||
|
.subscribe( |
||||||
|
(board:Board) => { |
||||||
|
this.BoardCache.update(board); |
||||||
|
this.notifications.addSuccess(this.text.updateSuccessful); |
||||||
|
}, |
||||||
|
(error:unknown) => this.halNotification.handleRawError(error) |
||||||
|
); |
||||||
|
|
||||||
|
this.removeTransitionSubscription = this.$transitions.onSuccess({}, (transition):any => { |
||||||
|
const toState = transition.to(); |
||||||
|
const params = transition.params('to'); |
||||||
|
|
||||||
|
this.showToolbarSaveButton = !!params.query_props |
||||||
|
this.setPartition(toState); |
||||||
|
this.cdRef.detectChanges(); |
||||||
|
}); |
||||||
|
|
||||||
|
this.board$ |
||||||
|
.pipe( |
||||||
|
this.untilDestroyed() |
||||||
|
) |
||||||
|
.subscribe(board => { |
||||||
|
let queryProps = this.state.params.query_props; |
||||||
|
this.editable = board.editable; |
||||||
|
this.selectedTitle = board.name; |
||||||
|
this.boardFilters.filters.putValue(queryProps ? JSON.parse(queryProps) : board.filters); |
||||||
|
this.cdRef.detectChanges(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
ngOnDestroy():void { |
||||||
|
super.ngOnDestroy(); |
||||||
|
this.removeTransitionSubscription(); |
||||||
|
} |
||||||
|
|
||||||
|
changeChangesFromTitle(newName:string) { |
||||||
|
this.board$ |
||||||
|
.pipe(take(1)) |
||||||
|
.subscribe(board => { |
||||||
|
board.name = newName; |
||||||
|
board.filters = this.boardFilters.current; |
||||||
|
|
||||||
|
let params = { isNew: false, query_props: null }; |
||||||
|
this.state.go('.', params, { custom: { notify: false } }); |
||||||
|
|
||||||
|
this.boardSaver.request(board); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
updateTitleName(val:string) { |
||||||
|
this.changeChangesFromTitle(val); |
||||||
|
} |
||||||
|
|
||||||
|
/** Whether the title can be edited */ |
||||||
|
get titleEditingEnabled():boolean { |
||||||
|
return this.editable; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* We need to set the current partition to the grid to ensure |
||||||
|
* either side gets expanded to full width if we're not in '-split' mode. |
||||||
|
* |
||||||
|
* @param state The current or entering state |
||||||
|
*/ |
||||||
|
protected setPartition(state:Ng2StateDeclaration) { |
||||||
|
this.currentPartition = (state.data && state.data.partition) ? state.data.partition : '-split'; |
||||||
|
} |
||||||
|
} |
@ -1,95 +0,0 @@ |
|||||||
<div *ngIf="board" |
|
||||||
[ngClass]="{ '-editable': board.editable, '-free' : board.isFree}" |
|
||||||
class="board--container"> |
|
||||||
|
|
||||||
<ng-container wp-isolated-query-space> |
|
||||||
<div class="toolbar-container -editable"> |
|
||||||
<div id="toolbar"> |
|
||||||
<div class="title-container board--header-container"> |
|
||||||
|
|
||||||
<back-button linkClass="board--back-button" |
|
||||||
[customBackMethod]="goBack.bind(this)"> |
|
||||||
</back-button> |
|
||||||
|
|
||||||
<editable-toolbar-title [title]="board.name" |
|
||||||
[inFlight]="inFlight" |
|
||||||
[initialFocus]="isNew" |
|
||||||
(onSave)="saveWithNameAndFilters(board, $event)" |
|
||||||
[editable]="board.editable" |
|
||||||
[showSaveCondition]="!!state.params.query_props"> |
|
||||||
</editable-toolbar-title> |
|
||||||
|
|
||||||
<ul class="toolbar-items" |
|
||||||
*ngIf="showBoardListView()"> |
|
||||||
|
|
||||||
<li class="toolbar-item hidden-for-mobile"> |
|
||||||
<wp-filter-button> |
|
||||||
</wp-filter-button> |
|
||||||
</li> |
|
||||||
|
|
||||||
<li class="toolbar-item hidden-for-mobile"> |
|
||||||
<zen-mode-toggle-button></zen-mode-toggle-button> |
|
||||||
</li> |
|
||||||
<li *ngIf="board.editable" |
|
||||||
class="toolbar-item hidden-for-mobile"> |
|
||||||
<button title="{{ text.button_more }}" |
|
||||||
class="button last board--settings-dropdown toolbar-icon" |
|
||||||
boardsToolbarMenu |
|
||||||
[boardsToolbarMenu-resource]="board"> |
|
||||||
<op-icon icon-classes="button--icon icon-show-more"></op-icon> |
|
||||||
</button> |
|
||||||
</li> |
|
||||||
</ul> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="boards-filters-container"> |
|
||||||
<board-filter [board]="board" |
|
||||||
[filters]="filters" |
|
||||||
(onFiltersChanged)="updateFilters($event)"></board-filter> |
|
||||||
</div> |
|
||||||
|
|
||||||
</ng-container> |
|
||||||
|
|
||||||
<div class="boards-list--container" |
|
||||||
#container |
|
||||||
*ngIf="showBoardListView()" |
|
||||||
cdkDropList |
|
||||||
[cdkDropListDisabled]="!board.editable" |
|
||||||
cdkDropListOrientation="horizontal" |
|
||||||
(cdkDropListDropped)="moveList(board, $event)" |
|
||||||
> |
|
||||||
<div *ngFor="let queryWidget of board.queries; trackBy:trackByQueryId" |
|
||||||
class="boards-list--item" |
|
||||||
wp-isolated-query-space |
|
||||||
cdkDrag |
|
||||||
vsDragScroll |
|
||||||
[cdkDragData]="queryWidget" |
|
||||||
[vsDragScrollContainer]="_container"> |
|
||||||
<span *ngIf="board.editable" |
|
||||||
class="boards-list-item-handle icon icon-drag-handle" |
|
||||||
cdkDragHandle></span> |
|
||||||
<board-list [resource]="queryWidget" |
|
||||||
[board]="board" |
|
||||||
(onRemove)="removeList(board, queryWidget)" |
|
||||||
[filters]="filters"></board-list> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="boards-list--add-item -no-text-select" |
|
||||||
*ngIf="board.editable" |
|
||||||
(click)="addList(board)"> |
|
||||||
<div class="boards-list--add-item-text"> |
|
||||||
<op-icon icon-classes="icon-add icon-context"></op-icon> |
|
||||||
<span [textContent]="text.addList"></span> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<enterprise-banner *ngIf="!showBoardListView()" |
|
||||||
[leftMargin]="true" |
|
||||||
[linkMessage]="text.upsaleCheckOutLink" |
|
||||||
[textMessage]="text.upsaleBoards" |
|
||||||
[opReferrer]="opReferrer(board)"> |
|
||||||
</enterprise-banner> |
|
||||||
</div> |
|
@ -0,0 +1,25 @@ |
|||||||
|
import {Component, Input} from "@angular/core"; |
||||||
|
import {I18nService} from "core-app/modules/common/i18n/i18n.service"; |
||||||
|
import {Board} from "core-app/modules/boards/board/board"; |
||||||
|
import {Observable} from "rxjs"; |
||||||
|
|
||||||
|
@Component({ |
||||||
|
template: ` |
||||||
|
<button title="{{ text.button_more }}" |
||||||
|
class="button last board--settings-dropdown toolbar-icon" |
||||||
|
boardsToolbarMenu |
||||||
|
[boardsToolbarMenu-resource]="board$ | async"> |
||||||
|
<op-icon icon-classes="button--icon icon-show-more"></op-icon> |
||||||
|
</button> |
||||||
|
` |
||||||
|
}) |
||||||
|
export class BoardsMenuButtonComponent { |
||||||
|
@Input() board$:Observable<Board>; |
||||||
|
|
||||||
|
text = { |
||||||
|
button_more: this.I18n.t('js.button_more'), |
||||||
|
}; |
||||||
|
|
||||||
|
constructor(readonly I18n:I18nService) { |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,107 @@ |
|||||||
|
// -- copyright
|
||||||
|
// OpenProject is an open source project management software.
|
||||||
|
// Copyright (C) 2012-2020 the OpenProject GmbH
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License version 3.
|
||||||
|
//
|
||||||
|
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||||
|
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||||
|
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// of the License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
//
|
||||||
|
// See docs/COPYRIGHT.rdoc for more details.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
import {Ng2StateDeclaration, UIRouter} from "@uirouter/angular"; |
||||||
|
import {BoardsRootComponent} from "core-app/modules/boards/boards-root/boards-root.component"; |
||||||
|
import {BoardsIndexPageComponent} from "core-app/modules/boards/index-page/boards-index-page.component"; |
||||||
|
import {BoardPartitionedPageComponent} from "core-app/modules/boards/board/board-partitioned-page/board-partitioned-page.component"; |
||||||
|
import {BoardListContainerComponent} from "core-app/modules/boards/board/board-partitioned-page/board-list-container.component"; |
||||||
|
import {makeSplitViewRoutes} from "core-app/modules/work_packages/routing/split-view-routes.template"; |
||||||
|
import {WorkPackageSplitViewComponent} from "core-app/modules/work_packages/routing/wp-split-view/wp-split-view.component"; |
||||||
|
|
||||||
|
export const menuItemClass = 'board-view-menu-item'; |
||||||
|
|
||||||
|
export const BOARDS_ROUTES:Ng2StateDeclaration[] = [ |
||||||
|
{ |
||||||
|
name: 'boards', |
||||||
|
parent: 'root', |
||||||
|
// The trailing slash is important
|
||||||
|
// cf., https://community.openproject.com/wp/29754
|
||||||
|
url: '/boards/?query_props', |
||||||
|
data: { |
||||||
|
bodyClasses: 'router--boards-view-base', |
||||||
|
menuItem: menuItemClass |
||||||
|
}, |
||||||
|
params: { |
||||||
|
// Use custom encoder/decoder that ensures validity of URL string
|
||||||
|
query_props: { type: 'opQueryString', dynamic: true } |
||||||
|
}, |
||||||
|
redirectTo: 'boards.list', |
||||||
|
component: BoardsRootComponent |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'boards.list', |
||||||
|
component: BoardsIndexPageComponent, |
||||||
|
data: { |
||||||
|
parent: 'boards', |
||||||
|
bodyClasses: 'router--boards-list-view', |
||||||
|
menuItem: menuItemClass |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'boards.partitioned', |
||||||
|
url: '{board_id}', |
||||||
|
params: { |
||||||
|
board_id: { type: 'int' }, |
||||||
|
isNew: { type: 'bool', inherit: false, dynamic: true } |
||||||
|
}, |
||||||
|
data: { |
||||||
|
parent: 'boards', |
||||||
|
bodyClasses: 'router--boards-full-view', |
||||||
|
menuItem: menuItemClass |
||||||
|
}, |
||||||
|
reloadOnSearch: false, |
||||||
|
component: BoardPartitionedPageComponent, |
||||||
|
redirectTo: 'boards.partitioned.show', |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'boards.partitioned.show', |
||||||
|
url: '', |
||||||
|
data: { |
||||||
|
baseRoute: 'boards.partitioned.show' |
||||||
|
}, |
||||||
|
views: { |
||||||
|
'content-left': { component: BoardListContainerComponent } |
||||||
|
} |
||||||
|
}, |
||||||
|
...makeSplitViewRoutes( |
||||||
|
'boards.partitioned.show', |
||||||
|
menuItemClass, |
||||||
|
WorkPackageSplitViewComponent |
||||||
|
) |
||||||
|
]; |
||||||
|
|
||||||
|
export function uiRouterBoardsConfiguration(uiRouter:UIRouter) { |
||||||
|
// Ensure boards/ are being redirected correctly
|
||||||
|
// cf., https://community.openproject.com/wp/29754
|
||||||
|
uiRouter.urlService.rules |
||||||
|
.when( |
||||||
|
new RegExp("^/projects/(.*)/boards$"), |
||||||
|
match => `/projects/${match[1]}/boards/` |
||||||
|
); |
||||||
|
} |
Loading…
Reference in new issue