mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Remove uiRouter from Boards module
This commit is contained in:
@@ -39,6 +39,7 @@ import { OpSharedModule } from 'core-app/shared/shared.module';
|
||||
import { OpSpotModule } from 'core-app/spot/spot.module';
|
||||
import { OpDragScrollDirective } from 'core-app/shared/directives/op-drag-scroll/op-drag-scroll.directive';
|
||||
import { OpenprojectWorkPackagesModule } from 'core-app/features/work-packages/openproject-work-packages.module';
|
||||
import { OpenprojectBoardsModule } from 'core-app/features/boards/openproject-boards.module';
|
||||
import { OpenprojectAttachmentsModule } from 'core-app/shared/components/attachments/openproject-attachments.module';
|
||||
import { OpenprojectEditorModule } from 'core-app/shared/components/editor/openproject-editor.module';
|
||||
import { OpenprojectGridsModule } from 'core-app/shared/components/grids/openproject-grids.module';
|
||||
@@ -149,6 +150,9 @@ import {
|
||||
import {
|
||||
WorkPackageSplitViewEntryComponent,
|
||||
} from 'core-app/features/work-packages/routing/wp-split-view/wp-split-view-entry.component';
|
||||
import {
|
||||
BoardEntryComponent,
|
||||
} from 'core-app/features/boards/board/board-partitioned-page/board-entry.component';
|
||||
import {
|
||||
StorageLoginButtonComponent,
|
||||
} from 'core-app/shared/components/storages/storage-login-button/storage-login-button.component';
|
||||
@@ -295,6 +299,9 @@ export function runBootstrap(appRef:ApplicationRef) {
|
||||
OpenprojectWorkPackagesModule,
|
||||
OpenprojectWorkPackageRoutesModule,
|
||||
|
||||
// Boards
|
||||
OpenprojectBoardsModule,
|
||||
|
||||
// Work packages in graph representation
|
||||
OpenprojectWorkPackageGraphsModule,
|
||||
// Calendar module
|
||||
@@ -394,6 +401,7 @@ export class OpenProjectModule implements DoBootstrap {
|
||||
registerCustomElement('opce-reminder-settings', ReminderSettingsPageComponent, { injector });
|
||||
registerCustomElement('opce-notification-center', InAppNotificationCenterComponent, { injector });
|
||||
registerCustomElement('opce-wp-split-view', WorkPackageSplitViewEntryComponent, { injector });
|
||||
registerCustomElement('opce-board-view', BoardEntryComponent, { injector });
|
||||
registerCustomElement('opce-wp-full-view', WorkPackageFullViewEntryComponent, { injector });
|
||||
registerCustomElement('opce-wp-full-create', WorkPackageFullCreateEntryComponent, { injector });
|
||||
registerCustomElement('opce-wp-full-copy', WorkPackageFullCopyEntryComponent, { injector });
|
||||
|
||||
@@ -247,6 +247,10 @@ export class PathHelperService {
|
||||
return `${this.boardsPath(projectIdentifier)}/new`;
|
||||
}
|
||||
|
||||
public boardDetailsPath(projectIdentifier:string|null, boardId:string|number, workPackageId:string|number) {
|
||||
return `${this.boardsPath(projectIdentifier)}/${boardId}/details/${workPackageId}`;
|
||||
}
|
||||
|
||||
public projectDashboardsPath(projectIdentifier:string) {
|
||||
return `${this.projectPath(projectIdentifier)}/dashboards`;
|
||||
}
|
||||
|
||||
@@ -68,12 +68,6 @@ export const OPENPROJECT_ROUTES:Ng2StateDeclaration[] = [
|
||||
'!$default': { component: ApplicationBaseComponent },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'boards.**',
|
||||
parent: 'optional_project',
|
||||
url: '/boards',
|
||||
loadChildren: () => import('../../features/boards/openproject-boards.module').then((m) => m.OpenprojectBoardsModule),
|
||||
},
|
||||
{
|
||||
name: 'bim.**',
|
||||
parent: 'optional_project',
|
||||
|
||||
@@ -34,7 +34,6 @@ import { OpModalComponent } from 'core-app/shared/components/modal/modal.compone
|
||||
import { OpModalLocalsToken } from 'core-app/shared/components/modal/modal.service';
|
||||
import { I18nService } from 'core-app/core/i18n/i18n.service';
|
||||
import { Board } from 'core-app/features/boards/board/board';
|
||||
import { StateService } from '@uirouter/core';
|
||||
import { BoardService } from 'core-app/features/boards/board/board.service';
|
||||
import { BoardActionsRegistryService } from 'core-app/features/boards/board/board-actions/board-actions-registry.service';
|
||||
import { BoardActionService } from 'core-app/features/boards/board/board-actions/board-action.service';
|
||||
@@ -42,6 +41,7 @@ import { HalResourceNotificationService } from 'core-app/features/hal/services/h
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
|
||||
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
|
||||
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
|
||||
import {
|
||||
firstValueFrom,
|
||||
Observable,
|
||||
@@ -113,11 +113,11 @@ export class AddListModalComponent extends OpModalComponent implements OnInit {
|
||||
readonly cdRef:ChangeDetectorRef,
|
||||
readonly boardActions:BoardActionsRegistryService,
|
||||
readonly halNotification:HalResourceNotificationService,
|
||||
readonly state:StateService,
|
||||
readonly boardService:BoardService,
|
||||
readonly I18n:I18nService,
|
||||
readonly apiV3Service:ApiV3Service,
|
||||
readonly currentProject:CurrentProjectService) {
|
||||
readonly currentProject:CurrentProjectService,
|
||||
readonly pathHelper:PathHelperService) {
|
||||
super(locals, cdRef, elementRef);
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ export class AddListModalComponent extends OpModalComponent implements OnInit {
|
||||
.then((board) => {
|
||||
this.inFlight = false;
|
||||
this.closeMe();
|
||||
void this.state.go('boards.partitioned.show', { board_id: board.id, isNew: true });
|
||||
void Turbo.visit(`${this.pathHelper.boardsPath(this.currentProject.identifier)}/${board.id}`);
|
||||
})
|
||||
.catch(() => (this.inFlight = false));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { HalResourceService } from 'core-app/features/hal/services/hal-resource.
|
||||
import { WorkPackageViewFiltersService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-filters.service';
|
||||
import { QueryFilterInstanceResource } from 'core-app/features/hal/resources/query-filter-instance-resource';
|
||||
import { UrlParamsHelperService } from 'core-app/features/work-packages/components/wp-query/url-params-helper';
|
||||
import { StateService } from '@uirouter/core';
|
||||
import { debounceTime, skip, take } from 'rxjs/operators';
|
||||
import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -32,8 +31,7 @@ export class BoardFilterComponent extends UntilDestroyedMixin implements AfterVi
|
||||
private readonly wpStatesInitialization:WorkPackageStatesInitializationService,
|
||||
private readonly wpTableFilters:WorkPackageViewFiltersService,
|
||||
private readonly urlParamsHelper:UrlParamsHelperService,
|
||||
private readonly boardFilters:BoardFiltersService,
|
||||
private readonly $state:StateService) {
|
||||
private readonly boardFilters:BoardFiltersService) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -71,9 +69,15 @@ export class BoardFilterComponent extends UntilDestroyedMixin implements AfterVi
|
||||
const filterHash = this.urlParamsHelper.buildV3GetFilters(filters);
|
||||
const query_props = JSON.stringify(filterHash);
|
||||
|
||||
this.boardFilters.filters.putValue(filterHash);
|
||||
const url = new URL(window.location.href);
|
||||
if (query_props) {
|
||||
url.searchParams.set('query_props', query_props);
|
||||
} else {
|
||||
url.searchParams.delete('query_props');
|
||||
}
|
||||
window.history.pushState({}, '', url);
|
||||
|
||||
this.$state.go('.', { query_props }, { custom: { notify: false } });
|
||||
this.boardFilters.filters.putValue(filterHash);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -185,9 +185,9 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
|
||||
this.resource.isNewWidget = false;
|
||||
|
||||
// Set initial selection if split view open
|
||||
if (this.state.includes(`${this.state.current.data.baseRoute}.details`)) {
|
||||
const wpId = this.state.params.workPackageId;
|
||||
this.wpViewSelectionService.initializeSelection([wpId]);
|
||||
const detailsMatch = window.location.pathname.match(/\/details\/(\d+)/);
|
||||
if (detailsMatch) {
|
||||
this.wpViewSelectionService.initializeSelection([detailsMatch[1]]);
|
||||
}
|
||||
|
||||
// If this query space changes its focused or selected
|
||||
@@ -495,15 +495,20 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
|
||||
}
|
||||
|
||||
openStateLink(event:{ workPackageId:string; requestedState:string }) {
|
||||
const params = { workPackageId: event.workPackageId };
|
||||
|
||||
if (event.requestedState === 'split') {
|
||||
this.keepTab.goCurrentDetailsState(params);
|
||||
this.goToSplitView(event.workPackageId);
|
||||
} else {
|
||||
this.keepTab.goCurrentShowState(params.workPackageId);
|
||||
this.keepTab.goCurrentShowState(event.workPackageId);
|
||||
}
|
||||
}
|
||||
|
||||
private goToSplitView(workPackageId:string):void {
|
||||
const base = this.pathHelper.boardDetailsPath(this.currentProject.identifier, this.board.id!, workPackageId);
|
||||
const search = window.location.search;
|
||||
const link = search ? `${base}${search}` : base;
|
||||
Turbo.visit(link, { frame: 'content-bodyRight', action: 'advance' });
|
||||
}
|
||||
|
||||
private schema(workPackage:WorkPackageResource) {
|
||||
return this.schemaCache.of(workPackage);
|
||||
}
|
||||
|
||||
+48
-11
@@ -1,20 +1,50 @@
|
||||
import { Component, Injector } from '@angular/core';
|
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 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 COPYRIGHT and LICENSE files for more details.
|
||||
//++
|
||||
|
||||
import { ChangeDetectionStrategy, Component, ElementRef, Injector, Input } from '@angular/core';
|
||||
import { populateInputsFromDataset } from 'core-app/shared/components/dataset-inputs';
|
||||
import {
|
||||
WorkPackageIsolatedQuerySpaceDirective,
|
||||
} from 'core-app/features/work-packages/directives/query-space/wp-isolated-query-space.directive';
|
||||
import { BoardConfigurationService } from 'core-app/features/boards/board/configuration-modal/board-configuration.service';
|
||||
import { BoardActionsRegistryService } from 'core-app/features/boards/board/board-actions/board-actions-registry.service';
|
||||
import { BoardStatusActionService } from 'core-app/features/boards/board/board-actions/status/status-action.service';
|
||||
import { BoardVersionActionService } from 'core-app/features/boards/board/board-actions/version/version-action.service';
|
||||
import { QueryUpdatedService } from 'core-app/features/boards/board/query-updated/query-updated.service';
|
||||
import { BoardAssigneeActionService } from 'core-app/features/boards/board/board-actions/assignee/assignee-action.service';
|
||||
import { BoardSubprojectActionService } from 'core-app/features/boards/board/board-actions/subproject/subproject-action.service';
|
||||
import { BoardSubtasksActionService } from 'core-app/features/boards/board/board-actions/subtasks/board-subtasks-action.service';
|
||||
import {
|
||||
WorkPackageIsolatedQuerySpaceDirective,
|
||||
} from 'core-app/features/work-packages/directives/query-space/wp-isolated-query-space.directive';
|
||||
import { QueryUpdatedService } from 'core-app/features/boards/board/query-updated/query-updated.service';
|
||||
|
||||
@Component({
|
||||
selector: 'boards-entry',
|
||||
selector: 'board-entry',
|
||||
hostDirectives: [WorkPackageIsolatedQuerySpaceDirective],
|
||||
template: '<ui-view />',
|
||||
template: `<board-partitioned-page [boardId]="boardId" />`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [
|
||||
BoardConfigurationService,
|
||||
BoardStatusActionService,
|
||||
@@ -26,11 +56,18 @@ import {
|
||||
],
|
||||
standalone: false,
|
||||
})
|
||||
export class BoardsRootComponent {
|
||||
constructor(readonly injector:Injector) {
|
||||
// Register action services
|
||||
const registry = injector.get(BoardActionsRegistryService);
|
||||
export class BoardEntryComponent {
|
||||
@Input() boardId:string;
|
||||
|
||||
constructor(
|
||||
readonly elementRef:ElementRef,
|
||||
readonly injector:Injector,
|
||||
) {
|
||||
populateInputsFromDataset(this);
|
||||
|
||||
document.body.classList.add('router--boards-full-view');
|
||||
|
||||
const registry = injector.get(BoardActionsRegistryService);
|
||||
registry.add('status', injector.get(BoardStatusActionService));
|
||||
registry.add('assignee', injector.get(BoardAssigneeActionService));
|
||||
registry.add('version', injector.get(BoardVersionActionService));
|
||||
+13
-9
@@ -2,6 +2,7 @@ import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
Input,
|
||||
Injector,
|
||||
OnInit,
|
||||
QueryList,
|
||||
@@ -36,11 +37,11 @@ import {
|
||||
BoardActionsRegistryService,
|
||||
} from 'core-app/features/boards/board/board-actions/board-actions-registry.service';
|
||||
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
|
||||
import {
|
||||
WorkPackageStatesInitializationService,
|
||||
} from 'core-app/features/work-packages/components/wp-list/wp-states-initialization.service';
|
||||
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
|
||||
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
|
||||
|
||||
@Component({
|
||||
selector: 'board-list-container',
|
||||
templateUrl: './board-list-container.component.html',
|
||||
styleUrls: ['./board-list-container.component.sass'],
|
||||
providers: [
|
||||
@@ -50,6 +51,7 @@ import {
|
||||
standalone: false,
|
||||
})
|
||||
export class BoardListContainerComponent extends UntilDestroyedMixin implements OnInit {
|
||||
@Input() boardId:string;
|
||||
text = {
|
||||
delete: this.I18n.t('js.button_delete'),
|
||||
areYouSure: this.I18n.t('js.text_are_you_sure'),
|
||||
@@ -90,7 +92,7 @@ export class BoardListContainerComponent extends UntilDestroyedMixin implements
|
||||
private currentQueryUpdatedMonitoring:Subscription;
|
||||
|
||||
constructor(
|
||||
readonly I18n:I18nService,
|
||||
readonly I18n:I18nService,
|
||||
readonly state:StateService,
|
||||
readonly toastService:ToastService,
|
||||
readonly halNotification:HalResourceNotificationService,
|
||||
@@ -102,16 +104,17 @@ readonly I18n:I18nService,
|
||||
readonly apiV3Service:ApiV3Service,
|
||||
readonly Boards:BoardService,
|
||||
readonly boardListCrossSelectionService:BoardListCrossSelectionService,
|
||||
readonly wpStatesInitialization:WorkPackageStatesInitializationService,
|
||||
readonly Drag:DragAndDropService,
|
||||
readonly apiv3Service:ApiV3Service,
|
||||
readonly QueryUpdated:QueryUpdatedService,
|
||||
readonly pathHelper:PathHelperService,
|
||||
readonly currentProject:CurrentProjectService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit():void {
|
||||
const id:string = this.state.params.board_id.toString();
|
||||
const id:string = this.boardId || this.state.params.board_id?.toString();
|
||||
this.board$ = this
|
||||
.apiV3Service
|
||||
.boards
|
||||
@@ -128,10 +131,11 @@ readonly I18n:I18nService,
|
||||
.pipe(
|
||||
this.untilDestroyed(),
|
||||
filter((state) => state.focusedWorkPackage !== null),
|
||||
filter(() => this.state.includes(`${this.state.current.data.baseRoute}.details`)),
|
||||
filter(() => window.location.pathname.includes('/details/')),
|
||||
).subscribe((selection) => {
|
||||
// Update split screen
|
||||
this.state.go(`${this.state.current.data.baseRoute}.details`, { workPackageId: selection.focusedWorkPackage });
|
||||
// Update split screen
|
||||
const link = this.pathHelper.boardDetailsPath(this.currentProject.identifier, id, selection.focusedWorkPackage!);
|
||||
Turbo.visit(link, { frame: 'content-bodyRight', action: 'advance' });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
<div class="work-packages-partitioned-query-space--container"
|
||||
[ngClass]="currentPartition">
|
||||
<op-breadcrumbs [items]="breadcrumbItems()"
|
||||
[lastItemSection]="currentMenuSectionHeader()"
|
||||
/>
|
||||
<div class="toolbar-container -editable">
|
||||
<div class="toolbar">
|
||||
<h2 class="toolbar-title--container">
|
||||
<editable-toolbar-title [title]="selectedTitle"
|
||||
[inFlight]="toolbarDisabled"
|
||||
[showSaveCondition]="showToolbarSaveButton"
|
||||
(onSave)="changeChangesFromTitle($event)"
|
||||
(onEmptySubmit)="updateTitleName('')"
|
||||
[editable]="titleEditingEnabled" />
|
||||
</h2>
|
||||
@if (showToolbar) {
|
||||
<ul class="toolbar-items hide-when-print"
|
||||
>
|
||||
@for (definition of toolbarButtonComponents; track definition) {
|
||||
@if (!definition.show || definition.show()) {
|
||||
<li class="toolbar-item"
|
||||
[ngClass]="definition.containerClasses">
|
||||
<ndc-dynamic [ndcDynamicComponent]="definition.component"
|
||||
[ndcDynamicInputs]="definition.inputs"
|
||||
[ndcDynamicInjector]="injector"
|
||||
[ndcDynamicOutputs]="definition.outputs" />
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="work-packages-partitioned-query-space--filter-area">
|
||||
@if (filterContainerDefinition) {
|
||||
<ndc-dynamic [ndcDynamicComponent]="filterContainerDefinition.component"
|
||||
[ndcDynamicInputs]="filterContainerDefinition.inputs"
|
||||
[ndcDynamicOutputs]="filterContainerDefinition.outputs"
|
||||
[ndcDynamicInjector]="injector" />
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="work-packages-partitioned-page--content-container">
|
||||
<!-- Board list content -->
|
||||
<board-list-container [boardId]="boardId" />
|
||||
</div>
|
||||
</div>
|
||||
+31
-53
@@ -2,7 +2,9 @@ import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Injector, OnInit, OnDestroy,
|
||||
Injector,
|
||||
Input,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
DynamicComponentDefinition,
|
||||
@@ -11,7 +13,6 @@ import {
|
||||
} from 'core-app/features/work-packages/routing/partitioned-query-space-page/partitioned-query-space-page.component';
|
||||
import {
|
||||
StateService,
|
||||
TransitionService,
|
||||
} from '@uirouter/core';
|
||||
import { BoardFilterComponent } from 'core-app/features/boards/board/board-filter/board-filter.component';
|
||||
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
|
||||
@@ -24,17 +25,18 @@ import { BoardsMenuButtonComponent } from 'core-app/features/boards/board/toolba
|
||||
import {
|
||||
catchError,
|
||||
finalize,
|
||||
skip,
|
||||
take,
|
||||
} from 'rxjs/operators';
|
||||
import { I18nService } from 'core-app/core/i18n/i18n.service';
|
||||
import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin';
|
||||
import { QueryResource } from 'core-app/features/hal/resources/query-resource';
|
||||
import { Ng2StateDeclaration } from '@uirouter/angular';
|
||||
import { Board } from 'core-app/features/boards/board/board';
|
||||
import { BoardFiltersService } from 'core-app/features/boards/board/board-filter/board-filters.service';
|
||||
import { CardViewHandlerRegistry } from 'core-app/features/work-packages/components/wp-card-view/event-handler/card-view-handler-registry';
|
||||
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
|
||||
import { OpTitleService } from 'core-app/core/html/op-title.service';
|
||||
import { EMPTY } from 'rxjs';
|
||||
import { EMPTY, ReplaySubject } from 'rxjs';
|
||||
import { SubmenuService } from 'core-app/core/main-menu/submenu.service';
|
||||
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
|
||||
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
|
||||
@@ -44,7 +46,8 @@ export function boardCardViewHandlerFactory(injector:Injector) {
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: '../../../work-packages/routing/partitioned-query-space-page/partitioned-query-space-page.component.html',
|
||||
selector: 'board-partitioned-page',
|
||||
templateUrl: './board-partitioned-page.component.html',
|
||||
styleUrls: [
|
||||
'../../../work-packages/routing/partitioned-query-space-page/partitioned-query-space-page.component.sass',
|
||||
'./board-partitioned-page.component.sass',
|
||||
@@ -56,7 +59,8 @@ export function boardCardViewHandlerFactory(injector:Injector) {
|
||||
],
|
||||
standalone: false,
|
||||
})
|
||||
export class BoardPartitionedPageComponent extends UntilDestroyedMixin implements OnInit, OnDestroy {
|
||||
export class BoardPartitionedPageComponent extends UntilDestroyedMixin implements OnInit {
|
||||
@Input() boardId:string;
|
||||
text = {
|
||||
button_more: this.I18n.t('js.button_more'),
|
||||
delete: this.I18n.t('js.button_delete'),
|
||||
@@ -71,15 +75,8 @@ export class BoardPartitionedPageComponent extends UntilDestroyedMixin implement
|
||||
unnamed_list: this.I18n.t('js.boards.label_unnamed_list'),
|
||||
};
|
||||
|
||||
/** Board observable */
|
||||
board$ = this
|
||||
.apiV3Service
|
||||
.boards
|
||||
.id(this.state.params.board_id.toString())
|
||||
.observe();
|
||||
|
||||
/** Whether this is a new board just created */
|
||||
isNew = !!this.state.params.isNew;
|
||||
/** Board subject */
|
||||
board$ = new ReplaySubject<Board>(1);
|
||||
|
||||
/** Whether the board is editable */
|
||||
editable:boolean;
|
||||
@@ -95,10 +92,6 @@ export class BoardPartitionedPageComponent extends UntilDestroyedMixin implement
|
||||
/** Do we currently have query props ? */
|
||||
showToolbarSaveButton:boolean;
|
||||
|
||||
/** Listener callbacks */
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
removeTransitionSubscription:Function;
|
||||
|
||||
/** Show a toolbar */
|
||||
showToolbar = true;
|
||||
|
||||
@@ -139,7 +132,6 @@ export class BoardPartitionedPageComponent extends UntilDestroyedMixin implement
|
||||
constructor(
|
||||
readonly I18n:I18nService,
|
||||
readonly cdRef:ChangeDetectorRef,
|
||||
readonly $transitions:TransitionService,
|
||||
readonly state:StateService,
|
||||
readonly toastService:ToastService,
|
||||
readonly halNotification:HalResourceNotificationService,
|
||||
@@ -159,29 +151,28 @@ export class BoardPartitionedPageComponent extends UntilDestroyedMixin implement
|
||||
// Ensure board is being loaded
|
||||
this.Boards.loadAllBoards();
|
||||
|
||||
this.removeTransitionSubscription = this.$transitions.onSuccess({}, (transition):any => {
|
||||
const toState = transition.to();
|
||||
const params = transition.params('to');
|
||||
const boardId = this.boardId || this.state.params.board_id?.toString();
|
||||
this.apiV3Service.boards.id(boardId).observe()
|
||||
.pipe(this.untilDestroyed())
|
||||
.subscribe((board) => this.board$.next(board));
|
||||
|
||||
this.showToolbarSaveButton = !!params.query_props;
|
||||
this.setPartition(toState);
|
||||
|
||||
this
|
||||
.board$
|
||||
.pipe(take(1))
|
||||
.subscribe((board) => {
|
||||
this.titleService.setFirstPart(board.name);
|
||||
});
|
||||
|
||||
this.cdRef.detectChanges();
|
||||
});
|
||||
// React to filter changes (board-filter updates boardFilters after pushing URL)
|
||||
this.boardFilters.filters.values$()
|
||||
.pipe(
|
||||
this.untilDestroyed(),
|
||||
skip(1), // skip the initial empty default value
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.showToolbarSaveButton = !!new URLSearchParams(window.location.search).get('query_props');
|
||||
this.cdRef.detectChanges();
|
||||
});
|
||||
|
||||
this.board$
|
||||
.pipe(
|
||||
this.untilDestroyed(),
|
||||
)
|
||||
.subscribe((board) => {
|
||||
const queryProps = this.state.params.query_props;
|
||||
const queryProps = new URLSearchParams(window.location.search).get('query_props');
|
||||
this.editable = board.editable;
|
||||
this.selectedTitle = board.name;
|
||||
this.titleService.setFirstPart(board.name);
|
||||
@@ -191,11 +182,6 @@ export class BoardPartitionedPageComponent extends UntilDestroyedMixin implement
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy():void {
|
||||
super.ngOnDestroy();
|
||||
this.removeTransitionSubscription();
|
||||
}
|
||||
|
||||
breadcrumbItems() {
|
||||
return [
|
||||
{ href: this.pathHelperService.projectPath(this.currentProject.identifier!), text: (this.currentProject.name) },
|
||||
@@ -213,8 +199,10 @@ export class BoardPartitionedPageComponent extends UntilDestroyedMixin implement
|
||||
board.name = newName;
|
||||
board.filters = this.boardFilters.current;
|
||||
|
||||
const params = { isNew: false, query_props: null };
|
||||
this.state.go('.', params, { custom: { notify: false } });
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete('query_props');
|
||||
window.history.pushState({}, '', url);
|
||||
this.showToolbarSaveButton = false;
|
||||
|
||||
this.toolbarDisabled = true;
|
||||
this.Boards
|
||||
@@ -245,16 +233,6 @@ export class BoardPartitionedPageComponent extends UntilDestroyedMixin implement
|
||||
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?.partition) ? state.data.partition : '-split';
|
||||
}
|
||||
|
||||
private reloadSidemenu():void {
|
||||
this.submenuService.reloadSubmenu(null);
|
||||
}
|
||||
|
||||
@@ -30,9 +30,7 @@ import { NgModule } from '@angular/core';
|
||||
import { OpSharedModule } from 'core-app/shared/shared.module';
|
||||
import { OpenprojectWorkPackagesModule } from 'core-app/features/work-packages/openproject-work-packages.module';
|
||||
import { OpenprojectModalModule } from 'core-app/shared/components/modal/modal.module';
|
||||
import { UIRouterModule } from '@uirouter/angular';
|
||||
import { BoardListComponent } from 'core-app/features/boards/board/board-list/board-list.component';
|
||||
import { BoardsRootComponent } from 'core-app/features/boards/boards-root/boards-root.component';
|
||||
import { BoardInlineAddAutocompleterComponent } from 'core-app/features/boards/board/inline-add/board-inline-add-autocompleter.component';
|
||||
import { BoardsToolbarMenuDirective } from 'core-app/features/boards/board/toolbar-menu/boards-toolbar-menu.directive';
|
||||
import { BoardConfigurationModalComponent } from 'core-app/features/boards/board/configuration-modal/board-configuration.modal';
|
||||
@@ -43,9 +41,9 @@ import { BoardFilterComponent } from 'core-app/features/boards/board/board-filte
|
||||
import { BoardListMenuComponent } from 'core-app/features/boards/board/board-list/board-list-menu.component';
|
||||
import { VersionBoardHeaderComponent } from 'core-app/features/boards/board/board-actions/version/version-board-header.component';
|
||||
import { DynamicModule } from 'ng-dynamic-component';
|
||||
import { BOARDS_ROUTES, uiRouterBoardsConfiguration } from 'core-app/features/boards/openproject-boards.routes';
|
||||
import { BoardPartitionedPageComponent } from 'core-app/features/boards/board/board-partitioned-page/board-partitioned-page.component';
|
||||
import { BoardListContainerComponent } from 'core-app/features/boards/board/board-partitioned-page/board-list-container.component';
|
||||
import { BoardEntryComponent } from 'core-app/features/boards/board/board-partitioned-page/board-entry.component';
|
||||
import { BoardsMenuButtonComponent } from 'core-app/features/boards/board/toolbar-menu/boards-menu-button.component';
|
||||
import { AssigneeBoardHeaderComponent } from 'core-app/features/boards/board/board-actions/assignee/assignee-board-header.component';
|
||||
import { SubprojectBoardHeaderComponent } from 'core-app/features/boards/board/board-actions/subproject/subproject-board-header.component';
|
||||
@@ -64,18 +62,12 @@ import { OpenprojectEnterpriseModule } from 'core-app/features/enterprise/openpr
|
||||
|
||||
// Dynamic Module for actions
|
||||
DynamicModule,
|
||||
|
||||
// Routes for /boards
|
||||
UIRouterModule.forChild({
|
||||
states: BOARDS_ROUTES,
|
||||
config: uiRouterBoardsConfiguration,
|
||||
}),
|
||||
],
|
||||
declarations: [
|
||||
BoardPartitionedPageComponent,
|
||||
BoardListContainerComponent,
|
||||
BoardEntryComponent,
|
||||
BoardListComponent,
|
||||
BoardsRootComponent,
|
||||
BoardInlineAddAutocompleterComponent,
|
||||
BoardHighlightingTabComponent,
|
||||
BoardConfigurationModalComponent,
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 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 COPYRIGHT and LICENSE files for more details.
|
||||
//++
|
||||
|
||||
import { Ng2StateDeclaration, UIRouter } from '@uirouter/angular';
|
||||
import { BoardsRootComponent } from 'core-app/features/boards/boards-root/boards-root.component';
|
||||
import { BoardPartitionedPageComponent } from 'core-app/features/boards/board/board-partitioned-page/board-partitioned-page.component';
|
||||
import { BoardListContainerComponent } from 'core-app/features/boards/board/board-partitioned-page/board-list-container.component';
|
||||
import { makeSplitViewRoutes } from 'core-app/features/work-packages/routing/split-view-routes.template';
|
||||
import { WorkPackageSplitViewComponent } from 'core-app/features/work-packages/routing/wp-split-view/wp-split-view.component';
|
||||
|
||||
export const menuItemClass = 'boards-menu-item';
|
||||
|
||||
export const sidemenuId = 'boards_sidemenu';
|
||||
export const sideMenuOptions = {
|
||||
sidemenuId,
|
||||
hardReloadOnBaseRoute: true,
|
||||
};
|
||||
|
||||
export const BOARDS_ROUTES:Ng2StateDeclaration[] = [
|
||||
{
|
||||
name: 'boards',
|
||||
parent: 'optional_project',
|
||||
// The trailing slash is important
|
||||
// cf., https://community.openproject.com/wp/29754
|
||||
url: '/boards/?query_props',
|
||||
data: {
|
||||
bodyClasses: 'router--boards-view-base',
|
||||
menuItem: menuItemClass,
|
||||
sideMenuOptions,
|
||||
},
|
||||
params: {
|
||||
// Use custom encoder/decoder that ensures validity of URL string
|
||||
query_props: { type: 'opQueryString', dynamic: true },
|
||||
},
|
||||
component: BoardsRootComponent,
|
||||
},
|
||||
{
|
||||
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,
|
||||
sideMenuOptions,
|
||||
},
|
||||
reloadOnSearch: false,
|
||||
component: BoardPartitionedPageComponent,
|
||||
redirectTo: 'boards.partitioned.show',
|
||||
},
|
||||
{
|
||||
name: 'boards.partitioned.show',
|
||||
url: '',
|
||||
data: {
|
||||
baseRoute: 'boards.partitioned.show',
|
||||
sideMenuOptions,
|
||||
},
|
||||
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/`,
|
||||
);
|
||||
}
|
||||
@@ -48,7 +48,7 @@
|
||||
data-notification-selector='notification-scroll-container'
|
||||
>
|
||||
<ndc-dynamic [ndcDynamicComponent]="activeTabComponent"
|
||||
[ndcDynamicInputs]="{ workPackage: workPackage, tab: tabIdentifier }" />
|
||||
[ndcDynamicInputs]="{ workPackage: workPackage }" />
|
||||
</div>
|
||||
}
|
||||
</edit-form>
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
module ::Boards
|
||||
class BoardsController < BaseController
|
||||
include Layout
|
||||
include WorkPackages::WithSplitView
|
||||
|
||||
before_action :load_and_authorize_in_optional_project
|
||||
before_action :find_board_for_deletion, only: %i[destroy]
|
||||
before_action :find_board, only: %i[show split_view]
|
||||
|
||||
# The boards permission alone does not suffice
|
||||
# to view work packages
|
||||
before_action :authorize_work_package_permission, only: %i[show]
|
||||
before_action :authorize_work_package_permission, only: %i[show split_view]
|
||||
|
||||
before_action :build_board_grid, only: %i[new]
|
||||
before_action :load_query, only: %i[index]
|
||||
@@ -21,7 +23,19 @@ module ::Boards
|
||||
end
|
||||
|
||||
def show
|
||||
render layout: "angular/angular"
|
||||
render
|
||||
end
|
||||
|
||||
def split_view
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
if turbo_frame_request?
|
||||
render "work_packages/split_view", layout: false
|
||||
else
|
||||
render :show
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def new; end
|
||||
@@ -56,6 +70,10 @@ module ::Boards
|
||||
|
||||
private
|
||||
|
||||
def split_view_base_route
|
||||
project_work_package_board_path(@project, params[:id], request.query_parameters)
|
||||
end
|
||||
|
||||
def load_query
|
||||
projects = @project || Project.allowed_to(User.current, :show_board_views)
|
||||
|
||||
@@ -64,6 +82,10 @@ module ::Boards
|
||||
.where(project: projects)
|
||||
end
|
||||
|
||||
def find_board
|
||||
@board_grid = Boards::Grid.find_by!(id: params[:id], project: @project)
|
||||
end
|
||||
|
||||
def find_board_for_deletion
|
||||
@board_grid = Boards::Grid.find_by!(id: params[:id], project: @project)
|
||||
end
|
||||
|
||||
@@ -27,4 +27,13 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
++#%>
|
||||
|
||||
<% html_title(t("boards.label_boards")) -%>
|
||||
<% html_title(@board_grid.name) -%>
|
||||
|
||||
<% content_for :content_body do %>
|
||||
<%= angular_component_tag "opce-board-view", inputs: { boardId: @board_grid.id.to_s } %>
|
||||
<% end %>
|
||||
|
||||
<% content_for :content_body_right do %>
|
||||
<%= turbo_stream.set_title(title: page_title(*html_title_parts)) if turbo_frame_request? %>
|
||||
<%= render(split_view_instance) if render_work_package_split_view? %>
|
||||
<% end %>
|
||||
|
||||
@@ -12,6 +12,13 @@ Rails.application.routes.draw do
|
||||
collection do
|
||||
get "menu" => "boards/menus#show"
|
||||
end
|
||||
member do
|
||||
get "details/:work_package_id(/:tab)",
|
||||
action: :split_view,
|
||||
defaults: { tab: :overview },
|
||||
as: :details,
|
||||
work_package_split_view: true
|
||||
end
|
||||
get "(/*state)" => "boards/boards#show", on: :member, as: "", constraints: { id: /\d+/ }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -28,7 +28,7 @@ module OpenProject::Boards
|
||||
settings: {} do
|
||||
project_module :board_view, dependencies: :work_package_tracking, order: 80 do
|
||||
permission :show_board_views,
|
||||
{ "boards/boards": %i[index show],
|
||||
{ "boards/boards": %i[index show split_view],
|
||||
"boards/menus": %i[show] },
|
||||
permissible_on: :project,
|
||||
dependencies: :view_work_packages,
|
||||
|
||||
Reference in New Issue
Block a user