diff --git a/app/assets/stylesheets/content/_forms.sass b/app/assets/stylesheets/content/_forms.sass index 411c4648a0..7dac44a519 100644 --- a/app/assets/stylesheets/content/_forms.sass +++ b/app/assets/stylesheets/content/_forms.sass @@ -161,7 +161,10 @@ $form--field-types: (text-field, text-area, select, check-box, radio-button, ran border-bottom-width: 0px .form--space - padding-top: 10px + padding-top: 1rem + + &.-left-spacing + padding-left: 1rem &.-big padding-top: 20px diff --git a/frontend/app/angular4-modules.ts b/frontend/app/angular4-modules.ts index dd2680934b..e8dd0a651c 100644 --- a/frontend/app/angular4-modules.ts +++ b/frontend/app/angular4-modules.ts @@ -74,7 +74,6 @@ import { TextileServiceToken, upgradeService, upgradeServiceWithToken, WorkPackageServiceToken, - wpDestroyModalToken, wpMoreMenuServiceToken } from './angular4-transition-utils'; import {WpCustomActionComponent} from 'core-components/wp-custom-actions/wp-custom-actions/wp-custom-action.component'; @@ -220,6 +219,7 @@ import {SaveQueryModal} from "core-components/modals/save-modal/save-query.modal import {QuerySharingForm} from "core-components/modals/share-modal/query-sharing-form.component"; import {RenameQueryModal} from "core-components/modals/rename-query-modal/rename-query.modal"; import {FocusHelperService} from 'core-components/common/focus/focus-helper'; +import {WpDestroyModal} from "core-components/modals/wp-destroy-modal/wp-destroy.modal"; @NgModule({ imports: [ @@ -250,7 +250,6 @@ import {FocusHelperService} from 'core-components/common/focus/focus-helper'; PathHelperService, upgradeServiceWithToken('wpMoreMenuService', wpMoreMenuServiceToken), TimezoneService, - upgradeServiceWithToken('wpDestroyModal', wpDestroyModalToken), upgradeService('wpRelations', WorkPackageRelationsService), UrlParamsHelperService, WorkPackageCacheService, @@ -453,6 +452,7 @@ import {FocusHelperService} from 'core-components/common/focus/focus-helper'; SaveQueryModal, QuerySharingForm, RenameQueryModal, + WpDestroyModal, // Notifications NotificationsContainerComponent, @@ -522,6 +522,7 @@ import {FocusHelperService} from 'core-components/common/focus/focus-helper'; QuerySharingModal, SaveQueryModal, RenameQueryModal, + WpDestroyModal, // Notifications NotificationsContainerComponent, diff --git a/frontend/app/angular4-transition-utils.ts b/frontend/app/angular4-transition-utils.ts index 6f2e7606f0..29b5a49b5b 100644 --- a/frontend/app/angular4-transition-utils.ts +++ b/frontend/app/angular4-transition-utils.ts @@ -43,7 +43,6 @@ export const AutoCompleteHelperServiceToken = new InjectionToken('AutoCompl export const TextileServiceToken = new InjectionToken('TextileServiceToken'); export const wpMoreMenuServiceToken = new InjectionToken('wpMoreMenuService'); export const $httpToken = new InjectionToken('$http'); -export const wpDestroyModalToken = new InjectionToken('wpDestroyModal'); export const OpContextMenuLocalsToken = new InjectionToken('CONTEXT_MENU_LOCALS'); export const OpModalLocalsToken = new InjectionToken('OP_MODAL_LOCALS'); export const HookServiceToken = new InjectionToken('HookService'); diff --git a/frontend/app/components/modals/wp-destroy-modal/wp-destroy-modal.controller.ts b/frontend/app/components/modals/wp-destroy-modal/wp-destroy-modal.controller.ts deleted file mode 100644 index 4aa3367aec..0000000000 --- a/frontend/app/components/modals/wp-destroy-modal/wp-destroy-modal.controller.ts +++ /dev/null @@ -1,105 +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 {wpControllersModule} from '../../../angular-modules'; -import {States} from '../../states.service'; -import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; -import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service'; -import {StateService} from '@uirouter/core'; - -export class WorkPackageDestroyModalController { - public text:any; - public workPackages:WorkPackageResource[]; - public workPackageLabel:string; - - constructor(private $scope:any, - private $state:StateService, - private states:States, - private WorkPackageService:any, - private wpTableFocus:WorkPackageTableFocusService, - private I18n:op.I18n, - private wpDestroyModal:any) { - - this.workPackages = $scope.workPackages; - this.workPackageLabel = I18n.t('js.units.workPackage', { count: this.workPackages.length }); - - this.text = { - close: I18n.t('js.close_popup_title'), - cancel: I18n.t('js.button_cancel'), - confirm: I18n.t('js.button_confirm'), - warning: I18n.t('js.label_warning'), - - title: I18n.t('js.modals.destroy_work_package.title', { label: this.workPackageLabel }), - text: I18n.t('js.modals.destroy_work_package.text', { label: this.workPackageLabel, count: this.workPackages.length }), - - childCount: (wp:WorkPackageResource) => { - const count = this.children(wp).length; - return this.I18n.t('js.units.child_work_packages', {count: count}); - }, - hasChildren: (wp:WorkPackageResource) => - I18n.t('js.modals.destroy_work_package.has_children', {childUnits: this.text.childCount(wp) }), - deletesChildren: I18n.t('js.modals.destroy_work_package.deletes_children') - }; - } - - public $onInit() { - // Created for interface compliance - } - - public close() { - try { - this.wpDestroyModal.deactivate(); - } catch(e) { - console.error("Failed to close deletion modal: " + e); - } - } - - public confirmDeletion() { - this.wpDestroyModal.deactivate(); - this.WorkPackageService.performBulkDelete(this.workPackages.map(el => el.id), true) - .then(() => { - this.close(); - this.wpTableFocus.clear(); - this.$state.go('work-packages.list'); - }); - } - - public childLabel (workPackage:WorkPackageResource) { - } - - public children(workPackage:WorkPackageResource) { - if (workPackage.hasOwnProperty('children')) { - return workPackage.children; - } else { - return []; - } - } - -} - -wpControllersModule.controller('WorkPackageDestroyModalController', WorkPackageDestroyModalController); diff --git a/frontend/app/components/modals/wp-destroy-modal/wp-destroy-modal.html b/frontend/app/components/modals/wp-destroy-modal/wp-destroy-modal.html deleted file mode 100644 index 555790bcb4..0000000000 --- a/frontend/app/components/modals/wp-destroy-modal/wp-destroy-modal.html +++ /dev/null @@ -1,76 +0,0 @@ -
-
- - -
-
-

- - - - -
- -
- - -
-
- -
-
diff --git a/frontend/app/components/modals/wp-destroy-modal/wp-destroy-modal.ts b/frontend/app/components/modals/wp-destroy-modal/wp-destroy-modal.ts deleted file mode 100644 index 95771a5f5f..0000000000 --- a/frontend/app/components/modals/wp-destroy-modal/wp-destroy-modal.ts +++ /dev/null @@ -1,39 +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 {wpControllersModule} from '../../../angular-modules'; - -function wpDestroyService(btfModal:any) { - return btfModal({ - controller: 'WorkPackageDestroyModalController', - controllerAs: '$ctrl', - templateUrl: '/components/modals/wp-destroy-modal/wp-destroy-modal.html' - }); -} - -wpControllersModule.factory('wpDestroyModal', wpDestroyService); diff --git a/frontend/app/components/modals/wp-destroy-modal/wp-destroy.modal.html b/frontend/app/components/modals/wp-destroy-modal/wp-destroy.modal.html new file mode 100644 index 0000000000..c686275ced --- /dev/null +++ b/frontend/app/components/modals/wp-destroy-modal/wp-destroy.modal.html @@ -0,0 +1,78 @@ +
+
+ + +
+
+

+ + + + +
+ +
+ + +
+
+
+
diff --git a/frontend/app/components/modals/wp-destroy-modal/wp-destroy.modal.ts b/frontend/app/components/modals/wp-destroy-modal/wp-destroy.modal.ts new file mode 100644 index 0000000000..f74abad431 --- /dev/null +++ b/frontend/app/components/modals/wp-destroy-modal/wp-destroy.modal.ts @@ -0,0 +1,118 @@ +//-- 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 {WorkPackagesListService} from '../../wp-list/wp-list.service'; +import {States} from '../../states.service'; +import {WorkPackageNotificationService} from '../../wp-edit/wp-notification.service'; +import {NotificationsService} from "core-components/common/notifications/notifications.service"; +import {OpModalComponent} from "core-components/op-modals/op-modal.component"; +import {Component, ElementRef, Inject, OnInit} from "@angular/core"; +import {$stateToken, I18nToken, OpModalLocalsToken, WorkPackageServiceToken} from "core-app/angular4-transition-utils"; +import {OpModalLocalsMap} from "core-components/op-modals/op-modal.types"; +import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; +import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service'; +import {StateService} from '@uirouter/core'; + +@Component({ + template: require('!!raw-loader!./wp-destroy.modal.html') +}) +export class WpDestroyModal extends OpModalComponent implements OnInit { + // When deleting multiple + public workPackages:WorkPackageResource[]; + public workPackageLabel:string; + + // Single work package + public singleWorkPackage:WorkPackageResource; + public singleWorkPackageChildren:WorkPackageResource[]; + + public text:{ [key:string]:any } = { + label_visibility_settings: this.I18n.t('js.label_visibility_settings'), + button_save: this.I18n.t('js.modals.button_save'), + confirm: this.I18n.t('js.button_confirm'), + warning: this.I18n.t('js.label_warning'), + cancel: this.I18n.t('js.button_cancel'), + close: this.I18n.t('js.close_popup_title'), + }; + + constructor(readonly elementRef:ElementRef, + @Inject(WorkPackageServiceToken) readonly WorkPackageService:any, + @Inject(OpModalLocalsToken) public locals:OpModalLocalsMap, + @Inject(I18nToken) readonly I18n:op.I18n, + @Inject($stateToken) readonly $state:StateService, + readonly states:States, + readonly wpTableFocus:WorkPackageTableFocusService, + readonly wpListService:WorkPackagesListService, + readonly wpNotificationsService:WorkPackageNotificationService, + readonly notificationsService:NotificationsService) { + super(locals, elementRef); + } + + ngOnInit() { + super.ngOnInit(); + + this.workPackages = this.locals.workPackages; + this.workPackageLabel = this.I18n.t('js.units.workPackage', { count: this.workPackages.length }); + + // Ugly way to provide the same view bindings as the ng-init in the previous template. + if (this.workPackages.length === 1) { + this.singleWorkPackage = this.workPackages[0]; + this.singleWorkPackageChildren = this.singleWorkPackage.children; + } + + this.text.title = this.I18n.t('js.modals.destroy_work_package.title', { label: this.workPackageLabel }), + this.text.text = this.I18n.t('js.modals.destroy_work_package.text', { label: this.workPackageLabel, count: this.workPackages.length }), + + this.text.childCount = (wp:WorkPackageResource) => { + const count = this.children(wp).length; + return this.I18n.t('js.units.child_work_packages', {count: count}); + }; + + this.text.hasChildren = (wp:WorkPackageResource) => + this.I18n.t('js.modals.destroy_work_package.has_children', {childUnits: this.text.childCount(wp) }), + + this.text.deletesChildren = this.I18n.t('js.modals.destroy_work_package.deletes_children'); + } + + + public confirmDeletion($event:JQueryEventObject) { + this.WorkPackageService.performBulkDelete(this.workPackages.map(el => el.id), true) + .then(() => { + this.closeMe($event); + this.wpTableFocus.clear(); + this.$state.go('work-packages.list'); + }); + } + + public children(workPackage:WorkPackageResource) { + if (workPackage.hasOwnProperty('children')) { + return workPackage.children; + } else { + return []; + } + } +} diff --git a/frontend/app/components/op-context-menu/wp-context-menu/wp-single-context-menu.ts b/frontend/app/components/op-context-menu/wp-context-menu/wp-single-context-menu.ts index e8d63ba9d1..c01659e0fc 100644 --- a/frontend/app/components/op-context-menu/wp-context-menu/wp-single-context-menu.ts +++ b/frontend/app/components/op-context-menu/wp-context-menu/wp-single-context-menu.ts @@ -2,8 +2,7 @@ import {Directive, ElementRef, Inject, Input} from "@angular/core"; import {WorkPackageAction} from "core-components/wp-table/context-menu-helper/wp-context-menu-helper.service"; import { $stateToken, - HookServiceToken, - wpDestroyModalToken + HookServiceToken } from "core-app/angular4-transition-utils"; import {LinkHandling} from "core-components/common/link-handling/link-handling"; import {OPContextMenuService} from "core-components/op-context-menu/op-context-menu.service"; @@ -13,6 +12,8 @@ import {OpContextMenuTrigger} from "core-components/op-context-menu/handlers/op- import {WorkPackageAuthorization} from "core-components/work-packages/work-package-authorization.service"; import {AuthorisationService} from "core-components/common/model-auth/model-auth.service"; import {StateService} from "@uirouter/core"; +import {OpModalService} from "core-components/op-modals/op-modal.service"; +import {WpDestroyModal} from "core-components/modals/wp-destroy-modal/wp-destroy.modal"; @Directive({ selector: '[wpSingleContextMenu]' @@ -21,9 +22,9 @@ export class WorkPackageSingleContextMenuDirective extends OpContextMenuTrigger @Input('wpSingleContextMenu-workPackage') public workPackage:WorkPackageResource; constructor(@Inject(HookServiceToken) readonly HookService:any, - @Inject(wpDestroyModalToken) readonly wpDestroyModal:any, @Inject($stateToken) readonly $state:StateService, readonly elementRef:ElementRef, + readonly opModalService:OpModalService, readonly opContextMenuService:OPContextMenuService, readonly authorisationService:AuthorisationService) { super(elementRef, opContextMenuService); @@ -50,7 +51,7 @@ export class WorkPackageSingleContextMenuDirective extends OpContextMenuTrigger this.$state.go('work-packages.copy', {copiedFromWorkPackageId: this.workPackage.id}); break; case 'delete': - this.wpDestroyModal.activate({workPackages: [this.workPackage]}); + this.opModalService.show(WpDestroyModal, {workPackages: [this.workPackage]}); break; default: @@ -78,7 +79,7 @@ export class WorkPackageSingleContextMenuDirective extends OpContextMenuTrigger private getPermittedPluginActions(authorization:WorkPackageAuthorization) { var pluginActions:WorkPackageAction[] = []; - angular.forEach(this.HookService.call('workPackageDetailsMoreMenu'), function (action) { + angular.forEach(this.HookService.call('workPackageDetailsMoreMenu'), function(action) { pluginActions = pluginActions.concat(action); }); diff --git a/frontend/app/components/op-context-menu/wp-context-menu/wp-table-context-menu.directive.ts b/frontend/app/components/op-context-menu/wp-context-menu/wp-table-context-menu.directive.ts index fd0ed7c529..5b2ff2624f 100644 --- a/frontend/app/components/op-context-menu/wp-context-menu/wp-table-context-menu.directive.ts +++ b/frontend/app/components/op-context-menu/wp-context-menu/wp-table-context-menu.directive.ts @@ -6,7 +6,7 @@ import { import {WorkPackageTable} from "core-components/wp-fast-table/wp-fast-table"; import {States} from "core-components/states.service"; import {WorkPackageRelationsHierarchyService} from "core-components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service"; -import {$stateToken, wpDestroyModalToken} from "core-app/angular4-transition-utils"; +import {$stateToken} from "core-app/angular4-transition-utils"; import {WorkPackageTableSelection} from "core-components/wp-fast-table/state/wp-table-selection.service"; import {LinkHandling} from "core-components/common/link-handling/link-handling"; import {OpContextMenuHandler} from "core-components/op-context-menu/op-context-menu-handler"; @@ -16,12 +16,14 @@ import { OpContextMenuLocalsMap } from "core-components/op-context-menu/op-context-menu.types"; import {PERMITTED_CONTEXT_MENU_ACTIONS} from "core-components/op-context-menu/wp-context-menu/wp-static-context-menu-actions"; +import {OpModalService} from "core-components/op-modals/op-modal.service"; +import {WpDestroyModal} from "core-components/modals/wp-destroy-modal/wp-destroy.modal"; export class OpWorkPackageContextMenu extends OpContextMenuHandler { private states = this.injector.get(States); private wpRelationsHierarchyService = this.injector.get(WorkPackageRelationsHierarchyService); - private wpDestroyModal = this.injector.get(wpDestroyModalToken); + private opModalService:OpModalService = this.injector.get(OpModalService); private $state = this.injector.get($stateToken); private wpTableSelection = this.injector.get(WorkPackageTableSelection); private WorkPackageContextMenuHelper = this.injector.get(WorkPackageContextMenuHelperService); @@ -89,7 +91,7 @@ export class OpWorkPackageContextMenu extends OpContextMenuHandler { private deleteSelectedWorkPackages() { var selected = this.getSelectedWorkPackages(); - this.wpDestroyModal.activate({workPackages: selected}); + this.opModalService.show(WpDestroyModal, {workPackages: selected}); } private editSelectedWorkPackages(link:any) { diff --git a/frontend/tests/unit/tests/ui_components/focus-directive-test.js b/frontend/tests/unit/tests/ui_components/focus-directive-test.js deleted file mode 100644 index 055f03a057..0000000000 --- a/frontend/tests/unit/tests/ui_components/focus-directive-test.js +++ /dev/null @@ -1,76 +0,0 @@ -//-- copyright -// OpenProject is a project management system. -// Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details. -//++ - -/*jshint expr: true*/ - -describe('focus Directive', function() { - var doc, compile, element, rootScope, scope, timeout, body; - - var input = '', - element = angular.element(input); - - beforeEach(angular.mock.module('openproject.uiComponents')); - beforeEach(angular.mock.module('openproject.templates')); - - beforeEach(angular.mock.module('openproject.uiComponents', function($provide) { - var configurationService = {}; - - configurationService.isTimezoneSet = sinon.stub().returns(false); - configurationService.accessibilityModeEnabled = sinon.stub().returns(false); - - $provide.constant('ConfigurationService', configurationService); - })); - - beforeEach(inject(function($compile, $rootScope, $document, $timeout) { - doc = $document[0]; - rootScope = $rootScope; - scope = $rootScope.$new(); - body = angular.element(doc.body); - body.append(element); - - compile = $compile; - timeout = $timeout; - })); - - afterEach(function() { - var body = angular.element(doc.body); - - body.find('#focusTest').remove(); - }); - - describe('element', function() { - it('should focus the element', function() { - compile(element)(scope); - scope.$digest(); - - timeout.flush(); - - expect(doc.activeElement).to.equal(element[0]); - }); - }); -});