Compare commits

...

4 Commits

Author SHA1 Message Date
Benjamin Bädorf e73c69c040
Fixed initial project select field loading 3 years ago
Benjamin Bädorf 4cd124d687
Now with a 500 error 3 years ago
Benjamin Bädorf 54f20fd22e
Fix ProjectResource creation 3 years ago
Benjamin Bädorf 9cbd957b4e
Work on project selector 3 years ago
  1. 6
      frontend/src/app/features/hal/resources/hal-resource.ts
  2. 6
      frontend/src/app/shared/components/fields/edit/edit-field.component.ts
  3. 7
      frontend/src/app/shared/components/fields/edit/edit-field.initializer.ts
  4. 7
      frontend/src/app/shared/components/fields/edit/editing-portal/edit-form-portal.component.ts
  5. 7
      frontend/src/app/shared/components/fields/edit/editing-portal/edit-form-portal.injector.ts
  6. 21
      frontend/src/app/shared/components/fields/edit/field-types/project-edit-field.component.html
  7. 176
      frontend/src/app/shared/components/fields/edit/field-types/project-edit-field.component.ts
  8. 29
      frontend/src/app/shared/components/fields/edit/field-types/select-edit-field/select-edit-field.component.html
  9. 2
      frontend/src/app/shared/components/fields/edit/field-types/select-edit-field/select-edit-field.component.ts
  10. 29
      frontend/src/app/shared/components/fields/edit/field-types/work-package-edit-field.component.html
  11. 2
      frontend/src/app/shared/components/fields/openproject-fields.module.ts

@ -93,11 +93,13 @@ export class HalResource {
* @param {Function} initializer The initializer callback to HAL-transform all linked and embedded resources.
*
*/
public constructor(public injector:Injector,
public constructor(
public injector:Injector,
public $source:any,
public $loaded:boolean,
public halInitializer:(halResource:any) => void,
$halType:string) {
$halType:string,
) {
this.$halType = $halType;
this.$initialize($source);
}

@ -59,13 +59,15 @@ export abstract class EditFieldComponent extends Field implements OnInit, OnDest
/** JQuery accessor to element ref */
protected $element:JQuery;
constructor(readonly I18n:I18nService,
constructor(
readonly I18n:I18nService,
readonly elementRef:ElementRef,
@Inject(OpEditingPortalChangesetToken) protected change:ResourceChangeset<HalResource>,
@Inject(OpEditingPortalSchemaToken) public schema:IFieldSchema,
@Inject(OpEditingPortalHandlerToken) readonly handler:EditFieldHandler,
readonly cdRef:ChangeDetectorRef,
readonly injector:Injector) {
readonly injector:Injector,
) {
super();
this.updateFromChangeset(change);

@ -45,6 +45,7 @@ import { CombinedDateEditFieldComponent } from 'core-app/shared/components/field
import { VersionAutocompleterComponent } from 'core-app/shared/components/autocompleter/version-autocompleter/version-autocompleter.component';
import { WorkPackageAutocompleterComponent } from 'core-app/shared/components/autocompleter/work-package-autocompleter/wp-autocompleter.component';
import { WorkPackageCommentFieldComponent } from 'core-app/features/work-packages/components/work-package-comment/wp-comment-field.component';
import { ProjectEditFieldComponent } from './field-types/project-edit-field.component';
export function initializeCoreEditFields(editFieldService:EditFieldService, selectAutocompleterRegisterService:SelectAutocompleterRegisterService) {
return () => {
@ -53,7 +54,9 @@ export function initializeCoreEditFields(editFieldService:EditFieldService, sele
.addFieldType(TextEditFieldComponent, 'text', ['String'])
.addFieldType(IntegerEditFieldComponent, 'integer', ['Integer'])
.addFieldType(DurationEditFieldComponent, 'duration', ['Duration'])
.addFieldType(SelectEditFieldComponent, 'select', ['Priority',
.addFieldType(ProjectEditFieldComponent, 'project', ['Project'])
.addFieldType(SelectEditFieldComponent, 'select', [
'Priority',
'Status',
'Type',
'User',
@ -61,7 +64,7 @@ export function initializeCoreEditFields(editFieldService:EditFieldService, sele
'TimeEntriesActivity',
'Category',
'CustomOption',
'Project'])
])
.addFieldType(MultiSelectEditFieldComponent, 'multi-select', [
'[]CustomOption',
'[]User',

@ -47,10 +47,11 @@ export class EditFormPortalComponent implements OnInit, OnDestroy, AfterViewInit
public label:string;
constructor(readonly injector:Injector,
constructor(
readonly injector:Injector,
readonly editField:EditFieldService,
readonly elementRef:ElementRef) {
}
readonly elementRef:ElementRef,
) { }
ngOnInit() {
if (this.editFieldHandler && this.schemaInput) {

@ -14,7 +14,12 @@ import { ResourceChangeset } from 'core-app/shared/components/fields/changeset/r
*
* @returns {PortalInjector}
*/
export function createLocalInjector(injector:Injector, change:ResourceChangeset, fieldHandler:EditFieldHandler, schema:IFieldSchema):Injector {
export function createLocalInjector(
injector:Injector,
change:ResourceChangeset,
fieldHandler:EditFieldHandler,
schema:IFieldSchema,
):Injector {
const injectorTokens = new WeakMap();
injectorTokens.set(OpEditingPortalChangesetToken, change);

@ -0,0 +1,21 @@
<ng-select
appendTo="body"
[loading]="loading$ | async"
[typeahead]="typeahead$"
[items]="projects$ | async"
[ngModel]="projectId"
(change)="onModelChange($event)"
bindLabel="name"
bindValue="id"
>
<!--Nothing found -->
<ng-template ng-notfound-tmp let-searchTerm="searchTerm">
<div class="ng-option disabled">
{{ text.noResultsFound }}
</div>
</ng-template>
</ng-select>

@ -0,0 +1,176 @@
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2022 the OpenProject GmbH
//
// 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 COPYRIGHT and LICENSE files for more details.
//++
import {
ChangeDetectorRef,
Component,
ElementRef,
Injector,
OnInit,
ViewChild,
Inject,
} from '@angular/core';
import { NgSelectComponent } from '@ng-select/ng-select';
import { HttpClient } from '@angular/common/http';
import { Observable, Subject } from 'rxjs';
import { ID } from '@datorama/akita';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import {
EditFieldComponent,
OpEditingPortalSchemaToken,
OpEditingPortalHandlerToken,
OpEditingPortalChangesetToken
} from 'core-app/shared/components/fields/edit/edit-field.component';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { getPaginatedResults } from 'core-app/core/apiv3/helpers/get-paginated-results';
import {
ApiV3ListFilter,
ApiV3ListParameters,
listParamsString,
} from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { IHALCollection } from 'core-app/core/apiv3/types/hal-collection.type';
import { IProject } from 'core-app/core/state/projects/project.model';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { ResourceChangeset } from '../../changeset/resource-changeset';
import { IFieldSchema } from '../../field.base';
import { EditFieldHandler } from '../editing-portal/edit-field-handler';
import { ProjectResource } from 'core-app/features/hal/resources/project-resource';
import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service';
import { FormResource } from 'core-app/features/hal/resources/form-resource';
import { finalize, tap } from 'rxjs/operators';
@Component({
templateUrl: './project-edit-field.component.html',
})
export class ProjectEditFieldComponent extends EditFieldComponent implements OnInit {
@ViewChild(NgSelectComponent, { static: true }) public ngSelectComponent:NgSelectComponent;
constructor(
readonly I18n:I18nService,
readonly elementRef:ElementRef,
@Inject(OpEditingPortalChangesetToken) protected change:ResourceChangeset<HalResource>,
@Inject(OpEditingPortalSchemaToken) public schema:IFieldSchema,
@Inject(OpEditingPortalHandlerToken) readonly handler:EditFieldHandler,
readonly cdRef:ChangeDetectorRef,
readonly injector:Injector,
readonly apiV3Service:ApiV3Service,
readonly http:HttpClient,
readonly halResourceService:HalResourceService,
) {
super(
I18n,
elementRef,
change,
schema,
handler,
cdRef,
injector,
);
}
typeahead$ = new Subject<string>();
projects$ = new Subject<IProject[]>();
projectId:ID|null = null;
loading$ = new Subject<boolean>();
public async ngOnInit() {
super.ngOnInit();
this.loading$.next(true);
await this.change.getForm();
this.projectId = parseInt(this.value.id, 10);
this.loadAllProjects().subscribe(() => {
this.ngSelectComponent.open();
});
this.typeahead$.subscribe((searchText:string) => {
this.loadAllProjects(searchText).subscribe();
});
}
public onModelChange(project?:IProject) {
if (project) {
this.projectId = project.id;
// We fake a HalResource here because we're using a plain JS object, but the schema loading and editing
// is part of the older HalResource stack
const newProject = { ...project };
const fakeProjectHal = this.halResourceService.createHalResourceOfType('project', newProject);
this.value = fakeProjectHal;
} else {
this.projectId = null;
this.value = null;
}
return this.handler.handleUserSubmit();
}
public loadAllProjects(searchText:string = ''):Observable<IProject[]> {
this.loading$.next(true);
return getPaginatedResults<IProject>(
(params) => {
const collectionURL = listParamsString({ ...this.getParams(searchText), ...params });
return this.http.get<IHALCollection<IProject>>(this.schema.allowedValues.$link.href + collectionURL);
},
)
.pipe(
finalize(() => this.loading$.next(false)),
tap((projects) => this.projects$.next(projects)),
);
}
public getParams(searchText:string = ''):ApiV3ListParameters {
const filters:ApiV3ListFilter[] = [
['active', '=', ['t']],
];
if (searchText) {
filters.push([
'name_and_identifier',
'~',
[searchText],
]);
}
return {
filters,
pageSize: -1,
select: [
'elements/id',
'elements/identifier',
'elements/name',
'elements/self',
'total',
'count',
'pageSize',
],
};
}
}

@ -1,13 +1,16 @@
<ndc-dynamic [ndcDynamicComponent]="autocompleterComponent()"
[ndcDynamicInputs]="{ availableValues: availableOptions,
appendTo: appendTo,
resource: this.resource,
model: selectedOption ? selectedOption : '',
required: required,
disabled: inFlight,
id: handler.htmlId,
finishedLoading: true,
classes: 'inline-edit--field ' + handler.fieldName,
showAddNewButton: showAddNewButton }"
[ndcDynamicOutputs]="referenceOutputs">
</ndc-dynamic>
<ndc-dynamic
[ndcDynamicComponent]="autocompleterComponent()"
[ndcDynamicInputs]="{
availableValues: availableOptions,
appendTo: appendTo,
model: selectedOption ? selectedOption : '',
required: required,
disabled: inFlight,
id: handler.htmlId,
finishedLoading: true,
classes: 'inline-edit--field ' + handler.fieldName,
resource: this.resource,
showAddNewButton: showAddNewButton
}"
[ndcDynamicOutputs]="referenceOutputs"
></ndc-dynamic>

@ -29,7 +29,7 @@
import { Component, InjectFlags, OnInit } from '@angular/core';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { SelectAutocompleterRegisterService } from 'core-app/shared/components/fields/edit/field-types/select-edit-field/select-autocompleter-register.service';
import { from } from 'rxjs';
import { from, Subject } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { CreateAutocompleterComponent } from 'core-app/shared/components/autocompleter/create-autocompleter/create-autocompleter.component';

@ -1,13 +1,16 @@
<ndc-dynamic [ndcDynamicComponent]="autocompleterComponent()"
[ndcDynamicInputs]="{ availableValues: requests.output$ | async,
appendTo: appendTo,
model: selectedOption ? selectedOption : '',
required: required,
disabled: inFlight,
typeahead: typeahead,
id: handler.htmlId,
finishedLoading: requests.loading$,
hideSelected: true,
classes: 'inline-edit--field ' + handler.fieldName }"
[ndcDynamicOutputs]="referenceOutputs">
</ndc-dynamic>
<ndc-dynamic
[ndcDynamicComponent]="autocompleterComponent()"
[ndcDynamicInputs]="{
availableValues: requests.output$ | async,
appendTo: appendTo,
model: selectedOption ? selectedOption : '',
required: required,
disabled: inFlight,
id: handler.htmlId,
finishedLoading: requests.loading$,
classes: 'inline-edit--field ' + handler.fieldName,
typeahead: typeahead,
hideSelected: true
}"
[ndcDynamicOutputs]="referenceOutputs"
></ndc-dynamic>

@ -60,6 +60,7 @@ import { DateEditFieldModule } from 'core-app/shared/components/fields/edit/fiel
import { SelectEditFieldModule } from 'core-app/shared/components/fields/edit/field-types/select-edit-field/select-edit-field.module';
import { FormattableEditFieldModule } from 'core-app/shared/components/fields/edit/field-types/formattable-edit-field/formattable-edit-field.module';
import { EditFieldControlsModule } from 'core-app/shared/components/fields/edit/field-controls/edit-field-controls.module';
import { ProjectEditFieldComponent } from './edit/field-types/project-edit-field.component';
@NgModule({
imports: [
@ -104,6 +105,7 @@ import { EditFieldControlsModule } from 'core-app/shared/components/fields/edit/
FloatEditFieldComponent,
PlainFormattableEditFieldComponent,
MultiSelectEditFieldComponent,
ProjectEditFieldComponent,
WorkPackageEditFieldComponent,
TimeEntryWorkPackageEditFieldComponent,
EditFormComponent,

Loading…
Cancel
Save