Dynamic component for referencing

pull/6788/head
Oliver Günther 6 years ago
parent 5fb09b2bd5
commit a31115a74b
No known key found for this signature in database
GPG Key ID: A3A8BDAD7C0C552C
  1. 9
      app/assets/stylesheets/content/work_packages/_table_inline_create.sass
  2. 8
      frontend/legacy/app/components/persistent-toggle/persistent-toggle.directive.ts
  3. 5
      frontend/src/app/angular4-modules.ts
  4. 67
      frontend/src/app/components/wp-inline-create/wp-inline-create.component.html
  5. 36
      frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts
  6. 43
      frontend/src/app/components/wp-inline-create/wp-inline-create.service.ts
  7. 80
      frontend/src/app/components/wp-inline-create/wp-inline-reference.component.ts
  8. 2
      frontend/src/app/modules/common/i18n/i18n.service.ts

@ -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

@ -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);
}
};
};

@ -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,

@ -1,30 +1,41 @@
<tr *ngIf="!isHidden && isAllowed"
class="wp-inline-create-button-row hide-when-print">
<td [attr.colspan]="colspan"
class="wp-inline-create-button-td -no-highlighting">
<div class="wp-inline-create-button">
<a class="wp-inline-create--add-link"
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">
<op-icon icon-classes="icon icon-add"></op-icon>
</a>
<ng-container *ngIf="isReferencable">
<a class="wp-inline-create--reference-link wp-inline-create--split-link"
href
role="link"
(accessibleClick)="handleReferenceClick()"
[attr.disabled]="!isAllowed || undefined"
[attr.aria-label]="text.create"
aria-haspopup="true">
<ng-container *ngIf="isAllowed">
<tr *ngIf="mode === 'inactive'"
class="wp-inline-create-button-row hide-when-print">
<td [attr.colspan]="colspan"
class="wp-inline-create-button-td -no-highlighting">
<div class="wp-inline-create-button">
<a class="wp-inline-create--add-link"
href
role="link"
[focus]="focus"
[ngClass]="{'wp-inline-create--split-link': hasReferenceClass }"
(accessibleClick)="handleAddRowClick()"
[attr.disabled]="!isAllowed || undefined"
[attr.aria-label]="text.create"
aria-haspopup="true">
<op-icon icon-classes="icon icon-add"></op-icon>
<span [textContent]="text.create"></span>
</a>
</ng-container>
</div>
</td>
</tr>
<ng-container *ngIf="hasReferenceClass">
<a class="wp-inline-create--reference-link wp-inline-create--split-link"
href
role="link"
(accessibleClick)="handleReferenceClick()"
[attr.disabled]="!isAllowed || undefined"
[attr.aria-label]="text.create"
aria-haspopup="true">
<op-icon icon-classes="icon icon-add"></op-icon>
<span [textContent]="text.reference"></span>
</a>
</ng-container>
</div>
</td>
</tr>
<tr *ngIf="hasReferenceClass && mode === 'reference'"
class="wp-inline-create-button-row hide-when-print">
<td [attr.colspan]="colspan"
class="wp-inline-reference-row-td -no-highlighting">
<ng-container *ngComponentOutlet="referenceClass; injector: injector"></ng-container>
</td>
</tr>
</ng-container>

@ -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 {

@ -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<any>|null {
return null;
}
/**
* Notify of a new inline work package that was created
* @param wp Work package that got created

@ -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: '<a (click)="cancel()">HELLO THERE</a>'
})
export class WorkPackageInlineReferenceComponent {
constructor(public parent:WorkPackageInlineCreateComponent) {
}
public cancel() {
this.parent.resetRow();
}
}

@ -11,7 +11,7 @@ export interface GlobalI18n {
@Injectable()
export class I18nService {
private _i18n:GlobalI18n
private _i18n:GlobalI18n;
constructor() {
this._i18n = (window as any).I18n;

Loading…
Cancel
Save