diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml index ccea0324fc..1771fff6b5 100644 --- a/config/locales/js-en.yml +++ b/config/locales/js-en.yml @@ -917,3 +917,8 @@ en: all_projects: "all projects" project_and_subprojects: "and all subprojects" search_for: "Search for" + + views: + card: 'Cards' + list: 'Table' + timeline: 'Gantt' diff --git a/frontend/src/app/components/op-context-menu/handlers/wp-view-dropdown-menu.directive.ts b/frontend/src/app/components/op-context-menu/handlers/wp-view-dropdown-menu.directive.ts new file mode 100644 index 0000000000..e513711cb4 --- /dev/null +++ b/frontend/src/app/components/op-context-menu/handlers/wp-view-dropdown-menu.directive.ts @@ -0,0 +1,107 @@ +//-- copyright +// OpenProject is a project management system. +// Copyright (C) 2012-2015 the OpenProject Foundation (OPF) +// +// 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 doc/COPYRIGHT.rdoc for more details. +//++ + +import {OPContextMenuService} from "core-components/op-context-menu/op-context-menu.service"; +import {Directive, ElementRef} from "@angular/core"; +import {OpContextMenuTrigger} from "core-components/op-context-menu/handlers/op-context-menu-trigger.directive"; +import { + WorkPackageDisplayRepresentationService, + wpDisplayCardRepresentation, + wpDisplayListRepresentation +} from "core-components/wp-fast-table/state/work-package-display-representation.service"; +import {I18nService} from "core-app/modules/common/i18n/i18n.service"; +import {WorkPackageTableTimelineService} from "core-components/wp-fast-table/state/wp-table-timeline.service"; + +@Directive({ + selector: '[wpViewDropdown]' +}) +export class WorkPackageViewDropdownMenuDirective extends OpContextMenuTrigger { + constructor(readonly elementRef:ElementRef, + readonly opContextMenu:OPContextMenuService, + readonly I18n:I18nService, + readonly wpDisplayRepresentationService:WorkPackageDisplayRepresentationService, + readonly wpTableTimeline:WorkPackageTableTimelineService) { + + super(elementRef, opContextMenu); + } + + protected open(evt:JQueryEventObject) { + this.buildItems(); + this.opContextMenu.show(this, evt); + } + + public get locals() { + return { + items: this.items, + contextMenuId: 'wp-view-context-menu' + }; + } + + private buildItems() { + this.items = [ + { + // Card View + linkText: this.I18n.t('js.views.card'), + icon: 'icon-view-card', + onClick: (evt:any) => { + this.wpDisplayRepresentationService.setDisplayRepresentation(wpDisplayCardRepresentation); + if (this.wpTableTimeline.isVisible) { + // Necessary for the timeline buttons to disappear + this.wpTableTimeline.toggle(); + } + return true; + } + }, + { + // List View + linkText: this.I18n.t('js.views.list'), + icon: 'icon-view-list', + onClick: (evt:any) => { + this.wpDisplayRepresentationService.setDisplayRepresentation(wpDisplayListRepresentation); + if (this.wpTableTimeline.isVisible) { + this.wpTableTimeline.toggle(); + } + return true; + } + }, + { + // List View with enabled Gantt + linkText: this.I18n.t('js.views.timeline'), + icon: 'icon-view-timeline', + onClick: (evt:any) => { + this.wpDisplayRepresentationService.setDisplayRepresentation(wpDisplayListRepresentation); + if (!this.wpTableTimeline.isVisible) { + this.wpTableTimeline.toggle(); + } + return true; + } + } + ]; + } +} + diff --git a/frontend/src/app/components/wp-buttons/wp-details-view-button/wp-details-view-button.component.ts b/frontend/src/app/components/wp-buttons/wp-details-view-button/wp-details-view-button.component.ts index 7580989f3d..a8de44d092 100644 --- a/frontend/src/app/components/wp-buttons/wp-details-view-button/wp-details-view-button.component.ts +++ b/frontend/src/app/components/wp-buttons/wp-details-view-button/wp-details-view-button.component.ts @@ -33,8 +33,6 @@ import {StateService, TransitionService} from '@uirouter/core'; import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core'; import {AbstractWorkPackageButtonComponent} from 'core-components/wp-buttons/wp-buttons.module'; import {I18nService} from "core-app/modules/common/i18n/i18n.service"; -import {untilComponentDestroyed} from "ng2-rx-componentdestroyed"; -import {WorkPackageViewDisplayRepresentationService} from "core-app/modules/work_packages/routing/wp-view-base/view-services/wp-view-display-representation.service"; @Component({ templateUrl: '../wp-button.template.html', @@ -62,9 +60,7 @@ export class WorkPackageDetailsViewButtonComponent extends AbstractWorkPackageBu readonly cdRef:ChangeDetectorRef, public states:States, public wpTableFocus:WorkPackageViewFocusService, - public keepTab:KeepTabService, - public wpDisplayRepresentationService:WorkPackageViewDisplayRepresentationService) { - + public keepTab:KeepTabService) { super(I18n); this.activateLabel = I18n.t('js.button_open_details'); @@ -77,13 +73,6 @@ export class WorkPackageDetailsViewButtonComponent extends AbstractWorkPackageBu } public ngOnInit() { - this.wpDisplayRepresentationService.live$() - .pipe( - untilComponentDestroyed(this) - ) - .subscribe(() => { - this.cdRef.detectChanges(); - }); } public ngOnDestroy() { diff --git a/frontend/src/app/components/wp-buttons/wp-timeline-toggle-button/wp-timeline-toggle-button.component.ts b/frontend/src/app/components/wp-buttons/wp-timeline-toggle-button/wp-timeline-toggle-button.component.ts index 5ed82bbda9..ea01998808 100644 --- a/frontend/src/app/components/wp-buttons/wp-timeline-toggle-button/wp-timeline-toggle-button.component.ts +++ b/frontend/src/app/components/wp-buttons/wp-timeline-toggle-button/wp-timeline-toggle-button.component.ts @@ -31,10 +31,6 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; import {TimelineZoomLevel} from 'core-app/modules/hal/resources/query-resource'; import {untilComponentDestroyed} from "ng2-rx-componentdestroyed"; -import { - WorkPackageViewDisplayRepresentationService, - wpDisplayCardRepresentation -} from "core-app/modules/work_packages/routing/wp-view-base/view-services/wp-view-display-representation.service"; import {WorkPackageViewTimelineService} from "core-app/modules/work_packages/routing/wp-view-base/view-services/wp-view-timeline.service"; export interface TimelineButtonText extends ButtonControllerText { @@ -67,8 +63,7 @@ export class WorkPackageTimelineButtonComponent extends AbstractWorkPackageButto constructor(readonly I18n:I18nService, readonly cdRef:ChangeDetectorRef, - public wpTableTimeline:WorkPackageViewTimelineService, - public wpDisplayRepresentationService:WorkPackageViewDisplayRepresentationService) { + public wpTableTimeline:WorkPackageViewTimelineService) { super(I18n); this.activateLabel = I18n.t('js.timelines.button_activate'); @@ -102,15 +97,6 @@ export class WorkPackageTimelineButtonComponent extends AbstractWorkPackageButto this.isMinLevel = current === this.minZoomLevel; this.cdRef.detectChanges(); }); - - this.wpDisplayRepresentationService.live$() - .pipe( - untilComponentDestroyed(this) - ) - .subscribe(() => { - this.disabled = this.wpDisplayRepresentationService.current === wpDisplayCardRepresentation; - this.cdRef.detectChanges(); - }); } ngOnDestroy():void { diff --git a/frontend/src/app/components/wp-buttons/wp-view-toggle-button/work-package-view-toggle-button.component.ts b/frontend/src/app/components/wp-buttons/wp-view-toggle-button/work-package-view-toggle-button.component.ts index bb8d9830e5..0d758527e7 100644 --- a/frontend/src/app/components/wp-buttons/wp-view-toggle-button/work-package-view-toggle-button.component.ts +++ b/frontend/src/app/components/wp-buttons/wp-view-toggle-button/work-package-view-toggle-button.component.ts @@ -39,52 +39,22 @@ import {untilComponentDestroyed} from "ng2-rx-componentdestroyed"; @Component({ - template: ` -
+ template: ` + `, changeDetection: ChangeDetectionStrategy.OnPush, selector: 'wp-view-toggle-button', }) -export class WorkPackageViewToggleButton extends AbstractWorkPackageButtonComponent implements OnInit, OnDestroy { - public iconListView:string = 'icon-view-list'; - public iconCardView:string = 'icon-view-card'; - - public inListView:boolean = true; - - public cardLabel:string; - public listLabel:string; - - constructor(readonly $state:StateService, - readonly I18n:I18nService, +export class WorkPackageViewToggleButton implements OnInit, OnDestroy { + constructor(readonly I18n:I18nService, readonly cdRef:ChangeDetectorRef, readonly wpDisplayRepresentationService:WorkPackageViewDisplayRepresentationService) { - super(I18n); - - this.cardLabel = I18n.t('js.button_card_list'); - this.listLabel = I18n.t('js.button_show_list'); } ngOnInit() { @@ -93,40 +63,13 @@ export class WorkPackageViewToggleButton extends AbstractWorkPackageButtonCompon untilComponentDestroyed(this) ) .subscribe(() => { - this.inListView = this.wpDisplayRepresentationService.current !== wpDisplayCardRepresentation; this.cdRef.detectChanges(); }); } ngOnDestroy() { - // - } - - public performAction(evt:Event):false { - if (this.inListView) { - this.activateCardView(); - } else { - this.activateListView(); - } - - evt.preventDefault(); - return false; + // Nothing to do } - - private activateCardView() { - this.inListView = false; - this.wpDisplayRepresentationService.setDisplayRepresentation(wpDisplayCardRepresentation); - - this.cdRef.detectChanges(); - } - - private activateListView() { - this.inListView = true; - this.wpDisplayRepresentationService.setDisplayRepresentation(wpDisplayListRepresentation); - - this.cdRef.detectChanges(); - } - } DynamicBootstrapper.register({ selector: 'wp-view-toggle-button', cls: WorkPackageViewToggleButton }); diff --git a/frontend/src/app/modules/work_packages/openproject-work-packages.module.ts b/frontend/src/app/modules/work_packages/openproject-work-packages.module.ts index 0c32977eb2..354e80c263 100644 --- a/frontend/src/app/modules/work_packages/openproject-work-packages.module.ts +++ b/frontend/src/app/modules/work_packages/openproject-work-packages.module.ts @@ -158,6 +158,7 @@ import {WorkPackagesTableConfigMenu} from "core-components/wp-table/config-menu/ import {WorkPackageIsolatedGraphQuerySpaceDirective} from "core-app/modules/work_packages/query-space/wp-isolated-graph-query-space.directive"; import {WorkPackageViewToggleButton} from "core-components/wp-buttons/wp-view-toggle-button/work-package-view-toggle-button.component"; import {WorkPackagesGridComponent} from "core-components/wp-grid/wp-grid.component"; +import {WorkPackageViewDropdownMenuDirective} from "core-components/op-context-menu/handlers/wp-view-dropdown-menu.directive"; @NgModule({ @@ -275,6 +276,7 @@ import {WorkPackagesGridComponent} from "core-components/wp-grid/wp-grid.compone WorkPackageCreateSettingsMenuDirective, WorkPackageSingleContextMenuDirective, WorkPackageQuerySelectDropdownComponent, + WorkPackageViewDropdownMenuDirective, // Timeline WorkPackageTimelineButtonComponent,