|
|
@ -26,53 +26,50 @@ |
|
|
|
// See doc/COPYRIGHT.rdoc for more details.
|
|
|
|
// See doc/COPYRIGHT.rdoc for more details.
|
|
|
|
// ++
|
|
|
|
// ++
|
|
|
|
|
|
|
|
|
|
|
|
import {Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, Injector, ViewChild, ElementRef} from '@angular/core'; |
|
|
|
import { |
|
|
|
|
|
|
|
ChangeDetectionStrategy, |
|
|
|
|
|
|
|
ChangeDetectorRef, |
|
|
|
|
|
|
|
Component, |
|
|
|
|
|
|
|
ElementRef, |
|
|
|
|
|
|
|
Injector, |
|
|
|
|
|
|
|
OnInit, |
|
|
|
|
|
|
|
ViewChild |
|
|
|
|
|
|
|
} from '@angular/core'; |
|
|
|
import {AbstractWidgetComponent} from "app/modules/grids/widgets/abstract-widget.component"; |
|
|
|
import {AbstractWidgetComponent} from "app/modules/grids/widgets/abstract-widget.component"; |
|
|
|
import {I18nService} from "core-app/modules/common/i18n/i18n.service"; |
|
|
|
import {I18nService} from "core-app/modules/common/i18n/i18n.service"; |
|
|
|
import {ProjectDmService} from "core-app/modules/hal/dm-services/project-dm.service"; |
|
|
|
import {ProjectDmService} from "core-app/modules/hal/dm-services/project-dm.service"; |
|
|
|
import {CurrentProjectService} from "core-components/projects/current-project.service"; |
|
|
|
import {CurrentProjectService} from "core-components/projects/current-project.service"; |
|
|
|
import {SchemaResource} from "core-app/modules/hal/resources/schema-resource"; |
|
|
|
import {SchemaResource} from "core-app/modules/hal/resources/schema-resource"; |
|
|
|
import {DisplayFieldContext, DisplayFieldService} from "core-app/modules/fields/display/display-field.service"; |
|
|
|
|
|
|
|
import {ProjectResource} from "core-app/modules/hal/resources/project-resource"; |
|
|
|
|
|
|
|
import {PortalCleanupService} from 'core-app/modules/fields/display/display-portal/portal-cleanup.service'; |
|
|
|
|
|
|
|
import {DisplayField} from "core-app/modules/fields/display/display-field.module"; |
|
|
|
|
|
|
|
import {WorkPackageViewHighlightingService} from "core-app/modules/work_packages/routing/wp-view-base/view-services/wp-view-highlighting.service"; |
|
|
|
|
|
|
|
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space"; |
|
|
|
|
|
|
|
import {ProjectCacheService} from "core-components/projects/project-cache.service"; |
|
|
|
import {ProjectCacheService} from "core-components/projects/project-cache.service"; |
|
|
|
|
|
|
|
import {Observable} from "rxjs"; |
|
|
|
export const emptyPlaceholder = '-'; |
|
|
|
import {ProjectResource} from "core-app/modules/hal/resources/project-resource"; |
|
|
|
|
|
|
|
import {HalResourceEditingService} from "core-app/modules/fields/edit/services/hal-resource-editing.service"; |
|
|
|
|
|
|
|
|
|
|
|
@Component({ |
|
|
|
@Component({ |
|
|
|
templateUrl: './project-details.component.html', |
|
|
|
templateUrl: './project-details.component.html', |
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush, |
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush, |
|
|
|
providers: [ |
|
|
|
providers: [ |
|
|
|
// required by the displayField service to render the fields
|
|
|
|
HalResourceEditingService |
|
|
|
PortalCleanupService, |
|
|
|
|
|
|
|
WorkPackageViewHighlightingService, |
|
|
|
|
|
|
|
IsolatedQuerySpace |
|
|
|
|
|
|
|
] |
|
|
|
] |
|
|
|
}) |
|
|
|
}) |
|
|
|
export class WidgetProjectDetailsComponent extends AbstractWidgetComponent implements OnInit { |
|
|
|
export class WidgetProjectDetailsComponent extends AbstractWidgetComponent implements OnInit { |
|
|
|
@ViewChild('contentContainer', { static: true }) readonly contentContainer:ElementRef; |
|
|
|
@ViewChild('contentContainer', { static: true }) readonly contentContainer:ElementRef; |
|
|
|
|
|
|
|
|
|
|
|
public noFields = false; |
|
|
|
public customFields:{key:string, label:string}[] = []; |
|
|
|
|
|
|
|
public project$:Observable<ProjectResource>; |
|
|
|
public text = { |
|
|
|
|
|
|
|
noResults: this.i18n.t('js.grid.widgets.project_details.no_results'), |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(protected readonly i18n:I18nService, |
|
|
|
constructor(protected readonly i18n:I18nService, |
|
|
|
protected readonly injector:Injector, |
|
|
|
protected readonly injector:Injector, |
|
|
|
protected readonly projectDm:ProjectDmService, |
|
|
|
protected readonly projectDm:ProjectDmService, |
|
|
|
protected readonly projectCache:ProjectCacheService, |
|
|
|
protected readonly projectCache:ProjectCacheService, |
|
|
|
protected readonly currentProject:CurrentProjectService, |
|
|
|
protected readonly currentProject:CurrentProjectService, |
|
|
|
protected readonly displayField:DisplayFieldService, |
|
|
|
protected readonly cdRef:ChangeDetectorRef) { |
|
|
|
protected readonly cdr:ChangeDetectorRef) { |
|
|
|
|
|
|
|
super(i18n, injector); |
|
|
|
super(i18n, injector); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
ngOnInit() { |
|
|
|
ngOnInit() { |
|
|
|
this.loadAndRender(); |
|
|
|
this.loadAndRender(); |
|
|
|
|
|
|
|
this.project$ = this.projectCache.requireAndStream(this.currentProject.id!); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public get isEditable() { |
|
|
|
public get isEditable() { |
|
|
@ -80,96 +77,25 @@ export class WidgetProjectDetailsComponent extends AbstractWidgetComponent imple |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private loadAndRender() { |
|
|
|
private loadAndRender() { |
|
|
|
Promise.all( |
|
|
|
Promise.all([ |
|
|
|
[this.loadCurrentProject(), |
|
|
|
this.loadProjectSchema() |
|
|
|
this.loadProjectSchema()] |
|
|
|
]) |
|
|
|
) |
|
|
|
.then(([schema]) => { |
|
|
|
.then(([project, schema]) => { |
|
|
|
this.setCustomFields(schema); |
|
|
|
this.renderCFs(project, schema as SchemaResource); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.redraw(); |
|
|
|
|
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private loadCurrentProject() { |
|
|
|
|
|
|
|
return this.projectCache.require(this.currentProject.id as string); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public get isLoaded() { |
|
|
|
|
|
|
|
return this.projectCache.state(this.currentProject.id as string).value; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private loadProjectSchema() { |
|
|
|
private loadProjectSchema() { |
|
|
|
return this.projectDm.schema(); |
|
|
|
return this.projectDm.schema(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private renderCFs(project:ProjectResource, schema:SchemaResource) { |
|
|
|
private setCustomFields(schema:SchemaResource) { |
|
|
|
const cfFields = this.collectFieldsForCfs(project, schema); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.noFields = cfFields.length === 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.sortFieldsLexicographically(cfFields); |
|
|
|
|
|
|
|
this.renderFields(cfFields); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private collectFieldsForCfs(project:ProjectResource, schema:SchemaResource) { |
|
|
|
|
|
|
|
let displayFields:Array<DisplayField> = []; |
|
|
|
|
|
|
|
// passing an arbitrary context to displayField.getField only to satisfy the interface
|
|
|
|
|
|
|
|
let context:DisplayFieldContext = {injector: this.injector, container: 'table', options: []}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Object.entries(schema).forEach(([key, keySchema]) => { |
|
|
|
Object.entries(schema).forEach(([key, keySchema]) => { |
|
|
|
if (key.match(/customField\d+/)) { |
|
|
|
if (key.match(/customField\d+/)) { |
|
|
|
let field = this.displayField.getField(project, key, keySchema, context); |
|
|
|
this.customFields.push({key: key, label: keySchema.name }); |
|
|
|
|
|
|
|
|
|
|
|
displayFields.push(field); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
return displayFields; |
|
|
|
this.cdRef.detectChanges(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private sortFieldsLexicographically(fields:Array<DisplayField>) { |
|
|
|
|
|
|
|
fields.sort((a, b) => { return a.label.localeCompare(b.label); }); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private renderFields(fields:Array<DisplayField>) { |
|
|
|
|
|
|
|
this.contentContainer.nativeElement.innerHTML = ''; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fields.forEach(field => { |
|
|
|
|
|
|
|
this.renderKeyValue(field); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private renderKeyValue(field:DisplayField) { |
|
|
|
|
|
|
|
this.contentContainer.nativeElement.appendChild(this.labelElement(field)); |
|
|
|
|
|
|
|
this.contentContainer.nativeElement.appendChild(this.valueElement(field)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private labelElement(field:DisplayField) { |
|
|
|
|
|
|
|
const label = document.createElement('div'); |
|
|
|
|
|
|
|
label.classList.add('attributes-map--key'); |
|
|
|
|
|
|
|
label.innerText = field.label; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return label; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private valueElement(field:DisplayField) { |
|
|
|
|
|
|
|
const value = document.createElement('div'); |
|
|
|
|
|
|
|
value.classList.add('attributes-map--value'); |
|
|
|
|
|
|
|
field.render(value, this.getText(field)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return value; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private getText(field:DisplayField):string { |
|
|
|
|
|
|
|
if (field.isEmpty()) { |
|
|
|
|
|
|
|
return emptyPlaceholder; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return field.valueString; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private redraw() { |
|
|
|
|
|
|
|
this.cdr.detectChanges(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|