Convert query dropdown

pull/6214/head
Oliver Günther 7 years ago
parent 9a8faafafb
commit 8b102726c7
No known key found for this signature in database
GPG Key ID: 88872239EB414F99
  1. 9
      frontend/app/angular4-modules.ts
  2. 140
      frontend/app/components/context-menus/context-menu.service.ts
  3. 4
      frontend/app/components/op-context-menu/op-context-menu-handler.ts
  4. 28
      frontend/app/components/op-context-menu/op-context-menu.service.ts
  5. 1
      frontend/app/components/routing/wp-list/wp-list.component.ts
  6. 20
      frontend/app/components/routing/wp-list/wp.list.component.html
  7. 6
      frontend/app/components/wp-edit/wp-edit-field/wp-edit-field.component.ts
  8. 73
      frontend/app/components/wp-query-select/wp-query-select-dropdown.component.ts
  9. 124
      frontend/app/components/wp-query-select/wp-query-select.service.test.ts
  10. 13
      frontend/app/components/wp-query-select/wp-query-select.service.ts
  11. 11
      frontend/app/components/wp-query-select/wp-query-select.template.html
  12. 73
      frontend/app/components/wp-query-select/wp-query-selectable-title.component.ts
  13. 13
      frontend/app/components/wp-query-select/wp-query-selectable-title.html
  14. 7
      frontend/app/components/wp-table/wp-table.directive.ts

@ -34,7 +34,6 @@ import {TablePaginationComponent} from 'core-app/components/table-pagination/tab
import {AccessibleByKeyboardDirectiveUpgraded} from 'core-app/ui_components/accessible-by-keyboard-directive-upgraded'; import {AccessibleByKeyboardDirectiveUpgraded} from 'core-app/ui_components/accessible-by-keyboard-directive-upgraded';
import {SimpleTemplateRenderer} from 'core-components/angular/simple-template-renderer'; import {SimpleTemplateRenderer} from 'core-components/angular/simple-template-renderer';
import {OpIcon} from 'core-components/common/icon/op-icon'; import {OpIcon} from 'core-components/common/icon/op-icon';
import {ContextMenuService} from 'core-components/context-menus/context-menu.service';
import {WorkPackagesListComponent} from 'core-components/routing/wp-list/wp-list.component'; import {WorkPackagesListComponent} from 'core-components/routing/wp-list/wp-list.component';
import {States} from 'core-components/states.service'; import {States} from 'core-components/states.service';
import {PaginationService} from 'core-components/table-pagination/pagination-service'; import {PaginationService} from 'core-components/table-pagination/pagination-service';
@ -163,6 +162,9 @@ import {OpSettingsMenuDirective} from "core-components/op-context-menu/handlers/
import {WorkPackageStatusDropdownDirective} from "core-components/op-context-menu/handlers/wp-status-dropdown-menu.directive"; import {WorkPackageStatusDropdownDirective} from "core-components/op-context-menu/handlers/wp-status-dropdown-menu.directive";
import {WorkPackageCreateSettingsMenuDirective} from "core-components/op-context-menu/handlers/wp-create-settings-menu.directive"; import {WorkPackageCreateSettingsMenuDirective} from "core-components/op-context-menu/handlers/wp-create-settings-menu.directive";
import {WorkPackageSingleContextMenuDirective} from "core-components/op-context-menu/wp-context-menu/wp-single-context-menu"; import {WorkPackageSingleContextMenuDirective} from "core-components/op-context-menu/wp-context-menu/wp-single-context-menu";
import {WorkPackageQuerySelectableTitleComponent} from "core-components/wp-query-select/wp-query-selectable-title.component";
import {WorkPackageQuerySelectDropdownComponent} from "core-components/wp-query-select/wp-query-select-dropdown.component";
import {QueryDmService} from "core-components/api/api-v3/hal-resource-dms/query-dm.service";
@NgModule({ @NgModule({
imports: [ imports: [
@ -223,11 +225,11 @@ import {WorkPackageSingleContextMenuDirective} from "core-components/op-context-
upgradeService('wpTableRelationColumns', WorkPackageTableRelationColumnsService), upgradeService('wpTableRelationColumns', WorkPackageTableRelationColumnsService),
upgradeService('wpTableGroupBy', WorkPackageTableGroupByService), upgradeService('wpTableGroupBy', WorkPackageTableGroupByService),
upgradeService('wpTableColumns', WorkPackageTableColumnsService), upgradeService('wpTableColumns', WorkPackageTableColumnsService),
upgradeService('contextMenu', ContextMenuService),
upgradeService('authorisationService', AuthorisationService), upgradeService('authorisationService', AuthorisationService),
upgradeService('ConfigurationService', ConfigurationService), upgradeService('ConfigurationService', ConfigurationService),
upgradeService('currentProject', CurrentProjectService), upgradeService('currentProject', CurrentProjectService),
upgradeService('RootDm', RootDmService), upgradeService('RootDm', RootDmService),
upgradeService('QueryDm', QueryDmService),
// Split view // Split view
upgradeService('wpCreate', WorkPackageCreateService), upgradeService('wpCreate', WorkPackageCreateService),
upgradeService('firstRoute', FirstRouteService), upgradeService('firstRoute', FirstRouteService),
@ -334,6 +336,8 @@ import {WorkPackageSingleContextMenuDirective} from "core-components/op-context-
WorkPackageStatusDropdownDirective, WorkPackageStatusDropdownDirective,
WorkPackageCreateSettingsMenuDirective, WorkPackageCreateSettingsMenuDirective,
WorkPackageSingleContextMenuDirective, WorkPackageSingleContextMenuDirective,
WorkPackageQuerySelectableTitleComponent,
WorkPackageQuerySelectDropdownComponent,
], ],
entryComponents: [ entryComponents: [
WorkPackagesListComponent, WorkPackagesListComponent,
@ -372,6 +376,7 @@ import {WorkPackageSingleContextMenuDirective} from "core-components/op-context-
WorkPackageCopySplitViewComponent, WorkPackageCopySplitViewComponent,
OPContextMenuComponent, OPContextMenuComponent,
WorkPackageQuerySelectDropdownComponent,
] ]
}) })
export class OpenProjectModule { export class OpenProjectModule {

@ -1,140 +0,0 @@
// -- 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 {TransitionService} from '@uirouter/core';
interface ContextMenu {
close(disableFocus?:boolean):Promise<void>;
open(nextTo:JQuery,locals:Object):Promise<JQuery>;
target?:JQuery;
menuElement:JQuery;
}
export class ContextMenuService {
private active_menu:ContextMenu|null;
private repositionCurrentElement:Function|null;
// RR: Ugly flag to avoid bug in Firefox:
// right-click immediately triggered $window.click -> close() event
private tempDisableGlobalClose = false;
constructor(public $window:ng.IWindowService,
public $injector:ng.auto.IInjectorService,
public $q:ng.IQService,
public $transitions:TransitionService,
public $timeout:ng.ITimeoutService,
public $rootScope:ng.IRootScopeService) {
"ngInject";
// Close context menus on state change
$transitions.onStart({}, () => this.close());
$rootScope.$on('repositionDropdown', () => {
this.repositionCurrentElement && this.repositionCurrentElement();
});
// Listen to keyups on window to close context menus
Mousetrap.bind('escape', () => this.close());
// Listen to any click and close the active context menu
jQuery($window).click(() => {
if (!this.tempDisableGlobalClose) {
this.close();
}
});
}
// Return the active context menu, if any
public get active():ContextMenu|null {
return this.active_menu;
}
public close(disableFocus:boolean = false):Promise<undefined> {
this.repositionCurrentElement = null;
if (!this.active) {
return this.$q.when(undefined);
} else {
return this.active.close(disableFocus) as any;
}
}
public activate(contextMenuName:string, event:Event, locals:Object, positionArgs?:any) {
this.tempDisableGlobalClose = true;
let deferred = this.$q.defer();
let target = jQuery(event.target);
let contextMenu:ContextMenu = <ContextMenu> this.$injector.get(contextMenuName);
// Close other context menu
this.close();
// Open the menu
contextMenu.open(target, locals).then((menuElement) => {
contextMenu.menuElement = menuElement;
this.active_menu = contextMenu;
(menuElement as any).trap();
menuElement.on('click', (evt) => {
// allow inputs to be clickable
// without closing the dropdown
if (angular.element(evt.target).is(':input')) {
evt.stopPropagation();
}
});
this.repositionCurrentElement = () => this.reposition(event, positionArgs);
this.$timeout(() => {
this.repositionCurrentElement!();
menuElement.css('visibility', 'visible');
deferred.resolve(menuElement);
this.tempDisableGlobalClose = false;
});
});
return deferred.promise;
}
public reposition(event:Event, positionArgs?:Object) {
if (!this.active) {
return;
}
let position = { my: 'left top', at: 'right bottom', of: event, collision: 'flipfit' };
_.assign(position, positionArgs);
this.active.menuElement.position(position);
}
}
angular
.module('openproject.services')
.service('contextMenu', ContextMenuService);

@ -25,6 +25,10 @@ export abstract class OpContextMenuHandler {
target.focus(); target.focus();
} }
public onOpen(menu:JQuery) {
menu.find('.menu-item').first().focus();
}
/** /**
* Positioning args for jquery-ui position. * Positioning args for jquery-ui position.
* *

@ -1,12 +1,14 @@
import {ApplicationRef, ComponentFactoryResolver, Injectable, Injector} from '@angular/core'; import {
ApplicationRef, ComponentFactoryResolver, Inject, Injectable,
Injector
} from '@angular/core';
import {ComponentPortal, DomPortalOutlet, PortalInjector} from "@angular/cdk/portal"; import {ComponentPortal, DomPortalOutlet, PortalInjector} from "@angular/cdk/portal";
import {TransitionService} from "@uirouter/core"; import {TransitionService} from "@uirouter/core";
import {OpContextMenuHandler} from "core-components/op-context-menu/op-context-menu-handler"; import {OpContextMenuHandler} from "core-components/op-context-menu/op-context-menu-handler";
import {OpContextMenuLocalsToken} from "core-app/angular4-transition-utils"; import {FocusHelperToken, OpContextMenuLocalsToken} from "core-app/angular4-transition-utils";
import {OpContextMenuLocalsMap} from "core-components/op-context-menu/op-context-menu.types"; import {OpContextMenuLocalsMap} from "core-components/op-context-menu/op-context-menu.types";
import {OPContextMenuComponent} from "core-components/op-context-menu/op-context-menu.component"; import {OPContextMenuComponent} from "core-components/op-context-menu/op-context-menu.component";
@Injectable() @Injectable()
export class OPContextMenuService { export class OPContextMenuService {
public active:OpContextMenuHandler|null = null; public active:OpContextMenuHandler|null = null;
@ -16,7 +18,11 @@ export class OPContextMenuService {
// And a reference to the actual portal host interface on top of the element // And a reference to the actual portal host interface on top of the element
private bodyPortalHost:DomPortalOutlet; private bodyPortalHost:DomPortalOutlet;
// Allow temporarily disabling the close handler
private isOpening = false;
constructor(private componentFactoryResolver:ComponentFactoryResolver, constructor(private componentFactoryResolver:ComponentFactoryResolver,
@Inject(FocusHelperToken) readonly FocusHelper:any,
private appRef:ApplicationRef, private appRef:ApplicationRef,
private $transitions:TransitionService, private $transitions:TransitionService,
private injector:Injector) { private injector:Injector) {
@ -39,8 +45,10 @@ export class OPContextMenuService {
Mousetrap.bind('escape', () => this.close()); Mousetrap.bind('escape', () => this.close());
// Listen to any click and close the active context menu // Listen to any click and close the active context menu
jQuery(window).click(() => { jQuery(window).click((evt) => {
this.active && this.close(); if (this.active && !this.portalHostElement.contains(evt.target)) {
this.close();
}
}); });
} }
@ -48,10 +56,11 @@ export class OPContextMenuService {
* Open a ContextMenu reference and append it to the portal * Open a ContextMenu reference and append it to the portal
* @param contextMenu A reference to a context menu handler * @param contextMenu A reference to a context menu handler
*/ */
public show(menu:OpContextMenuHandler, event:Event, component = OPContextMenuComponent) { public show(menu:OpContextMenuHandler, event:Event, component:any = OPContextMenuComponent) {
this.close(); this.close();
// Create a portal for the given component class and render it // Create a portal for the given component class and render it
this.isOpening = true;
const portal = new ComponentPortal(component, null, this.injectorFor(menu.locals)); const portal = new ComponentPortal(component, null, this.injectorFor(menu.locals));
this.bodyPortalHost.attach(portal); this.bodyPortalHost.attach(portal);
this.portalHostElement.style.display = 'block'; this.portalHostElement.style.display = 'block';
@ -60,7 +69,8 @@ export class OPContextMenuService {
setTimeout(() => { setTimeout(() => {
this.reposition(event); this.reposition(event);
// Focus on the first element // Focus on the first element
this.activeMenu.find('.menu-item').first().focus(); this.active && this.active.onOpen(this.activeMenu);
this.isOpening = false;
}); });
} }
@ -72,6 +82,10 @@ export class OPContextMenuService {
* Closes all currently open context menus. * Closes all currently open context menus.
*/ */
public close() { public close() {
if (this.isOpening) {
return;
}
// Detach any component currently in the portal // Detach any component currently in the portal
this.bodyPortalHost.detach(); this.bodyPortalHost.detach();
this.portalHostElement.style.display = 'none'; this.portalHostElement.style.display = 'none';

@ -64,7 +64,6 @@ export class WorkPackagesListComponent implements OnInit, OnDestroy {
text = { text = {
'jump_to_pagination': this.I18n.t('js.work_packages.jump_marks.pagination'), 'jump_to_pagination': this.I18n.t('js.work_packages.jump_marks.pagination'),
'text_jump_to_pagination': this.I18n.t('js.work_packages.jump_marks.label_pagination'), 'text_jump_to_pagination': this.I18n.t('js.work_packages.jump_marks.label_pagination'),
'search_query_title': this.I18n.t('js.toolbar.search_query_title'),
'button_settings': this.I18n.t('js.button_settings') 'button_settings': this.I18n.t('js.button_settings')
}; };

@ -1,25 +1,7 @@
<div class="work-packages-list-view--container"> <div class="work-packages-list-view--container">
<div class="toolbar-container"> <div class="toolbar-container">
<div class="toolbar"> <div class="toolbar">
<div class="title-container"> <wp-query-selectable-title [selectedTitle]="selectedTitle"></wp-query-selectable-title>
<div class="text">
<h2>
<span
hasDropdownMenu
[hasDropdownMenu-locals]="{'selectedTitle': selectedTitle }"
[hasDropdownMenu-collisionContainer]="'#content'"
[hasDropdownMenu-target]="'wpQuerySelectService'">
<accessible-by-keyboard
linkClass="wp-table--query-menu-link"
[linkTitle]="text.search_query_title">
<op-icon
icon-classes="icon-pulldown icon-button icon-small hide-when-print"></op-icon>
{{ selectedTitle | slice:0:50 }}
</accessible-by-keyboard>
</span>
</h2>
</div>
</div>
<ul class="toolbar-items hide-when-print" <ul class="toolbar-items hide-when-print"
*ngIf="tableInformationLoaded"> *ngIf="tableInformationLoaded">

@ -27,7 +27,6 @@
// ++ // ++
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service'; import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {ContextMenuService} from '../../context-menus/context-menu.service';
import {WorkPackageCacheService} from '../../work-packages/work-package-cache.service'; import {WorkPackageCacheService} from '../../work-packages/work-package-cache.service';
import {WorkPackageNotificationService} from '../wp-notification.service'; import {WorkPackageNotificationService} from '../wp-notification.service';
import {opWorkPackagesModule} from '../../../angular-modules'; import {opWorkPackagesModule} from '../../../angular-modules';
@ -48,6 +47,7 @@ import {Component, ElementRef, Inject, Input, OnInit} from '@angular/core';
import {I18nToken, NotificationsServiceToken} from 'core-app/angular4-transition-utils'; import {I18nToken, NotificationsServiceToken} from 'core-app/angular4-transition-utils';
import {WorkPackageEditFieldGroupComponent} from 'core-components/wp-edit/wp-edit-field/wp-edit-field-group.directive'; import {WorkPackageEditFieldGroupComponent} from 'core-components/wp-edit/wp-edit-field/wp-edit-field-group.directive';
import {ConfigurationService} from 'core-components/common/config/configuration.service'; import {ConfigurationService} from 'core-components/common/config/configuration.service';
import {OPContextMenuService} from "core-components/op-context-menu/op-context-menu.service";
@Component({ @Component({
template: require('!!raw-loader!./wp-edit-field.html'), template: require('!!raw-loader!./wp-edit-field.html'),
@ -69,7 +69,7 @@ export class WorkPackageEditFieldComponent implements OnInit {
protected elementRef:ElementRef, protected elementRef:ElementRef,
protected wpNotificationsService:WorkPackageNotificationService, protected wpNotificationsService:WorkPackageNotificationService,
protected ConfigurationService:ConfigurationService, protected ConfigurationService:ConfigurationService,
protected contextMenu:ContextMenuService, protected opContextMenu:OPContextMenuService,
protected wpEditing:WorkPackageEditingService, protected wpEditing:WorkPackageEditingService,
protected wpCacheService:WorkPackageCacheService, protected wpCacheService:WorkPackageCacheService,
// Get parent field group from injector // Get parent field group from injector
@ -122,7 +122,7 @@ export class WorkPackageEditFieldComponent implements OnInit {
this.handleUserActivate(event); this.handleUserActivate(event);
} }
this.contextMenu.close(); this.opContextMenu.close();
event.stopImmediatePropagation(); event.stopImmediatePropagation();
return false; return false;

@ -30,10 +30,15 @@ import {QueryDmService} from '../api/api-v3/hal-resource-dms/query-dm.service';
import {QueryResource} from '../api/api-v3/hal-resources/query-resource.service'; import {QueryResource} from '../api/api-v3/hal-resources/query-resource.service';
import {States} from '../states.service'; import {States} from '../states.service';
import {WorkPackagesListService} from '../wp-list/wp-list.service'; import {WorkPackagesListService} from '../wp-list/wp-list.service';
import {ContextMenuService} from '../context-menus/context-menu.service';
import {LoadingIndicatorService} from '../common/loading-indicator/loading-indicator.service'; import {LoadingIndicatorService} from '../common/loading-indicator/loading-indicator.service';
import {WorkPackagesListChecksumService} from '../wp-list/wp-list-checksum.service'; import {WorkPackagesListChecksumService} from '../wp-list/wp-list-checksum.service';
import {StateService} from '@uirouter/core'; import {StateService} from '@uirouter/core';
import {Component, Inject, OnInit} from "@angular/core";
import {
$stateToken, FocusHelperToken, I18nToken,
OpContextMenuLocalsToken
} from "core-app/angular4-transition-utils";
import {OpContextMenuLocalsMap} from "core-components/op-context-menu/op-context-menu.types";
interface IAutocompleteItem { interface IAutocompleteItem {
label:string; label:string;
@ -44,43 +49,31 @@ interface IQueryAutocompleteJQuery extends JQuery {
querycomplete({}):void; querycomplete({}):void;
} }
interface MyScope extends ng.IScope { @Component({
loaded:boolean; template: require('!!raw-loader!./wp-query-select.template.html')
i18n:MyI18n; })
} export class WorkPackageQuerySelectDropdownComponent implements OnInit {
public loaded = false;
interface MyI18n { public text = {
loading:string; loading: this.I18n.t('js.ajax.loading'),
label:string; label: this.I18n.t('js.toolbar.search_query_label'),
scope_global:string; scope_global: this.I18n.t('js.label_global_queries'),
scope_private:string; scope_private: this.I18n.t('js.label_custom_queries'),
no_results:string; no_results: this.I18n.t('js.work_packages.query.text_no_results')
} };
export class WorkPackageQuerySelectController { constructor(readonly QueryDm:QueryDmService,
constructor(private $scope:MyScope, @Inject($stateToken) readonly $state:StateService,
private QueryDm:QueryDmService, @Inject(I18nToken) readonly I18n:op.I18n,
private $state:StateService, @Inject(OpContextMenuLocalsToken) public locals:OpContextMenuLocalsMap,
private states:States, readonly states:States,
private wpListService:WorkPackagesListService, readonly wpListService:WorkPackagesListService,
private contextMenu:ContextMenuService, readonly wpListChecksumService:WorkPackagesListChecksumService,
private I18n:op.I18n, readonly loadingIndicator:LoadingIndicatorService) {
private wpListChecksumService:WorkPackagesListChecksumService,
private loadingIndicator:LoadingIndicatorService) {
this.$scope.loaded = false;
this.$scope.i18n = {
loading: I18n.t('js.ajax.loading'),
label: I18n.t('js.toolbar.search_query_label'),
scope_global: I18n.t('js.label_global_queries'),
scope_private: I18n.t('js.label_custom_queries'),
no_results: I18n.t('js.work_packages.query.text_no_results')
};
this.setup();
} }
private setup() { public ngOnInit() {
this.loadQueries().then(collection => { this.loadQueries().then(collection => {
let sortedQueries = _.reverse(_.sortBy(collection.elements, 'public')); let sortedQueries = _.reverse(_.sortBy(collection.elements, 'public'));
let autocompleteValues = _.map(sortedQueries, (query:any) => { return { label: query.name, query: query }; } ); let autocompleteValues = _.map(sortedQueries, (query:any) => { return { label: query.name, query: query }; } );
@ -123,9 +116,9 @@ export class WorkPackageQuerySelectController {
private defineJQueryQueryComplete() { private defineJQueryQueryComplete() {
let labelFunction = (isPublic:boolean) => { let labelFunction = (isPublic:boolean) => {
if (isPublic) { if (isPublic) {
return this.$scope.i18n.scope_global; return this.text.scope_global;
} else { } else {
return this.$scope.i18n.scope_private; return this.text.scope_private;
} }
}; };
@ -154,11 +147,11 @@ export class WorkPackageQuerySelectController {
private loadQuery(query:QueryResource) { private loadQuery(query:QueryResource) {
this.wpListChecksumService.clear(); this.wpListChecksumService.clear();
this.loadingIndicator.table.promise = this.wpListService.reloadQuery(query); this.loadingIndicator.table.promise = this.wpListService.reloadQuery(query);
this.contextMenu.close(); this.locals.service.close();
} }
private setLoaded() { private setLoaded() {
this.$scope.loaded = true; this.loaded = true;
this.$scope.i18n.loading = ''; this.text.loading = '';
} }
} }

@ -1,124 +0,0 @@
// -- 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 {WorkPackageQuerySelectController} from './wp-query-select.controller'
describe('Work package query select', function() {
var container:any, contextMenu:any, $rootScope:any, scope:any, ngContextMenu:any, queriesPromise:any, I18n:any;
beforeEach(angular.mock.module('ng-context-menu',
'openproject.workPackages',
'openproject.workPackages.controllers',
'openproject.models',
'openproject.api',
'openproject.layout',
'openproject.templates'));
beforeEach(angular.mock.module('openproject.api', ($provide:ng.auto.IProvideService) => {
var queryDm = {
all: () => queriesPromise
};
$provide.constant('QueryDm', queryDm);
}));
beforeEach(function() {
var html = '<div></div>';
container = angular.element(html);
})
beforeEach(inject(function(_$rootScope_:any, _ngContextMenu_:any, $q:ng.IQService, $templateCache:any, _I18n_:any) {
$rootScope = _$rootScope_;
ngContextMenu = _ngContextMenu_;
I18n = _I18n_;
var template = $templateCache.get('/components/wp-query-select/wp-query-select.template.html');
$templateCache.put('template.html', [200, template, {}]);
var queries = {
elements: [
{
name: 'firstQuery',
$href: 'api/firstQuery',
public: true,
},
{
name: 'secondQuery',
$href: 'api/secondQuery',
public: false
}
]
}
sinon.stub(_I18n_, 't')
.withArgs('js.label_global_queries')
.returns('Public Queries')
.withArgs('js.label_custom_queries')
.returns('Private Queries');
var deferred = $q.defer()
deferred.resolve(queries);
queriesPromise = deferred.promise;
contextMenu = ngContextMenu({
controller: WorkPackageQuerySelectController,
container: container,
templateUrl: 'template.html'
});
contextMenu.open({x: 0, y: 0});
}));
beforeEach(function() {
// for jQuery to work, we need to append the element
// to the dom
container.appendTo(document.body);
$rootScope.$digest();
scope = container.children().scope();
$rootScope.$apply();
});
afterEach(angular.mock.inject(() => {
I18n.t.restore();
container.remove();
}));
describe('element', () => {
it('has the queries as options grouped by their public attribute', () => {
expect(container.find('.ui-autocomplete--category:first').text()).to.eq('Public Queries');
expect(container.find('.ui-autocomplete--category:first + .ui-menu-item').text()).to.eq('firstQuery');
expect(container.find('.ui-autocomplete--category:last').text()).to.eq('Private Queries');
expect(container.find('.ui-autocomplete--category:last + .ui-menu-item').text()).to.eq('secondQuery');
});
});
});

@ -1,13 +0,0 @@
import {WorkPackageQuerySelectController} from './wp-query-select.controller'
import {opWorkPackagesModule} from '../../angular-modules';
function wpQuerySelect(ngContextMenu:any) {
return ngContextMenu({
templateUrl: '/components/wp-query-select/wp-query-select.template.html',
container: '.title-container',
controller: WorkPackageQuerySelectController
});
}
opWorkPackagesModule.factory('wpQuerySelectService', wpQuerySelect);

@ -1,11 +1,16 @@
<div class="dropdown dropdown-relative" id="querySelectDropdown"> <div class="dropdown dropdown-relative" id="querySelectDropdown">
<div class="search-query-wrapper"> <div class="search-query-wrapper">
<label for="title-filter" class="hidden-for-sighted">{{ ::i18n.label }}</label> <label for="query-title-filter"
class="hidden-for-sighted"
[textContent]="text.label" ></label>
<input type="text" <input type="text"
class="ui-autocomplete--input" class="ui-autocomplete--input"
name="query-title-filter"
id="query-title-filter" id="query-title-filter"
placeholder="{{ i18n.loading }}"> [attr.placeholder]="text.loading">
<i id="magnifier" class="icon-search"></i> <i id="magnifier" class="icon-search"></i>
</div> </div>
<p class="query-select-dropdown--no-results" hidden ng-bind="::i18n.no_results"></p> <p class="query-select-dropdown--no-results"
hidden
[textContent]="text.no_results"></p>
</div> </div>

@ -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 {OPContextMenuService} from "core-components/op-context-menu/op-context-menu.service";
import {Component, ElementRef, Inject, Input} from "@angular/core";
import {OpContextMenuTrigger} from "core-components/op-context-menu/handlers/op-context-menu-trigger.directive";
import {WorkPackageQuerySelectDropdownComponent} from "core-components/wp-query-select/wp-query-select-dropdown.component";
import {I18nToken} from "core-app/angular4-transition-utils";
@Component({
selector: 'wp-query-selectable-title',
template: require('!!raw-loader!./wp-query-selectable-title.html')
})
export class WorkPackageQuerySelectableTitleComponent extends OpContextMenuTrigger {
@Input('selectedTitle') public selectedTitle:string;
public text = {
search_query_title: this.I18n.t('js.toolbar.search_query_title')
};
constructor(readonly elementRef:ElementRef,
readonly opContextMenu:OPContextMenuService,
@Inject(I18nToken) readonly I18n:op.I18n) {
super(elementRef, opContextMenu);
}
public showDropDown(evt:Event) {
this.opContextMenu.show(this, evt, WorkPackageQuerySelectDropdownComponent);
}
public onOpen(menu:JQuery) {
menu.find('#query-title-filter').focus();
}
/**
* Positioning args for jquery-ui position.
*
* @param {Event} openerEvent
*/
public positionArgs(openerEvent:Event) {
return {
my: 'left top',
at: 'left bottom',
of: this.$element.find('.wp-table--query-menu-link')
};
}
}

@ -0,0 +1,13 @@
<div class="title-container">
<div class="text">
<h2>
<accessible-by-keyboard
linkClass="wp-table--query-menu-link"
(execute)="showDropDown($event)"
[linkTitle]="text.search_query_title">
<op-icon icon-classes="icon-pulldown icon-button icon-small hide-when-print"></op-icon>
{{ selectedTitle | slice:0:50 }}
</accessible-by-keyboard>
</h2>
</div>
</div>

@ -27,7 +27,6 @@
// ++ // ++
import {Component, ElementRef, Inject, Injector, Input, OnDestroy, OnInit} from '@angular/core'; import {Component, ElementRef, Inject, Injector, Input, OnDestroy, OnInit} from '@angular/core';
import {downgradeComponent} from '@angular/upgrade/static';
import {columnsModalToken, I18nToken} from 'core-app/angular4-transition-utils'; import {columnsModalToken, I18nToken} from 'core-app/angular4-transition-utils';
import {QueryGroupByResource} from 'core-components/api/api-v3/hal-resources/query-group-by-resource.service'; import {QueryGroupByResource} from 'core-components/api/api-v3/hal-resources/query-group-by-resource.service';
import {QueryResource} from 'core-components/api/api-v3/hal-resources/query-resource.service'; import {QueryResource} from 'core-components/api/api-v3/hal-resources/query-resource.service';
@ -36,7 +35,6 @@ import {TableState, TableStateHolder} from 'core-components/wp-table/table-state
import {untilComponentDestroyed} from 'ng2-rx-componentdestroyed'; import {untilComponentDestroyed} from 'ng2-rx-componentdestroyed';
import {combineLatest} from 'rxjs/observable/combineLatest'; import {combineLatest} from 'rxjs/observable/combineLatest';
import {debugLog} from '../../helpers/debug_output'; import {debugLog} from '../../helpers/debug_output';
import {ContextMenuService} from '../context-menus/context-menu.service';
import {States} from '../states.service'; import {States} from '../states.service';
import {WorkPackageTableColumnsService} from '../wp-fast-table/state/wp-table-columns.service'; import {WorkPackageTableColumnsService} from '../wp-fast-table/state/wp-table-columns.service';
import {WorkPackageTableGroupByService} from '../wp-fast-table/state/wp-table-group-by.service'; import {WorkPackageTableGroupByService} from '../wp-fast-table/state/wp-table-group-by.service';
@ -45,6 +43,7 @@ import {WorkPackageTable} from '../wp-fast-table/wp-fast-table';
import {WorkPackageTimelineTableController} from './timeline/container/wp-timeline-container.directive'; import {WorkPackageTimelineTableController} from './timeline/container/wp-timeline-container.directive';
import {WpTableHoverSync} from './wp-table-hover-sync'; import {WpTableHoverSync} from './wp-table-hover-sync';
import {createScrollSync} from './wp-table-scroll-sync'; import {createScrollSync} from './wp-table-scroll-sync';
import {OPContextMenuService} from "core-components/op-context-menu/op-context-menu.service";
@Component({ @Component({
template: require('!!raw-loader!./wp-table.directive.html'), template: require('!!raw-loader!./wp-table.directive.html'),
@ -91,7 +90,7 @@ export class WorkPackagesTableController implements OnInit, OnDestroy {
public injector:Injector, public injector:Injector,
private readonly tableStateHolder:TableStateHolder, private readonly tableStateHolder:TableStateHolder,
@Inject(columnsModalToken) private columnsModal:any, @Inject(columnsModalToken) private columnsModal:any,
private contextMenu:ContextMenuService, private opContextMenu:OPContextMenuService,
private states:States, private states:States,
@Inject(I18nToken) private I18n:op.I18n, @Inject(I18nToken) private I18n:op.I18n,
private wpTableGroupBy:WorkPackageTableGroupByService, private wpTableGroupBy:WorkPackageTableGroupByService,
@ -180,7 +179,7 @@ export class WorkPackagesTableController implements OnInit, OnDestroy {
} }
public openColumnsModal() { public openColumnsModal() {
this.contextMenu.close(); this.opContextMenu.close();
this.columnsModal.activate(); this.columnsModal.activate();
} }

Loading…
Cancel
Save