Merge pull request #7124 from opf/feature/board-highlighting

Replace display-mode tab with highlighting tab
pull/7134/head
Henriette Dinger 6 years ago committed by GitHub
commit eb040ee2ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      config/locales/js-en.yml
  2. 4
      frontend/src/app/components/wp-card-view/wp-card-view.component.html
  3. 30
      frontend/src/app/components/wp-card-view/wp-card-view.component.ts
  4. 2
      frontend/src/app/components/wp-fast-table/builders/highlighting/highlighting-mode.const.ts
  5. 13
      frontend/src/app/modules/boards/board/board-list/board-list.component.html
  6. 12
      frontend/src/app/modules/boards/board/board.ts
  7. 8
      frontend/src/app/modules/boards/board/configuration-modal/board-configuration.service.ts
  8. 31
      frontend/src/app/modules/boards/board/configuration-modal/tabs/display-settings-tab.component.html
  9. 39
      frontend/src/app/modules/boards/board/configuration-modal/tabs/display-settings-tab.component.ts
  10. 60
      frontend/src/app/modules/boards/board/configuration-modal/tabs/highlighting-tab.component.html
  11. 70
      frontend/src/app/modules/boards/board/configuration-modal/tabs/highlighting-tab.component.ts
  12. 6
      frontend/src/app/modules/boards/openproject-boards.module.ts
  13. 104
      modules/boards/spec/features/board_highlighting_spec.rb
  14. 16
      modules/boards/spec/features/support/board_page.rb

@ -49,6 +49,9 @@ en:
card: card:
add_new: 'Add new card' add_new: 'Add new card'
highlighting:
inline: 'Highlight inline:'
entire_card_by: 'Entire card by'
clipboard: clipboard:
browser_error: "Your browser doesn't support copying to clipboard. Please copy the selected text manually." browser_error: "Your browser doesn't support copying to clipboard. Please copy the selected text manually."

@ -4,7 +4,7 @@
*ngFor="let wp of workPackages; trackBy:trackByHref" *ngFor="let wp of workPackages; trackBy:trackByHref"
[attr.data-is-new]="wp.isNew || undefined" [attr.data-is-new]="wp.isNew || undefined"
[attr.data-work-package-id]="wp.id" [attr.data-work-package-id]="wp.id"
[ngClass]="{ '-draggable': isDraggable }" [ngClass]="cardClasses(wp)"
(dblclick)="handleDblClick(wp)"> (dblclick)="handleDblClick(wp)">
<wp-edit-field-group [workPackage]="wp" [inEditMode]="wp.isNew"> <wp-edit-field-group [workPackage]="wp" [inEditMode]="wp.isNew">
@ -31,7 +31,7 @@
*ngIf="!wp.isNew"> *ngIf="!wp.isNew">
<span [textContent]="wpTypeAttribute(wp)" <span [textContent]="wpTypeAttribute(wp)"
class="work-package--card--type" class="work-package--card--type"
[ngClass]="typeDotClass(wp)"></span> [ngClass]="typeHighlightingClass(wp)"></span>
<span [textContent]="wpSubject(wp)" <span [textContent]="wpSubject(wp)"
class="work-package--card--subject -bold"></span> class="work-package--card--subject -bold"></span>
</div> </div>

@ -4,7 +4,7 @@ import {
Component, Component,
ElementRef, ElementRef,
Inject, Inject,
Injector, Injector, Input,
OnInit, OnInit,
ViewChild ViewChild
} from "@angular/core"; } from "@angular/core";
@ -47,6 +47,7 @@ import {DragAndDropHelpers} from "core-app/modules/boards/drag-and-drop/drag-and
import {WorkPackageNotificationService} from "core-components/wp-edit/wp-notification.service"; import {WorkPackageNotificationService} from "core-components/wp-edit/wp-notification.service";
import {WorkPackageEditFieldComponent} from "app/components/wp-edit/wp-edit-field/wp-edit-field.component"; import {WorkPackageEditFieldComponent} from "app/components/wp-edit/wp-edit-field/wp-edit-field.component";
import {Highlighting} from "core-components/wp-fast-table/builders/highlighting/highlighting.functions"; import {Highlighting} from "core-components/wp-fast-table/builders/highlighting/highlighting.functions";
import {CardHighlightingMode} from "core-components/wp-fast-table/builders/highlighting/highlighting-mode.const";
@Component({ @Component({
@ -79,6 +80,8 @@ import {Highlighting} from "core-components/wp-fast-table/builders/highlighting/
] ]
}) })
export class WorkPackageCardViewComponent extends WorkPackageEmbeddedTableComponent implements OnInit { export class WorkPackageCardViewComponent extends WorkPackageEmbeddedTableComponent implements OnInit {
@Input() public highlightingMode:CardHighlightingMode;
public trackByHref = AngularTrackingHelpers.trackByHref; public trackByHref = AngularTrackingHelpers.trackByHref;
public query:QueryResource; public query:QueryResource;
public workPackages:any[]; public workPackages:any[];
@ -168,8 +171,29 @@ export class WorkPackageCardViewComponent extends WorkPackageEmbeddedTableCompon
return wp.subject; return wp.subject;
} }
public typeDotClass(wp:WorkPackageResource) { public cardClasses(wp:WorkPackageResource) {
return Highlighting.dotClass('type', wp.type.getId()); let classes:string[] = [];
this.isDraggable ? classes.push('-draggable') : classes.push('');
classes.push(this.cardHighlighting(wp));
return classes;
}
public typeHighlightingClass(wp:WorkPackageResource) {
return this.attributeDotHighlighting('type', wp);
}
private cardHighlighting(wp:WorkPackageResource) {
if (['status', 'priority', 'type'].includes(this.highlightingMode)) {
return Highlighting.rowClass(this.highlightingMode, wp[this.highlightingMode].getId());
}
return '';
}
private attributeDotHighlighting(type:string, wp:WorkPackageResource) {
if (this.highlightingMode === 'inline') {
return Highlighting.dotClass(type, wp.type.getId());
}
return '';
} }
removeDragged() { removeDragged() {

@ -1 +1,3 @@
export type HighlightingMode = 'status'|'priority'|'type'|'inline'|'none'; export type HighlightingMode = 'status'|'priority'|'type'|'inline'|'none';
export type CardHighlightingMode = 'status'|'priority'|'type'|'inline'|'none'|'entire-card';

@ -25,18 +25,11 @@
</div> </div>
<div class="board-list--query-container"> <div class="board-list--query-container">
<wp-embedded-table *ngIf="boardTableConfiguration.isEmbedded && !boardTableConfiguration.isCardView" <wp-card-view [loadedQuery]="query"
[queryId]="query.id"
[loadedQuery]="query"
[queryProps]="columnsQueryProps"
[configuration]="boardTableConfiguration">
</wp-embedded-table>
<wp-card-view *ngIf="boardTableConfiguration.isCardView"
[loadedQuery]="query"
[queryId]="query.id" [queryId]="query.id"
[queryProps]="columnsQueryProps" [queryProps]="columnsQueryProps"
[configuration]="boardTableConfiguration"> [configuration]="boardTableConfiguration"
[highlightingMode]="board.highlightingMode">
</wp-card-view> </wp-card-view>
</div> </div>
</ng-container> </ng-container>

@ -1,5 +1,6 @@
import {GridWidgetResource} from "core-app/modules/hal/resources/grid-widget-resource"; import {GridWidgetResource} from "core-app/modules/hal/resources/grid-widget-resource";
import {GridResource} from "core-app/modules/hal/resources/grid-resource"; import {GridResource} from "core-app/modules/hal/resources/grid-resource";
import {CardHighlightingMode} from "core-components/wp-fast-table/builders/highlighting/highlighting-mode.const";
export type BoardDisplayMode = 'table'|'cards'; export type BoardDisplayMode = 'table'|'cards';
export type BoardType = 'free'|'action'; export type BoardType = 'free'|'action';
@ -37,12 +38,15 @@ export class Board {
} }
public get displayMode():BoardDisplayMode { public get displayMode():BoardDisplayMode {
const mode = this.grid.options.display_mode; return 'cards';
return (mode === 'table') ? 'table' : 'cards';
} }
public set displayMode(value:BoardDisplayMode) { public set highlightingMode(val:CardHighlightingMode) {
this.grid.options.display_mode = value; this.grid.options.highlightingMode = val;
}
public get highlightingMode():CardHighlightingMode {
return (this.grid.options.highlightingMode || 'none') as CardHighlightingMode;
} }
public set name(name:string) { public set name(name:string) {

@ -1,16 +1,16 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; import {I18nService} from 'core-app/modules/common/i18n/i18n.service';
import {TabInterface} from "core-components/wp-table/configuration-modal/tab-portal-outlet"; import {TabInterface} from "core-components/wp-table/configuration-modal/tab-portal-outlet";
import {BoardConfigurationDisplaySettingsTab} from "core-app/modules/boards/board/configuration-modal/tabs/display-settings-tab.component"; import {BoardHighlightingTabComponent} from "core-app/modules/boards/board/configuration-modal/tabs/highlighting-tab.component";
@Injectable() @Injectable()
export class BoardConfigurationService { export class BoardConfigurationService {
protected _tabs:TabInterface[] = [ protected _tabs:TabInterface[] = [
{ {
name: 'display', name: 'highlighting',
title: this.I18n.t('js.work_packages.table_configuration.display_settings'), title: this.I18n.t('js.work_packages.table_configuration.highlighting'),
componentClass: BoardConfigurationDisplaySettingsTab, componentClass: BoardHighlightingTabComponent,
} }
]; ];

@ -1,31 +0,0 @@
<div>
<form>
<p [textContent]="text.choose_mode"></p>
<div class="form--field -full-width">
<div class="form--field-container">
<label class="option-label">
<input type="radio"
[(ngModel)]="displayMode"
value="cards"
name="display_mode_switch">
<op-icon icon-classes="icon-projects" [attr.icon-title]="text.card_mode"></op-icon>
&ngsp;
<span [textContent]="text.card_mode"></span>
</label>
</div>
</div>
<div class="form--field -full-width">
<div class="form--field-container">
<label class="option-label">
<input type="radio"
[(ngModel)]="displayMode"
value="table"
name="display_mode_switch">
<op-icon icon-classes="icon-view-list" [attr.icon-title]="text.table_mode"></op-icon>
&ngsp;
<span [textContent]="text.table_mode"></span>
</label>
</div>
</div>
</form>
</div>

@ -1,39 +0,0 @@
import {Component, Inject, Injector} from '@angular/core';
import {I18nService} from 'core-app/modules/common/i18n/i18n.service';
import {TabComponent} from 'core-components/wp-table/configuration-modal/tab-portal-outlet';
import {OpModalLocalsMap} from "core-components/op-modals/op-modal.types";
import {Board, BoardDisplayMode} from "core-app/modules/boards/board/board";
import {OpModalLocalsToken} from "core-components/op-modals/op-modal.service";
@Component({
templateUrl: './display-settings-tab.component.html'
})
export class BoardConfigurationDisplaySettingsTab implements TabComponent {
// Current board resource
public board:Board;
// Display mode
public displayMode:BoardDisplayMode = 'cards';
public text = {
choose_mode: this.I18n.t('js.work_packages.table_configuration.choose_display_mode'),
card_mode: this.I18n.t('js.boards.configuration_modal.display_settings.card_mode'),
table_mode: this.I18n.t('js.boards.configuration_modal.display_settings.table_mode'),
};
constructor(readonly injector:Injector,
@Inject(OpModalLocalsToken) public locals:OpModalLocalsMap,
readonly I18n:I18nService) {
}
public onSave() {
this.board.displayMode = this.displayMode;
}
ngOnInit() {
this.board = this.locals.board;
this.displayMode = this.board.displayMode;
}
}

@ -0,0 +1,60 @@
<div>
<form>
<p [textContent]="text.highlighting_mode.description"></p>
<div class="form--field -full-width">
<div class="form--field-container">
<label class="option-label">
<input type="radio"
[(ngModel)]="highlightingMode"
(change)="updateMode($event.target.value)"
value="inline"
name="highlighting_mode_switch">
{{ text.highlighting_mode.inline }}
{{ text.highlighting_mode.type }}
</label>
</div>
</div>
<div class="form--field -full-width">
<div class="form--field-container">
<label class="option-label">
<input type="radio"
[(ngModel)]="entireCardMode"
(change)="updateMode('entire-card')"
[value]="true"
name="entire_card_switch">
<span [textContent]="text.highlighting_mode.entire_card_by"></span>
&ngsp;
<select (change)="updateMode($event.target.value)"
id="selected_attribute"
name="selected_attribute"
class="form--select form--inline-select">
<option [textContent]="text.highlighting_mode.status"
[selected]="lastEntireCardAttribute === 'status'"
value="status"></option>
<option [textContent]="text.highlighting_mode.type"
[selected]="lastEntireCardAttribute === 'type'"
value="type"></option>
<option [textContent]="text.highlighting_mode.priority"
[selected]="lastEntireCardAttribute === 'priority'"
value="priority"></option>
</select>
</label>
</div>
</div>
<div class="form--field -full-width">
<div class="form--field-container">
<label class="option-label">
<input type="radio"
[(ngModel)]="highlightingMode"
(change)="updateMode($event.target.value)"
value="none"
name="highlighting_mode_switch">
<span [textContent]="text.highlighting_mode.none"></span>
</label>
</div>
</div>
</form>
</div>

@ -0,0 +1,70 @@
import {Component, Inject, Injector} from '@angular/core';
import {I18nService} from 'core-app/modules/common/i18n/i18n.service';
import {TabComponent} from 'core-components/wp-table/configuration-modal/tab-portal-outlet';
import {OpModalLocalsMap} from "core-components/op-modals/op-modal.types";
import {Board} from "core-app/modules/boards/board/board";
import {OpModalLocalsToken} from "core-components/op-modals/op-modal.service";
import {
CardHighlightingMode,
HighlightingMode
} from "core-components/wp-fast-table/builders/highlighting/highlighting-mode.const";
import {WorkPackageTableHighlightingService} from "core-components/wp-fast-table/state/wp-table-highlighting.service";
import {HalResource} from "core-app/modules/hal/resources/hal-resource";
@Component({
templateUrl: './highlighting-tab.component.html'
})
export class BoardHighlightingTabComponent implements TabComponent {
// Highlighting mode
public highlightingMode:CardHighlightingMode = 'inline';
public entireCardMode:boolean = false;
public lastEntireCardAttribute:CardHighlightingMode = 'status';
// Current board resource
public board:Board;
public text = {
highlighting_mode: {
description: this.I18n.t('js.work_packages.table_configuration.highlighting_mode.description'),
none: this.I18n.t('js.work_packages.table_configuration.highlighting_mode.none'),
inline: this.I18n.t('js.card.highlighting.inline'),
status: this.I18n.t('js.work_packages.table_configuration.highlighting_mode.status'),
type: this.I18n.t('js.work_packages.properties.type'),
priority: this.I18n.t('js.work_packages.table_configuration.highlighting_mode.priority'),
entire_card_by: this.I18n.t('js.card.highlighting.entire_card_by'),
}
};
constructor(readonly injector:Injector,
@Inject(OpModalLocalsToken) public locals:OpModalLocalsMap,
readonly I18n:I18nService) {
}
public onSave() {
this.updateMode(this.highlightingMode);
this.board.highlightingMode = this.highlightingMode;
}
ngOnInit() {
this.board = this.locals.board;
this.highlightingMode = this.board.highlightingMode;
this.updateMode(this.highlightingMode);
}
public updateMode(mode:CardHighlightingMode) {
if (mode === 'entire-card') {
this.highlightingMode = this.lastEntireCardAttribute;
} else {
this.highlightingMode = mode;
}
if (['status', 'priority', 'type'].indexOf(this.highlightingMode) !== -1) {
this.lastEntireCardAttribute = this.highlightingMode;
this.entireCardMode = true;
} else {
this.entireCardMode = false;
}
}
}

@ -37,7 +37,6 @@ import {BoardListsService} from "core-app/modules/boards/board/board-list/board-
import {BoardService} from "core-app/modules/boards/board/board.service"; import {BoardService} from "core-app/modules/boards/board/board.service";
import {BoardInlineAddAutocompleterComponent} from "core-app/modules/boards/board/inline-add/board-inline-add-autocompleter.component"; import {BoardInlineAddAutocompleterComponent} from "core-app/modules/boards/board/inline-add/board-inline-add-autocompleter.component";
import {BoardCacheService} from "core-app/modules/boards/board/board-cache.service"; import {BoardCacheService} from "core-app/modules/boards/board/board-cache.service";
import {BoardConfigurationDisplaySettingsTab} from "core-app/modules/boards/board/configuration-modal/tabs/display-settings-tab.component";
import {BoardsToolbarMenuDirective} from "core-app/modules/boards/board/toolbar-menu/boards-toolbar-menu.directive"; import {BoardsToolbarMenuDirective} from "core-app/modules/boards/board/toolbar-menu/boards-toolbar-menu.directive";
import {BoardConfigurationService} from "core-app/modules/boards/board/configuration-modal/board-configuration.service"; import {BoardConfigurationService} from "core-app/modules/boards/board/configuration-modal/board-configuration.service";
import {BoardConfigurationModal} from "core-app/modules/boards/board/configuration-modal/board-configuration.modal"; import {BoardConfigurationModal} from "core-app/modules/boards/board/configuration-modal/board-configuration.modal";
@ -48,6 +47,7 @@ import {NewBoardModalComponent} from "core-app/modules/boards/new-board-modal/ne
import {BoardStatusActionService} from "core-app/modules/boards/board/board-actions/status-action.service"; import {BoardStatusActionService} from "core-app/modules/boards/board/board-actions/status-action.service";
import {BoardActionsRegistryService} from "core-app/modules/boards/board/board-actions/board-actions-registry.service"; import {BoardActionsRegistryService} from "core-app/modules/boards/board/board-actions/board-actions-registry.service";
import {AddListModalComponent} from "core-app/modules/boards/board/add-list-modal/add-list-modal.component"; import {AddListModalComponent} from "core-app/modules/boards/board/add-list-modal/add-list-modal.component";
import {BoardHighlightingTabComponent} from "core-app/modules/boards/board/configuration-modal/tabs/highlighting-tab.component";
export const BOARDS_ROUTES:Ng2StateDeclaration[] = [ export const BOARDS_ROUTES:Ng2StateDeclaration[] = [
{ {
@ -117,7 +117,7 @@ export function registerActionServices(injector:Injector) {
BoardsRootComponent, BoardsRootComponent,
BoardInlineAddAutocompleterComponent, BoardInlineAddAutocompleterComponent,
BoardsMenuComponent, BoardsMenuComponent,
BoardConfigurationDisplaySettingsTab, BoardHighlightingTabComponent,
BoardConfigurationModal, BoardConfigurationModal,
BoardsToolbarMenuDirective, BoardsToolbarMenuDirective,
NewBoardModalComponent, NewBoardModalComponent,
@ -127,7 +127,7 @@ export function registerActionServices(injector:Injector) {
BoardInlineAddAutocompleterComponent, BoardInlineAddAutocompleterComponent,
BoardsMenuComponent, BoardsMenuComponent,
BoardConfigurationModal, BoardConfigurationModal,
BoardConfigurationDisplaySettingsTab, BoardHighlightingTabComponent,
NewBoardModalComponent, NewBoardModalComponent,
AddListModalComponent, AddListModalComponent,
] ]

@ -0,0 +1,104 @@
#-- 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!(:wp) do
FactoryBot.create(:work_package,
project: project,
type: type,
priority: priority,
status: open_status)
end
let!(:wp2) do
FactoryBot.create(:work_package,
project: project,
type: type2,
priority: priority2,
status: open_status)
end
let!(:priority) { FactoryBot.create :priority, color: color }
let!(:priority2) { FactoryBot.create :priority, color: color2 }
let!(:type) { FactoryBot.create :type, color: color }
let!(:type2) { FactoryBot.create :type, color: color2 }
let!(:open_status) { FactoryBot.create :default_status, name: 'Open' }
let(:board_index) { Pages::BoardIndex.new(project) }
let(:color) { FactoryBot.create :color }
let(:color2) { FactoryBot.create :color }
before do
project
login_as(user)
end
it 'navigates from boards to the WP full view and back' do
board_index.visit!
board_page = board_index.create_board action: :Status
# See the work packages
board_page.expect_query 'Open', editable: true
board_page.expect_card 'Open', wp.subject
board_page.expect_card 'Open', wp2.subject
# Highlight inline
board_page.change_board_highlighting 'inline'
expect(page).to have_selector('.__hl_dot_type_' + type.id.to_s)
expect(page).to have_selector('.__hl_dot_type_' + type2.id.to_s)
# Highlight whole card by type
board_page.change_board_highlighting 'entire-card', 'Type'
expect(page).to have_selector('.__hl_row_type_' + type.id.to_s)
expect(page).to have_selector('.__hl_row_type_' + type2.id.to_s)
# Highlight whole card by priority
board_page.change_board_highlighting 'entire-card', 'Priority'
expect(page).to have_selector('.__hl_row_priority_' + priority.id.to_s)
expect(page).to have_selector('.__hl_row_priority_' + priority2.id.to_s)
# Disable highlighting
board_page.change_board_highlighting 'none'
expect(page).not_to have_selector('.__hl_row_priority_' + priority.id.to_s)
expect(page).not_to have_selector('.__hl_row_priority_' + priority2.id.to_s)
end
end

@ -56,10 +56,6 @@ module Pages
@board.options.attribute @board.options.attribute
end end
def card_view?
board.options['display_mode'] == 'cards'
end
def list_count def list_count
page.all('.board-list--container').count page.all('.board-list--container').count
end end
@ -228,5 +224,17 @@ module Pages
expect(page).to have_selector('.editable-toolbar-title--fixed', text: name) expect(page).to have_selector('.editable-toolbar-title--fixed', text: name)
end end
end end
def change_board_highlighting(mode, attribute = nil)
click_dropdown_entry 'Configure view'
if attribute.nil?
choose(option: mode)
else
select attribute, from: 'selected_attribute'
end
click_button 'Apply'
end
end end
end end

Loading…
Cancel
Save