parent
9a8faafafb
commit
8b102726c7
@ -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); |
|
@ -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> |
Loading…
Reference in new issue