[26006][25518] Replace details icon with context menu, open on first click (#5835)

* [26006] Replace details icon with context menu

* [25518] Open details view on click in table

* Show dropdown icons always for mobile

* Move context menu column to the right

* Add three icon with three horizontal dots

* Change context menu icon for three horizontal dots
pull/5847/head
Oliver Günther 7 years ago committed by Wieland Lindenthal
parent 77460e0146
commit 7878734e6b
  1. 577
      app/assets/fonts/openproject_icon/openproject-icon-font.svg
  2. BIN
      app/assets/fonts/openproject_icon/openproject-icon-font.ttf
  3. BIN
      app/assets/fonts/openproject_icon/openproject-icon-font.woff
  4. BIN
      app/assets/fonts/openproject_icon/openproject-icon-font.woff2
  5. 12
      app/assets/fonts/openproject_icon/src/show-more-horizontal.svg
  6. 42
      app/assets/stylesheets/content/work_packages/_table_content.sass
  7. 198
      app/assets/stylesheets/fonts/_openproject_icon_definitions.scss
  8. 1
      app/assets/stylesheets/fonts/_openproject_icon_font.lsg
  9. 2
      app/assets/stylesheets/layout/_print.sass
  10. 14
      frontend/app/components/routing/wp-details/wp-details.controller.ts
  11. 35
      frontend/app/components/wp-fast-table/builders/context-link-icon-builder.ts
  12. 43
      frontend/app/components/wp-fast-table/builders/details-link-builder.ts
  13. 24
      frontend/app/components/wp-fast-table/builders/rows/single-row-builder.ts
  14. 43
      frontend/app/components/wp-fast-table/handlers/context-menu/context-menu-click-handler.ts
  15. 44
      frontend/app/components/wp-fast-table/handlers/context-menu/context-menu-handler.ts
  16. 23
      frontend/app/components/wp-fast-table/handlers/context-menu/context-menu-keyboard-handler.ts
  17. 21
      frontend/app/components/wp-fast-table/handlers/context-menu/context-menu-rightclick-handler.ts
  18. 12
      frontend/app/components/wp-fast-table/handlers/row/click-handler.ts
  19. 9
      frontend/app/components/wp-fast-table/handlers/table-handler-registry.ts
  20. 4
      frontend/app/components/wp-inline-create/inline-create-row-builder.ts
  21. 2
      frontend/app/components/wp-table/wp-table.directive.html
  22. 4
      frontend/app/globals/browser-specific-flags.ts
  23. 2
      spec/features/work_packages/select_work_package_row_spec.rb
  24. 2
      spec/features/work_packages/table/switch_types_spec.rb
  25. 2
      spec/features/work_packages/tabs/keep_tab_spec.rb
  26. 2
      spec/features/work_packages/timeline/timeline_navigation_spec.rb
  27. 11
      spec/support/pages/work_packages_table.rb

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 387 KiB

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="595.279px" height="841.891px" viewBox="0 0 595.279 841.891" enable-background="new 0 0 595.279 841.891"
xml:space="preserve">
<path d="M109.29,356.632c40.693,0,74.41,32.554,74.41,74.41c0,40.692-33.717,74.409-74.41,74.409
c-40.693,0-74.41-33.717-74.41-74.409C34.88,389.186,68.597,356.632,109.29,356.632z M302.291,356.632
c41.855,0,74.41,32.554,74.41,74.41c0,40.692-32.555,74.409-74.41,74.409c-40.693,0-74.41-33.717-74.41-74.409
C227.882,389.186,261.598,356.632,302.291,356.632z M499.943,356.632c40.693,0,74.41,32.554,74.41,74.41
c0,40.692-33.717,74.409-74.41,74.409c-40.692,0-74.409-33.717-74.409-74.409C425.533,389.186,459.25,356.632,499.943,356.632z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -30,8 +30,12 @@
.wp--row
user-select: none
.wp-table--row.-collapsed
display: none
.wp-table--row
cursor: pointer
// Hide collapsed rows (hierarchies, relations)
&.-collapsed
display: none
.work-package-table--container table.generic-table tbody td
padding-left: 0
@ -54,32 +58,35 @@
// Shrink column of details / inline-create icons
.wp-table--details-column
// Set a minimum width for the cell to avoid movement
min-width: 40px
.wp-table--context-menu-column
width: 40px
// Center the th icon
text-align: center !important
// Take care that the header can overlap the icon
z-index: 1
padding: 0 !important
// Details link column width
.work-package-table
col:last-of-type
width: 40px
// Hide the context menu button outside mobile
html:not(.-browser-mobile)
.wp-table-context-menu-link
@extend .hidden-for-sighted
// Show details view button when hovering
.wp-table--details-link
.issue:hover &,
&:focus
.issue:hover .wp-table-context-menu-link,
.wp-table-context-menu-link:focus
// override the hidden-for-sighted definition
position: relative
left: 0
left: -5px
width: initial
height: initial
.icon:before
color: $body-font-color
padding: 0 0 0 0.25rem
// Show context menu button when hovering
.wp-table-context-menu-link
font-size: 1.2rem
vertical-align: -3px
.icon:before
@include varprop(color, content-link-color)
padding: 0 0 0 0.25rem
&:hover
text-decoration: none
@ -116,4 +123,3 @@ table.generic-table tbody tr.issue .checkbox
.wp-table--cell-td.startDate,
.wp-table--cell-td.dueDate
width: 160px

@ -1138,291 +1138,297 @@
.icon-show-all-projects:before {
content: "\f1bd";
}
@mixin icon-mixin-show-more {
@mixin icon-mixin-show-more-horizontal {
content: "\f1be";
}
.icon-show-more:before {
.icon-show-more-horizontal:before {
content: "\f1be";
}
@mixin icon-mixin-sort-ascending {
@mixin icon-mixin-show-more {
content: "\f1bf";
}
.icon-sort-ascending:before {
.icon-show-more:before {
content: "\f1bf";
}
@mixin icon-mixin-sort-by {
@mixin icon-mixin-sort-ascending {
content: "\f1c0";
}
.icon-sort-by:before {
.icon-sort-ascending:before {
content: "\f1c0";
}
@mixin icon-mixin-sort-descending {
@mixin icon-mixin-sort-by {
content: "\f1c1";
}
.icon-sort-descending:before {
.icon-sort-by:before {
content: "\f1c1";
}
@mixin icon-mixin-sort-down {
@mixin icon-mixin-sort-descending {
content: "\f1c2";
}
.icon-sort-down:before {
.icon-sort-descending:before {
content: "\f1c2";
}
@mixin icon-mixin-sort-up {
@mixin icon-mixin-sort-down {
content: "\f1c3";
}
.icon-sort-up:before {
.icon-sort-down:before {
content: "\f1c3";
}
@mixin icon-mixin-square {
@mixin icon-mixin-sort-up {
content: "\f1c4";
}
.icon-square:before {
.icon-sort-up:before {
content: "\f1c4";
}
@mixin icon-mixin-star {
@mixin icon-mixin-square {
content: "\f1c5";
}
.icon-star:before {
.icon-square:before {
content: "\f1c5";
}
@mixin icon-mixin-status-reporting {
@mixin icon-mixin-star {
content: "\f1c6";
}
.icon-status-reporting:before {
.icon-star:before {
content: "\f1c6";
}
@mixin icon-mixin-status {
@mixin icon-mixin-status-reporting {
content: "\f1c7";
}
.icon-status:before {
.icon-status-reporting:before {
content: "\f1c7";
}
@mixin icon-mixin-strike-through {
@mixin icon-mixin-status {
content: "\f1c8";
}
.icon-strike-through:before {
.icon-status:before {
content: "\f1c8";
}
@mixin icon-mixin-text {
@mixin icon-mixin-strike-through {
content: "\f1c9";
}
.icon-text:before {
.icon-strike-through:before {
content: "\f1c9";
}
@mixin icon-mixin-ticket-checked {
@mixin icon-mixin-text {
content: "\f1ca";
}
.icon-ticket-checked:before {
.icon-text:before {
content: "\f1ca";
}
@mixin icon-mixin-ticket-down {
@mixin icon-mixin-ticket-checked {
content: "\f1cb";
}
.icon-ticket-down:before {
.icon-ticket-checked:before {
content: "\f1cb";
}
@mixin icon-mixin-ticket-edit {
@mixin icon-mixin-ticket-down {
content: "\f1cc";
}
.icon-ticket-edit:before {
.icon-ticket-down:before {
content: "\f1cc";
}
@mixin icon-mixin-ticket-minus {
@mixin icon-mixin-ticket-edit {
content: "\f1cd";
}
.icon-ticket-minus:before {
.icon-ticket-edit:before {
content: "\f1cd";
}
@mixin icon-mixin-ticket-note {
@mixin icon-mixin-ticket-minus {
content: "\f1ce";
}
.icon-ticket-note:before {
.icon-ticket-minus:before {
content: "\f1ce";
}
@mixin icon-mixin-ticket {
@mixin icon-mixin-ticket-note {
content: "\f1cf";
}
.icon-ticket:before {
.icon-ticket-note:before {
content: "\f1cf";
}
@mixin icon-mixin-time {
@mixin icon-mixin-ticket {
content: "\f1d0";
}
.icon-time:before {
.icon-ticket:before {
content: "\f1d0";
}
@mixin icon-mixin-to-fullscreen {
@mixin icon-mixin-time {
content: "\f1d1";
}
.icon-to-fullscreen:before {
.icon-time:before {
content: "\f1d1";
}
@mixin icon-mixin-toggle {
@mixin icon-mixin-to-fullscreen {
content: "\f1d2";
}
.icon-toggle:before {
.icon-to-fullscreen:before {
content: "\f1d2";
}
@mixin icon-mixin-training-consulting {
@mixin icon-mixin-toggle {
content: "\f1d3";
}
.icon-training-consulting:before {
.icon-toggle:before {
content: "\f1d3";
}
@mixin icon-mixin-types {
@mixin icon-mixin-training-consulting {
content: "\f1d4";
}
.icon-types:before {
.icon-training-consulting:before {
content: "\f1d4";
}
@mixin icon-mixin-underline {
@mixin icon-mixin-types {
content: "\f1d5";
}
.icon-underline:before {
.icon-types:before {
content: "\f1d5";
}
@mixin icon-mixin-undo {
@mixin icon-mixin-underline {
content: "\f1d6";
}
.icon-undo:before {
.icon-underline:before {
content: "\f1d6";
}
@mixin icon-mixin-unit {
@mixin icon-mixin-undo {
content: "\f1d7";
}
.icon-unit:before {
.icon-undo:before {
content: "\f1d7";
}
@mixin icon-mixin-unlocked {
@mixin icon-mixin-unit {
content: "\f1d8";
}
.icon-unlocked:before {
.icon-unit:before {
content: "\f1d8";
}
@mixin icon-mixin-unordered-list {
@mixin icon-mixin-unlocked {
content: "\f1d9";
}
.icon-unordered-list:before {
.icon-unlocked:before {
content: "\f1d9";
}
@mixin icon-mixin-unwatched {
@mixin icon-mixin-unordered-list {
content: "\f1da";
}
.icon-unwatched:before {
.icon-unordered-list:before {
content: "\f1da";
}
@mixin icon-mixin-upload {
@mixin icon-mixin-unwatched {
content: "\f1db";
}
.icon-upload:before {
.icon-unwatched:before {
content: "\f1db";
}
@mixin icon-mixin-user-minus {
@mixin icon-mixin-upload {
content: "\f1dc";
}
.icon-user-minus:before {
.icon-upload:before {
content: "\f1dc";
}
@mixin icon-mixin-user-plus {
@mixin icon-mixin-user-minus {
content: "\f1dd";
}
.icon-user-plus:before {
.icon-user-minus:before {
content: "\f1dd";
}
@mixin icon-mixin-user {
@mixin icon-mixin-user-plus {
content: "\f1de";
}
.icon-user:before {
.icon-user-plus:before {
content: "\f1de";
}
@mixin icon-mixin-view-fullscreen {
@mixin icon-mixin-user {
content: "\f1df";
}
.icon-view-fullscreen:before {
.icon-user:before {
content: "\f1df";
}
@mixin icon-mixin-view-list {
@mixin icon-mixin-view-fullscreen {
content: "\f1e0";
}
.icon-view-list:before {
.icon-view-fullscreen:before {
content: "\f1e0";
}
@mixin icon-mixin-view-split {
@mixin icon-mixin-view-list {
content: "\f1e1";
}
.icon-view-split:before {
.icon-view-list:before {
content: "\f1e1";
}
@mixin icon-mixin-view-timeline {
@mixin icon-mixin-view-split {
content: "\f1e2";
}
.icon-view-timeline:before {
.icon-view-split:before {
content: "\f1e2";
}
@mixin icon-mixin-warning {
@mixin icon-mixin-view-timeline {
content: "\f1e3";
}
.icon-warning:before {
.icon-view-timeline:before {
content: "\f1e3";
}
@mixin icon-mixin-watched {
@mixin icon-mixin-warning {
content: "\f1e4";
}
.icon-watched:before {
.icon-warning:before {
content: "\f1e4";
}
@mixin icon-mixin-wiki-edit {
@mixin icon-mixin-watched {
content: "\f1e5";
}
.icon-wiki-edit:before {
.icon-watched:before {
content: "\f1e5";
}
@mixin icon-mixin-wiki {
@mixin icon-mixin-wiki-edit {
content: "\f1e6";
}
.icon-wiki:before {
.icon-wiki-edit:before {
content: "\f1e6";
}
@mixin icon-mixin-wiki2 {
@mixin icon-mixin-wiki {
content: "\f1e7";
}
.icon-wiki2:before {
.icon-wiki:before {
content: "\f1e7";
}
@mixin icon-mixin-work-packages {
@mixin icon-mixin-wiki2 {
content: "\f1e8";
}
.icon-work-packages:before {
.icon-wiki2:before {
content: "\f1e8";
}
@mixin icon-mixin-workflow {
@mixin icon-mixin-work-packages {
content: "\f1e9";
}
.icon-workflow:before {
.icon-work-packages:before {
content: "\f1e9";
}
@mixin icon-mixin-yes {
@mixin icon-mixin-workflow {
content: "\f1ea";
}
.icon-yes:before {
.icon-workflow:before {
content: "\f1ea";
}
@mixin icon-mixin-zen-mode {
@mixin icon-mixin-yes {
content: "\f1eb";
}
.icon-zen-mode:before {
.icon-yes:before {
content: "\f1eb";
}
@mixin icon-mixin-zoom-in {
@mixin icon-mixin-zen-mode {
content: "\f1ec";
}
.icon-zoom-in:before {
.icon-zen-mode:before {
content: "\f1ec";
}
@mixin icon-mixin-zoom-out {
@mixin icon-mixin-zoom-in {
content: "\f1ed";
}
.icon-zoom-out:before {
.icon-zoom-in:before {
content: "\f1ed";
}
@mixin icon-mixin-zoom-out {
content: "\f1ee";
}
.icon-zoom-out:before {
content: "\f1ee";
}

@ -192,6 +192,7 @@
<li><span class="icon icon-settings4"></span>settings4</li>
<li><span class="icon icon-shortcuts"></span>shortcuts</li>
<li><span class="icon icon-show-all-projects"></span>show-all-projects</li>
<li><span class="icon icon-show-more-horizontal"></span>show-more-horizontal</li>
<li><span class="icon icon-show-more"></span>show-more</li>
<li><span class="icon icon-sort-ascending"></span>sort-ascending</li>
<li><span class="icon icon-sort-by"></span>sort-by</li>

@ -120,7 +120,7 @@
tr.wp-table--row
page-break-inside: avoid
.wp-table--details-column
.wp-table--context-menu-column
display: none // remove column for info icon.

@ -54,20 +54,6 @@ export class WorkPackageDetailsController extends WorkPackageViewController {
} else if (!this.wpTableSelection.isSelected(wpId)) {
this.wpTableSelection.setRowState(wpId, true);
}
scopedObservable(
$scope,
this.states.focusedWorkPackage.values$())
.map(wpId => wpId.toString())
.distinctUntilChanged()
.subscribe((newId) => {
if (wpId !== newId && $state.includes('work-packages.list.details')) {
$state.go(
($state.current.name as string),
{workPackageId: newId, focus: false }
);
}
});
}
public close() {

@ -0,0 +1,35 @@
import {$injectFields} from '../../angular/angular-injector-bridge.functions';
import {opIconElement} from "../../../helpers/op-icon-builder";
import {wpCellTdClassName} from "./cell-builder";
export const contextMenuTdClassName = 'wp-table--context-menu-column';
export const contextMenuLinkClassName = 'wp-table-context-menu-link';
export class ContextLinkIconBuilder {
// Injections
public I18n: op.I18n;
public text: any;
constructor() {
$injectFields(this, 'I18n');
this.text = {
button: this.I18n.t('js.button_open_details')
};
}
public build(): HTMLElement {
// Append details button
let td = document.createElement('td');
td.classList.add(wpCellTdClassName, contextMenuTdClassName, 'hide-when-print');
// Enter the context menu arrow
let detailsLink = document.createElement('a');
detailsLink.classList.add(contextMenuLinkClassName);
detailsLink.appendChild(opIconElement('icon', 'icon-show-more-horizontal'));
td.appendChild(detailsLink);
return td;
}
}

@ -1,43 +0,0 @@
import {WorkPackageResource} from './../../api/api-v3/hal-resources/work-package-resource.service';
import {injectorBridge} from '../../angular/angular-injector-bridge.functions';
import {UiStateLinkBuilder} from './ui-state-link-builder';
import {opIconElement} from "../../../helpers/op-icon-builder";
export const detailsLinkTdClass = 'wp-table--details-column';
export const detailsLinkClassName = 'wp-table--details-link';
export class DetailsLinkBuilder {
// Injections
public I18n: op.I18n;
public text: any;
private uiStatebuilder: UiStateLinkBuilder;
constructor() {
injectorBridge(this);
this.text = {
button: this.I18n.t('js.button_open_details')
};
this.uiStatebuilder = new UiStateLinkBuilder();
}
public build(workPackage: WorkPackageResource): HTMLElement {
// Append details button
let td = document.createElement('td');
td.classList.add(detailsLinkTdClass, 'hide-when-print');
let detailsLink = this.uiStatebuilder.linkToDetails(
workPackage.id,
this.text.button,
''
);
detailsLink.classList.add(detailsLinkClassName, 'hidden-for-sighted');
detailsLink.appendChild(opIconElement('icon', 'icon-info2'));
td.appendChild(detailsLink);
return td;
}
}
DetailsLinkBuilder.$inject = ['I18n'];

@ -1,26 +1,22 @@
import {WorkPackageTableSelection} from '../../state/wp-table-selection.service';
import {CellBuilder, wpCellTdClassName} from '../cell-builder';
import {DetailsLinkBuilder} from '../details-link-builder';
import {$injectFields} from '../../../angular/angular-injector-bridge.functions';
import {
WorkPackageResource,
WorkPackageResourceInterface
} from '../../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResourceInterface} from '../../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageTableColumnsService} from '../../state/wp-table-columns.service';
import {checkedClassName} from '../ui-state-link-builder';
import {WorkPackageTable} from '../../wp-fast-table';
import {isRelationColumn, QueryColumn} from '../../../wp-query/query-column';
import {RelationCellbuilder} from '../relation-cell-builder';
import {WorkPackageEditForm} from '../../../wp-edit-form/work-package-edit-form';
import {WorkPackageChangeset} from '../../../wp-edit-form/work-package-changeset';
import {ContextLinkIconBuilder} from "../context-link-icon-builder";
import {$injectFields} from "../../../angular/angular-injector-bridge.functions";
// Work package table row entries
export const tableRowClassName = 'wp-table--row';
// Work package and timeline rows
export const commonRowClassName = 'wp--row';
export const internalDetailsColumn = {
id: '__internal-detailsLink'
export const internalContextMenuColumn = {
id: '__internal-contextMenu'
} as QueryColumn;
export class SingleRowBuilder {
@ -35,7 +31,7 @@ export class SingleRowBuilder {
protected relationCellBuilder = new RelationCellbuilder();
// Details Link builder
protected detailsLinkBuilder = new DetailsLinkBuilder();
protected contextLinkBuilder = new ContextLinkIconBuilder();
constructor(protected workPackageTable:WorkPackageTable) {
$injectFields(this, 'wpTableSelection', 'wpTableColumns', 'I18n');
@ -53,7 +49,7 @@ export class SingleRowBuilder {
* we add for buttons and timeline.
*/
public get augmentedColumns():QueryColumn[] {
return this.columns.concat(internalDetailsColumn);
return this.columns.concat([internalContextMenuColumn]);
}
public buildCell(workPackage:WorkPackageResourceInterface, column:QueryColumn):HTMLElement {
@ -65,8 +61,8 @@ export class SingleRowBuilder {
// Handle property types
switch (column.id) {
case internalDetailsColumn.id:
return this.detailsLinkBuilder.build(workPackage);
case internalContextMenuColumn.id:
return this.contextLinkBuilder.build();
default:
return this.cellBuilder.build(workPackage, column.id);
}
@ -115,7 +111,7 @@ export class SingleRowBuilder {
// Remember the order of all new edit cells
const newCells:HTMLElement[] = [];
this.columns.forEach((column:QueryColumn) => {
this.augmentedColumns.forEach((column:QueryColumn) => {
const oldTd = cells.filter(`td.${column.id}`);
// Skip the replacement of the column if this is being edited.

@ -0,0 +1,43 @@
import {debugLog} from "../../../../helpers/debug_output";
import {WorkPackageTable} from "../../wp-fast-table";
import {uiStateLinkClass} from "../../builders/ui-state-link-builder";
import {ContextMenuHandler} from "./context-menu-handler";
import {contextMenuLinkClassName} from "../../builders/context-link-icon-builder";
export class ContextMenuClickHandler extends ContextMenuHandler {
constructor(table: WorkPackageTable) {
super(table);
}
public get EVENT() {
return 'click.table.contextmenu';
}
public get SELECTOR() {
return `.${contextMenuLinkClassName}`;
}
public handleEvent(table: WorkPackageTable, evt:JQueryEventObject):boolean {
let target = jQuery(evt.target);
// We want to keep the original context menu on hrefs
// (currently, this is only the id
if (target.closest(`.${uiStateLinkClass}`).length) {
debugLog('Allowing original context menu on state link');
return true;
}
evt.preventDefault();
evt.stopPropagation();
// Locate the row from event
const element = target.closest(this.rowSelector);
const wpId = element.data('workPackageId');
if (wpId) {
super.openContextMenu(evt, wpId);
}
return false;
}
}

@ -0,0 +1,44 @@
import {debugLog} from "../../../../helpers/debug_output";
import {$injectFields, injectorBridge} from "../../../angular/angular-injector-bridge.functions";
import {WorkPackageTable} from "../../wp-fast-table";
import {TableEventHandler} from "../table-handler-registry";
import {tableRowClassName} from "../../builders/rows/single-row-builder";
import {uiStateLinkClass} from "../../builders/ui-state-link-builder";
import {ContextMenuService} from "../../../context-menus/context-menu.service";
import {timelineCellClassName} from "../../builders/timeline/timeline-row-builder";
export abstract class ContextMenuHandler implements TableEventHandler {
// Injections
public contextMenu:ContextMenuService;
constructor(protected table: WorkPackageTable) {
$injectFields(this, 'contextMenu');
}
public get rowSelector() {
return `.${tableRowClassName}`;
}
public abstract get EVENT():string;
public abstract get SELECTOR():string;
public eventScope(table:WorkPackageTable) {
return jQuery(table.container);
}
public abstract handleEvent(table: WorkPackageTable, evt:JQueryEventObject):boolean;
protected openContextMenu(evt:JQueryEventObject, workPackageId:string, positionArgs?:any):void {
let [index,] = this.table.findRenderedRow(workPackageId);
this.contextMenu.activate(
'WorkPackageContextMenu',
evt,
{
workPackageId: workPackageId,
rowIndex: index,
table: this.table
}
);
}
}

@ -1,16 +1,13 @@
import {injectorBridge} from "../../../angular/angular-injector-bridge.functions";
import {WorkPackageTable} from "../../wp-fast-table";
import {TableEventHandler} from "../table-handler-registry";
import {tableRowClassName} from "../../builders/rows/single-row-builder";
import {ContextMenuService} from "../../../context-menus/context-menu.service";
import {keyCodes} from "../../../common/keyCodes.enum";
import {ContextMenuHandler} from "./context-menu-handler";
export class ContextMenuKeyboardHandler implements TableEventHandler {
// Injections
public contextMenu:ContextMenuService;
constructor(private table:WorkPackageTable) {
injectorBridge(this);
export class ContextMenuKeyboardHandler extends ContextMenuHandler {
constructor(table:WorkPackageTable) {
super(table);
}
public get EVENT() {
@ -18,11 +15,7 @@ export class ContextMenuKeyboardHandler implements TableEventHandler {
}
public get SELECTOR() {
return `.${tableRowClassName}`;
}
public eventScope(table:WorkPackageTable) {
return jQuery(table.tbody);
return this.rowSelector;
}
public handleEvent(table:WorkPackageTable, evt:JQueryEventObject):boolean {
@ -38,14 +31,12 @@ export class ContextMenuKeyboardHandler implements TableEventHandler {
// Locate the row from event
const element = target.closest(this.SELECTOR);
const wpId = element.data('workPackageId');
const [index,] = table.findRenderedRow(element.data('workPackageId'));
// Set position args to open at element
let position = { of: target };
this.contextMenu.activate('WorkPackageContextMenu', evt, { workPackageId: wpId, rowIndex: index, table: this.table}, position);
super.openContextMenu(evt, wpId, position);
return false;
}
}
ContextMenuKeyboardHandler.$inject = ['contextMenu'];

@ -6,13 +6,11 @@ import {tableRowClassName} from "../../builders/rows/single-row-builder";
import {uiStateLinkClass} from "../../builders/ui-state-link-builder";
import {ContextMenuService} from "../../../context-menus/context-menu.service";
import {timelineCellClassName} from "../../builders/timeline/timeline-row-builder";
import {ContextMenuHandler} from "./context-menu-handler";
export class ContextMenuHandler implements TableEventHandler {
// Injections
public contextMenu:ContextMenuService;
export class ContextMenuRightClickHandler extends ContextMenuHandler {
constructor(table: WorkPackageTable) {
injectorBridge(this);
super(table);
}
public get EVENT() {
@ -23,10 +21,6 @@ export class ContextMenuHandler implements TableEventHandler {
return `.${tableRowClassName},.${timelineCellClassName}`;
}
public eventScope(table:WorkPackageTable) {
return jQuery(table.container);
}
public handleEvent(table: WorkPackageTable, evt:JQueryEventObject):boolean {
let target = jQuery(evt.target);
@ -36,6 +30,7 @@ export class ContextMenuHandler implements TableEventHandler {
debugLog('Allowing original context menu on state link');
return true;
}
evt.preventDefault();
evt.stopPropagation();
@ -43,14 +38,10 @@ export class ContextMenuHandler implements TableEventHandler {
const element = target.closest(this.SELECTOR);
const wpId = element.data('workPackageId');
if (!wpId) {
return false;
if (wpId) {
super.openContextMenu(evt, wpId);
}
let [index,] = table.findRenderedRow(element.data('workPackageId'));
this.contextMenu.activate('WorkPackageContextMenu', evt, {workPackageId: wpId, rowIndex: index, table: table});
return false;
}
}
ContextMenuHandler.$inject = ['contextMenu'];

@ -1,19 +1,22 @@
import {debugLog} from '../../../../helpers/debug_output';
import {injectorBridge} from '../../../angular/angular-injector-bridge.functions';
import {$injectFields, injectorBridge} from '../../../angular/angular-injector-bridge.functions';
import {WorkPackageTable} from '../../wp-fast-table';
import {States} from '../../../states.service';
import {TableEventHandler} from '../table-handler-registry';
import {WorkPackageTableSelection} from '../../state/wp-table-selection.service';
import {tableRowClassName} from '../../builders/rows/single-row-builder';
import {tdClassName} from '../../builders/cell-builder';
import {KeepTabService} from "../../../wp-panels/keep-tab/keep-tab.service";
export class RowClickHandler implements TableEventHandler {
// Injections
public $state:ng.ui.IStateService;
public states:States;
public keepTab:KeepTabService;
public wpTableSelection:WorkPackageTableSelection;
constructor(table: WorkPackageTable) {
injectorBridge(this);
$injectFields(this, 'keepTab', '$state', 'states', 'wpTableSelection');
}
public get EVENT() {
@ -57,6 +60,10 @@ export class RowClickHandler implements TableEventHandler {
// Thus save that row for the details view button.
let [index, row] = table.findRenderedRow(classIdentifier);
this.states.focusedWorkPackage.putValue(wpId);
this.$state.go(
this.keepTab.currentDetailsState,
{ workPackageId: wpId, focus: true }
);
// Update single selection if no modifier present
if (!(evt.ctrlKey || evt.metaKey || evt.shiftKey)) {
@ -77,4 +84,3 @@ export class RowClickHandler implements TableEventHandler {
}
}
RowClickHandler.$inject = ['states', 'wpTableSelection'];

@ -4,8 +4,7 @@ import {WorkPackageTable} from "../wp-fast-table";
import {SelectionTransformer} from "./state/selection-transformer";
import {RowsTransformer} from "./state/rows-transformer";
import {ColumnsTransformer} from "./state/columns-transformer";
import {ContextMenuKeyboardHandler} from "./row/context-menu-keyboard-handler";
import {ContextMenuHandler} from "./row/context-menu-handler";
import {ContextMenuKeyboardHandler} from "./context-menu/context-menu-keyboard-handler";
import {GroupRowHandler} from "./row/group-row-handler";
import {RowDoubleClickHandler} from "./row/double-click-handler";
import {RowClickHandler} from "./row/click-handler";
@ -14,6 +13,8 @@ import {EditCellHandler} from "./cell/edit-cell-handler";
import {HierarchyClickHandler} from "./row/hierarchy-click-handler";
import {RelationsCellHandler} from './cell/relations-cell-handler';
import {RelationsTransformer} from './state/relations-transformer';
import {ContextMenuRightClickHandler} from "./context-menu/context-menu-rightclick-handler";
import {ContextMenuClickHandler} from "./context-menu/context-menu-click-handler";
export interface TableEventHandler {
EVENT:string;
@ -36,7 +37,9 @@ export class TableHandlerRegistry {
// Clicking on group headers
t => new GroupRowHandler(t),
// Right clicking on rows
t => new ContextMenuHandler(t),
t => new ContextMenuRightClickHandler(t),
// Left clicking on the dropdown icon
t => new ContextMenuClickHandler(t),
// SHIFT+ALT+F10 on rows
t => new ContextMenuKeyboardHandler(t),
// Clicking on relations cells

@ -10,7 +10,7 @@ import {States} from '../states.service';
import {WorkPackageTableSelection} from '../wp-fast-table/state/wp-table-selection.service';
import {WorkPackageTableColumnsService} from '../wp-fast-table/state/wp-table-columns.service';
import {
internalDetailsColumn,
internalContextMenuColumn,
tableRowClassName,
SingleRowBuilder, commonRowClassName
} from '../wp-fast-table/builders/rows/single-row-builder';
@ -41,7 +41,7 @@ export class InlineCreateRowBuilder extends SingleRowBuilder {
public buildCell(workPackage:WorkPackageResourceInterface, column:QueryColumn):HTMLElement {
switch (column.id) {
case internalDetailsColumn.id:
case internalContextMenuColumn.id:
return this.buildCancelButton();
default:
return super.buildCell(workPackage, column);

@ -21,7 +21,7 @@
locale="column.custom_field && columns.custom_field.name_locale || locale"
header-column="column">
</th>
<th class="wp-table--details-column -short hide-when-print">
<th class="wp-table--context-menu-column -short hide-when-print">
<div class="generic-table--sort-header-outer">
<accessible-by-keyboard
execute="openColumnsModal()"

@ -46,4 +46,8 @@ jQuery(function() {
if (bowser.ios) {
document.documentElement.classList.add('-browser-ios');
}
if (bowser.mobile || bowser.ios || bowser.android) {
document.documentElement.classList.add('-browser-mobile');
}
});

@ -214,8 +214,6 @@ describe 'Select work package row', type: :feature, js:true, selenium: true do
end
it do
find('#work-packages-details-view-button').click
split_wp = Pages::SplitWorkPackage.new(work_package_2)
split_wp.expect_attributes Subject: work_package_2.subject

@ -41,7 +41,7 @@ describe 'Switching types in work package table', js: true do
let(:query) do
query = FactoryGirl.build(:query, user: user, project: project)
query.column_names = ['subject', 'type', "cf_#{cf_text.id}"]
query.column_names = ['id', 'subject', 'type', "cf_#{cf_text.id}"]
query.save!
query

@ -57,7 +57,7 @@ RSpec.feature 'Keep current details tab', js: true, selenium: true do
wp_split2.visit_tab! :relations
# Open first WP by click on table
wp_table.click_on_row(wp1)
wp_table.open_split_view(wp1)
wp_split1.expect_subject
wp_split1.expect_tab :relations

@ -156,7 +156,7 @@ RSpec.feature 'Work package timeline navigation', js: true, selenium: true do
let!(:query) do
query = FactoryGirl.build(:query, user: user, project: project)
query.column_names = ['subject', 'category']
query.column_names = ['id', 'subject', 'category']
query.show_hierarchies = false
query.timeline_visible = true

@ -99,11 +99,9 @@ module Pages
def open_split_view(work_package)
split_page = SplitWorkPackage.new(work_package, project)
# Hover row to show split screen button
# The 'id' column should have enough space to be clicked
row_element = row(work_package)
row_element.hover
row_element.find('.wp-table--details-link').click
row_element.find('td.id').click
split_page
end
@ -128,11 +126,6 @@ module Pages
expect(page).to have_select("values-#{filter_name}", selected: value) if value
end
def click_on_row(work_package)
loading_indicator_saveguard
page.driver.browser.action.click(row(work_package).native).perform
end
def open_full_screen_by_doubleclick(work_package)
loading_indicator_saveguard
# The 'id' column should have enough space to be clicked

Loading…
Cancel
Save