From 8b102726c7f21e70a2a4d58cc90650306dea9619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Fri, 16 Mar 2018 16:11:20 +0100 Subject: [PATCH] Convert query dropdown --- frontend/app/angular4-modules.ts | 9 +- .../context-menus/context-menu.service.ts | 140 ------------------ .../op-context-menu-handler.ts | 4 + .../op-context-menu.service.ts | 28 +++- .../routing/wp-list/wp-list.component.ts | 1 - .../routing/wp-list/wp.list.component.html | 20 +-- .../wp-edit-field/wp-edit-field.component.ts | 6 +- ... => wp-query-select-dropdown.component.ts} | 73 +++++---- .../wp-query-select.service.test.ts | 124 ---------------- .../wp-query-select.service.ts | 13 -- .../wp-query-select.template.html | 11 +- .../wp-query-selectable-title.component.ts | 73 +++++++++ .../wp-query-selectable-title.html | 13 ++ .../components/wp-table/wp-table.directive.ts | 7 +- 14 files changed, 166 insertions(+), 356 deletions(-) delete mode 100644 frontend/app/components/context-menus/context-menu.service.ts rename frontend/app/components/wp-query-select/{wp-query-select.controller.ts => wp-query-select-dropdown.component.ts} (74%) delete mode 100644 frontend/app/components/wp-query-select/wp-query-select.service.test.ts delete mode 100644 frontend/app/components/wp-query-select/wp-query-select.service.ts create mode 100644 frontend/app/components/wp-query-select/wp-query-selectable-title.component.ts create mode 100644 frontend/app/components/wp-query-select/wp-query-selectable-title.html diff --git a/frontend/app/angular4-modules.ts b/frontend/app/angular4-modules.ts index b40aaf75cf..fc2f76bdb8 100644 --- a/frontend/app/angular4-modules.ts +++ b/frontend/app/angular4-modules.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 {SimpleTemplateRenderer} from 'core-components/angular/simple-template-renderer'; 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 {States} from 'core-components/states.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 {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 {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({ imports: [ @@ -223,11 +225,11 @@ import {WorkPackageSingleContextMenuDirective} from "core-components/op-context- upgradeService('wpTableRelationColumns', WorkPackageTableRelationColumnsService), upgradeService('wpTableGroupBy', WorkPackageTableGroupByService), upgradeService('wpTableColumns', WorkPackageTableColumnsService), - upgradeService('contextMenu', ContextMenuService), upgradeService('authorisationService', AuthorisationService), upgradeService('ConfigurationService', ConfigurationService), upgradeService('currentProject', CurrentProjectService), upgradeService('RootDm', RootDmService), + upgradeService('QueryDm', QueryDmService), // Split view upgradeService('wpCreate', WorkPackageCreateService), upgradeService('firstRoute', FirstRouteService), @@ -334,6 +336,8 @@ import {WorkPackageSingleContextMenuDirective} from "core-components/op-context- WorkPackageStatusDropdownDirective, WorkPackageCreateSettingsMenuDirective, WorkPackageSingleContextMenuDirective, + WorkPackageQuerySelectableTitleComponent, + WorkPackageQuerySelectDropdownComponent, ], entryComponents: [ WorkPackagesListComponent, @@ -372,6 +376,7 @@ import {WorkPackageSingleContextMenuDirective} from "core-components/op-context- WorkPackageCopySplitViewComponent, OPContextMenuComponent, + WorkPackageQuerySelectDropdownComponent, ] }) export class OpenProjectModule { diff --git a/frontend/app/components/context-menus/context-menu.service.ts b/frontend/app/components/context-menus/context-menu.service.ts deleted file mode 100644 index 6e86dce0f5..0000000000 --- a/frontend/app/components/context-menus/context-menu.service.ts +++ /dev/null @@ -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; - open(nextTo:JQuery,locals:Object):Promise; - - 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 { - 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 = 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); diff --git a/frontend/app/components/op-context-menu/op-context-menu-handler.ts b/frontend/app/components/op-context-menu/op-context-menu-handler.ts index 8a07dcce1f..c5b36a1af1 100644 --- a/frontend/app/components/op-context-menu/op-context-menu-handler.ts +++ b/frontend/app/components/op-context-menu/op-context-menu-handler.ts @@ -25,6 +25,10 @@ export abstract class OpContextMenuHandler { target.focus(); } + public onOpen(menu:JQuery) { + menu.find('.menu-item').first().focus(); + } + /** * Positioning args for jquery-ui position. * diff --git a/frontend/app/components/op-context-menu/op-context-menu.service.ts b/frontend/app/components/op-context-menu/op-context-menu.service.ts index 4eace4f2e9..49d065c303 100644 --- a/frontend/app/components/op-context-menu/op-context-menu.service.ts +++ b/frontend/app/components/op-context-menu/op-context-menu.service.ts @@ -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 {TransitionService} from "@uirouter/core"; 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 {OPContextMenuComponent} from "core-components/op-context-menu/op-context-menu.component"; - @Injectable() export class OPContextMenuService { 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 private bodyPortalHost:DomPortalOutlet; + // Allow temporarily disabling the close handler + private isOpening = false; + constructor(private componentFactoryResolver:ComponentFactoryResolver, + @Inject(FocusHelperToken) readonly FocusHelper:any, private appRef:ApplicationRef, private $transitions:TransitionService, private injector:Injector) { @@ -39,8 +45,10 @@ export class OPContextMenuService { Mousetrap.bind('escape', () => this.close()); // Listen to any click and close the active context menu - jQuery(window).click(() => { - this.active && this.close(); + jQuery(window).click((evt) => { + 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 * @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(); // Create a portal for the given component class and render it + this.isOpening = true; const portal = new ComponentPortal(component, null, this.injectorFor(menu.locals)); this.bodyPortalHost.attach(portal); this.portalHostElement.style.display = 'block'; @@ -60,7 +69,8 @@ export class OPContextMenuService { setTimeout(() => { this.reposition(event); // 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. */ public close() { + if (this.isOpening) { + return; + } + // Detach any component currently in the portal this.bodyPortalHost.detach(); this.portalHostElement.style.display = 'none'; diff --git a/frontend/app/components/routing/wp-list/wp-list.component.ts b/frontend/app/components/routing/wp-list/wp-list.component.ts index 76b7f547ac..cf42037657 100644 --- a/frontend/app/components/routing/wp-list/wp-list.component.ts +++ b/frontend/app/components/routing/wp-list/wp-list.component.ts @@ -64,7 +64,6 @@ export class WorkPackagesListComponent implements OnInit, OnDestroy { text = { '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'), - 'search_query_title': this.I18n.t('js.toolbar.search_query_title'), 'button_settings': this.I18n.t('js.button_settings') }; diff --git a/frontend/app/components/routing/wp-list/wp.list.component.html b/frontend/app/components/routing/wp-list/wp.list.component.html index a199e446b4..e825240201 100644 --- a/frontend/app/components/routing/wp-list/wp.list.component.html +++ b/frontend/app/components/routing/wp-list/wp.list.component.html @@ -1,25 +1,7 @@
-
-
-

- - - - {{ selectedTitle | slice:0:50 }} - - -

-
-
+
    diff --git a/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field.component.ts b/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field.component.ts index 8c4bfcfd9a..69cfd89dce 100644 --- a/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field.component.ts +++ b/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field.component.ts @@ -27,7 +27,6 @@ // ++ 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 {WorkPackageNotificationService} from '../wp-notification.service'; 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 {WorkPackageEditFieldGroupComponent} from 'core-components/wp-edit/wp-edit-field/wp-edit-field-group.directive'; import {ConfigurationService} from 'core-components/common/config/configuration.service'; +import {OPContextMenuService} from "core-components/op-context-menu/op-context-menu.service"; @Component({ template: require('!!raw-loader!./wp-edit-field.html'), @@ -69,7 +69,7 @@ export class WorkPackageEditFieldComponent implements OnInit { protected elementRef:ElementRef, protected wpNotificationsService:WorkPackageNotificationService, protected ConfigurationService:ConfigurationService, - protected contextMenu:ContextMenuService, + protected opContextMenu:OPContextMenuService, protected wpEditing:WorkPackageEditingService, protected wpCacheService:WorkPackageCacheService, // Get parent field group from injector @@ -122,7 +122,7 @@ export class WorkPackageEditFieldComponent implements OnInit { this.handleUserActivate(event); } - this.contextMenu.close(); + this.opContextMenu.close(); event.stopImmediatePropagation(); return false; diff --git a/frontend/app/components/wp-query-select/wp-query-select.controller.ts b/frontend/app/components/wp-query-select/wp-query-select-dropdown.component.ts similarity index 74% rename from frontend/app/components/wp-query-select/wp-query-select.controller.ts rename to frontend/app/components/wp-query-select/wp-query-select-dropdown.component.ts index c6e6f59b77..0bb866a20f 100644 --- a/frontend/app/components/wp-query-select/wp-query-select.controller.ts +++ b/frontend/app/components/wp-query-select/wp-query-select-dropdown.component.ts @@ -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 {States} from '../states.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 {WorkPackagesListChecksumService} from '../wp-list/wp-list-checksum.service'; 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 { label:string; @@ -44,43 +49,31 @@ interface IQueryAutocompleteJQuery extends JQuery { querycomplete({}):void; } -interface MyScope extends ng.IScope { - loaded:boolean; - i18n:MyI18n; -} - -interface MyI18n { - loading:string; - label:string; - scope_global:string; - scope_private:string; - no_results:string; -} - -export class WorkPackageQuerySelectController { - constructor(private $scope:MyScope, - private QueryDm:QueryDmService, - private $state:StateService, - private states:States, - private wpListService:WorkPackagesListService, - private contextMenu:ContextMenuService, - private I18n:op.I18n, - 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') - }; +@Component({ + template: require('!!raw-loader!./wp-query-select.template.html') +}) +export class WorkPackageQuerySelectDropdownComponent implements OnInit { + public loaded = false; + public text = { + loading: this.I18n.t('js.ajax.loading'), + label: this.I18n.t('js.toolbar.search_query_label'), + scope_global: this.I18n.t('js.label_global_queries'), + scope_private: this.I18n.t('js.label_custom_queries'), + no_results: this.I18n.t('js.work_packages.query.text_no_results') + }; + + constructor(readonly QueryDm:QueryDmService, + @Inject($stateToken) readonly $state:StateService, + @Inject(I18nToken) readonly I18n:op.I18n, + @Inject(OpContextMenuLocalsToken) public locals:OpContextMenuLocalsMap, + readonly states:States, + readonly wpListService:WorkPackagesListService, + readonly wpListChecksumService:WorkPackagesListChecksumService, + readonly loadingIndicator:LoadingIndicatorService) { - this.setup(); } - private setup() { + public ngOnInit() { this.loadQueries().then(collection => { let sortedQueries = _.reverse(_.sortBy(collection.elements, 'public')); let autocompleteValues = _.map(sortedQueries, (query:any) => { return { label: query.name, query: query }; } ); @@ -123,9 +116,9 @@ export class WorkPackageQuerySelectController { private defineJQueryQueryComplete() { let labelFunction = (isPublic:boolean) => { if (isPublic) { - return this.$scope.i18n.scope_global; + return this.text.scope_global; } else { - return this.$scope.i18n.scope_private; + return this.text.scope_private; } }; @@ -154,11 +147,11 @@ export class WorkPackageQuerySelectController { private loadQuery(query:QueryResource) { this.wpListChecksumService.clear(); this.loadingIndicator.table.promise = this.wpListService.reloadQuery(query); - this.contextMenu.close(); + this.locals.service.close(); } private setLoaded() { - this.$scope.loaded = true; - this.$scope.i18n.loading = ''; + this.loaded = true; + this.text.loading = ''; } } diff --git a/frontend/app/components/wp-query-select/wp-query-select.service.test.ts b/frontend/app/components/wp-query-select/wp-query-select.service.test.ts deleted file mode 100644 index 26b7f8abdd..0000000000 --- a/frontend/app/components/wp-query-select/wp-query-select.service.test.ts +++ /dev/null @@ -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 = '
    '; - 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'); - }); - }); -}); diff --git a/frontend/app/components/wp-query-select/wp-query-select.service.ts b/frontend/app/components/wp-query-select/wp-query-select.service.ts deleted file mode 100644 index 19ae3c1f6c..0000000000 --- a/frontend/app/components/wp-query-select/wp-query-select.service.ts +++ /dev/null @@ -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); diff --git a/frontend/app/components/wp-query-select/wp-query-select.template.html b/frontend/app/components/wp-query-select/wp-query-select.template.html index b16d17f3d8..2977f94b44 100644 --- a/frontend/app/components/wp-query-select/wp-query-select.template.html +++ b/frontend/app/components/wp-query-select/wp-query-select.template.html @@ -1,11 +1,16 @@ diff --git a/frontend/app/components/wp-query-select/wp-query-selectable-title.component.ts b/frontend/app/components/wp-query-select/wp-query-selectable-title.component.ts new file mode 100644 index 0000000000..55caeb2908 --- /dev/null +++ b/frontend/app/components/wp-query-select/wp-query-selectable-title.component.ts @@ -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') + }; + } +} + diff --git a/frontend/app/components/wp-query-select/wp-query-selectable-title.html b/frontend/app/components/wp-query-select/wp-query-selectable-title.html new file mode 100644 index 0000000000..c2b29f51a8 --- /dev/null +++ b/frontend/app/components/wp-query-select/wp-query-selectable-title.html @@ -0,0 +1,13 @@ +
    +
    +

    + + + {{ selectedTitle | slice:0:50 }} + +

    +
    +
    diff --git a/frontend/app/components/wp-table/wp-table.directive.ts b/frontend/app/components/wp-table/wp-table.directive.ts index ff8012c848..d82f16f036 100644 --- a/frontend/app/components/wp-table/wp-table.directive.ts +++ b/frontend/app/components/wp-table/wp-table.directive.ts @@ -27,7 +27,6 @@ // ++ 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 {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'; @@ -36,7 +35,6 @@ import {TableState, TableStateHolder} from 'core-components/wp-table/table-state import {untilComponentDestroyed} from 'ng2-rx-componentdestroyed'; import {combineLatest} from 'rxjs/observable/combineLatest'; import {debugLog} from '../../helpers/debug_output'; -import {ContextMenuService} from '../context-menus/context-menu.service'; import {States} from '../states.service'; import {WorkPackageTableColumnsService} from '../wp-fast-table/state/wp-table-columns.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 {WpTableHoverSync} from './wp-table-hover-sync'; import {createScrollSync} from './wp-table-scroll-sync'; +import {OPContextMenuService} from "core-components/op-context-menu/op-context-menu.service"; @Component({ template: require('!!raw-loader!./wp-table.directive.html'), @@ -91,7 +90,7 @@ export class WorkPackagesTableController implements OnInit, OnDestroy { public injector:Injector, private readonly tableStateHolder:TableStateHolder, @Inject(columnsModalToken) private columnsModal:any, - private contextMenu:ContextMenuService, + private opContextMenu:OPContextMenuService, private states:States, @Inject(I18nToken) private I18n:op.I18n, private wpTableGroupBy:WorkPackageTableGroupByService, @@ -180,7 +179,7 @@ export class WorkPackagesTableController implements OnInit, OnDestroy { } public openColumnsModal() { - this.contextMenu.close(); + this.opContextMenu.close(); this.columnsModal.activate(); }