Merge pull request #6776 from opf/feature/inline-create-service

Create an inline create service to listen to inline created WPs

[ci skip]
pull/6781/head
Oliver Günther 6 years ago committed by GitHub
commit a8908b307e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      frontend/src/app/angular4-modules.ts
  2. 195
      frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts
  3. 96
      frontend/src/app/components/wp-inline-create/wp-inline-create.service.ts

@ -219,6 +219,7 @@ import {WorkPackageTableHighlightingService} from "core-components/wp-fast-table
import {ChartsModule} from "ng2-charts";
import {WorkPackageEmbeddedGraphComponent} from "core-components/wp-table/embedded/wp-embedded-graph.component";
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";
@NgModule({
@ -298,6 +299,11 @@ import {WorkPackageCommentFieldComponent} from "core-components/work-packages/wo
// Provide both serves with tokens to avoid tight dependency cycles
{ provide: IWorkPackageCreateServiceToken, useClass: WorkPackageCreateService },
{ provide: IWorkPackageEditingServiceToken, useClass: WorkPackageEditingService },
// 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,
OpTableActionsService,
CurrentProjectService,
FirstRouteService,

@ -58,12 +58,13 @@ import {
inlineCreateRowClassName
} from './inline-create-row-builder';
import {TableState} from 'core-components/wp-table/table-state/table-state';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
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";
@Component({
selector: '[wpInlineCreate]',
@ -94,19 +95,19 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
private $element:JQuery;
constructor(readonly elementRef:ElementRef,
readonly injector:Injector,
readonly FocusHelper:FocusHelperService,
readonly I18n:I18nService,
readonly tableState:TableState,
readonly wpCacheService:WorkPackageCacheService,
readonly currentUser:CurrentUserService,
@Inject(IWorkPackageEditingServiceToken) protected wpEditing:WorkPackageEditingService,
@Inject(IWorkPackageCreateServiceToken) protected wpCreate:WorkPackageCreateService,
readonly wpTableColumns:WorkPackageTableColumnsService,
readonly wpTableFilters:WorkPackageTableFiltersService,
readonly wpTableFocus:WorkPackageTableFocusService,
readonly authorisationService:AuthorisationService) {
constructor(protected readonly elementRef:ElementRef,
protected readonly injector:Injector,
protected readonly FocusHelper:FocusHelperService,
protected readonly I18n:I18nService,
protected readonly tableState:TableState,
protected readonly wpCacheService:WorkPackageCacheService,
protected readonly currentUser:CurrentUserService,
@Inject(IWorkPackageEditingServiceToken) protected readonly wpEditing:WorkPackageEditingService,
@Inject(IWorkPackageCreateServiceToken) protected readonly wpCreate:WorkPackageCreateService,
protected readonly wpInlineCreate:WorkPackageInlineCreateService,
protected readonly wpTableColumns:WorkPackageTableColumnsService,
protected readonly wpTableFocus:WorkPackageTableFocusService,
protected readonly authorisationService:AuthorisationService) {
}
ngOnDestroy() {
@ -129,11 +130,59 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
const container = jQuery(this.table.timelineBody);
container.addClass('-inline-create-mirror');
// Remove temporary rows on creation of new work package
this.wpCreate.onNewWorkPackage()
// Register callback on newly created work packages
this.registerCreationCallback();
// Watch on this scope when the columns change and refresh this row
this.refreshOnColumnChanges();
// Cancel edition of current new row
this.registerCancelHandler();
}
/**
* Reset the inline creation row on the cancel button,
* which is dynamically inserted into the action row by the inline create renderer.
*/
private registerCancelHandler() {
this.$element.on('click keydown', `.${inlineCreateCancelClassName}`, (evt) => {
onClickOrEnter(evt, () => {
this.resetRow();
});
evt.stopImmediatePropagation();
return false;
});
}
/**
* Since the table is refreshed imperatively whenever columns are changed,
* we need to manually ensure the inline create row gets refreshed as well.
*/
private refreshOnColumnChanges() {
this.tableState.columns
.values$()
.pipe(
takeUntil(componentDestroyed(this))
filter(() => this.isHidden), // Take only when row is inserted
untilComponentDestroyed(this),
)
.subscribe(() => {
const rowElement = this.$element.find(`.${inlineCreateRowClassName}`);
if (rowElement.length && this.currentWorkPackage) {
this.rowBuilder.refreshRow(this.currentWorkPackage, rowElement);
}
});
}
/**
* Listen to newly created work packages to detect whether the WP is the one we created,
* and properly reset inline create in this case
*/
private registerCreationCallback() {
this.wpCreate
.onNewWorkPackage()
.pipe(untilComponentDestroyed(this))
.subscribe((wp:WorkPackageResource) => {
if (this.currentWorkPackage && this.currentWorkPackage === wp) {
// Remove row and focus
@ -143,6 +192,9 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
if (!this.table.configuration.isEmbedded) {
this.wpTableFocus.updateFocus(wp.id);
}
// Notify inline create service
this.wpInlineCreate.newInlineWorkPackage(wp);
} else {
// Remove current row
this.table.editing.stopEditing('new');
@ -150,30 +202,6 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
this.showRow();
}
});
// Watch on this scope when the columns change and refresh this row
this.tableState.columns.values$()
.pipe(
filter(() => this.isHidden), // Take only when row is inserted
takeUntil(componentDestroyed(this))
)
.subscribe(() => {
const rowElement = this.$element.find(`.${inlineCreateRowClassName}`);
if (rowElement.length && this.currentWorkPackage) {
this.rowBuilder.refreshRow(this.currentWorkPackage, rowElement);
}
});
// Cancel edition of current new row
this.$element.on('click keydown', `.${inlineCreateCancelClassName}`, (evt) => {
onClickOrEnter(evt, () => {
this.resetRow();
});
evt.stopImmediatePropagation();
return false;
});
}
public handleAddRowClick() {
@ -182,41 +210,77 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
}
public addWorkPackageRow() {
this.wpCreate.createNewWorkPackage(this.projectIdentifier).then((changeset:WorkPackageChangeset) => {
this.wpCreate
.createNewWorkPackage(this.projectIdentifier)
.then((changeset:WorkPackageChangeset) => {
if (!changeset) {
throw 'No new work package was created';
}
const wp = this.currentWorkPackage = changeset.workPackage;
this.applyDefaultsAndInsert(changeset, wp);
});
}
/**
* Apply values to the work package from the current set of filters
*
* @param changeset
* @param wp
*/
private applyDefaultsAndInsert(changeset:WorkPackageChangeset, wp:WorkPackageResource) {
let promise:Promise<any>;
// Apply filter values
if (this.tableState.query.hasValue()) {
const filter = new WorkPackageFilterValues(this.currentUser, changeset, this.tableState.query.value!.filters);
filter.applyDefaultsFromFilters().then(() => {
this.wpEditing.updateValue('new', changeset);
this.wpCacheService.updateWorkPackage(this.currentWorkPackage!);
// Set editing context to table
const context = new TableRowEditContext(
this.table,
this.injector,
wp.id,
this.rowBuilder.classIdentifier(wp)
);
this.workPackageEditForm = WorkPackageEditForm.createInContext(this.injector, context, wp, false);
this.workPackageEditForm.changeset.clear();
const row = this.rowBuilder.buildNew(wp, this.workPackageEditForm);
this.$element.append(row);
setTimeout(() => {
this.workPackageEditForm!.activateMissingFields();
this.hideRow();
});
promise = filter.applyDefaultsFromFilters();
} else {
promise = Promise.resolve();
}
promise.then(() => {
// Update the changeset with any added filtered values
this.wpEditing.updateValue('new', changeset);
this.wpCacheService.updateWorkPackage(this.currentWorkPackage!);
// Actually render the row
const form = this.workPackageEditForm = this.renderInlineCreateRow(wp);
setTimeout(() => {
// Activate any required fields
form.activateMissingFields();
// Hide the button row
this.hideRow();
});
});
}
/**
* Actually render the row manually
* in the same fashion as all rows in the table are rendered.
*
* @param wp Work package to be rendered
* @returns The work package form of the row
*/
private renderInlineCreateRow(wp:WorkPackageResource):WorkPackageEditForm {
// Set editing context to table
const context = new TableRowEditContext(
this.table,
this.injector,
wp.id,
this.rowBuilder.classIdentifier(wp)
);
const form = WorkPackageEditForm.createInContext(this.injector, context, wp, false);
form.changeset.clear();
const row = this.rowBuilder.buildNew(wp, form);
this.$element.append(row);
return form;
}
/**
* Reset the new work package row and refocus on the button
*/
@ -253,4 +317,5 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
return this.authorisationService.can('work_packages', 'createWorkPackage') ||
this.authorisationService.can('work_package', 'addChild');
}
}

@ -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,
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 {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";
@Injectable()
export class WorkPackageInlineCreateService implements OnDestroy {
/** Allow callbacks to happen on newly created inline work packages */
protected _newInlineWorkPackage = new Subject<WorkPackageResource>();
/**
* Ensure hierarchical injected versions of this service correctly unregister
*/
ngOnDestroy() {
this._newInlineWorkPackage.complete();
}
/**
* Returns an observable that fires whenever a new INLINE work packages was created.
*/
public newInlineWorkPackageCreated$():Observable<WorkPackageResource> {
return this._newInlineWorkPackage.asObservable();
}
/**
* Notify of a new inline work package that was created
* @param wp Work package that got created
*/
public newInlineWorkPackage(wp:WorkPackageResource) {
this._newInlineWorkPackage.next(wp);
}
}
Loading…
Cancel
Save