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="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" |
||||
class="ui-autocomplete--input" |
||||
name="query-title-filter" |
||||
id="query-title-filter" |
||||
placeholder="{{ i18n.loading }}"> |
||||
[attr.placeholder]="text.loading"> |
||||
<i id="magnifier" class="icon-search"></i> |
||||
</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> |
||||
|
@ -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