Merge pull request #7481 from opf/feature/30575-Include-Cards-View-into-work-packages-module
[30575] Include Cards View into work packages module [ci skip]pull/7493/head
commit
5e4ec13e3b
@ -0,0 +1,5 @@ |
||||
class AddDisplayRepresentationToQuery < ActiveRecord::Migration[5.2] |
||||
def change |
||||
add_column :queries, :display_representation, :text |
||||
end |
||||
end |
@ -0,0 +1,136 @@ |
||||
// -- 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 {AbstractWorkPackageButtonComponent} from '../wp-buttons.module'; |
||||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core'; |
||||
import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; |
||||
import {DynamicBootstrapper} from "core-app/globals/dynamic-bootstrapper"; |
||||
import {StateService} from "@uirouter/core"; |
||||
import { |
||||
WorkPackageDisplayRepresentationService, wpDisplayCardRepresentation, |
||||
wpDisplayListRepresentation |
||||
} from "core-components/wp-fast-table/state/work-package-display-representation.service"; |
||||
import {untilComponentDestroyed} from "ng2-rx-componentdestroyed"; |
||||
|
||||
|
||||
@Component({ |
||||
template: ` |
||||
<ul class="toolbar-button-group"> |
||||
<li> |
||||
<button class="button" |
||||
type="button" |
||||
[ngClass]="{ '-active': inListView }" |
||||
[disabled]="inListView" |
||||
id="wp-view-toggle-button--list" |
||||
[attr.title]="listLabel" |
||||
[attr.accesskey]="accessKey" |
||||
(accessibleClick)="performAction($event)"> |
||||
<op-icon icon-classes="{{ iconListView }} button--icon"></op-icon> |
||||
</button> |
||||
</li> |
||||
<li> |
||||
<button class="button" |
||||
[ngClass]="{ '-active': !inListView }" |
||||
id="wp-view-toggle-button--card" |
||||
[attr.title]="cardLabel" |
||||
[disabled]="!inListView" |
||||
(click)="performAction($event)"> |
||||
<op-icon icon-classes="{{ iconCardView }} button--icon"></op-icon> |
||||
</button> |
||||
</li> |
||||
</ul> |
||||
`,
|
||||
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-image2'; |
||||
|
||||
public inListView:boolean = true; |
||||
|
||||
public cardLabel:string; |
||||
public listLabel:string; |
||||
|
||||
constructor(readonly $state:StateService, |
||||
readonly I18n:I18nService, |
||||
readonly cdRef:ChangeDetectorRef, |
||||
readonly wpDisplayRepresentationService:WorkPackageDisplayRepresentationService) { |
||||
super(I18n); |
||||
|
||||
this.cardLabel = I18n.t('js.button_card_list'); |
||||
this.listLabel = I18n.t('js.button_show_list'); |
||||
} |
||||
|
||||
ngOnInit() { |
||||
this.wpDisplayRepresentationService.state.values$() |
||||
.pipe( |
||||
untilComponentDestroyed(this) |
||||
) |
||||
.subscribe(() => { |
||||
this.inListView = this.wpDisplayRepresentationService.current !== wpDisplayCardRepresentation; |
||||
this.cdRef.detectChanges(); |
||||
}); |
||||
} |
||||
|
||||
ngOnDestroy() { |
||||
//
|
||||
} |
||||
|
||||
public isActive():boolean { |
||||
return false; |
||||
} |
||||
|
||||
public performAction(evt:Event):false { |
||||
if (this.inListView) { |
||||
this.activateCardView(); |
||||
} else { |
||||
this.activateListView(); |
||||
} |
||||
|
||||
evt.preventDefault(); |
||||
return false; |
||||
} |
||||
|
||||
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 }); |
@ -0,0 +1,4 @@ |
||||
.wp-cards-container.-horizontal |
||||
display: grid |
||||
grid-template-columns: repeat(auto-fit, minmax(100px, 300px)) |
||||
grid-column-gap: 10px |
@ -0,0 +1,19 @@ |
||||
@import 'helpers' |
||||
|
||||
.wp-cards-container.-vertical |
||||
display: flex |
||||
flex-direction: column |
||||
// Ensure 100% height for drag & drop area |
||||
height: 100% |
||||
// Some minor left/right padding |
||||
padding: 0 15px |
||||
border-radius: 2px |
||||
// We let the scrollbar be always there, so that we can align the button above with the card view |
||||
// independently of whether we scroll or not. |
||||
overflow-y: scroll |
||||
@include styled-scroll-bar |
||||
|
||||
.wp-card |
||||
// Take care that the shadow of the last element is still visible |
||||
&:last-of-type |
||||
margin-bottom: 3px |
@ -1,3 +1,3 @@ |
||||
export type HighlightingMode = 'status'|'priority'|'type'|'inline'|'none'; |
||||
|
||||
export type CardHighlightingMode = 'priority'|'type'|'none'|'entire-card'; |
||||
export type CardHighlightingMode = 'priority'|'type'|'none'|'inline'; |
||||
|
@ -0,0 +1,73 @@ |
||||
// -- 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 {QueryResource} from 'core-app/modules/hal/resources/query-resource'; |
||||
import {WorkPackageQueryStateService} from './wp-table-base.service'; |
||||
import {States} from 'core-components/states.service'; |
||||
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space"; |
||||
import {Injectable} from '@angular/core'; |
||||
import {InputState} from "reactivestates"; |
||||
|
||||
|
||||
export const wpDisplayListRepresentation:string = 'list'; |
||||
export const wpDisplayCardRepresentation:string = 'card'; |
||||
export type wpDisplayRepresentation = 'list'|'card'; |
||||
|
||||
@Injectable() |
||||
export class WorkPackageDisplayRepresentationService extends WorkPackageQueryStateService<string|null> { |
||||
public constructor(readonly states:States, |
||||
readonly querySpace:IsolatedQuerySpace) { |
||||
super(querySpace); |
||||
} |
||||
|
||||
public get state():InputState<string|null> { |
||||
return this.querySpace.displayRepresentation; |
||||
} |
||||
|
||||
public hasChanged(query:QueryResource) { |
||||
return this.current !== query.displayRepresentation; |
||||
} |
||||
|
||||
valueFromQuery(query:QueryResource) { |
||||
return query.displayRepresentation || null; |
||||
} |
||||
|
||||
public applyToQuery(query:QueryResource) { |
||||
const current = this.current; |
||||
query.displayRepresentation = current === null ? undefined : current; |
||||
return true; |
||||
} |
||||
|
||||
public get current():string|null { |
||||
return this.state.getValueOr(null); |
||||
} |
||||
|
||||
public setDisplayRepresentation(representation:string) { |
||||
this.update(representation); |
||||
} |
||||
} |
@ -0,0 +1,74 @@ |
||||
// -- 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 {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit} from "@angular/core"; |
||||
import {WorkPackageTableHighlightingService} from "core-components/wp-fast-table/state/wp-table-highlighting.service"; |
||||
import {CardViewOrientation} from "core-components/wp-card-view/wp-card-view.component"; |
||||
import {takeUntil} from "rxjs/operators"; |
||||
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space"; |
||||
import {HighlightingMode} from "core-components/wp-fast-table/builders/highlighting/highlighting-mode.const"; |
||||
import {componentDestroyed, untilComponentDestroyed} from "ng2-rx-componentdestroyed"; |
||||
|
||||
@Component({ |
||||
selector: 'wp-grid', |
||||
template: ` |
||||
<wp-card-view [dragOutOfHandler]="canDragOutOf" |
||||
[dragInto]="false" |
||||
[cardsRemovable]="false" |
||||
[highlightingMode]="highlightingMode" |
||||
[showStatusButton]="true" |
||||
[orientation]="gridOrientation"> |
||||
</wp-card-view> |
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
}) |
||||
export class WorkPackagesGridComponent implements OnInit, OnDestroy { |
||||
public canDragOutOf = () => { return false; }; |
||||
public gridOrientation:CardViewOrientation = 'horizontal'; |
||||
public highlightingMode:HighlightingMode = 'none'; |
||||
|
||||
constructor(readonly wpTableHighlight:WorkPackageTableHighlightingService, |
||||
readonly querySpace:IsolatedQuerySpace, |
||||
readonly cdRef:ChangeDetectorRef) { |
||||
} |
||||
|
||||
ngOnInit() { |
||||
this.querySpace.highlighting.values$() |
||||
.pipe( |
||||
takeUntil(this.querySpace.stopAllSubscriptions), |
||||
untilComponentDestroyed(this) |
||||
) |
||||
.subscribe(() => { |
||||
this.highlightingMode = this.wpTableHighlight.current.mode; |
||||
this.cdRef.detectChanges(); |
||||
}); |
||||
} |
||||
|
||||
ngOnDestroy():void { |
||||
} |
||||
} |
@ -0,0 +1,109 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 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-2017 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. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe 'Work package timeline navigation', |
||||
with_ee: %i[conditional_highlighting], |
||||
js: true do |
||||
let(:user) { FactoryBot.create(:admin) } |
||||
let(:project) { FactoryBot.create(:project) } |
||||
let(:wp_table) { Pages::WorkPackagesTable.new(project) } |
||||
let(:highlighting) { ::Components::WorkPackages::Highlighting.new } |
||||
let(:display_representation) { ::Components::WorkPackages::DisplayRepresentation.new } |
||||
|
||||
let(:priority1) { FactoryBot.create :issue_priority, color: FactoryBot.create(:color, hexcode: '#123456') } |
||||
let(:priority2) { FactoryBot.create :issue_priority, color: FactoryBot.create(:color, hexcode: '#332211') } |
||||
let(:status) { FactoryBot.create :status, color: FactoryBot.create(:color, hexcode: '#654321') } |
||||
|
||||
let(:wp_1) do |
||||
FactoryBot.create :work_package, |
||||
project: project, |
||||
priority: priority1, |
||||
status: status |
||||
end |
||||
let(:wp_2) do |
||||
FactoryBot.create :work_package, |
||||
project: project, |
||||
priority: priority2, |
||||
status: status |
||||
end |
||||
|
||||
before do |
||||
wp_1 |
||||
wp_2 |
||||
allow(EnterpriseToken).to receive(:show_banners?).and_return(false) |
||||
|
||||
login_as(user) |
||||
wp_table.visit! |
||||
wp_table.expect_work_package_listed wp_1, wp_2 |
||||
|
||||
# Enable card representation |
||||
display_representation.switch_to_card_layout |
||||
expect(page).to have_selector(".wp-card[data-work-package-id='#{wp_1.id}']") |
||||
expect(page).to have_selector(".wp-card[data-work-package-id='#{wp_2.id}']") |
||||
end |
||||
|
||||
it 'can switch the representations and keep the configuration settings' do |
||||
# Enable highlighting |
||||
highlighting.switch_entire_row_highlight "Priority" |
||||
within ".wp-card[data-work-package-id='#{wp_1.id}']" do |
||||
expect(page).to have_selector(".wp-card--highlighting.__hl_background_priority_#{priority1.id}") |
||||
end |
||||
within ".wp-card[data-work-package-id='#{wp_2.id}']" do |
||||
expect(page).to have_selector(".wp-card--highlighting.__hl_background_priority_#{priority2.id}") |
||||
end |
||||
|
||||
# Switch back to list representation & Highlighting is kept |
||||
display_representation.switch_to_list_layout |
||||
wp_table.expect_work_package_listed wp_1, wp_2 |
||||
expect(page).to have_selector("#{wp_table.row_selector(wp_1)}.__hl_background_priority_#{priority1.id}") |
||||
expect(page).to have_selector("#{wp_table.row_selector(wp_2)}.__hl_background_priority_#{priority2.id}") |
||||
|
||||
# Change attribute |
||||
highlighting.switch_entire_row_highlight "Status" |
||||
expect(page).to have_selector("#{wp_table.row_selector(wp_1)}.__hl_background_status_#{status.id}") |
||||
expect(page).to have_selector("#{wp_table.row_selector(wp_2)}.__hl_background_status_#{status.id}") |
||||
|
||||
# Switch back to card representation & Highlighting is kept, too |
||||
display_representation.switch_to_card_layout |
||||
within ".wp-card[data-work-package-id='#{wp_1.id}']" do |
||||
expect(page).to have_selector(".wp-card--highlighting.__hl_background_status_#{status.id}") |
||||
end |
||||
within ".wp-card[data-work-package-id='#{wp_2.id}']" do |
||||
expect(page).to have_selector(".wp-card--highlighting.__hl_background_status_#{status.id}") |
||||
end |
||||
end |
||||
|
||||
it 'saves the representation in the query' do |
||||
# After refresh the WP are still disaplyed as cards |
||||
page.driver.browser.navigate.refresh |
||||
expect(page).to have_selector(".wp-card[data-work-package-id='#{wp_1.id}']") |
||||
expect(page).to have_selector(".wp-card[data-work-package-id='#{wp_2.id}']") |
||||
end |
||||
end |
@ -0,0 +1,50 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 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-2017 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. |
||||
#++ |
||||
|
||||
module Components |
||||
module WorkPackages |
||||
class DisplayRepresentation |
||||
include Capybara::DSL |
||||
include RSpec::Matchers |
||||
|
||||
def initialize; end |
||||
|
||||
def switch_to_card_layout |
||||
expect(page).to have_button('wp-view-toggle-button--card', disabled: false) |
||||
expect(page).to have_button('wp-view-toggle-button--list', disabled: true) |
||||
page.find('#wp-view-toggle-button--card').click |
||||
end |
||||
|
||||
def switch_to_list_layout |
||||
expect(page).to have_button('wp-view-toggle-button--card', disabled: true) |
||||
expect(page).to have_button('wp-view-toggle-button--list', disabled: false) |
||||
page.find('#wp-view-toggle-button--list').click |
||||
end |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue