Dynamic toolbar buttons in partitioned page

pull/8082/head
Oliver Günther 5 years ago
parent e4b9aabf91
commit 88f8e0a112
No known key found for this signature in database
GPG Key ID: A3A8BDAD7C0C552C
  1. 37
      frontend/src/app/components/wp-buttons/wp-create-button/wp-create-button.component.ts
  2. 7
      frontend/src/app/components/wp-buttons/wp-settings-button/wp-settings-button.component.html
  3. 43
      frontend/src/app/components/wp-buttons/wp-settings-button/wp-settings-button.component.ts
  4. 2
      frontend/src/app/modules/bcf/openproject-bcf.module.ts
  5. 12
      frontend/src/app/modules/work_packages/openproject-work-packages.module.ts
  6. 53
      frontend/src/app/modules/work_packages/routing/partitioned-query-space-page/partitioned-query-space-page.component.html
  7. 18
      frontend/src/app/modules/work_packages/routing/partitioned-query-space-page/partitioned-query-space-page.component.ts
  8. 5
      frontend/src/app/modules/work_packages/routing/wp-full-view/wp-full-view.component.ts
  9. 2
      frontend/src/app/modules/work_packages/routing/wp-full-view/wp-full-view.html
  10. 2
      frontend/src/app/modules/work_packages/routing/wp-list-view/wp-list-view.component.ts
  11. 61
      frontend/src/app/modules/work_packages/routing/wp-view-page/wp-view-page.component.ts

@ -27,22 +27,26 @@
// ++
import {StateService} from '@uirouter/core';
import {ChangeDetectionStrategy, Component, Input, OnInit} from '@angular/core';
import {ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit} from '@angular/core';
import {I18nService} from "core-app/modules/common/i18n/i18n.service";
import {CurrentProjectService} from "core-components/projects/current-project.service";
import {AuthorisationService} from "core-app/modules/common/model-auth/model-auth.service";
import {componentDestroyed} from "ng2-rx-componentdestroyed";
@Component({
selector: 'wp-create-button',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './wp-create-button.html'
})
export class WorkPackageCreateButtonComponent implements OnInit {
export class WorkPackageCreateButtonComponent implements OnInit, OnDestroy {
@Input('allowed') allowedWhen:string[];
@Input('stateName') stateName:string;
@Input('allowed') allowed:boolean;
public projectIdentifier:string|null;
public types:any;
public text = {
allowed:boolean;
projectIdentifier:string|null;
types:any;
text = {
createWithDropdown: this.I18n.t('js.work_packages.create.button'),
createButton: this.I18n.t('js.label_work_package'),
explanation: this.I18n.t('js.label_create_work_package')
@ -50,19 +54,34 @@ export class WorkPackageCreateButtonComponent implements OnInit {
constructor(readonly $state:StateService,
readonly currentProject:CurrentProjectService,
readonly authorisationService:AuthorisationService,
readonly I18n:I18nService) {
}
public ngOnInit() {
ngOnInit() {
this.projectIdentifier = this.currentProject.identifier;
// Created for interface compliance
this.authorisationService
.observeUntil(componentDestroyed(this))
.subscribe(() => {
this.allowed = this
.allowedWhen
.filter(value => this.authorisationService.can('work_packages', value))
.length === this.allowedWhen.length;
});
}
ngOnDestroy():void {
// Nothing to do
}
public createWorkPackage() {
createWorkPackage() {
this.$state.go(this.stateName, {projectPath: this.projectIdentifier});
}
public isDisabled() {
isDisabled() {
return !this.allowed || this.$state.includes('**.new');
}
}

@ -0,0 +1,7 @@
<button id="work-packages-settings-button"
title="{{ text.button_settings }}"
class="button last work-packages-settings-button toolbar-icon"
opSettingsContextMenu
opSettingsContextMenu-query="query">
<op-icon icon-classes="button--icon icon-show-more"></op-icon>
</button>

@ -0,0 +1,43 @@
// -- 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 {ChangeDetectionStrategy, Component} from '@angular/core';
import {I18nService} from "core-app/modules/common/i18n/i18n.service";
@Component({
templateUrl: './wp-settings-button.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WorkPackageSettingsButtonComponent {
public text = {
'button_settings': this.I18n.t('js.button_settings')
};
constructor(readonly I18n:I18nService) {
}
}

@ -59,6 +59,8 @@ import {BcfExportButtonComponent} from "core-app/modules/bcf/bcf-buttons/bcf-exp
BcfExportButtonComponent
],
entryComponents: [
BcfImportButtonComponent,
BcfExportButtonComponent
]
})
export class OpenprojectBcfModule {

@ -166,6 +166,8 @@ import { TimeEntryChangeset } from 'core-app/components/time-entries/time-entry-
import {WorkPackageListViewComponent} from "core-app/modules/work_packages/routing/wp-list-view/wp-list-view.component";
import {PartitionedQuerySpacePageComponent} from "core-app/modules/work_packages/routing/partitioned-query-space-page/partitioned-query-space-page.component";
import {WorkPackageViewPageComponent} from "core-app/modules/work_packages/routing/wp-view-page/wp-view-page.component";
import {ZenModeButtonComponent} from "core-components/wp-buttons/zen-mode-toggle-button/zen-mode-toggle-button.component";
import {WorkPackageSettingsButtonComponent} from "core-components/wp-buttons/wp-settings-button/wp-settings-button.component";
@NgModule({
@ -233,6 +235,7 @@ import {WorkPackageViewPageComponent} from "core-app/modules/work_packages/routi
// WP list side
WorkPackageListViewComponent,
WorkPackageSettingsButtonComponent,
// Query injector isolation
WorkPackageIsolatedQuerySpaceDirective,
@ -387,6 +390,15 @@ import {WorkPackageViewPageComponent} from "core-app/modules/work_packages/routi
// List view
WorkPackageListViewComponent,
// List view toolbar
WorkPackageCreateButtonComponent,
WorkPackageTimelineButtonComponent,
WorkPackageFilterButtonComponent,
WorkPackageDetailsViewButtonComponent,
WorkPackageViewToggleButton,
ZenModeButtonComponent,
WorkPackageSettingsButtonComponent,
// Split view
WorkPackageSplitViewComponent,

@ -12,49 +12,16 @@
<ul class="toolbar-items hide-when-print"
*ngIf="tableInformationLoaded">
<li class="toolbar-item">
<wp-create-button
[allowed]="allowed('work_packages', 'createWorkPackage')"
stateName="work-packages.partitioned.list.new">
</wp-create-button>
</li>
<li class="toolbar-item"
*ngIf="bcfActivated()">
<bcf-import-button></bcf-import-button>
</li>
<li class="toolbar-item"
*ngIf="bcfActivated()">
<bcf-export-button></bcf-export-button>
</li>
<li class="toolbar-item">
<wp-filter-button>
</wp-filter-button>
</li>
<li class="toolbar-item hidden-for-mobile">
<wp-view-toggle-button>
</wp-view-toggle-button>
</li>
<li class="toolbar-item hidden-for-mobile">
<wp-details-view-button>
</wp-details-view-button>
</li>
<li class="toolbar-item hidden-for-mobile -no-spacing">
<wp-timeline-toggle-button>
</wp-timeline-toggle-button>
</li>
<li class="toolbar-item hidden-for-mobile">
<zen-mode-toggle-button>
</zen-mode-toggle-button>
</li>
<li class="toolbar-item">
<button id="work-packages-settings-button"
title="{{ text.button_settings }}"
class="button last work-packages-settings-button toolbar-icon"
opSettingsContextMenu
opSettingsContextMenu-query="query">
<op-icon icon-classes="button--icon icon-show-more"></op-icon>
</button>
</li>
<ng-container *ngFor="let definition of toolbarButtonComponents">
<li class="toolbar-item" *ngIf="!definition.show || definition.show()" [ngClass]="definition.containerClasses">
<ndc-dynamic [ndcDynamicComponent]="definition.component"
[ndcDynamicInputs]="definition.inputs"
[ndcDynamicInjector]="injector"
[ndcDynamicOutputs]="definition.outputs">
</ndc-dynamic>
</li>
</ng-container>
</ul>
</div>
</div>

@ -26,7 +26,7 @@
// See docs/COPYRIGHT.rdoc for more details.
// ++
import {ChangeDetectionStrategy, Component, HostBinding, OnDestroy, OnInit} from "@angular/core";
import {ChangeDetectionStrategy, Component, ComponentRef, HostBinding, OnDestroy, OnInit} from "@angular/core";
import {untilComponentDestroyed} from 'ng2-rx-componentdestroyed';
import {QueryResource} from 'core-app/modules/hal/resources/query-resource';
import {OpTitleService} from "core-components/html/op-title.service";
@ -41,6 +41,15 @@ import {HalResourceNotificationService} from "core-app/modules/hal/services/hal-
import {WorkPackageNotificationService} from "core-app/modules/work_packages/notifications/work-package-notification.service";
import {QueryParamListenerService} from "core-components/wp-query/query-param-listener.service";
import {InjectField} from "core-app/helpers/angular/inject-field.decorator";
import {ComponentType} from "@angular/cdk/overlay";
export interface ToolbarButtonComponentDefinition {
component:ComponentType<any>;
containerClasses?:string;
show?:() => boolean;
inputs?:{[inputName:string]:any};
outputs?:{[outputName:string]:Function};
}
@Component({
selector: 'partitioned-query-space-page',
@ -86,8 +95,12 @@ export class PartitionedQuerySpacePageComponent extends WorkPackagesViewBase imp
tableInformationLoaded = false;
/** An overlay over the table shown for example when the filters are invalid */
// TODO DOES NOT PROPAGATE
showResultOverlay = false;
/** The toolbar buttons to render */
toolbarButtonComponents:ToolbarButtonComponentDefinition[] = [];
// ToDo: Return correct value
public currentPartition():string {
return '-split';
@ -194,7 +207,7 @@ export class PartitionedQuerySpacePageComponent extends WorkPackagesViewBase imp
}
}
public refresh(visibly:boolean = false, firstPage:boolean = false):Promise<unknown> {
refresh(visibly:boolean = false, firstPage:boolean = false):Promise<unknown> {
let promise:Promise<unknown>;
if (firstPage) {
@ -223,5 +236,4 @@ export class PartitionedQuerySpacePageComponent extends WorkPackagesViewBase imp
protected set loadingIndicator(promise:Promise<unknown>) {
this.loadingIndicatorService.table.promise = promise;
}
}

@ -28,17 +28,12 @@
import {UserResource} from 'core-app/modules/hal/resources/user-resource';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageViewFocusService} from 'core-app/modules/work_packages/routing/wp-view-base/view-services/wp-view-focus.service';
import {StateService} from '@uirouter/core';
import {TypeResource} from 'core-app/modules/hal/resources/type-resource';
import {Component, Injector, OnInit} from '@angular/core';
import {WorkPackageViewSelectionService} from 'core-app/modules/work_packages/routing/wp-view-base/view-services/wp-view-selection.service';
import {States} from 'core-components/states.service';
import {KeepTabService} from 'core-components/wp-single-view-tabs/keep-tab/keep-tab.service';
import {FirstRouteService} from "core-app/modules/router/first-route-service";
import {WorkPackageSingleViewBase} from "core-app/modules/work_packages/routing/wp-view-base/work-package-single-view.base";
import {BackRoutingService} from "core-app/modules/common/back-routing/back-routing.service";
import {InjectField} from "core-app/helpers/angular/inject-field.decorator";
@Component({
templateUrl: './wp-full-view.html',

@ -25,7 +25,7 @@
<ul id="toolbar-items" class="toolbar-items hide-when-print">
<li class="toolbar-item hidden-for-mobile">
<wp-create-button
[allowed]="!!(workPackage.addChild || workPackage.$links.copy)"
[allowed]="['addChild', 'copy']"
stateName="work-packages.new">
</wp-create-button>
</li>

@ -89,7 +89,7 @@ export class WorkPackageListViewComponent implements OnInit, OnDestroy {
constructor(private I18n:I18nService,
private querySpace:IsolatedQuerySpace,
private wpView:WorkPackageViewPageComponent,
readonly wpView:WorkPackageViewPageComponent,
private deviceService:DeviceService,
private CurrentProject:CurrentProjectService,
private wpDisplayRepresentation:WorkPackageViewDisplayRepresentationService,

@ -33,7 +33,19 @@ import {HalResourceNotificationService} from "core-app/modules/hal/services/hal-
import {WorkPackageNotificationService} from "core-app/modules/work_packages/notifications/work-package-notification.service";
import {QueryParamListenerService} from "core-components/wp-query/query-param-listener.service";
import {InjectField} from "core-app/helpers/angular/inject-field.decorator";
import {PartitionedQuerySpacePageComponent} from "core-app/modules/work_packages/routing/partitioned-query-space-page/partitioned-query-space-page.component";
import {
PartitionedQuerySpacePageComponent,
ToolbarButtonComponentDefinition
} from "core-app/modules/work_packages/routing/partitioned-query-space-page/partitioned-query-space-page.component";
import {WorkPackageCreateButtonComponent} from "core-components/wp-buttons/wp-create-button/wp-create-button.component";
import {WorkPackageFilterButtonComponent} from "core-components/wp-buttons/wp-filter-button/wp-filter-button.component";
import {WorkPackageViewToggleButton} from "core-components/wp-buttons/wp-view-toggle-button/work-package-view-toggle-button.component";
import {WorkPackageDetailsViewButtonComponent} from "core-components/wp-buttons/wp-details-view-button/wp-details-view-button.component";
import {WorkPackageTimelineButtonComponent} from "core-components/wp-buttons/wp-timeline-toggle-button/wp-timeline-toggle-button.component";
import {BcfImportButtonComponent} from "core-app/modules/bcf/bcf-buttons/bcf-import-button.component";
import {BcfExportButtonComponent} from "core-app/modules/bcf/bcf-buttons/bcf-export-button.component";
import {ZenModeButtonComponent} from "core-components/wp-buttons/zen-mode-toggle-button/zen-mode-toggle-button.component";
import {WorkPackageSettingsButtonComponent} from "core-components/wp-buttons/wp-settings-button/wp-settings-button.component";
@Component({
selector: 'wp-view-page',
@ -54,18 +66,51 @@ export class WorkPackageViewPageComponent extends PartitionedQuerySpacePageCompo
transitionListenerState = 'work-packages.partitioned.list';
toolbarButtonComponents:ToolbarButtonComponentDefinition[] = [
{
component: WorkPackageCreateButtonComponent,
inputs: {
stateName: "work-packages.partitioned.list.new",
allowed: ['createWorkPackage']
}
},
{
component: BcfImportButtonComponent,
show: () => this.bcfDetectorService.isBcfActivated
},
{
component: BcfExportButtonComponent,
show: () => this.bcfDetectorService.isBcfActivated
},
{
component: WorkPackageFilterButtonComponent
},
{
component: WorkPackageViewToggleButton,
containerClasses: 'hidden-for-mobile'
},
{
component: WorkPackageDetailsViewButtonComponent,
containerClasses: 'hidden-for-mobile'
},
{
component: WorkPackageTimelineButtonComponent,
containerClasses: 'hidden-for-mobile -no-spacing'
},
{
component: ZenModeButtonComponent,
containerClasses: 'hidden-for-mobile'
},
{
component: WorkPackageSettingsButtonComponent
}
];
ngOnInit() {
super.ngOnInit();
this.text.button_settings = this.I18n.t('js.button_settings');
}
public allowed(model:string, permission:string) {
return this.authorisationService.can(model, permission);
}
public bcfActivated() {
return this.bcfDetectorService.isBcfActivated;
}
protected additionalLoadingTime():Promise<unknown> {
if (this.wpTableTimeline.isVisible) {
return this.querySpace.timelineRendered.pipe(take(1)).toPromise();

Loading…
Cancel
Save