Skip to content

Commit 9a7ff60

Browse files
Replace jquery-autocomplete in wp-relations-autocomplete
1 parent d6d4628 commit 9a7ff60

21 files changed

+251
-222
lines changed

.rubocop.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,7 @@ UnderscorePrefixedVariableName:
271271

272272
Void:
273273
Enabled: false
274+
275+
Layout/MultilineMethodCallIndentation:
276+
Enabled: false
277+

config/locales/js-en.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ en:
518518

519519
relations_autocomplete:
520520
placeholder: "Enter the related work package id"
521-
parent_placeholder: "Choose new parent, press enter to unset, escape to cancel."
521+
parent_placeholder: "Choose new parent or press escape to cancel."
522522

523523
repositories:
524524
select_tag: 'Select tag'

frontend/src/app/components/work-packages/wp-breadcrumb/wp-breadcrumb-parent.component.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export class WorkPackageBreadcrumbParentComponent {
4444
public text = {
4545
edit_parent: this.I18n.t('js.relation_buttons.change_parent'),
4646
set_or_remove_parent: this.I18n.t('js.relations_autocomplete.parent_placeholder'),
47+
remove_parent: this.I18n.t('js.relation_buttons.remove_parent'),
4748
set_parent: this.I18n.t('js.relation_buttons.set_parent'),
4849
};
4950

@@ -76,12 +77,9 @@ export class WorkPackageBreadcrumbParentComponent {
7677
this.toggle(true);
7778
}
7879

79-
public updateParent(newParentId:string|null) {
80+
public updateParent(newParent:WorkPackageResource|null) {
8081
this.close();
81-
if (_.isNil(newParentId)) {
82-
newParentId = null;
83-
}
84-
82+
let newParentId = newParent ? newParent.id : null;
8583
if (_.get(this.parent, 'id', null) === newParentId) {
8684
return;
8785
}

frontend/src/app/components/work-packages/wp-breadcrumb/wp-breadcrumb-parent.html

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,21 @@
1010
(execute)="open()"
1111
*ngIf="canModifyParent()"
1212
[linkTitle]="parent ? text.edit_parent : text.set_parent"
13-
linkClass="wp-relation--parent-change hide-when-print">
14-
<op-icon icon-classes="icon-small {{ parent ? 'icon-edit icon2' : 'icon-add icon4' }}"></op-icon>
13+
linkClass="wp-relation--parent-change -no-decoration hide-when-print icon-small {{ parent ? 'icon-edit icon5' : 'icon-add icon4' }}">
1514
<span *ngIf="!parent" [textContent]="text.set_parent"></span>
1615
</accessible-by-keyboard>
16+
<accessible-by-keyboard
17+
(execute)="updateParent(null)"
18+
*ngIf="canModifyParent() && parent"
19+
[linkTitle]="text.remove_parent"
20+
linkClass="wp-relation--parent-remove hide-when-print -no-decoration icon-small icon-remove icon4">
21+
</accessible-by-keyboard>
1722
</ng-container>
18-
<wp-relations-autocomplete-upgraded
23+
<wp-relations-autocomplete
1924
*ngIf="active"
2025
[inputPlaceholder]="text.set_or_remove_parent"
2126
[workPackage]="workPackage"
22-
(onEscape)="close()"
23-
(onBlur)="close()"
24-
(onWorkPackageIdSelected)="updateParent($event)"
27+
(onCancel)="close()"
28+
(onReferenced)="updateParent($event)"
2529
filterCandidatesFor="parent">
26-
</wp-relations-autocomplete-upgraded>
30+
</wp-relations-autocomplete>

frontend/src/app/components/wp-relations/embedded/inline/add-existing/wp-relation-inline-add-existing.component.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
data-indicator-name="relationAddChild">
33
<div class="v-align wp-relations-create--form wp-relations--add-form">
44
<div class="grid-content medium-10">
5-
<wp-relations-autocomplete-upgraded
5+
<wp-relations-autocomplete
66
[workPackage]="workPackage"
7-
(onWorkPackageIdSelected)="updateSelectedId($event)"
7+
(onReferenced)="onReferenced($event)"
88
[filterCandidatesFor]="relationType">
9-
</wp-relations-autocomplete-upgraded>
9+
</wp-relations-autocomplete>
1010
</div>
1111
<div class="grid-content medium-2 collapse wp-relations-controls-section relation-row">
1212
<accessible-by-keyboard

frontend/src/app/components/wp-relations/embedded/inline/add-existing/wp-relation-inline-add-existing.component.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {WorkPackageNotificationService} from "core-components/wp-edit/wp-notific
3535
import {WorkPackageCacheService} from "core-components/work-packages/work-package-cache.service";
3636
import {WpRelationInlineCreateServiceInterface} from "core-components/wp-relations/embedded/wp-relation-inline-create.service.interface";
3737
import {WorkPackageTableRefreshService} from "core-components/wp-table/wp-table-refresh-request.service";
38+
import {WorkPackageResource} from "core-app/modules/hal/resources/work-package-resource";
3839

3940
@Component({
4041
templateUrl: './wp-relation-inline-add-existing.component.html'
@@ -80,8 +81,11 @@ export class WpRelationInlineAddExistingComponent {
8081
});
8182
}
8283

83-
public updateSelectedId(workPackageId:string) {
84-
this.selectedWpId = workPackageId;
84+
public onReferenced(workPackage?:WorkPackageResource) {
85+
if (workPackage) {
86+
this.selectedWpId = workPackage.id!;
87+
this.addExisting();
88+
}
8589
}
8690

8791
public get relationType() {

frontend/src/app/components/wp-relations/wp-relations-create/wp-relation-create.template.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@
3333
</select>
3434
</div>
3535
<div class="grid-content medium-7">
36-
<wp-relations-autocomplete-upgraded
36+
<wp-relations-autocomplete
3737
[workPackage]="workPackage"
38-
(onWorkPackageIdSelected)="updateSelectedId($event)"
38+
(onReferenced)="onReferenced($event)"
3939
appendToContainer=".work-packages-tab-view--overflow"
4040
selectedRelationType="parent">
41-
</wp-relations-autocomplete-upgraded>
41+
</wp-relations-autocomplete>
4242
</div>
4343
<div class="grid-content medium-2 collapse wp-relations-controls-section relation-row">
4444
<accessible-by-keyboard
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
//-- copyright
2+
// OpenProject is a project management system.
3+
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
4+
//
5+
// This program is free software; you can redistribute it and/or
6+
// modify it under the terms of the GNU General Public License version 3.
7+
//
8+
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
9+
// Copyright (C) 2006-2013 Jean-Philippe Lang
10+
// Copyright (C) 2010-2013 the ChiliProject Team
11+
//
12+
// This program is free software; you can redistribute it and/or
13+
// modify it under the terms of the GNU General Public License
14+
// as published by the Free Software Foundation; either version 2
15+
// of the License, or (at your option) any later version.
16+
//
17+
// This program is distributed in the hope that it will be useful,
18+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
// GNU General Public License for more details.
21+
//
22+
// You should have received a copy of the GNU General Public License
23+
// along with this program; if not, write to the Free Software
24+
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25+
//
26+
// See doc/COPYRIGHT.rdoc for more details.
27+
//++
28+
29+
import {
30+
AfterContentInit,
31+
ChangeDetectorRef,
32+
Component,
33+
EventEmitter,
34+
Input,
35+
Output,
36+
ViewChild,
37+
ViewEncapsulation
38+
} from '@angular/core';
39+
import {I18nService} from 'core-app/modules/common/i18n/i18n.service';
40+
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
41+
import {from, Observable, of, Subject} from "rxjs";
42+
import {catchError, debounceTime, distinctUntilChanged, map, switchMap, tap} from "rxjs/operators";
43+
import {WorkPackageNotificationService} from "core-components/wp-edit/wp-notification.service";
44+
import {NgSelectComponent} from "@ng-select/ng-select";
45+
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space";
46+
import {PathHelperService} from "core-app/modules/common/path-helper/path-helper.service";
47+
import {WorkPackageCollectionResource} from "core-app/modules/hal/resources/wp-collection-resource";
48+
import {CurrentProjectService} from "core-components/projects/current-project.service";
49+
import {ApiV3FilterBuilder} from "core-components/api/api-v3/api-v3-filter-builder";
50+
import {HalResourceService} from "core-app/modules/hal/services/hal-resource.service";
51+
import {SchemaCacheService} from "core-components/schemas/schema-cache.service";
52+
53+
@Component({
54+
selector: 'wp-relations-autocomplete',
55+
templateUrl: './wp-relations-autocomplete.html',
56+
57+
// Allow styling the embedded ng-select
58+
encapsulation: ViewEncapsulation.None,
59+
styleUrls: ['./wp-relations-autocomplete.sass']
60+
})
61+
export class WorkPackageRelationsAutocomplete implements AfterContentInit {
62+
readonly text = {
63+
placeholder: this.I18n.t('js.relations_autocomplete.placeholder')
64+
};
65+
66+
@Input() inputPlaceholder:string = this.text.placeholder;
67+
@Input() workPackage:WorkPackageResource;
68+
@Input() selectedRelationType:string;
69+
@Input() filterCandidatesFor:string;
70+
71+
@Input() appendToContainer:string = 'body';
72+
@ViewChild(NgSelectComponent) public ngSelectComponent:NgSelectComponent;
73+
74+
@Output() onCancel = new EventEmitter<undefined>();
75+
@Output() onReferenced = new EventEmitter<WorkPackageResource>();
76+
@Output() onEmptySelected = new EventEmitter<undefined>();
77+
78+
// Whether we're currently loading
79+
public isLoading = false;
80+
81+
// Search input from ng-select
82+
public searchInput$ = new Subject<string>();
83+
84+
// Search results mapped to input
85+
public results$:Observable<WorkPackageResource[]> = this.searchInput$.pipe(
86+
debounceTime(250),
87+
distinctUntilChanged(),
88+
tap(() => this.isLoading = true),
89+
switchMap(queryString => this.autocompleteWorkPackages(queryString))
90+
);
91+
92+
constructor(private readonly querySpace:IsolatedQuerySpace,
93+
private readonly pathHelper:PathHelperService,
94+
private readonly wpNotificationsService:WorkPackageNotificationService,
95+
private readonly CurrentProject:CurrentProjectService,
96+
private readonly halResourceService:HalResourceService,
97+
private readonly schemaCacheService:SchemaCacheService,
98+
private readonly cdRef:ChangeDetectorRef,
99+
private readonly I18n:I18nService) {
100+
}
101+
102+
ngAfterContentInit():void {
103+
if (!this.ngSelectComponent) {
104+
return;
105+
}
106+
this.ngSelectComponent.open();
107+
108+
setTimeout(() => {
109+
this.ngSelectComponent.focus();
110+
}, 25);
111+
}
112+
113+
cancel() {
114+
this.onCancel.emit();
115+
}
116+
117+
public onWorkPackageSelected(workPackage?:WorkPackageResource) {
118+
if (workPackage) {
119+
this.schemaCacheService
120+
.ensureLoaded(workPackage)
121+
.then(() => {
122+
this.onReferenced.emit(workPackage);
123+
this.ngSelectComponent.close();
124+
});
125+
}
126+
}
127+
128+
private autocompleteWorkPackages(query:string):Observable<WorkPackageResource[]> {
129+
// Return when the search string is empty
130+
if (query.length === 0) {
131+
this.isLoading = false;
132+
return of([]);
133+
}
134+
135+
// Remove prefix # from search
136+
query = query.replace(/^#/, '');
137+
138+
return from(
139+
this.workPackage.availableRelationCandidates.$link.$fetch({
140+
query: query,
141+
type: this.filterCandidatesFor || this.selectedRelationType
142+
}) as Promise<WorkPackageCollectionResource>
143+
)
144+
.pipe(
145+
map(collection => collection.elements),
146+
catchError((error:unknown) => {
147+
this.wpNotificationsService.handleRawError(error);
148+
return of([]);
149+
}),
150+
tap(() => this.isLoading = false)
151+
);
152+
}
153+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<div class="wp-relations--autocomplete">
2+
<ng-select ngClass="wp-inline-create--reference-autocompleter"
3+
[items]="results$ | async"
4+
[appendTo]="appendToContainer"
5+
[multiple]="false"
6+
[loading]="isLoading"
7+
[placeholder]="inputPlaceholder"
8+
[typeahead]="searchInput$"
9+
[closeOnSelect]="true"
10+
(close)="cancel()"
11+
(change)="onWorkPackageSelected($event)">
12+
<ng-template ng-label-tmp let-item="item">
13+
{{item.type.name }} #{{ item.id }} {{ item.subject }}
14+
</ng-template>
15+
<ng-template ng-option-tmp let-item="item" let-index="index" let-search="searchTerm">
16+
{{item.type.name }} #{{ item.id }} {{ item.subject }}
17+
</ng-template>
18+
</ng-select>
19+
</div>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
ng-select.wp-inline-create--reference-autocompleter
2+
border: none
3+
4+
.ng-clear-wrapper
5+
display: none

0 commit comments

Comments
 (0)