Merge pull request #7112 from opf/feature/29672-Show-work-package-in-full-view-when-double-clicking-on-a-card

[29672] [Boards] Show work package in full view when double-clicking on a card
pull/7123/head
Henriette Dinger 6 years ago committed by GitHub
commit ba314e6f97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      app/assets/javascripts/onboarding/work_package_tour.js
  2. 2
      app/assets/stylesheets/layout/_base.sass
  3. 1
      config/locales/js-en.yml
  4. 3
      frontend/src/app/components/wp-card-view/wp-card-view.component.html
  5. 11
      frontend/src/app/components/wp-card-view/wp-card-view.component.ts
  6. 4
      frontend/src/app/components/wp-single-view-tabs/keep-tab/keep-tab.service.ts
  7. 10
      frontend/src/app/modules/boards/openproject-boards.module.ts
  8. 5
      frontend/src/app/modules/calendar/openproject-calendar.module.ts
  9. 81
      frontend/src/app/modules/common/back-routing/back-routing.service.ts
  10. 2
      frontend/src/app/modules/common/openproject-common.module.ts
  11. 5
      frontend/src/app/modules/router/openproject.routes.ts
  12. 49
      frontend/src/app/modules/work_packages/routing/work-packages-routes.ts
  13. 10
      frontend/src/app/modules/work_packages/routing/wp-full-view/wp-full-view.component.ts
  14. 8
      frontend/src/app/modules/work_packages/routing/wp-full-view/wp-full-view.html
  15. 76
      modules/boards/spec/features/board_navigation_spec.rb
  16. 2
      modules/boards/spec/features/support/board_index_page.rb
  17. 15
      spec/features/calendars/calendars_spec.rb
  18. 13
      spec/features/work_packages/navigation_spec.rb
  19. 2
      spec/features/work_packages/zen_mode_spec.rb
  20. 4
      spec/support/pages/work_packages/abstract_work_package.rb
  21. 4
      spec/support/pages/work_packages/split_work_package.rb

@ -16,11 +16,11 @@
'containerClass': '-dark -hidden-arrow'
},
{
'next .work-packages-list-view-button': I18n.t('js.onboarding.steps.wp_back_button'),
'next .work-packages-back-button': I18n.t('js.onboarding.steps.wp_back_button'),
'showSkip': false,
'nextButton': {text: I18n.t('js.onboarding.buttons.next')},
onNext: function () {
$('.work-packages-list-view-button')[0].click();
$('.work-packages-back-button')[0].click();
}
},
{

@ -72,4 +72,4 @@
background-color: $body-background
.-draggable
cursor: move
cursor: grab

@ -52,6 +52,7 @@ en:
copied_successful: "Sucessfully copied to clipboard!"
button_add_watcher: "Add watcher"
button_back: "Back"
button_back_to_list_view: "Back to list view"
button_cancel: "Cancel"
button_close: "Close"

@ -4,7 +4,8 @@
*ngFor="let wp of workPackages; trackBy:trackByHref"
[attr.data-is-new]="wp.isNew || undefined"
[attr.data-work-package-id]="wp.id"
[ngClass]="{ '-draggable': isDraggable }">
[ngClass]="{ '-draggable': isDraggable }"
(dblclick)="handleDblClick(wp)">
<wp-edit-field-group [workPackage]="wp" [inEditMode]="wp.isNew">
<accessible-by-keyboard *ngIf="wp.isNew"

@ -152,6 +152,17 @@ export class WorkPackageCardViewComponent extends WorkPackageEmbeddedTableCompon
return this.configuration.dragAndDropEnabled;
}
public handleDblClick(wp:WorkPackageResource) {
this.goToWpFullView(wp.id);
}
private goToWpFullView(wpId:string) {
this.$state.go(
'work-packages.show',
{workPackageId: wpId}
);
}
removeDragged() {
this.container.nativeElement
.querySelectorAll('.__was_dragged')

@ -68,6 +68,10 @@ export class KeepTabService {
return 'work-packages.list.details.' + this.currentDetailsTab;
}
public isDetailsState(stateName:string) {
return stateName === 'work-packages.list.details';
}
public get currentShowTab():string {
// Show view doesn't have overview
// use activity instead

@ -55,7 +55,10 @@ export const BOARDS_ROUTES:Ng2StateDeclaration[] = [
},
{
name: 'boards.list',
component: BoardsIndexPageComponent
component: BoardsIndexPageComponent,
data: {
parent: 'boards'
}
},
{
name: 'boards.show',
@ -64,7 +67,10 @@ export const BOARDS_ROUTES:Ng2StateDeclaration[] = [
board_id: { type: 'int' },
isNew: { type: 'bool' }
},
component: BoardComponent
component: BoardComponent,
data: {
parent: 'boards'
}
}
];

@ -42,7 +42,10 @@ export const CALENDAR_ROUTES:Ng2StateDeclaration[] = [
name: 'work-packages.calendar',
url: '/calendar',
component: WorkPackagesCalendarEntryComponent,
reloadOnSearch: false
reloadOnSearch: false,
data: {
parent: 'work-packages'
}
}
];

@ -0,0 +1,81 @@
// -- 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 {Injectable, Injector} from '@angular/core';
import {StateService, Transition} from "@uirouter/core";
import {KeepTabService} from "core-components/wp-single-view-tabs/keep-tab/keep-tab.service";
interface BackRouteOptions {
name:string;
params:{};
parent:string;
}
@Injectable()
export class BackRoutingService {
private _backRoute:BackRouteOptions;
private $state:StateService = this.injector.get(StateService);
private keepTab:KeepTabService = this.injector.get(KeepTabService);
constructor(protected injector:Injector) {
}
public goBack() {
if (!this.backRoute) {
this.$state.go('work-packages.list', this.$state.params);
} else {
if (this.keepTab.isDetailsState(this.backRoute.parent)) {
this.$state.go(this.keepTab.currentDetailsState, this.$state.params);
} else {
this.$state.go(this.backRoute.name, this.backRoute.params);
}
}
}
public sync(transition:Transition) {
const fromState = transition.from();
const toState = transition.to();
// Set backRoute to know where we came from
if (fromState.name &&
fromState.data &&
toState.data &&
fromState.data.parent !== toState.data.parent) {
const paramsFromCopy = { ...transition.params('from') };
this.backRoute = { name: fromState.name, params: paramsFromCopy, parent: fromState.data.parent };
}
}
public set backRoute(route:BackRouteOptions) {
this._backRoute = route;
}
public get backRoute():BackRouteOptions {
return this._backRoute;
}
}

@ -79,6 +79,7 @@ import {BrowserDetector} from "core-app/modules/common/browser/browser-detector.
import {EditableToolbarTitleComponent} from "core-app/modules/common/editable-toolbar-title/editable-toolbar-title.component";
import {UserAvatarComponent} from "core-components/user/user-avatar/user-avatar.component";
import {GonService} from "core-app/modules/common/gon/gon.service";
import {BackRoutingService} from "core-app/modules/common/back-routing/back-routing.service";
export function bootstrapModule(injector:Injector) {
return () => {
@ -250,6 +251,7 @@ export function bootstrapModule(injector:Injector) {
TimezoneService,
BrowserDetector,
GonService,
BackRoutingService,
]
})
export class OpenprojectCommonModule { }

@ -33,6 +33,7 @@ import {Injector} from "@angular/core";
import {FirstRouteService} from "core-app/modules/router/first-route-service";
import {StatesModule} from "@uirouter/angular";
import {appBaseSelector, ApplicationBaseComponent} from "core-app/modules/router/base/application-base.component";
import {BackRoutingService} from "core-app/modules/common/back-routing/back-routing.service";
export const OPENPROJECT_ROUTES = [
{
@ -98,6 +99,7 @@ export function initializeUiRouterListeners(injector:Injector) {
const notificationsService:NotificationsService = injector.get(NotificationsService);
const currentProject:CurrentProjectService = injector.get(CurrentProjectService);
const firstRoute:FirstRouteService = injector.get(FirstRouteService);
const backRoutingService:BackRoutingService = injector.get(BackRoutingService);
// Check whether we are running within our complete app, or only within some other bootstrapped
// component
@ -133,6 +135,9 @@ export function initializeUiRouterListeners(injector:Injector) {
return $state.target(transition.to(), paramsCopy);
}
// Set backRoute to know where we came from
backRoutingService.sync(transition);
// Reset profiler, if we're actually profiling
const profiler:any = (window as any).MiniProfiler;
profiler && profiler.pageTransition();

@ -89,22 +89,34 @@ export const WORK_PACKAGES_ROUTES:Ng2StateDeclaration[] = [
{
name: 'work-packages.show.activity',
url: '/activity',
component: WorkPackageActivityTabComponent
component: WorkPackageActivityTabComponent,
data: {
parent: 'work-packages.show'
}
},
{
name: 'work-packages.show.activity.details',
url: '/activity/details/#{activity_no:\d+}',
component: WorkPackageActivityTabComponent
component: WorkPackageActivityTabComponent,
data: {
parent: 'work-packages.show'
}
},
{
name: 'work-packages.show.relations',
url: '/relations',
component: WorkPackageRelationsTabComponent
component: WorkPackageRelationsTabComponent,
data: {
parent: 'work-packages.show'
}
},
{
name: 'work-packages.show.watchers',
url: '/watchers',
component: WorkPackageWatchersTabComponent
component: WorkPackageWatchersTabComponent,
data: {
parent: 'work-packages.show'
}
},
{
name: 'work-packages.list',
@ -122,7 +134,8 @@ export const WORK_PACKAGES_ROUTES:Ng2StateDeclaration[] = [
reloadOnSearch: false,
data: {
allowMovingInEditMode: true,
bodyClasses: 'action-create'
bodyClasses: 'action-create',
parent: 'work-packages.list'
},
},
{
@ -132,7 +145,8 @@ export const WORK_PACKAGES_ROUTES:Ng2StateDeclaration[] = [
reloadOnSearch: false,
data: {
allowMovingInEditMode: true,
bodyClasses: 'action-details'
bodyClasses: 'action-details',
parent: 'work-packages.list'
},
},
{
@ -154,27 +168,42 @@ export const WORK_PACKAGES_ROUTES:Ng2StateDeclaration[] = [
{
name: 'work-packages.list.details.overview',
url: '/overview',
component: WorkPackageOverviewTabComponent
component: WorkPackageOverviewTabComponent,
data: {
parent: 'work-packages.list.details'
}
},
{
name: 'work-packages.list.details.activity',
url: '/activity',
component: WorkPackageActivityTabComponent,
data: {
parent: 'work-packages.list.details'
}
},
{
name: 'work-packages.list.details.activity.details',
url: '/activity/details/#{activity_no:\d+}',
component: WorkPackageActivityTabComponent
component: WorkPackageActivityTabComponent,
data: {
parent: 'work-packages.list.details'
}
},
{
name: 'work-packages.list.details.relations',
url: '/relations',
component: WorkPackageRelationsTabComponent
component: WorkPackageRelationsTabComponent,
data: {
parent: 'work-packages.list.details'
}
},
{
name: 'work-packages.list.details.watchers',
url: '/watchers',
component: WorkPackageWatchersTabComponent
component: WorkPackageWatchersTabComponent,
data: {
parent: 'work-packages.list.details'
}
},
// Avoid lazy-loading the routes for now
// {

@ -37,6 +37,7 @@ 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";
@Component({
templateUrl: './wp-full-view.html',
@ -63,6 +64,8 @@ export class WorkPackagesFullViewComponent extends WorkPackageSingleViewBase {
public actionsAvailable:any;
public triggerMoreMenuAction:Function;
public backRoutingService:BackRoutingService = this.injector.get(BackRoutingService);
constructor(public injector:Injector,
public states:States,
public firstRoute:FirstRouteService,
@ -89,13 +92,12 @@ export class WorkPackagesFullViewComponent extends WorkPackageSingleViewBase {
this.wpTableFocus.updateFocus(this.workPackage.id);
this.setWorkPackageScopeProperties(this.workPackage);
this.text.goToList = this.I18n.t('js.button_back_to_list_view');
this.text.goBack = this.I18n.t('js.button_back');
}
public goToList() {
this.$state.go('work-packages.list', this.$state.params);
public goBack() {
this.backRoutingService.goBack();
}
private setWorkPackageScopeProperties(wp:WorkPackageResource) {
this.isWatched = wp.hasOwnProperty('unwatch');
this.displayWatchButton = wp.hasOwnProperty('unwatch') || wp.hasOwnProperty('watch');

@ -9,10 +9,10 @@
<div id="toolbar">
<div class="wp-show--header-container">
<div class="wp-show--back-button hide-when-print">
<accessible-by-keyboard (execute)="goToList()"
linkClass="work-packages-list-view-button button"
[linkAriaLabel]="text.goToList"
[linkTitle]="text.goToList">
<accessible-by-keyboard (execute)="goBack()"
linkClass="work-packages-back-button button"
[linkAriaLabel]="text.goBack"
[linkTitle]="text.goBack">
<op-icon icon-classes="button--icon icon-back-up"></op-icon>
</accessible-by-keyboard>
</div>

@ -0,0 +1,76 @@
#-- 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'
require_relative './support/board_index_page'
require_relative './support/board_page'
describe 'Work Package boards spec', type: :feature, js: true do
let(:user) do
FactoryBot.create(:user,
member_in_project: project,
member_through_role: role)
end
let(:project) { FactoryBot.create(:project, enabled_module_names: %i[work_package_tracking board_view]) }
let(:permissions) { %i[show_board_views manage_board_views add_work_packages view_work_packages manage_public_queries] }
let(:role) { FactoryBot.create(:role, permissions: permissions) }
let!(:priority) { FactoryBot.create :default_priority }
let!(:status) { FactoryBot.create :default_status }
let(:board_index) { Pages::BoardIndex.new(project) }
let(:board_view) { FactoryBot.create :board_grid_with_query, project: project }
let(:project_html_title) { ::Components::HtmlTitle.new project }
before do
project
login_as(user)
end
it 'navigates from boards to the WP full view and back' do
board_view
board_index.visit!
# Add a new WP on the board
board_page = board_index.open_board board_view
board_page.expect_query 'List 1', editable: true
board_page.add_card 'List 1', 'Task 1'
board_page.expect_notification message: I18n.t(:notice_successful_create)
# Double click leads to the full view
click_target = board_page.find('.work-package--card--author')
page.driver.browser.action.double_click(click_target.native).perform
wp = WorkPackage.last
expect(current_path).to eq project_work_package_path(project, wp.id, 'activity')
# Click back goes back to the board
find('.work-packages-back-button').click
expect(current_path).to eq project_work_package_boards_path(project, board_view.id)
end
end

@ -39,7 +39,7 @@ module Pages
def visit!
if project
visit project_work_package_boards_path(project_id: project.id)
visit project_work_package_boards_path(project)
else
visit work_package_boards_path
end

@ -178,6 +178,19 @@ describe 'Work package calendars', type: :feature, js: true do
page.go_back
# click goes to work package show page
expect(page).to have_selector('.fc-event-container', text: current_work_package.subject, wait: 20)
expect(page)
.to have_selector('.fc-event-container', text: current_work_package.subject, wait: 20)
# click goes to work package show page again
page.find('.fc-event-container', text: current_work_package.subject).click
expect(page)
.to have_selector('.subject-header', text: current_work_package.subject)
# click back goes back to calendar
page.find('.work-packages-back-button').click
expect(page)
.to have_selector '.fc-event-container', text: current_work_package.subject, wait: 20
end
end

@ -122,15 +122,20 @@ RSpec.feature 'Work package navigation', js: true, selenium: true do
expect(current_path).to eq project_work_package_path(project, work_package, 'activity')
project_html_title.expect_first_segment wp_title_segment
# Back to table using the button
find('.work-packages-list-view-button').click
# Switch tabs
full_work_package.switch_to_tab tab: :relations
expect(current_path).to eq project_work_package_path(project, work_package, 'relations')
project_html_title.expect_first_segment wp_title_segment
# Back to split screen using the button
find('.work-packages-back-button').click
global_work_packages.expect_work_package_listed(work_package)
expect(current_path).to eq project_work_packages_path(project)
project_html_title.expect_first_segment 'All open'
expect(current_path).to eq project_work_packages_path(project) + "/details/#{work_package.id}/relations"
# Link to full screen from index
global_work_packages.open_full_screen_by_link(work_package)
full_work_package.switch_to_tab tab: :activity
full_work_package.expect_subject
full_work_package.expect_current_path

@ -39,7 +39,7 @@ describe 'Zen mode', js: true do
wp_page.expect_no_zen_mode
wp_page.page.find('#work-packages-zen-mode-toggle-button').click
wp_page.expect_zen_mode
wp_page.page.find('.work-packages-list-view-button').click
wp_page.page.find('.work-packages-back-button').click
wp_page.expect_zen_mode
wp_page.page.find('#work-packages-zen-mode-toggle-button').click
wp_page.expect_no_zen_mode

@ -44,6 +44,10 @@ module Pages
visit path(tab)
end
def switch_to_tab(tab:)
find('.tabrow li a', text: tab.upcase).click
end
def expect_tab(tab)
expect(page).to have_selector('.tabrow li.selected', text: tab.to_s.upcase)
end

@ -38,10 +38,6 @@ module Pages
@selector = '.work-packages--details'
end
def switch_to_tab(tab:)
find('.tabrow li a', text: tab.upcase).click
end
def switch_to_fullscreen
find('.work-packages--details-fullscreen-icon').click
FullWorkPackage.new(work_package, project)

Loading…
Cancel
Save