From 5fb09b2bd5ee5fc561665965918c5fd9e6276605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Thu, 25 Oct 2018 11:41:43 +0200 Subject: [PATCH 1/6] Allow split inline-create for referencable environments --- .../wp-inline-create/wp-inline-create.component.html | 12 ++++++++++++ .../wp-inline-create/wp-inline-create.component.ts | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/frontend/src/app/components/wp-inline-create/wp-inline-create.component.html b/frontend/src/app/components/wp-inline-create/wp-inline-create.component.html index 643b059ef2..4878d5cb09 100644 --- a/frontend/src/app/components/wp-inline-create/wp-inline-create.component.html +++ b/frontend/src/app/components/wp-inline-create/wp-inline-create.component.html @@ -7,12 +7,24 @@ href role="link" [focus]="focus" + [ngClass]="{'wp-inline-create--split-link': isReferencable }" (accessibleClick)="handleAddRowClick()" [attr.disabled]="!isAllowed || undefined" [attr.aria-label]="text.create" aria-haspopup="true"> + + + + + diff --git a/frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts b/frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts index 4aff8dced6..36e0e23009 100644 --- a/frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts +++ b/frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts @@ -85,6 +85,10 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe create: this.I18n.t('js.label_create_work_package') }; + // Linking state + + public isReferencable = false; + private currentWorkPackage:WorkPackageResource | null; private workPackageEditForm:WorkPackageEditForm | undefined; From a31115a74b272a56281547d80cb22aea16f594ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Thu, 25 Oct 2018 14:32:36 +0200 Subject: [PATCH 2/6] Dynamic component for referencing --- .../work_packages/_table_inline_create.sass | 9 ++- .../persistent-toggle.directive.ts | 8 +- frontend/src/app/angular4-modules.ts | 5 ++ .../wp-inline-create.component.html | 67 +++++++++------- .../wp-inline-create.component.ts | 36 ++++++--- .../wp-inline-create.service.ts | 43 ++-------- .../wp-inline-reference.component.ts | 80 +++++++++++++++++++ .../app/modules/common/i18n/i18n.service.ts | 2 +- 8 files changed, 169 insertions(+), 81 deletions(-) create mode 100644 frontend/src/app/components/wp-inline-create/wp-inline-reference.component.ts diff --git a/app/assets/stylesheets/content/work_packages/_table_inline_create.sass b/app/assets/stylesheets/content/work_packages/_table_inline_create.sass index ba9b5744e6..0a9e3e5a5b 100644 --- a/app/assets/stylesheets/content/work_packages/_table_inline_create.sass +++ b/app/assets/stylesheets/content/work_packages/_table_inline_create.sass @@ -11,14 +11,19 @@ a width: 100% padding: 0.5rem 0 - display: block + display: inline-block line-height: 1.6 + a.wp-inline-create--split-link + width: 50% + + &:hover background: #e4f7fb -.wp-inline-create--add-link +.wp-inline-create--add-link, +.wp-inline-create--reference-link font-weight: bold .icon::before diff --git a/frontend/legacy/app/components/persistent-toggle/persistent-toggle.directive.ts b/frontend/legacy/app/components/persistent-toggle/persistent-toggle.directive.ts index 5a04ee2917..d824d92a84 100644 --- a/frontend/legacy/app/components/persistent-toggle/persistent-toggle.directive.ts +++ b/frontend/legacy/app/components/persistent-toggle/persistent-toggle.directive.ts @@ -36,13 +36,13 @@ function persistentToggleDirective($timeout:ITimeoutService) { var clickHandler = element.find('.persistent-toggle--click-handler'), targetNotification = element.find('.persistent-toggle--notification'); - scope.isHidden = window.OpenProject.guardedLocalStorage(attributes.identifier) === 'true'; + scope.isActive = window.OpenProject.guardedLocalStorage(attributes.identifier) === 'true'; function toggle(isNowHidden:boolean) { window.OpenProject.guardedLocalStorage(attributes.identifier, (!!isNowHidden).toString()); scope.$apply(function() { - scope.isHidden = isNowHidden; + scope.isActive = isNowHidden; }); if (isNowHidden) { @@ -58,7 +58,7 @@ function persistentToggleDirective($timeout:ITimeoutService) { // Clicking the handler toggles the notification clickHandler.bind('click', function() { - toggle(!scope.isHidden); + toggle(!scope.isActive); }); // Closing the notification remembers the decision @@ -67,7 +67,7 @@ function persistentToggleDirective($timeout:ITimeoutService) { }); // Set initial state - targetNotification.prop('hidden', !!scope.isHidden); + targetNotification.prop('hidden', !!scope.isActive); } }; }; diff --git a/frontend/src/app/angular4-modules.ts b/frontend/src/app/angular4-modules.ts index 2aefddc8e6..9266205fde 100644 --- a/frontend/src/app/angular4-modules.ts +++ b/frontend/src/app/angular4-modules.ts @@ -221,6 +221,7 @@ import {WorkPackageEmbeddedGraphComponent} from "core-components/wp-table/embedd import {WorkPackageByVersionGraphComponent} from "core-components/wp-by-version-graph/wp-by-version-graph.component"; import {WorkPackageInlineCreateService} from "core-components/wp-inline-create/wp-inline-create.service"; import {WorkPackageCommentFieldComponent} from "core-components/work-packages/work-package-comment/wp-comment-field.component"; +import {WorkPackageInlineReferenceComponent} from "core-components/wp-inline-create/wp-inline-reference.component"; @NgModule({ imports: [ @@ -447,6 +448,7 @@ import {WorkPackageCommentFieldComponent} from "core-components/work-packages/wo // Inline create WorkPackageInlineCreateComponent, + WorkPackageInlineReferenceComponent, // Embedded table WorkPackageEmbeddedTableComponent, @@ -528,6 +530,9 @@ import {WorkPackageCommentFieldComponent} from "core-components/work-packages/wo WorkPackageEditFieldGroupComponent, WorkPackageCommentFieldComponent, + // Inline create + WorkPackageInlineReferenceComponent, + // Searchbar ExpandableSearchComponent, diff --git a/frontend/src/app/components/wp-inline-create/wp-inline-create.component.html b/frontend/src/app/components/wp-inline-create/wp-inline-create.component.html index 4878d5cb09..a51d4d226c 100644 --- a/frontend/src/app/components/wp-inline-create/wp-inline-create.component.html +++ b/frontend/src/app/components/wp-inline-create/wp-inline-create.component.html @@ -1,30 +1,41 @@ - - -
- - - - - + + + + - - + + + + + + +
+ + + + + + + + diff --git a/frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts b/frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts index 36e0e23009..5a9cdafeee 100644 --- a/frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts +++ b/frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts @@ -65,6 +65,7 @@ import {IWorkPackageEditingServiceToken} from "../wp-edit-form/work-package-edit import {IWorkPackageCreateServiceToken} from "core-components/wp-new/wp-create.service.interface"; import {CurrentUserService} from "core-components/user/current-user.service"; import {WorkPackageInlineCreateService} from "core-components/wp-inline-create/wp-inline-create.service"; +import {WorkPackageInlineReferenceComponent} from "core-components/wp-inline-create/wp-inline-reference.component"; @Component({ selector: '[wpInlineCreate]', @@ -77,18 +78,18 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe // inner state - public isHidden:boolean = false; + // Inline create / reference row is active + public mode:'inactive'|'create'|'reference' = 'inactive'; public focus:boolean = false; public text = { - create: this.I18n.t('js.label_create_work_package') + create: this.I18n.t('js.label_create_work_package'), + reference: 'Reference', }; // Linking state - public isReferencable = false; - private currentWorkPackage:WorkPackageResource | null; private workPackageEditForm:WorkPackageEditForm | undefined; @@ -99,8 +100,8 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe private $element:JQuery; - constructor(protected readonly elementRef:ElementRef, - protected readonly injector:Injector, + constructor(public readonly injector:Injector, + protected readonly elementRef:ElementRef, protected readonly FocusHelper:FocusHelperService, protected readonly I18n:I18nService, protected readonly tableState:TableState, @@ -122,6 +123,10 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe this.$element = jQuery(this.elementRef.nativeElement); } + get isActive():boolean { + return this.mode !== 'inactive'; + } + ngOnChanges() { if (_.isNil(this.table)) { return; @@ -167,7 +172,7 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe this.tableState.columns .values$() .pipe( - filter(() => this.isHidden), // Take only when row is inserted + filter(() => this.isActive), // Take only when row is inserted untilComponentDestroyed(this), ) .subscribe(() => { @@ -213,6 +218,19 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe return false; } + public handleReferenceClick() { + this.mode = 'reference'; + return false; + } + + public get referenceClass() { + return WorkPackageInlineReferenceComponent; + } + + public get hasReferenceClass() { + return true; // !!this.wpInlineCreate.referenceComponentClass; + } + public addWorkPackageRow() { this.wpCreate .createNewWorkPackage(this.projectIdentifier) @@ -306,11 +324,11 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe } public showRow() { - return this.isHidden = false; + this.mode = 'inactive'; } public hideRow() { - return this.isHidden = true; + this.mode = 'create'; } public get colspan():number { diff --git a/frontend/src/app/components/wp-inline-create/wp-inline-create.service.ts b/frontend/src/app/components/wp-inline-create/wp-inline-create.service.ts index 02fe6bbe95..6096a19e21 100644 --- a/frontend/src/app/components/wp-inline-create/wp-inline-create.service.ts +++ b/frontend/src/app/components/wp-inline-create/wp-inline-create.service.ts @@ -26,45 +26,10 @@ // See doc/COPYRIGHT.rdoc for more details. // ++ -import { - Component, - ElementRef, HostListener, - Inject, Injectable, - Injector, - Input, - OnChanges, - OnDestroy, - OnInit -} from '@angular/core'; -import {AuthorisationService} from 'core-app/modules/common/model-auth/model-auth.service'; -import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service'; -import {filter, takeUntil} from 'rxjs/operators'; +import {Injectable, OnDestroy} from '@angular/core'; import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; -import {WorkPackageCacheService} from '../work-packages/work-package-cache.service'; -import {TableRowEditContext} from '../wp-edit-form/table-row-edit-context'; -import {WorkPackageChangeset} from '../wp-edit-form/work-package-changeset'; -import {WorkPackageEditForm} from '../wp-edit-form/work-package-edit-form'; -import {WorkPackageEditingService} from '../wp-edit-form/work-package-editing-service'; -import {WorkPackageFilterValues} from '../wp-edit-form/work-package-filter-values'; -import {TimelineRowBuilder} from '../wp-fast-table/builders/timeline/timeline-row-builder'; -import {onClickOrEnter} from '../wp-fast-table/handlers/click-or-enter-handler'; -import {WorkPackageTableColumnsService} from '../wp-fast-table/state/wp-table-columns.service'; -import {WorkPackageTableFiltersService} from '../wp-fast-table/state/wp-table-filters.service'; -import {WorkPackageTable} from '../wp-fast-table/wp-fast-table'; -import {WorkPackageCreateService} from '../wp-new/wp-create.service'; -import { - inlineCreateCancelClassName, - InlineCreateRowBuilder, - inlineCreateRowClassName -} from './inline-create-row-builder'; -import {TableState} from 'core-components/wp-table/table-state/table-state'; -import {componentDestroyed} from 'ng2-rx-componentdestroyed'; -import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; -import {FocusHelperService} from 'core-app/modules/common/focus/focus-helper'; -import {IWorkPackageEditingServiceToken} from "../wp-edit-form/work-package-editing.service.interface"; -import {IWorkPackageCreateServiceToken} from "core-components/wp-new/wp-create.service.interface"; -import {CurrentUserService} from "core-components/user/current-user.service"; import {Observable, Subject} from "rxjs"; +import {ComponentType} from "@angular/cdk/portal"; @Injectable() export class WorkPackageInlineCreateService implements OnDestroy { @@ -86,6 +51,10 @@ export class WorkPackageInlineCreateService implements OnDestroy { return this._newInlineWorkPackage.asObservable(); } + public get referenceComponentClass():ComponentType|null { + return null; + } + /** * Notify of a new inline work package that was created * @param wp Work package that got created diff --git a/frontend/src/app/components/wp-inline-create/wp-inline-reference.component.ts b/frontend/src/app/components/wp-inline-create/wp-inline-reference.component.ts new file mode 100644 index 0000000000..8012928047 --- /dev/null +++ b/frontend/src/app/components/wp-inline-create/wp-inline-reference.component.ts @@ -0,0 +1,80 @@ +// -- 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 { + Component, + ElementRef, HostListener, + Inject, + Injector, + Input, + OnChanges, + OnDestroy, + OnInit +} from '@angular/core'; +import {AuthorisationService} from 'core-app/modules/common/model-auth/model-auth.service'; +import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service'; +import {filter, takeUntil} from 'rxjs/operators'; +import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; +import {WorkPackageCacheService} from '../work-packages/work-package-cache.service'; +import {TableRowEditContext} from '../wp-edit-form/table-row-edit-context'; +import {WorkPackageChangeset} from '../wp-edit-form/work-package-changeset'; +import {WorkPackageEditForm} from '../wp-edit-form/work-package-edit-form'; +import {WorkPackageEditingService} from '../wp-edit-form/work-package-editing-service'; +import {WorkPackageFilterValues} from '../wp-edit-form/work-package-filter-values'; +import {TimelineRowBuilder} from '../wp-fast-table/builders/timeline/timeline-row-builder'; +import {onClickOrEnter} from '../wp-fast-table/handlers/click-or-enter-handler'; +import {WorkPackageTableColumnsService} from '../wp-fast-table/state/wp-table-columns.service'; +import {WorkPackageTableFiltersService} from '../wp-fast-table/state/wp-table-filters.service'; +import {WorkPackageTable} from '../wp-fast-table/wp-fast-table'; +import {WorkPackageCreateService} from '../wp-new/wp-create.service'; +import { + inlineCreateCancelClassName, + InlineCreateRowBuilder, + inlineCreateRowClassName +} from './inline-create-row-builder'; +import {TableState} from 'core-components/wp-table/table-state/table-state'; +import {componentDestroyed, untilComponentDestroyed} from 'ng2-rx-componentdestroyed'; +import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; +import {FocusHelperService} from 'core-app/modules/common/focus/focus-helper'; +import {IWorkPackageEditingServiceToken} from "../wp-edit-form/work-package-editing.service.interface"; +import {IWorkPackageCreateServiceToken} from "core-components/wp-new/wp-create.service.interface"; +import {CurrentUserService} from "core-components/user/current-user.service"; +import {WorkPackageInlineCreateService} from "core-components/wp-inline-create/wp-inline-create.service"; +import {WorkPackageInlineCreateComponent} from "core-components/wp-inline-create/wp-inline-create.component"; + +@Component({ + template: 'HELLO THERE' +}) +export class WorkPackageInlineReferenceComponent { + constructor(public parent:WorkPackageInlineCreateComponent) { + } + + public cancel() { + this.parent.resetRow(); + } +} diff --git a/frontend/src/app/modules/common/i18n/i18n.service.ts b/frontend/src/app/modules/common/i18n/i18n.service.ts index 525d7869f1..e152cb2952 100644 --- a/frontend/src/app/modules/common/i18n/i18n.service.ts +++ b/frontend/src/app/modules/common/i18n/i18n.service.ts @@ -11,7 +11,7 @@ export interface GlobalI18n { @Injectable() export class I18nService { - private _i18n:GlobalI18n + private _i18n:GlobalI18n; constructor() { this._i18n = (window as any).I18n; From b44511787321dd94119d62f9a48dbe7d00358dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Fri, 26 Oct 2018 15:32:32 +0200 Subject: [PATCH 3/6] Provide subclass of inline create service for reference in child queries --- frontend/src/app/angular4-modules.ts | 8 +- .../wp-inline-create.component.ts | 17 ++-- .../wp-inline-create.service.ts | 37 +++---- .../wp-inline-reference.component.ts | 80 ---------------- ...p-inline-add-existing-child.component.html | 29 ++++++ .../wp-inline-add-existing-child.component.ts | 96 +++++++++++++++++++ .../wp-inline-add-existing-child.service.ts | 72 ++++++++++++++ .../wp-children-query.component.ts | 25 ++++- ...lations-autocomplete.upgraded.component.ts | 2 +- 9 files changed, 252 insertions(+), 114 deletions(-) delete mode 100644 frontend/src/app/components/wp-inline-create/wp-inline-reference.component.ts create mode 100644 frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.component.html create mode 100644 frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.component.ts create mode 100644 frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.service.ts diff --git a/frontend/src/app/angular4-modules.ts b/frontend/src/app/angular4-modules.ts index 9266205fde..ab82a0e4ee 100644 --- a/frontend/src/app/angular4-modules.ts +++ b/frontend/src/app/angular4-modules.ts @@ -221,7 +221,8 @@ import {WorkPackageEmbeddedGraphComponent} from "core-components/wp-table/embedd import {WorkPackageByVersionGraphComponent} from "core-components/wp-by-version-graph/wp-by-version-graph.component"; import {WorkPackageInlineCreateService} from "core-components/wp-inline-create/wp-inline-create.service"; import {WorkPackageCommentFieldComponent} from "core-components/work-packages/work-package-comment/wp-comment-field.component"; -import {WorkPackageInlineReferenceComponent} from "core-components/wp-inline-create/wp-inline-reference.component"; +import {WorkPackageInlineAddExistingChildService} from "core-components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.service"; +import {WorkPackageInlineAddExistingChildComponent} from "core-components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.component"; @NgModule({ imports: [ @@ -304,6 +305,7 @@ import {WorkPackageInlineReferenceComponent} from "core-components/wp-inline-cre // Provide a separate service for creation events of WP Inline create // This can be hierarchically injected to provide isolated events on an embedded table WorkPackageInlineCreateService, + WorkPackageInlineAddExistingChildService, OpTableActionsService, CurrentProjectService, @@ -448,7 +450,7 @@ import {WorkPackageInlineReferenceComponent} from "core-components/wp-inline-cre // Inline create WorkPackageInlineCreateComponent, - WorkPackageInlineReferenceComponent, + WorkPackageInlineAddExistingChildComponent, // Embedded table WorkPackageEmbeddedTableComponent, @@ -531,7 +533,7 @@ import {WorkPackageInlineReferenceComponent} from "core-components/wp-inline-cre WorkPackageCommentFieldComponent, // Inline create - WorkPackageInlineReferenceComponent, + WorkPackageInlineAddExistingChildComponent, // Searchbar ExpandableSearchComponent, diff --git a/frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts b/frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts index 5a9cdafeee..b668138b78 100644 --- a/frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts +++ b/frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts @@ -28,7 +28,8 @@ import { Component, - ElementRef, HostListener, + ElementRef, + HostListener, Inject, Injector, Input, @@ -38,7 +39,7 @@ import { } from '@angular/core'; import {AuthorisationService} from 'core-app/modules/common/model-auth/model-auth.service'; import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service'; -import {filter, takeUntil} from 'rxjs/operators'; +import {filter} from 'rxjs/operators'; import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; import {WorkPackageCacheService} from '../work-packages/work-package-cache.service'; import {TableRowEditContext} from '../wp-edit-form/table-row-edit-context'; @@ -49,7 +50,6 @@ import {WorkPackageFilterValues} from '../wp-edit-form/work-package-filter-value import {TimelineRowBuilder} from '../wp-fast-table/builders/timeline/timeline-row-builder'; import {onClickOrEnter} from '../wp-fast-table/handlers/click-or-enter-handler'; import {WorkPackageTableColumnsService} from '../wp-fast-table/state/wp-table-columns.service'; -import {WorkPackageTableFiltersService} from '../wp-fast-table/state/wp-table-filters.service'; import {WorkPackageTable} from '../wp-fast-table/wp-fast-table'; import {WorkPackageCreateService} from '../wp-new/wp-create.service'; import { @@ -58,14 +58,13 @@ import { inlineCreateRowClassName } from './inline-create-row-builder'; import {TableState} from 'core-components/wp-table/table-state/table-state'; -import {componentDestroyed, untilComponentDestroyed} from 'ng2-rx-componentdestroyed'; +import {untilComponentDestroyed} from 'ng2-rx-componentdestroyed'; import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; import {FocusHelperService} from 'core-app/modules/common/focus/focus-helper'; import {IWorkPackageEditingServiceToken} from "../wp-edit-form/work-package-editing.service.interface"; import {IWorkPackageCreateServiceToken} from "core-components/wp-new/wp-create.service.interface"; import {CurrentUserService} from "core-components/user/current-user.service"; import {WorkPackageInlineCreateService} from "core-components/wp-inline-create/wp-inline-create.service"; -import {WorkPackageInlineReferenceComponent} from "core-components/wp-inline-create/wp-inline-reference.component"; @Component({ selector: '[wpInlineCreate]', @@ -85,7 +84,7 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe public text = { create: this.I18n.t('js.label_create_work_package'), - reference: 'Reference', + reference: this.wpInlineCreate.referenceButtonText }; // Linking state @@ -203,7 +202,7 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe } // Notify inline create service - this.wpInlineCreate.newInlineWorkPackage(wp); + this.wpInlineCreate.newInlineWorkPackageCreated.next(wp.id); } else { // Remove current row this.table.editing.stopEditing('new'); @@ -224,11 +223,11 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe } public get referenceClass() { - return WorkPackageInlineReferenceComponent; + return this.wpInlineCreate.referenceComponentClass; } public get hasReferenceClass() { - return true; // !!this.wpInlineCreate.referenceComponentClass; + return !!this.referenceClass; } public addWorkPackageRow() { diff --git a/frontend/src/app/components/wp-inline-create/wp-inline-create.service.ts b/frontend/src/app/components/wp-inline-create/wp-inline-create.service.ts index 6096a19e21..f6b048f2e9 100644 --- a/frontend/src/app/components/wp-inline-create/wp-inline-create.service.ts +++ b/frontend/src/app/components/wp-inline-create/wp-inline-create.service.ts @@ -28,38 +28,39 @@ import {Injectable, OnDestroy} from '@angular/core'; import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; -import {Observable, Subject} from "rxjs"; +import {Subject} from "rxjs"; import {ComponentType} from "@angular/cdk/portal"; @Injectable() export class WorkPackageInlineCreateService implements OnDestroy { - /** Allow callbacks to happen on newly created inline work packages */ - protected _newInlineWorkPackage = new Subject(); + /** + * A separate reference pane for the inline create component + */ + public readonly referenceComponentClass:ComponentType|null = null; /** - * Ensure hierarchical injected versions of this service correctly unregister + * A related work package for the inline create context */ - ngOnDestroy() { - this._newInlineWorkPackage.complete(); - } + public referenceTarget:WorkPackageResource|null = null; /** - * Returns an observable that fires whenever a new INLINE work packages was created. + * Reference button text */ - public newInlineWorkPackageCreated$():Observable { - return this._newInlineWorkPackage.asObservable(); - } + public readonly referenceButtonText:string = ''; - public get referenceComponentClass():ComponentType|null { - return null; - } + /** Allow callbacks to happen on newly created inline work packages */ + public newInlineWorkPackageCreated = new Subject(); + + /** Allow callbacks to happen on newly created inline work packages */ + public newInlineWorkPackageReferenced = new Subject(); /** - * Notify of a new inline work package that was created - * @param wp Work package that got created + * Ensure hierarchical injected versions of this service correctly unregister */ - public newInlineWorkPackage(wp:WorkPackageResource) { - this._newInlineWorkPackage.next(wp); + ngOnDestroy() { + this.newInlineWorkPackageCreated.complete(); + this.newInlineWorkPackageReferenced.complete(); } + } diff --git a/frontend/src/app/components/wp-inline-create/wp-inline-reference.component.ts b/frontend/src/app/components/wp-inline-create/wp-inline-reference.component.ts deleted file mode 100644 index 8012928047..0000000000 --- a/frontend/src/app/components/wp-inline-create/wp-inline-reference.component.ts +++ /dev/null @@ -1,80 +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 { - Component, - ElementRef, HostListener, - Inject, - Injector, - Input, - OnChanges, - OnDestroy, - OnInit -} from '@angular/core'; -import {AuthorisationService} from 'core-app/modules/common/model-auth/model-auth.service'; -import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service'; -import {filter, takeUntil} from 'rxjs/operators'; -import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; -import {WorkPackageCacheService} from '../work-packages/work-package-cache.service'; -import {TableRowEditContext} from '../wp-edit-form/table-row-edit-context'; -import {WorkPackageChangeset} from '../wp-edit-form/work-package-changeset'; -import {WorkPackageEditForm} from '../wp-edit-form/work-package-edit-form'; -import {WorkPackageEditingService} from '../wp-edit-form/work-package-editing-service'; -import {WorkPackageFilterValues} from '../wp-edit-form/work-package-filter-values'; -import {TimelineRowBuilder} from '../wp-fast-table/builders/timeline/timeline-row-builder'; -import {onClickOrEnter} from '../wp-fast-table/handlers/click-or-enter-handler'; -import {WorkPackageTableColumnsService} from '../wp-fast-table/state/wp-table-columns.service'; -import {WorkPackageTableFiltersService} from '../wp-fast-table/state/wp-table-filters.service'; -import {WorkPackageTable} from '../wp-fast-table/wp-fast-table'; -import {WorkPackageCreateService} from '../wp-new/wp-create.service'; -import { - inlineCreateCancelClassName, - InlineCreateRowBuilder, - inlineCreateRowClassName -} from './inline-create-row-builder'; -import {TableState} from 'core-components/wp-table/table-state/table-state'; -import {componentDestroyed, untilComponentDestroyed} from 'ng2-rx-componentdestroyed'; -import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; -import {FocusHelperService} from 'core-app/modules/common/focus/focus-helper'; -import {IWorkPackageEditingServiceToken} from "../wp-edit-form/work-package-editing.service.interface"; -import {IWorkPackageCreateServiceToken} from "core-components/wp-new/wp-create.service.interface"; -import {CurrentUserService} from "core-components/user/current-user.service"; -import {WorkPackageInlineCreateService} from "core-components/wp-inline-create/wp-inline-create.service"; -import {WorkPackageInlineCreateComponent} from "core-components/wp-inline-create/wp-inline-create.component"; - -@Component({ - template: 'HELLO THERE' -}) -export class WorkPackageInlineReferenceComponent { - constructor(public parent:WorkPackageInlineCreateComponent) { - } - - public cancel() { - this.parent.resetRow(); - } -} diff --git a/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.component.html b/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.component.html new file mode 100644 index 0000000000..f33a7d2b2a --- /dev/null +++ b/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.component.html @@ -0,0 +1,29 @@ +
+
+
+ + +
+
+ + + +   + + + +
+
+
diff --git a/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.component.ts b/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.component.ts new file mode 100644 index 0000000000..e3d166c5f3 --- /dev/null +++ b/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.component.ts @@ -0,0 +1,96 @@ +// -- 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 {Component} from '@angular/core'; +import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; +import {WorkPackageInlineCreateService} from "core-components/wp-inline-create/wp-inline-create.service"; +import {WorkPackageInlineCreateComponent} from "core-components/wp-inline-create/wp-inline-create.component"; +import {WorkPackageRelationsService} from "core-components/wp-relations/wp-relations.service"; +import {WorkPackageRelationsHierarchyService} from "core-components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service"; +import {WorkPackageNotificationService} from "core-components/wp-edit/wp-notification.service"; +import {WorkPackageCacheService} from "core-components/work-packages/work-package-cache.service"; + +@Component({ + templateUrl: './wp-inline-add-existing-child.component.html' +}) +export class WorkPackageInlineAddExistingChildComponent { + public selectedWpId:string; + public isDisabled = false; + + public text = { + save: this.I18n.t('js.relation_buttons.save'), + abort: this.I18n.t('js.relation_buttons.abort'), + addNewChild: this.I18n.t('js.relation_buttons.add_new_child'), + addExistingChild: this.I18n.t('js.relation_buttons.add_existing_child') + }; + + constructor(protected readonly parent:WorkPackageInlineCreateComponent, + protected readonly wpInlineCreate:WorkPackageInlineCreateService, + protected wpCacheService:WorkPackageCacheService, + protected wpRelations:WorkPackageRelationsService, + protected wpRelationsHierarchyService:WorkPackageRelationsHierarchyService, + protected wpNotificationsService:WorkPackageNotificationService, + protected readonly I18n:I18nService) { + } + + public addExistingChild() { + if (_.isNil(this.selectedWpId)) { + return; + } + + const newChildId = this.selectedWpId; + this.isDisabled = true; + + this.wpRelationsHierarchyService + .addExistingChildWp(this.workPackage, newChildId) + .then(() => { + this.wpCacheService.loadWorkPackage(this.workPackage.id, true); + this.isDisabled = false; + this.wpInlineCreate.newInlineWorkPackageReferenced.next(newChildId); + this.cancel(); + }) + .catch((err:any) => { + this.wpNotificationsService.handleRawError(err, this.workPackage); + this.isDisabled = false; + this.cancel(); + }); + } + + public updateSelectedId(workPackageId:string) { + this.selectedWpId = workPackageId; + } + + public get workPackage() { + return this.wpInlineCreate.referenceTarget!; + } + + + public cancel() { + this.parent.resetRow(); + } +} diff --git a/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.service.ts b/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.service.ts new file mode 100644 index 0000000000..70ded37b2e --- /dev/null +++ b/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.service.ts @@ -0,0 +1,72 @@ +// -- 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 {Injectable, OnDestroy} from '@angular/core'; +import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; +import {Subject} from "rxjs"; +import {I18nService} from "core-app/modules/common/i18n/i18n.service"; +import {WorkPackageInlineCreateService} from "core-components/wp-inline-create/wp-inline-create.service"; +import {WorkPackageInlineAddExistingChildComponent} from "core-components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.component"; + +@Injectable() +export class WorkPackageInlineAddExistingChildService extends WorkPackageInlineCreateService implements OnDestroy { + + constructor(private readonly I18n:I18nService) { + super(); + } + + /** + * A separate reference pane for the inline create component + */ + public readonly referenceComponentClass = WorkPackageInlineAddExistingChildComponent; + + /** + * A related work package for the inline create context + */ + public referenceTarget:WorkPackageResource|null = null; + + /** + * Reference button text + */ + public referenceButtonText = this.I18n.t('js.relation_buttons.add_existing_child'); + + /** Allow callbacks to happen on newly created inline work packages */ + public newInlineWorkPackageCreated = new Subject(); + + /** Allow callbacks to happen on newly created inline work packages */ + public newInlineWorkPackageReferenced = new Subject(); + + /** + * Ensure hierarchical injected versions of this service correctly unregister + */ + ngOnDestroy() { + this.newInlineWorkPackageCreated.complete(); + this.newInlineWorkPackageReferenced.complete(); + } + +} diff --git a/frontend/src/app/components/wp-relations/wp-relation-children/wp-children-query.component.ts b/frontend/src/app/components/wp-relations/wp-relation-children/wp-children-query.component.ts index be9a19f762..75c06c509b 100644 --- a/frontend/src/app/components/wp-relations/wp-relation-children/wp-children-query.component.ts +++ b/frontend/src/app/components/wp-relations/wp-relation-children/wp-children-query.component.ts @@ -26,7 +26,7 @@ // See doc/COPYRIGHT.rdoc for more details. //++ -import {Component, Input, OnInit, ViewChild} from '@angular/core'; +import {Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core'; import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; import {PathHelperService} from 'core-app/modules/common/path-helper/path-helper.service'; import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; @@ -35,12 +35,18 @@ import {WorkPackageRelationsHierarchyService} from 'core-components/wp-relations import {WorkPackageEmbeddedTableComponent} from 'core-components/wp-table/embedded/wp-embedded-table.component'; import {OpUnlinkTableAction} from 'core-components/wp-table/table-actions/actions/unlink-table-action'; import {OpTableActionFactory} from 'core-components/wp-table/table-actions/table-action'; +import {WorkPackageInlineCreateService} from "core-components/wp-inline-create/wp-inline-create.service"; +import {untilComponentDestroyed} from "ng2-rx-componentdestroyed"; +import {WorkPackageInlineAddExistingChildService} from "core-components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.service"; @Component({ selector: 'wp-children-query', - templateUrl: './wp-children-query.html' + templateUrl: './wp-children-query.html', + providers: [ + { provide: WorkPackageInlineCreateService, useClass: WorkPackageInlineAddExistingChildService } + ] }) -export class WorkPackageChildrenQueryComponent implements OnInit { +export class WorkPackageChildrenQueryComponent implements OnInit, OnDestroy { @Input() public workPackage:WorkPackageResource; @Input() public query:any; @Input() public addExistingChildEnabled:boolean = false; @@ -66,11 +72,20 @@ export class WorkPackageChildrenQueryComponent implements OnInit { constructor(protected wpRelationsHierarchyService:WorkPackageRelationsHierarchyService, protected PathHelper:PathHelperService, + protected wpInlineCreate:WorkPackageInlineCreateService, protected queryUrlParamsHelper:UrlParamsHelperService, readonly I18n:I18nService) { } ngOnInit() { + // Set reference target and reference class + this.wpInlineCreate.referenceTarget = this.workPackage; + + // Wire the successful saving of a new addition to refreshing the embedded table + this.wpInlineCreate.newInlineWorkPackageReferenced + .pipe(untilComponentDestroyed(this)) + .subscribe(() => this.refreshTable()); + this.canHaveChildren = !this.workPackage.isMilestone; this.canModifyHierarchy = !!this.workPackage.changeParent; @@ -82,6 +97,10 @@ export class WorkPackageChildrenQueryComponent implements OnInit { } } + ngOnDestroy() { + // Nothing to do + } + public refreshTable() { this.childrenEmbeddedTable.refresh(); } diff --git a/frontend/src/app/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.upgraded.component.ts b/frontend/src/app/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.upgraded.component.ts index 21cb6ad963..a0fee2430e 100644 --- a/frontend/src/app/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.upgraded.component.ts +++ b/frontend/src/app/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.upgraded.component.ts @@ -70,7 +70,7 @@ export class WpRelationsAutocompleteComponent implements OnInit { input.autocomplete({ delay: 250, autoFocus: false, // Accessibility! - appendTo: '.detail-panel--autocomplete-target', + appendTo: '#content', classes: { 'ui-autocomplete': 'wp-relations-autocomplete--results' }, From af8d240230543ee72bcbd691c5ce9e7860b90b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Mon, 29 Oct 2018 08:31:50 +0100 Subject: [PATCH 4/6] Remove separate wp-relation-add-child component --- .../layout/work_packages/_table_embedded.sass | 3 +- frontend/src/app/angular4-modules.ts | 2 - .../wp-inline-create.component.html | 5 +- .../wp-inline-create.component.ts | 16 ++-- .../wp-inline-create.service.ts | 20 ++++- .../wp-inline-add-existing-child.service.ts | 18 ++-- .../wp-relation-add-child.html | 60 ------------- .../wp-relation-add-child.ts | 88 ------------------- .../wp-children-query.html | 5 -- .../details/relations/hierarchy_spec.rb | 6 +- .../components/work_packages/relations.rb | 2 +- 11 files changed, 47 insertions(+), 178 deletions(-) delete mode 100644 frontend/src/app/components/wp-relations/wp-relation-add-child/wp-relation-add-child.html delete mode 100644 frontend/src/app/components/wp-relations/wp-relation-add-child/wp-relation-add-child.ts diff --git a/app/assets/stylesheets/layout/work_packages/_table_embedded.sass b/app/assets/stylesheets/layout/work_packages/_table_embedded.sass index b607327333..e3d9459d7e 100644 --- a/app/assets/stylesheets/layout/work_packages/_table_embedded.sass +++ b/app/assets/stylesheets/layout/work_packages/_table_embedded.sass @@ -72,7 +72,8 @@ $table-timeline--compact-row-height: 28px padding-bottom: 2px // Fix inline create heights - .wp-inline-create--add-link + .wp-inline-create--add-link, + .wp-inline-create--reference-link height: $table-timeline--compact-row-height padding: 0 line-height: 2 diff --git a/frontend/src/app/angular4-modules.ts b/frontend/src/app/angular4-modules.ts index ab82a0e4ee..bd0f5c8157 100644 --- a/frontend/src/app/angular4-modules.ts +++ b/frontend/src/app/angular4-modules.ts @@ -122,7 +122,6 @@ import {WorkPackageNewSplitViewComponent} from 'core-components/wp-new/wp-new-sp import {WorkPackageQuerySelectDropdownComponent} from 'core-components/wp-query-select/wp-query-select-dropdown.component'; import {WorkPackageQuerySelectableTitleComponent} from 'core-components/wp-query-select/wp-query-selectable-title.component'; import {UrlParamsHelperService} from 'core-components/wp-query/url-params-helper'; -import {WpRelationAddChildComponent} from 'core-components/wp-relations/wp-relation-add-child/wp-relation-add-child'; import {WorkPackageRelationsHierarchyComponent} from 'core-components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive'; import {WorkPackageRelationsHierarchyService} from 'core-components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service'; import {WpRelationParentComponent} from 'core-components/wp-relations/wp-relations-parent/wp-relations-parent.component'; @@ -415,7 +414,6 @@ import {WorkPackageInlineAddExistingChildComponent} from "core-components/wp-rel WorkPackageRelationsCreateComponent, WorkPackageRelationsHierarchyComponent, WpRelationsAutocompleteComponent, - WpRelationAddChildComponent, WpRelationParentComponent, // Watchers tab diff --git a/frontend/src/app/components/wp-inline-create/wp-inline-create.component.html b/frontend/src/app/components/wp-inline-create/wp-inline-create.component.html index a51d4d226c..3bfe1b175a 100644 --- a/frontend/src/app/components/wp-inline-create/wp-inline-create.component.html +++ b/frontend/src/app/components/wp-inline-create/wp-inline-create.component.html @@ -1,4 +1,4 @@ - + - + (); diff --git a/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.service.ts b/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.service.ts index 70ded37b2e..d9fd5efabd 100644 --- a/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.service.ts +++ b/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.service.ts @@ -29,17 +29,12 @@ import {Injectable, OnDestroy} from '@angular/core'; import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; import {Subject} from "rxjs"; -import {I18nService} from "core-app/modules/common/i18n/i18n.service"; import {WorkPackageInlineCreateService} from "core-components/wp-inline-create/wp-inline-create.service"; import {WorkPackageInlineAddExistingChildComponent} from "core-components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.component"; @Injectable() export class WorkPackageInlineAddExistingChildService extends WorkPackageInlineCreateService implements OnDestroy { - constructor(private readonly I18n:I18nService) { - super(); - } - /** * A separate reference pane for the inline create component */ @@ -50,10 +45,21 @@ export class WorkPackageInlineAddExistingChildService extends WorkPackageInlineC */ public referenceTarget:WorkPackageResource|null = null; + public get canAdd() { + return !!(this.referenceTarget && this.referenceTarget.addChild); + } + + public get canReference() { + return !!(this.referenceTarget && this.referenceTarget.changeParent); + } + /** * Reference button text */ - public referenceButtonText = this.I18n.t('js.relation_buttons.add_existing_child'); + public readonly buttonTexts = { + reference: this.I18n.t('js.relation_buttons.add_existing_child'), + create: this.I18n.t('js.relation_buttons.add_new_child') + }; /** Allow callbacks to happen on newly created inline work packages */ public newInlineWorkPackageCreated = new Subject(); diff --git a/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-relation-add-child.html b/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-relation-add-child.html deleted file mode 100644 index 0d26665f28..0000000000 --- a/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-relation-add-child.html +++ /dev/null @@ -1,60 +0,0 @@ -
- -
-
-
- - -
-
- - - - - - -
-
-
-
- - - - diff --git a/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-relation-add-child.ts b/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-relation-add-child.ts deleted file mode 100644 index effeb0fca2..0000000000 --- a/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-relation-add-child.ts +++ /dev/null @@ -1,88 +0,0 @@ -import {WorkPackageCacheService} from '../../work-packages/work-package-cache.service'; -import {WorkPackageNotificationService} from '../../wp-edit/wp-notification.service'; -import {WorkPackageRelationsHierarchyService} from '../wp-relations-hierarchy/wp-relations-hierarchy.service'; -import {WorkPackageRelationsService} from '../wp-relations.service'; -import {Component, ElementRef, EventEmitter, Inject, Input, OnInit, Output} from '@angular/core'; -import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; -import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; - -@Component({ - selector: 'wp-relation-add-child', - templateUrl: './wp-relation-add-child.html' -}) -export class WpRelationAddChildComponent implements OnInit { - @Input() public workPackage:WorkPackageResource; - @Output() public onAdded = new EventEmitter(); - - public showRelationsCreateForm: boolean = false; - - public isDisabled = false; - public canAddChildren:boolean = false; - public canLinkChildren:boolean = false; - public selectedWpId:string|null = null; - - public text = { - save: this.I18n.t('js.relation_buttons.save'), - abort: this.I18n.t('js.relation_buttons.abort'), - addNewChild: this.I18n.t('js.relation_buttons.add_new_child'), - addExistingChild: this.I18n.t('js.relation_buttons.add_existing_child') - }; - - private $element:JQuery; - - constructor(readonly I18n:I18nService, - readonly elementRef:ElementRef, - protected wpRelations:WorkPackageRelationsService, - protected wpRelationsHierarchyService:WorkPackageRelationsHierarchyService, - protected wpNotificationsService:WorkPackageNotificationService, - protected wpCacheService:WorkPackageCacheService) { - } - - ngOnInit() { - this.$element = jQuery(this.elementRef.nativeElement); - this.canAddChildren = !!this.workPackage.addChild; - this.canLinkChildren = !!this.workPackage.changeParent; - } - - public updateSelectedId(workPackageId:string) { - this.selectedWpId = workPackageId; - } - - public addExistingChild() { - if (_.isNil(this.selectedWpId)) { - return; - } - - const newChildId = this.selectedWpId; - this.isDisabled = true; - - this.wpRelationsHierarchyService - .addExistingChildWp(this.workPackage, newChildId) - .then(() => { - this.wpCacheService.loadWorkPackage(this.workPackage.id, true); - this.isDisabled = false; - this.onAdded.emit(newChildId); - this.toggleRelationsCreateForm(); - }) - .catch(err => { - this.wpNotificationsService.handleRawError(err, this.workPackage); - this.isDisabled = false; - this.toggleRelationsCreateForm(); - }); - } - - public createNewChildWorkPackage() { - this.wpRelationsHierarchyService.addNewChildWp(this.workPackage); - } - - public toggleRelationsCreateForm() { - this.showRelationsCreateForm = !this.showRelationsCreateForm; - - setTimeout(() => { - if (!this.showRelationsCreateForm) { - this.selectedWpId = null; - this.$element.find('.-focus-after-save').first().focus(); - } - }); - } -} diff --git a/frontend/src/app/components/wp-relations/wp-relation-children/wp-children-query.html b/frontend/src/app/components/wp-relations/wp-relation-children/wp-children-query.html index 308c85abbb..0b2623aa5d 100644 --- a/frontend/src/app/components/wp-relations/wp-relation-children/wp-children-query.html +++ b/frontend/src/app/components/wp-relations/wp-relation-children/wp-children-query.html @@ -11,8 +11,3 @@ projectIdentifier: workPackage.project.idFromLink, projectContext: false }" > - - diff --git a/spec/features/work_packages/details/relations/hierarchy_spec.rb b/spec/features/work_packages/details/relations/hierarchy_spec.rb index ec1385d829..6eed267a51 100644 --- a/spec/features/work_packages/details/relations/hierarchy_spec.rb +++ b/spec/features/work_packages/details/relations/hierarchy_spec.rb @@ -46,14 +46,14 @@ shared_examples 'work package relations tab', js: true, selenium: true do ## # Add child #1 - find('.wp-inline-create--add-link', + find('.wp-inline-create--reference-link', text: I18n.t('js.relation_buttons.add_existing_child')).click relations.add_existing_child(child) ## # Add child #2 - find('.wp-inline-create--add-link', + find('.wp-inline-create--reference-link', text: I18n.t('js.relation_buttons.add_existing_child')).click relations.add_existing_child(child2) @@ -172,7 +172,7 @@ shared_examples 'work package relations tab', js: true, selenium: true do ## # Add child - find('.wp-inline-create--add-link', + find('.wp-inline-create--reference-link', text: I18n.t('js.relation_buttons.add_existing_child')).click relations.add_existing_child(child) diff --git a/spec/support/components/work_packages/relations.rb b/spec/support/components/work_packages/relations.rb index f25ce0b69c..e60f2a5841 100644 --- a/spec/support/components/work_packages/relations.rb +++ b/spec/support/components/work_packages/relations.rb @@ -131,7 +131,7 @@ module Components autocomplete = container.find(".wp-relations--autocomplete") select_autocomplete autocomplete, query: query, - results_selector: '.detail-panel--relations .wp-relations-autocomplete--results', + results_selector: '.wp-relations-autocomplete--results', select_text: work_package.id container.find('.wp-create-relation--save').click From 6c490dc295956c7c2778ddb33fdc4e57eaac2294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Mon, 29 Oct 2018 09:18:56 +0100 Subject: [PATCH 5/6] Remove create_child spec This functionality is no longer available (we may want to restore it?) --- .../persistent-toggle.directive.ts | 8 +- .../work_packages/new/create_child_spec.rb | 176 ------------------ 2 files changed, 4 insertions(+), 180 deletions(-) delete mode 100644 spec/features/work_packages/new/create_child_spec.rb diff --git a/frontend/legacy/app/components/persistent-toggle/persistent-toggle.directive.ts b/frontend/legacy/app/components/persistent-toggle/persistent-toggle.directive.ts index d824d92a84..5a04ee2917 100644 --- a/frontend/legacy/app/components/persistent-toggle/persistent-toggle.directive.ts +++ b/frontend/legacy/app/components/persistent-toggle/persistent-toggle.directive.ts @@ -36,13 +36,13 @@ function persistentToggleDirective($timeout:ITimeoutService) { var clickHandler = element.find('.persistent-toggle--click-handler'), targetNotification = element.find('.persistent-toggle--notification'); - scope.isActive = window.OpenProject.guardedLocalStorage(attributes.identifier) === 'true'; + scope.isHidden = window.OpenProject.guardedLocalStorage(attributes.identifier) === 'true'; function toggle(isNowHidden:boolean) { window.OpenProject.guardedLocalStorage(attributes.identifier, (!!isNowHidden).toString()); scope.$apply(function() { - scope.isActive = isNowHidden; + scope.isHidden = isNowHidden; }); if (isNowHidden) { @@ -58,7 +58,7 @@ function persistentToggleDirective($timeout:ITimeoutService) { // Clicking the handler toggles the notification clickHandler.bind('click', function() { - toggle(!scope.isActive); + toggle(!scope.isHidden); }); // Closing the notification remembers the decision @@ -67,7 +67,7 @@ function persistentToggleDirective($timeout:ITimeoutService) { }); // Set initial state - targetNotification.prop('hidden', !!scope.isActive); + targetNotification.prop('hidden', !!scope.isHidden); } }; }; diff --git a/spec/features/work_packages/new/create_child_spec.rb b/spec/features/work_packages/new/create_child_spec.rb deleted file mode 100644 index 858776139d..0000000000 --- a/spec/features/work_packages/new/create_child_spec.rb +++ /dev/null @@ -1,176 +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. -#++ - -require 'spec_helper' - -RSpec.feature 'Work package create children', js: true, selenium: true do - let(:tabs) { ::Components::WorkPackages::Tabs.new(original_work_package) } - let(:relations_tab) { find('.tabrow li', text: 'RELATIONS') } - let(:user) do - FactoryBot.create(:user, - member_in_project: project, - member_through_role: create_role) - end - let(:work_flow) do - FactoryBot.create(:workflow, - role: create_role, - type_id: original_work_package.type_id, - old_status: original_work_package.status, - new_status: FactoryBot.create(:status)) - end - let(:create_role) do - FactoryBot.create(:role, - permissions: [:view_work_packages, - :add_work_packages, - :edit_work_packages, - :manage_subtasks]) - end - let(:project) { FactoryBot.create(:project) } - let(:original_work_package) do - FactoryBot.build(:work_package, - project: project, - assigned_to: assignee, - responsible: responsible, - fixed_version: version, - priority: default_priority, - author: author, - status: default_status) - end - let(:default_priority) do - FactoryBot.build(:default_priority) - end - let(:default_status) do - FactoryBot.build(:default_status) - end - let(:role) { FactoryBot.build(:role, permissions: [:view_work_packages]) } - let(:assignee) do - FactoryBot.build(:user, - firstname: 'An', - lastname: 'assignee', - member_in_project: project, - member_through_role: role) - end - let(:responsible) do - FactoryBot.build(:user, - firstname: 'The', - lastname: 'responsible', - member_in_project: project, - member_through_role: role) - end - let(:author) do - FactoryBot.build(:user, - firstname: 'The', - lastname: 'author', - member_in_project: project, - member_through_role: role) - end - let(:version) do - FactoryBot.build(:version, - project: project) - end - - before do - login_as(user) - allow(user.pref).to receive(:warn_on_leaving_unsaved?).and_return(false) - original_work_package.save! - work_flow.save! - end - - scenario 'on fullscreen page' do - original_work_package_page = Pages::FullWorkPackage.new(original_work_package) - - child_work_package_page = original_work_package_page.add_child - expect_angular_frontend_initialized - - type_field = child_work_package_page.edit_field :type - - type_field.expect_active! - expect(type_field.input_element).to have_selector('option[selected]', text: 'Please select') - child_work_package_page.expect_current_path - - child_work_package_page.update_attributes Subject: 'Child work package', - Type: 'None' - - expect(type_field.input_element).to have_selector('option[selected]', text: 'None') - child_work_package_page.save! - - expect(page).to have_selector('.notification-box--content', - text: I18n.t('js.notice_successful_create')) - - # Relations counter in full view should equal 1 - tabs.expect_counter(relations_tab, 1) - - child_work_package = WorkPackage.order(created_at: 'desc').first - - expect(child_work_package).to_not eql original_work_package - - child_work_package_page = Pages::FullWorkPackage.new(child_work_package, project) - - child_work_package_page.ensure_page_loaded - child_work_package_page.expect_subject - child_work_package_page.expect_current_path - - child_work_package_page.expect_parent(original_work_package) - end - - scenario 'on split screen page' do - original_work_package_page = Pages::SplitWorkPackage.new(original_work_package, project) - - child_work_package_page = original_work_package_page.add_child - expect_angular_frontend_initialized - - type_field = child_work_package_page.edit_field :type - - expect(type_field.input_element).to have_selector('option[selected]', text: 'Please select') - child_work_package_page.expect_current_path - - child_work_package_page.update_attributes Subject: 'Child work package', - Type: 'None' - - expect(type_field.input_element).to have_selector('option[selected]', text: 'None') - child_work_package_page.save! - - expect(page).to have_selector('.notification-box--content', - text: I18n.t('js.notice_successful_create')) - - # # Relations counter in split view should equal 1 - tabs.expect_counter(relations_tab, 1) - - child_work_package = WorkPackage.order(created_at: 'desc').first - - expect(child_work_package).to_not eql original_work_package - - child_work_package_page = Pages::SplitWorkPackage.new(child_work_package, project) - - child_work_package_page.ensure_page_loaded - child_work_package_page.expect_subject - child_work_package_page.expect_current_path - - child_work_package_page.expect_parent(original_work_package) - end -end From ccc74577e4ee8348e20d0e792ed4cc184f441381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Tue, 30 Oct 2018 10:55:57 +0100 Subject: [PATCH 6/6] [28745] Do not allow inline create for children of milestones https://community.openproject.com/wp/28745 --- .../wp-inline-add-existing-child.service.ts | 2 +- lib/api/v3/work_packages/work_package_representer.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.service.ts b/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.service.ts index d9fd5efabd..6dde82cf89 100644 --- a/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.service.ts +++ b/frontend/src/app/components/wp-relations/wp-relation-add-child/wp-inline-add-existing-child.service.ts @@ -50,7 +50,7 @@ export class WorkPackageInlineAddExistingChildService extends WorkPackageInlineC } public get canReference() { - return !!(this.referenceTarget && this.referenceTarget.changeParent); + return !!(this.referenceTarget && !this.referenceTarget.isMilestone && this.referenceTarget.changeParent); } /** diff --git a/lib/api/v3/work_packages/work_package_representer.rb b/lib/api/v3/work_packages/work_package_representer.rb index 28e79f977a..3ccbb587bf 100644 --- a/lib/api/v3/work_packages/work_package_representer.rb +++ b/lib/api/v3/work_packages/work_package_representer.rb @@ -245,7 +245,7 @@ module API link :addChild, cache_if: -> { current_user_allowed_to(:add_work_packages, context: represented.project) } do - next if represented.new_record? + next if represented.milestone? || represented.new_record? { href: api_v3_paths.work_packages_by_project(represented.project.identifier), method: :post,