+
diff --git a/apps/dsp-app/src/app/project/project.component.ts b/apps/dsp-app/src/app/project/project.component.ts
index b14029655a..e2ab9b943a 100644
--- a/apps/dsp-app/src/app/project/project.component.ts
+++ b/apps/dsp-app/src/app/project/project.component.ts
@@ -59,13 +59,6 @@ export class ProjectComponent extends ProjectBase implements OnInit, OnDestroy {
)
);
- readProject$: Observable = (
- this._store.select(ProjectsSelectors.allProjects) as Observable
- ).pipe(
- take(1),
- map(projects => this.getCurrentProject(projects))
- );
-
projectOntologies$: Observable = combineLatest([
this.isProjectsLoading$,
this._store.select(OntologiesSelectors.isLoading),
@@ -82,6 +75,7 @@ export class ProjectComponent extends ProjectBase implements OnInit, OnDestroy {
);
@Select(OntologiesSelectors.hasLoadingErrors) hasLoadingErrors$: Observable;
+ @Select(ProjectsSelectors.currentProject) currentProject$: Observable;
constructor(
private _componentCommsService: ComponentCommunicationEventService,
diff --git a/libs/vre/advanced-search/src/lib/ui/property-form/property-form-link-value/property-form-link-value.component.ts b/libs/vre/advanced-search/src/lib/ui/property-form/property-form-link-value/property-form-link-value.component.ts
index a639177e20..ec0af0833d 100644
--- a/libs/vre/advanced-search/src/lib/ui/property-form/property-form-link-value/property-form-link-value.component.ts
+++ b/libs/vre/advanced-search/src/lib/ui/property-form/property-form-link-value/property-form-link-value.component.ts
@@ -3,11 +3,11 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, EventEmitter, Input,
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { MatInputModule } from '@angular/material/input';
+import { MatAutocompleteOptionsScrollDirective } from '@dasch-swiss/vre/shared/app-common';
import { AppProgressIndicatorComponent } from '@dasch-swiss/vre/shared/app-progress-indicator';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { ApiData } from '../../../data-access/advanced-search-service/advanced-search.service';
import { PropertyFormItem } from '../../../data-access/advanced-search-store/advanced-search-store.service';
-import { MatAutocompleteOptionsScrollDirective } from '../../directives/mat-autocomplete-options-scroll.directive';
@Component({
selector: 'dasch-swiss-property-form-link-value',
diff --git a/libs/vre/shared/app-common/src/index.ts b/libs/vre/shared/app-common/src/index.ts
index b602926548..df78f11d76 100644
--- a/libs/vre/shared/app-common/src/index.ts
+++ b/libs/vre/shared/app-common/src/index.ts
@@ -3,3 +3,4 @@ export * from './lib/custom-regex';
export * from './lib/dsp-resource';
export * from './lib/property-info-values.interface';
export * from './lib/common';
+export * from './lib/directives/mat-autocomplete-options-scroll.directive';
diff --git a/libs/vre/advanced-search/src/lib/ui/directives/mat-autocomplete-options-scroll.directive.ts b/libs/vre/shared/app-common/src/lib/directives/mat-autocomplete-options-scroll.directive.ts
similarity index 93%
rename from libs/vre/advanced-search/src/lib/ui/directives/mat-autocomplete-options-scroll.directive.ts
rename to libs/vre/shared/app-common/src/lib/directives/mat-autocomplete-options-scroll.directive.ts
index 167cb3b3a3..5b1c82878d 100644
--- a/libs/vre/advanced-search/src/lib/ui/directives/mat-autocomplete-options-scroll.directive.ts
+++ b/libs/vre/shared/app-common/src/lib/directives/mat-autocomplete-options-scroll.directive.ts
@@ -32,7 +32,9 @@ export class MatAutocompleteOptionsScrollDirective implements OnDestroy {
setTimeout(() => {
// Note: remove listner just for safety, in case the close event is skipped.
this.removeScrollEventListener();
- this.autoComplete.panel.nativeElement.addEventListener('scroll', this.onScroll.bind(this));
+ if (this.autoComplete.panel) {
+ this.autoComplete.panel.nativeElement.addEventListener('scroll', this.onScroll.bind(this));
+ }
}, 500);
}),
takeUntil(this._onDestroy)
diff --git a/libs/vre/shared/app-resource-properties/src/lib/property-value.component.ts b/libs/vre/shared/app-resource-properties/src/lib/property-value.component.ts
index d33c2f6dd6..69ae7defe4 100644
--- a/libs/vre/shared/app-resource-properties/src/lib/property-value.component.ts
+++ b/libs/vre/shared/app-resource-properties/src/lib/property-value.component.ts
@@ -72,7 +72,7 @@ import { propertiesTypeMapping } from './resource-payloads-mapping';
.item {
flex: 1;
&.hover:hover {
- background: $primary_50;
+ background: $primary_100;
}
}
`,
diff --git a/libs/vre/shared/app-resource-properties/src/lib/property-values.component.ts b/libs/vre/shared/app-resource-properties/src/lib/property-values.component.ts
index 223ea3bc63..56c14c93cc 100644
--- a/libs/vre/shared/app-resource-properties/src/lib/property-values.component.ts
+++ b/libs/vre/shared/app-resource-properties/src/lib/property-values.component.ts
@@ -19,9 +19,10 @@ import { propertiesTypeMapping } from './resource-payloads-mapping';
}
div.property-value {
display: flex;
- padding: 5px;
+ padding: 0 5px;
&:nth-child(even) {
+ padding: 5px;
background-color: $primary_50;
}
}
diff --git a/libs/vre/shared/app-resource-properties/src/lib/switch-components/link-switch.component.ts b/libs/vre/shared/app-resource-properties/src/lib/switch-components/link-switch.component.ts
index 2600ba16ef..d260e28376 100644
--- a/libs/vre/shared/app-resource-properties/src/lib/switch-components/link-switch.component.ts
+++ b/libs/vre/shared/app-resource-properties/src/lib/switch-components/link-switch.component.ts
@@ -36,7 +36,7 @@ export class LinkSwitchComponent implements IsSwitchComponent {
}
get link() {
- return `/resource${this._resourceService.getResourcePath(this.control.value)}`;
+ return this.control.value ? `/resource${this._resourceService.getResourcePath(this.control.value)}` : '#';
}
constructor(private _resourceService: ResourceService) {}
diff --git a/libs/vre/shared/app-resource-properties/src/lib/value-components/link-value.component.scss b/libs/vre/shared/app-resource-properties/src/lib/value-components/link-value.component.scss
new file mode 100644
index 0000000000..5c7b25031d
--- /dev/null
+++ b/libs/vre/shared/app-resource-properties/src/lib/value-components/link-value.component.scss
@@ -0,0 +1,27 @@
+@use '../../../../../../../apps/dsp-app/src/styles/config' as *;
+
+::ng-deep {
+ div.mat-mdc-autocomplete-panel {
+ max-height: 40vh !important;
+
+ .mat-mdc-option.loader {
+ flex-direction: column;
+
+ .mdc-list-item__primary-text {
+ margin-right: initial;
+ }
+ }
+
+ .mat-mdc-option {
+ padding: 10px;
+
+ &:hover:not(.mdc-list-item--disabled) {
+ background: $primary_100;
+ }
+
+ &:nth-child(even) {
+ background-color: $primary_50;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/vre/shared/app-resource-properties/src/lib/value-components/link-value.component.ts b/libs/vre/shared/app-resource-properties/src/lib/value-components/link-value.component.ts
index 8c4a345f59..9a64c0efaf 100644
--- a/libs/vre/shared/app-resource-properties/src/lib/value-components/link-value.component.ts
+++ b/libs/vre/shared/app-resource-properties/src/lib/value-components/link-value.component.ts
@@ -1,24 +1,40 @@
-import { ChangeDetectorRef, Component, ElementRef, Inject, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
+import {
+ AfterViewInit,
+ ChangeDetectorRef,
+ Component,
+ ElementRef,
+ HostBinding,
+ Inject,
+ Input,
+ OnDestroy,
+ OnInit,
+ ViewChild,
+} from '@angular/core';
import { FormControl } from '@angular/forms';
-import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
+import { MatAutocomplete, MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { MatDialog } from '@angular/material/dialog';
import {
+ ApiResponseError,
+ CountQueryResponse,
KnoraApiConnection,
ReadProject,
ReadResource,
ReadResourceSequence,
ResourceClassAndPropertyDefinitions,
+ ResourceClassDefinition,
} from '@dasch-swiss/dsp-js';
+import { MatAutocompleteOptionsScrollDirective } from '@dasch-swiss/vre/shared/app-common';
import { DspApiConnectionToken } from '@dasch-swiss/vre/shared/app-config';
import { ProjectsSelectors } from '@dasch-swiss/vre/shared/app-state';
import { Store } from '@ngxs/store';
-import { Subscription } from 'rxjs';
-import { filter, finalize, switchMap } from 'rxjs/operators';
+import { Observable, Subject, of } from 'rxjs';
+import { debounceTime, filter, finalize, map, switchMap, take, takeUntil } from 'rxjs/operators';
import { CreateResourceDialogComponent, CreateResourceDialogProps } from '../create-resource-dialog.component';
import { LinkValueDataService } from './link-value-data.service';
@Component({
selector: 'app-link-value',
+ styleUrls: ['./link-value.component.scss'],
template: `
- No results were found.
+ No results were found.
Create New: {{ rc?.label }}
- {{ res.label }}
+ 0" [disabled]="true"> {{ searchResultCount }} results found
+
+ {{ res.label }}
+
+
+
+
-
{{ 'appLabels.form.action.searchHelp' | translate }}
{{ errors | humanReadableError }}
`,
- providers: [LinkValueDataService],
+ providers: [LinkValueDataService, MatAutocompleteOptionsScrollDirective],
})
-export class LinkValueComponent implements OnInit, OnDestroy {
+export class LinkValueComponent implements OnInit, AfterViewInit, OnDestroy {
+ private readonly pageResultsLimit: number = 25;
+ private cancelPreviousCountRequest$ = new Subject();
+ private cancelPreviousSearchRequest$ = new Subject();
+
@Input({ required: true }) control!: FormControl;
@Input({ required: true }) propIri!: string;
@Input({ required: true }) resourceClassIri!: string;
@Input({ required: true }) defaultValue!: string;
@ViewChild(MatAutocompleteTrigger) autoComplete!: MatAutocompleteTrigger;
+ @ViewChild(MatAutocomplete) auto!: MatAutocomplete;
@ViewChild('input') input!: ElementRef;
+ @HostBinding('attr.scroll') matAutocompleteOptionsScrollDirective: MatAutocompleteOptionsScrollDirective | undefined;
+ destroyed$ = new Subject();
loading = false;
useDefaultValue = true;
resources: ReadResource[] = [];
-
readResource: ReadResource | undefined;
- subscription: Subscription | undefined;
+
+ searchResultCount: number = 0;
+ nextPageNumber: number = 0;
constructor(
@Inject(DspApiConnectionToken)
@@ -78,6 +107,10 @@ export class LinkValueComponent implements OnInit, OnDestroy {
this._getResourceProperties();
}
+ ngAfterViewInit() {
+ this._initAutocompleteScroll();
+ }
+
handleNonSelectedValues() {
const text = this._getTextInput();
if (text !== this.displayResource(this.control.value)) {
@@ -85,32 +118,29 @@ export class LinkValueComponent implements OnInit, OnDestroy {
}
}
- search() {
- const readResource = this.readResource as ReadResource;
+ onInputValueChange() {
+ this.searchResultCount = 0;
+ this.nextPageNumber = 0;
+ this.resources = [];
const searchTerm = this._getTextInput();
if (searchTerm?.length < 3) {
return;
}
- this.loading = true;
- this.subscription = this._dspApiConnection.v2.search
- .doSearchByLabel(searchTerm, 0, {
- limitToResourceClass: this._getRestrictToResourceClass(readResource),
- })
- .pipe(
- finalize(() => {
- this.loading = false;
- })
- )
- .subscribe(response => {
- this.resources = (response as ReadResourceSequence).resources;
- this._cd.detectChanges();
+ this.loading = true;
+ const resourceClassIri = this._getRestrictToResourceClass(this.readResource as ReadResource)!;
+ this._getResourcesListCount(searchTerm, resourceClassIri)
+ .pipe(take(1))
+ .subscribe(count => {
+ this.searchResultCount = count;
+ this._cd.markForCheck();
});
+
+ this._search(searchTerm);
}
openCreateResourceDialog(event: any, resourceClassIri: string, resourceType: string) {
let myResourceId: string;
-
event.stopPropagation();
const projectIri = (this._store.selectSnapshot(ProjectsSelectors.currentProject) as ReadProject).id;
this._dialog
@@ -130,6 +160,8 @@ export class LinkValueComponent implements OnInit, OnDestroy {
})
)
.subscribe(res => {
+ this.resources = [];
+ this.searchResultCount = 1;
this.resources.push(res as ReadResource);
this.control.setValue(myResourceId);
this.autoComplete.closePanel();
@@ -137,10 +169,9 @@ export class LinkValueComponent implements OnInit, OnDestroy {
});
}
- private _getRestrictToResourceClass(resource: ReadResource) {
- const linkType = resource.getLinkPropertyIriFromLinkValuePropertyIri(this.propIri);
- return resource.entityInfo.properties[linkType].objectType;
- }
+ trackByResourcesFn = (index: number, item: ReadResource) => `${index}-${item.id}`;
+
+ trackByResourceClassFn = (index: number, item: ResourceClassDefinition) => `${index}-${item.id}`;
displayResource(resId: string | null): string {
if (this.useDefaultValue) return this.defaultValue;
@@ -148,6 +179,71 @@ export class LinkValueComponent implements OnInit, OnDestroy {
return this.resources.find(res => res.id === resId)?.label ?? '';
}
+ ngOnDestroy() {
+ this.destroyed$.next();
+ this.destroyed$.complete();
+ }
+
+ private _getResourcesListCount(searchValue: string, resourceClassIri: string): Observable {
+ this.cancelPreviousCountRequest$.next();
+ if (!searchValue || searchValue.length <= 2 || typeof searchValue !== 'string') return of(0);
+
+ return this._dspApiConnection.v2.search
+ .doSearchByLabelCountQuery(searchValue, {
+ limitToResourceClass: resourceClassIri,
+ })
+ .pipe(
+ takeUntil(this.cancelPreviousCountRequest$),
+ switchMap((response: CountQueryResponse | ApiResponseError) => {
+ const countQuery = response as CountQueryResponse;
+ return of(countQuery.numberOfResults);
+ })
+ );
+ }
+
+ private _search(searchTerm: string, offset = 0) {
+ this.cancelPreviousSearchRequest$.next();
+ const resourceClassIri = this._getRestrictToResourceClass(this.readResource as ReadResource)!;
+ this._dspApiConnection.v2.search
+ .doSearchByLabel(searchTerm, offset, {
+ limitToResourceClass: resourceClassIri,
+ })
+ .pipe(
+ takeUntil(this.cancelPreviousSearchRequest$),
+ take(1),
+ finalize(() => {
+ this.loading = false;
+ })
+ )
+ .subscribe(response => {
+ this.resources = this.resources.concat((response as ReadResourceSequence).resources);
+ this.nextPageNumber += 1;
+ this._cd.markForCheck();
+ });
+ }
+
+ private _getRestrictToResourceClass(resource: ReadResource) {
+ const linkType = resource.getLinkPropertyIriFromLinkValuePropertyIri(this.propIri);
+ return resource.entityInfo.properties[linkType].objectType;
+ }
+
+ private _initAutocompleteScroll() {
+ this.matAutocompleteOptionsScrollDirective = new MatAutocompleteOptionsScrollDirective(this.auto);
+ this.matAutocompleteOptionsScrollDirective.scrollEvent
+ .pipe(
+ filter(() => {
+ return !this.loading && this.searchResultCount > this.resources.length;
+ }),
+ takeUntil(this.destroyed$),
+ map(() => {
+ this.loading = true;
+ this._cd.markForCheck();
+ }),
+ debounceTime(300)
+ )
+ .subscribe(() => this._search(this._getTextInput(), this.nextPageNumber));
+ }
+
private _getResourceProperties() {
const ontologyIri = this.resourceClassIri.split('#')[0];
this._dspApiConnection.v2.ontologyCache
@@ -166,8 +262,4 @@ export class LinkValueComponent implements OnInit, OnDestroy {
private _getTextInput() {
return this.input.nativeElement.value;
}
-
- ngOnDestroy() {
- this.subscription?.unsubscribe();
- }
}
- {{(readProject$ | async)?.longname}} + {{(currentProject$ | async)?.longname}}