@knora/search
Version:
Knora ui module: search
773 lines (766 loc) • 92.5 kB
JavaScript
import { __decorate, __metadata, __param } from 'tslib';
import { OverlayConfig, ConnectionPositionPair, Overlay } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { EventEmitter, Inject, ViewContainerRef, Input, Output, ViewChild, ElementRef, TemplateRef, Component, ViewChildren, QueryList, Host, NgModule } from '@angular/core';
import { MatMenuTrigger } from '@angular/material';
import { Router, ActivatedRoute } from '@angular/router';
import { Constants, KnoraApiConnection, ReadResource, KnoraApiConfig } from '@knora/api';
import { KnoraApiConnectionToken, OntologyCacheService, GravsearchGenerationService, ExtendedSearchParams, KnoraApiConfigToken, SearchParamsService, Exists, Equals, NotEquals, LessThan, LessThanEquals, GreaterThan, GreaterThanEquals, Like, Match, ComparisonOperatorAndValue, Property, CardinalityOccurrence, OntologyInformation, PropertyWithValue, Properties, ResourceClass, ValueLiteral, KnoraConstants, IRI, Utils, KuiCoreModule } from '@knora/core';
import { FormBuilder, FormGroup, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { DateAdapter } from '@angular/material/core';
import { MatCalendar, MatDatepickerContent, MatDatepickerModule } from '@angular/material/datepicker';
import { JDNConvertibleCalendarDateAdapter, MatJDNConvertibleCalendarDateAdapterModule } from 'jdnconvertiblecalendardateadapter';
import { MatInputModule } from '@angular/material/input';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { CommonModule } from '@angular/common';
import { MatListModule } from '@angular/material/list';
import { MatMenuTrigger as MatMenuTrigger$1, MatMenuModule } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
import { KuiActionModule } from '@knora/action';
import { KuiViewerModule } from '@knora/viewer';
/**
*
*/
let FulltextSearchComponent = class FulltextSearchComponent {
constructor(knoraApiConnection, _overlay, _router, _viewContainerRef) {
this.knoraApiConnection = knoraApiConnection;
this._overlay = _overlay;
this._router = _router;
this._viewContainerRef = _viewContainerRef;
/**
*
* @param {string} route Route to navigate after search.
* This route path should contain a component for search results.
*/
this.route = '/search';
/**
*
* @param {boolean} [projectfilter] If true it shows the selection
* of projects to filter by one of them
*/
this.projectfilter = false;
this.showState = new EventEmitter();
// previous search = full-text search history
this.prevSearch = JSON.parse(localStorage.getItem('prevSearch'));
this.defaultProjectLabel = 'All projects';
this.projectLabel = this.defaultProjectLabel;
// is search panel focused?
this.searchPanelFocus = false;
// do not show the following projects: default system projects from knora
this.doNotDisplay = [
Constants.SystemProjectIRI,
Constants.DefaultSharedOntologyIRI
];
}
ngOnInit() {
// this.setFocus();
if (this.filterbyproject) {
this.getProject(this.filterbyproject);
}
if (this.projectfilter) {
this.getAllProjects();
if (localStorage.getItem('currentProject') !== null) {
this.setProject(JSON.parse(localStorage.getItem('currentProject')));
}
}
}
openPanelWithBackdrop() {
const config = new OverlayConfig({
hasBackdrop: true,
backdropClass: 'cdk-overlay-transparent-backdrop',
// backdropClass: 'cdk-overlay-dark-backdrop',
positionStrategy: this.getOverlayPosition(),
scrollStrategy: this._overlay.scrollStrategies.block()
});
this.overlayRef = this._overlay.create(config);
this.overlayRef.attach(new TemplatePortal(this.searchMenu, this._viewContainerRef));
this.overlayRef.backdropClick().subscribe(() => {
this.searchPanelFocus = false;
this.overlayRef.detach();
});
}
getOverlayPosition() {
const positions = [
new ConnectionPositionPair({ originX: 'start', originY: 'bottom' }, { overlayX: 'start', overlayY: 'top' }),
new ConnectionPositionPair({ originX: 'start', originY: 'top' }, { overlayX: 'start', overlayY: 'bottom' })
];
const overlayPosition = this._overlay.position().flexibleConnectedTo(this.searchPanel).withPositions(positions).withLockedPosition(false);
return overlayPosition;
}
getAllProjects() {
this.knoraApiConnection.admin.projectsEndpoint.getProjects().subscribe((response) => {
this.projects = response.body.projects;
// this.loadSystem = false;
if (localStorage.getItem('currentProject') !== null) {
this.project = JSON.parse(localStorage.getItem('currentProject'));
}
}, (error) => {
console.error(error);
this.error = error;
});
}
getProject(id) {
this.knoraApiConnection.admin.projectsEndpoint.getProjectByIri(id).subscribe((project) => {
this.setProject(project.body.project);
}, (error) => {
console.error(error);
});
}
// set current project and switch focus to input field
setProject(project) {
if (!project) {
// set default project: all
this.projectLabel = this.defaultProjectLabel;
this.projectIri = undefined;
localStorage.removeItem('currentProject');
}
else {
// set current project shortname and id
this.projectLabel = project.shortname;
this.projectIri = project.id;
localStorage.setItem('currentProject', JSON.stringify(project));
}
}
doSearch() {
if (this.searchQuery !== undefined && this.searchQuery !== null) {
if (this.projectIri !== undefined) {
this._router.navigate([
this.route +
'/fulltext/' +
this.searchQuery +
'/' +
encodeURIComponent(this.projectIri)
]);
}
else {
this._router.navigate([
this.route + '/fulltext/' + this.searchQuery
]);
}
// push the search query into the local storage prevSearch array (previous search)
// to have a list of recent search requests
let existingPrevSearch = JSON.parse(localStorage.getItem('prevSearch'));
if (existingPrevSearch === null) {
existingPrevSearch = [];
}
let i = 0;
for (const entry of existingPrevSearch) {
// remove entry, if exists already
if (this.searchQuery === entry.query && this.projectIri === entry.projectIri) {
existingPrevSearch.splice(i, 1);
}
i++;
}
// A search value is expected to have at least length of 3
if (this.searchQuery.length > 2) {
let currentQuery = {
query: this.searchQuery
};
if (this.projectIri) {
currentQuery = {
projectIri: this.projectIri,
projectLabel: this.projectLabel,
query: this.searchQuery
};
}
existingPrevSearch.push(currentQuery);
localStorage.setItem('prevSearch', JSON.stringify(existingPrevSearch));
}
}
this.resetSearch();
this.overlayRef.detach();
this.show = false;
this.showState.emit(this.show);
}
resetSearch() {
this.searchPanelFocus = false;
this.searchInput.nativeElement.blur();
this.overlayRef.detach();
}
setFocus() {
this.prevSearch = JSON.parse(localStorage.getItem('prevSearch'));
this.searchPanelFocus = true;
this.openPanelWithBackdrop();
}
doPrevSearch(prevSearch) {
this.searchQuery = prevSearch.query;
if (prevSearch.projectIri !== undefined) {
this.projectIri = prevSearch.projectIri;
this.projectLabel = prevSearch.projectLabel;
this._router.navigate([this.route + '/fulltext/' + this.searchQuery + '/' + encodeURIComponent(prevSearch.projectIri)]);
}
else {
this.projectIri = undefined;
this.projectLabel = this.defaultProjectLabel;
this._router.navigate([this.route + '/fulltext/' + this.searchQuery]);
}
this.resetSearch();
this.overlayRef.detach();
}
resetPrevSearch(prevSearch) {
if (prevSearch) {
// delete only this item with the name ...
const i = this.prevSearch.indexOf(prevSearch);
this.prevSearch.splice(i, 1);
localStorage.setItem('prevSearch', JSON.stringify(this.prevSearch));
}
else {
// delete the whole "previous search" array
localStorage.removeItem('prevSearch');
}
this.prevSearch = JSON.parse(localStorage.getItem('prevSearch'));
}
changeFocus() {
this.selectProject.closeMenu();
this.searchInput.nativeElement.focus();
this.setFocus();
}
};
FulltextSearchComponent.ctorParameters = () => [
{ type: KnoraApiConnection, decorators: [{ type: Inject, args: [KnoraApiConnectionToken,] }] },
{ type: Overlay },
{ type: Router },
{ type: ViewContainerRef }
];
__decorate([
Input(),
__metadata("design:type", String)
], FulltextSearchComponent.prototype, "route", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], FulltextSearchComponent.prototype, "projectfilter", void 0);
__decorate([
Input(),
__metadata("design:type", String)
], FulltextSearchComponent.prototype, "filterbyproject", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], FulltextSearchComponent.prototype, "show", void 0);
__decorate([
Output(),
__metadata("design:type", Object)
], FulltextSearchComponent.prototype, "showState", void 0);
__decorate([
ViewChild('fulltextSearchPanel', { static: false }),
__metadata("design:type", ElementRef)
], FulltextSearchComponent.prototype, "searchPanel", void 0);
__decorate([
ViewChild('fulltextSearchInput', { static: false }),
__metadata("design:type", ElementRef)
], FulltextSearchComponent.prototype, "searchInput", void 0);
__decorate([
ViewChild('fulltextSearchMenu', { static: false }),
__metadata("design:type", TemplateRef)
], FulltextSearchComponent.prototype, "searchMenu", void 0);
__decorate([
ViewChild('btnToSelectProject', { static: false }),
__metadata("design:type", MatMenuTrigger)
], FulltextSearchComponent.prototype, "selectProject", void 0);
FulltextSearchComponent = __decorate([
Component({
selector: 'kui-fulltext-search',
template: "<!-- full-text search panel -->\n<div class=\"kui-fulltext-search-panel\" [class.active]=\"searchPanelFocus\" [class.with-project-filter]=\"projectfilter\"\n #fulltextSearchPanel cdkOverlayOrigin>\n\n <!-- DESKTOP / TABLET VERSION -->\n <div class=\"kui-project-filter\" *ngIf=\"projectfilter\">\n <button mat-button class=\"kui-project-filter-button\" [matMenuTriggerFor]=\"selectProject\"\n #btnToSelectProject=\"matMenuTrigger\" isIconButton>\n <p class=\"mat-caption placeholder\">Filter by project</p>\n <p class=\"label\">{{projectLabel}}</p>\n <mat-icon class=\"icon\" matSuffix>keyboard_arrow_down</mat-icon>\n </button>\n <mat-menu #selectProject=\"matMenu\">\n <div class=\"kui-project-filter-menu\">\n <button mat-menu-item class=\"center\"\n (click)=\"setProject();changeFocus()\">{{defaultProjectLabel}}</button>\n <mat-divider></mat-divider>\n <span *ngFor=\"let project of projects | kuiSortBy: 'shortname'\">\n <button mat-menu-item *ngIf=\"!doNotDisplay.includes(project.id)\"\n (click)=\"setProject(project);changeFocus()\" [matTooltip]=\"project.longname\"\n [matTooltipPosition]=\"'after'\">{{project.shortname}}</button>\n </span>\n </div>\n </mat-menu>\n </div>\n\n <div class=\"kui-fulltext-search\" [class.with-project-filter]=\"projectfilter\">\n <div class=\"kui-fulltext-search-field\">\n <input #fulltextSearchInput class=\"kui-fulltext-search-input\" type=\"search\" [(ngModel)]=\"searchQuery\"\n name=\"search\" minlength=\"3\" autocomplete=\"off\" [placeholder]=\"'Search'\" (keyup.esc)=\"resetSearch()\"\n (keyup.enter)=\"doSearch()\" (click)=\"setFocus()\">\n </div>\n <button class=\"kui-fulltext-search-button suffix\" (click)=\"doSearch()\" type=\"submit\">\n <mat-icon>search</mat-icon>\n </button>\n </div>\n\n <!-- PHONE VERSION *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** -->\n <div class=\"kui-project-filter-mobile\" *ngIf=\"projectfilter\">\n <button mat-stroked-button class=\"kui-project-filter-button kui-project-filter-button-mobile\"\n [matMenuTriggerFor]=\"selectProject\" #btnToSelectProject=\"matMenuTrigger\" isIconButton>\n <p class=\"mat-caption placeholder\">Filter by project</p>\n <p class=\"label\">{{projectLabel}}</p>\n <mat-icon class=\"icon\" matSuffix>keyboard_arrow_down</mat-icon>\n </button>\n <mat-menu #selectProject=\"matMenu\">\n <div class=\"kui-project-filter-menu-mobile\">\n <button mat-menu-item class=\"center\"\n (click)=\"setProject();changeFocus()\">{{defaultProjectLabel}}</button>\n <mat-divider></mat-divider>\n <span *ngFor=\"let project of projects | kuiSortBy: 'shortname'\">\n <button mat-menu-item *ngIf=\"!doNotDisplay.includes(project.id)\"\n (click)=\"setProject(project);changeFocus()\" [matTooltip]=\"project.longname\"\n [matTooltipPosition]=\"'after'\">{{project.shortname}}</button>\n </span>\n </div>\n </mat-menu>\n <!-- <mat-form-field class=\"kui-project-filter-select-mobile\">\n <mat-select [(ngModel)]=\"All projects\">\n <mat-option *ngFor=\"let project of projects | kuiSortBy: 'shortname'\" [value]=\"project.shortname\">\n {{project.shortname}}\n </mat-option>\n </mat-select>\n </mat-form-field> -->\n </div>\n\n <div class=\"kui-fulltext-search-mobile\" [class.with-project-filter]=\"projectfilter\">\n <div class=\"kui-fulltext-search-field-mobile\">\n <input #fulltextSearchInput class=\"kui-fulltext-search-input-mobile\" type=\"search\" [(ngModel)]=\"searchQuery\"\n name=\"search\" minlength=\"3\" autocomplete=\"off\" [placeholder]=\"'Search'\" (keyup.esc)=\"resetSearch()\"\n (keyup.enter)=\"doSearch()\" (click)=\"setFocus()\">\n </div>\n <button mat-stroked-button class=\"kui-fulltext-search-button-mobile suffix-mobile\" (click)=\"doSearch()\"\n type=\"submit\">\n Search\n </button>\n </div>\n <!-- *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** -->\n\n</div>\n\n<!-- full-text search menu - only for desktop/tablet versions -->\n<ng-template #fulltextSearchMenu>\n\n <div class=\"kui-search-menu\" [class.with-project-filter]=\"projectfilter\">\n <div class=\"kui-menu-content\">\n <mat-list class=\"kui-previous-search-list\">\n <div *ngFor=\"let item of prevSearch | kuiReverse; let i=index\">\n <mat-list-item *ngIf=\"i<10\">\n <h4 mat-line (click)=\"doPrevSearch(item)\" class=\"kui-previous-search-item\">\n <div class=\"kui-project-filter-label\" [class.not-empty]=\"item.projectIri\"\n *ngIf=\"projectfilter && !error && projects?.length > 0\">\n <span *ngIf=\"item.projectIri\">{{item.projectLabel}}</span>\n </div>\n <div class=\"kui-previous-search-query\" [class.fix-width]=\"projectfilter\">\n {{item.query}}\n </div>\n </h4>\n <button mat-icon-button (click)=\"resetPrevSearch(item)\">\n <mat-icon class=\"mat-list-close-icon\" aria-label=\"close\">close</mat-icon>\n </button>\n </mat-list-item>\n </div>\n </mat-list>\n </div>\n\n <div class=\"kui-menu-action\" *ngIf=\"prevSearch\">\n <mat-divider></mat-divider>\n <button mat-button color=\"primary\" class=\"center\" (click)=\"resetPrevSearch()\">Clear list\n </button>\n </div>\n </div>\n\n</ng-template>\n",
styles: ["input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration,input[type=search]::-webkit-search-results-button,input[type=search]::-webkit-search-results-decoration{display:none}input[type=search]{-moz-appearance:none;-webkit-appearance:none}.center{text-align:center}.kui-fulltext-search-panel{border-radius:4px;display:-webkit-box;display:flex;height:40px;position:relative;width:480px;z-index:100;background-color:#f9f9f9}.kui-fulltext-search-panel.active{box-shadow:0 1px 3px rgba(0,0,0,.5)}.kui-fulltext-search-panel.with-project-filter{width:calc(480px + 160px)}.kui-fulltext-search-panel .kui-project-filter-button{font-size:inherit;overflow:hidden;text-overflow:ellipsis;width:160px;margin:1px;border-radius:4px 0 0 4px}.kui-fulltext-search-panel .kui-fulltext-search{background-color:#f9f9f9;border-radius:4px;display:-webkit-inline-box;display:inline-flex;position:relative;z-index:10}.kui-fulltext-search-panel .kui-fulltext-search.with-project-filter{width:calc(480px + 160px);border-top-left-radius:0;border-bottom-left-radius:0}.kui-fulltext-search-panel .kui-fulltext-search .kui-fulltext-search-field{-webkit-box-flex:1;flex:1;width:calc(480px - 40px);margin:1px}.kui-fulltext-search-panel .kui-fulltext-search .kui-fulltext-search-field .kui-fulltext-search-input{border-style:none;font-size:14pt;height:38px;position:absolute;padding-left:12px;width:calc(100% - 40px)}.kui-fulltext-search-panel .kui-fulltext-search .kui-fulltext-search-field .kui-fulltext-search-input.with-project-filter{width:calc(100% - 40px - 160px)}.kui-fulltext-search-panel .kui-fulltext-search .kui-fulltext-search-field .kui-fulltext-search-input:active,.kui-fulltext-search-panel .kui-fulltext-search .kui-fulltext-search-field .kui-fulltext-search-input:focus{outline:0}.kui-fulltext-search-panel .kui-fulltext-search .kui-fulltext-search-button{background-color:#fff}.kui-fulltext-search-panel .kui-fulltext-search .suffix{margin:1px 0 1px -3px;border-radius:0 4px 4px 0}.kui-fulltext-search-panel .kui-fulltext-search .prefix{margin:1px 0 1px 3px;border-radius:4px 0 0 4px}.kui-fulltext-search-panel .kui-fulltext-search .prefix,.kui-fulltext-search-panel .kui-fulltext-search .suffix{border-style:none;color:rgba(41,41,41,.4);cursor:pointer;height:38px;outline:0;position:relative;width:39px}.kui-fulltext-search-panel .kui-fulltext-search .prefix.disabled,.kui-fulltext-search-panel .kui-fulltext-search .suffix.disabled{cursor:auto}.kui-fulltext-search-panel .kui-fulltext-search .prefix:active,.kui-fulltext-search-panel .kui-fulltext-search .suffix:active{color:#515151}.kui-search-menu{height:100%}.kui-search-menu .kui-menu-content{display:block}.kui-search-menu .kui-menu-content .mat-list{padding-bottom:8px}.kui-search-menu .kui-menu-content .mat-list .kui-previous-search-query{overflow:hidden;text-overflow:ellipsis}.kui-search-menu .kui-menu-content .mat-list .kui-previous-search-query.fix-width{width:calc(100% - 160px)}.kui-project-filter-button{height:38px!important;display:block;text-align:left}.kui-project-filter-button .placeholder{margin:0;padding:0;font-size:x-small}.kui-project-filter-button .icon,.kui-project-filter-button .label{display:inline;position:relative}.kui-project-filter-button .label{top:-12px;font-size:smaller;text-transform:capitalize}.kui-project-filter-button .icon{top:-6px;float:right}.kui-project-filter-button.kui-project-filter-button-mobile{height:100%!important}.kui-project-filter-button.kui-project-filter-button-mobile .icon,.kui-project-filter-button.kui-project-filter-button-mobile .label{top:0;font-size:inherit}@media (min-width:576px){.kui-fulltext-search-mobile,.kui-fulltext-search-mobile.with-project-filter,.kui-project-filter-mobile{display:none}}@media (max-width:576px){.kui-fulltext-search,.kui-project-filter{display:none}.kui-fulltext-search.with-project-filter{display:none!important}.kui-search-menu{display:none}.kui-search-menu.with-project-filter{display:none!important}.kui-fulltext-search-panel{height:100vh!important;background-color:rgba(220,218,218,.9);position:relative;width:100%!important;z-index:100;display:block;border-radius:0}.kui-fulltext-search-panel.with-project-filter{width:100%!important}.kui-fulltext-search-panel .kui-project-filter-mobile{height:54px;margin:0 2% 5%;padding-top:3%}.kui-fulltext-search-panel .kui-project-filter-mobile.mat-stroked-button{padding:0}.kui-fulltext-search-panel .kui-project-filter-mobile .kui-project-filter-button-mobile{width:calc(100% - 16px);margin:8px;height:100%;background-color:#b8b8b8;cursor:pointer}.kui-fulltext-search-panel .kui-project-filter-mobile .kui-project-filter-menu-mobile{width:calc(100% - 32px)!important;max-width:100%;margin:0 8px;height:100%!important}.kui-fulltext-search-panel .kui-fulltext-search-mobile{display:-webkit-box;display:flex;height:64px;margin-top:5%;margin-right:4%;margin-left:4%}.kui-fulltext-search-panel .kui-fulltext-search-mobile .kui-fulltext-search-field-mobile{width:80%;margin-right:2px}.kui-fulltext-search-panel .kui-fulltext-search-mobile .kui-fulltext-search-field-mobile .kui-fulltext-search-input-mobile{width:72%;height:64px;font-size:14pt;position:absolute;padding-left:12px;border-radius:5px;border:.6px solid #b8b8b8}.kui-fulltext-search-panel .kui-fulltext-search-mobile .kui-fulltext-search-field-mobile .kui-fulltext-search-input-mobile:active,.kui-fulltext-search-panel .kui-fulltext-search-mobile .kui-fulltext-search-field-mobile .kui-fulltext-search-input-mobile:focus{outline:0}.kui-fulltext-search-panel .kui-fulltext-search-mobile .kui-fulltext-search-button-mobile{background-color:#fff;width:20%;height:64px;padding:0;margin-left:2px;border-radius:5px;border:.8px solid #b8b8b8}.kui-fulltext-search-panel .kui-fulltext-search-mobile .suffix{margin:1px 0 1px -3px;border-radius:0 4px 4px 0}.kui-fulltext-search-panel .kui-fulltext-search-mobile .prefix,.kui-fulltext-search-panel .kui-fulltext-search-mobile .suffix{border-style:none;color:rgba(41,41,41,.4);cursor:pointer;height:100%;outline:0;position:relative;width:20%}.kui-fulltext-search-panel .kui-fulltext-search-mobile .prefix.disabled,.kui-fulltext-search-panel .kui-fulltext-search-mobile .suffix.disabled{cursor:auto}.kui-fulltext-search-panel .kui-fulltext-search-mobile .prefix:active,.kui-fulltext-search-panel .kui-fulltext-search-mobile .suffix:active{color:#515151}::ng-deep .cdk-overlay-pane .mat-menu-panel{box-shadow:none}::ng-deep .cdk-overlay-pane .mat-select-panel-wrap{margin-top:20%}::ng-deep .cdk-overlay-pane .mat-select-panel-wrap .mat-select-panel{max-height:100%!important}}"]
}),
__param(0, Inject(KnoraApiConnectionToken)),
__metadata("design:paramtypes", [KnoraApiConnection,
Overlay,
Router,
ViewContainerRef])
], FulltextSearchComponent);
/**
* The search-panel contains the kui-fulltext-search and the kui-extended-search components.
*/
let SearchPanelComponent = class SearchPanelComponent {
constructor(_overlay, _viewContainerRef) {
this._overlay = _overlay;
this._viewContainerRef = _viewContainerRef;
/**
* @param {string} route Route to navigate after search. This route path should contain a component for search results.
*/
this.route = '/search';
/**
*@param {boolean} [projectfilter] If true it shows the selection of projects to filter by one of them
*/
this.projectfilter = false;
/**
* @param {boolean} [advanced] Adds the extended / advanced search to the panel
*/
this.advanced = false;
/**
* @param {boolean} [expert] Adds the expert search / gravsearch editor to the panel
*/
this.expert = false;
}
openPanelWithBackdrop(type) {
this.showAdvanced = (type === 'advanced');
const config = new OverlayConfig({
hasBackdrop: true,
backdropClass: 'cdk-overlay-transparent-backdrop',
// backdropClass: 'cdk-overlay-dark-backdrop',
positionStrategy: this.getOverlayPosition(),
scrollStrategy: this._overlay.scrollStrategies.block()
});
this.overlayRef = this._overlay.create(config);
this.overlayRef.attach(new TemplatePortal(this.searchMenu, this._viewContainerRef));
this.overlayRef.backdropClick().subscribe(() => {
this.overlayRef.detach();
});
}
getOverlayPosition() {
const positions = [
new ConnectionPositionPair({ originX: 'start', originY: 'bottom' }, { overlayX: 'start', overlayY: 'top' }),
new ConnectionPositionPair({ originX: 'start', originY: 'top' }, { overlayX: 'start', overlayY: 'bottom' })
];
const overlayPosition = this._overlay.position().flexibleConnectedTo(this.searchPanel).withPositions(positions).withLockedPosition(false);
return overlayPosition;
}
closeMenu() {
this.overlayRef.detach();
}
};
SearchPanelComponent.ctorParameters = () => [
{ type: Overlay },
{ type: ViewContainerRef }
];
__decorate([
Input(),
__metadata("design:type", String)
], SearchPanelComponent.prototype, "route", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], SearchPanelComponent.prototype, "projectfilter", void 0);
__decorate([
Input(),
__metadata("design:type", String)
], SearchPanelComponent.prototype, "filterbyproject", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], SearchPanelComponent.prototype, "advanced", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], SearchPanelComponent.prototype, "expert", void 0);
__decorate([
ViewChild('fullSearchPanel', { static: false }),
__metadata("design:type", ElementRef)
], SearchPanelComponent.prototype, "searchPanel", void 0);
__decorate([
ViewChild('searchMenu', { static: false }),
__metadata("design:type", TemplateRef)
], SearchPanelComponent.prototype, "searchMenu", void 0);
SearchPanelComponent = __decorate([
Component({
selector: 'kui-search-panel',
template: "<div class=\"kui-search-panel\" #fullSearchPanel cdkOverlayOrigin>\n\n <!-- DESKTOP VERSION -->\n <kui-fulltext-search class=\"kui-fulltext-search\" [route]=\"route\" [projectfilter]=\"projectfilter\"\n [filterbyproject]=\"filterbyproject\">\n </kui-fulltext-search>\n\n <!-- advanced search button: if advanced === true -->\n <button mat-button *ngIf=\"advanced && !expert\" color=\"primary\"\n (click)=\"openPanelWithBackdrop('advanced')\">advanced</button>\n\n <!-- expert search button: if expert === true -->\n <button mat-button *ngIf=\"!advanced && expert\" color=\"primary\"\n (click)=\"openPanelWithBackdrop('expert')\">expert</button>\n\n <!-- button to select advanced or expert search: if advanced AND expert === true; open menu to select -->\n <button mat-button *ngIf=\"advanced && expert\" [matMenuTriggerFor]=\"selectSearch\">\n <mat-icon>filter_list</mat-icon>\n </button>\n <mat-menu #selectSearch=\"matMenu\">\n <button mat-menu-item (click)=\"openPanelWithBackdrop('advanced')\">\n <span>Advanced search</span>\n </button>\n <button mat-menu-item (click)=\"openPanelWithBackdrop('expert')\">\n <span>Expert search</span>\n </button>\n </mat-menu>\n\n</div>\n\n<!-- full-text search menu -->\n<ng-template #searchMenu>\n <div class=\"kui-search-menu with-extended-search\" [class.with-project-filter]=\"projectfilter\">\n <div class=\"kui-menu-header\">\n <span class=\"kui-menu-title\">\n <h4 *ngIf=\"showAdvanced\">Advanced search</h4>\n <h4 *ngIf=\"!showAdvanced\">Expert search</h4>\n </span>\n <span class=\"fill-remaining-space\"></span>\n <span class=\"kui-menu-close\">\n <button mat-icon-button (click)=\"closeMenu()\">\n <mat-icon>close</mat-icon>\n </button>\n </span>\n </div>\n <div class=\"kui-menu-content\">\n <kui-extended-search *ngIf=\"showAdvanced\" [route]=\"route\" (toggleExtendedSearchForm)=\"closeMenu()\">\n </kui-extended-search>\n <kui-expert-search *ngIf=\"!showAdvanced\" [route]=\"route\" (toggleExpertSearchForm)=\"closeMenu()\">\n </kui-expert-search>\n </div>\n </div>\n</ng-template>\n",
styles: [".advanced-btn{margin-left:10px}.kui-search-panel{display:-webkit-box;display:flex;position:relative;z-index:100}.extended-search-box{margin:12px}@media (max-width:576px){.kui-fulltext-search{height:100%;width:100%!important}}"]
}),
__metadata("design:paramtypes", [Overlay,
ViewContainerRef])
], SearchPanelComponent);
// https://stackoverflow.com/questions/45661010/dynamic-nested-reactive-form-expressionchangedafterithasbeencheckederror
const resolvedPromise = Promise.resolve(null);
let SelectResourceClassComponent = class SelectResourceClassComponent {
constructor(fb) {
this.fb = fb;
// event emitted to parent component once a resource class is selected by the user
this.resourceClassSelectedEvent = new EventEmitter();
}
// setter method for resource classes when being updated by parent component
set resourceClasses(value) {
this.resourceClassSelected = undefined; // reset on updates
this._resourceClasses = value;
}
// getter method for resource classes (used in template)
get resourceClasses() {
return this._resourceClasses;
}
/**
* Returns the Iri of the selected resource class.
*
* @returns the Iri of the selected resource class or false in case no resource class is selected.
*/
getResourceClassSelected() {
if (this.resourceClassSelected !== undefined && this.resourceClassSelected !== null) {
return this.resourceClassSelected;
}
else {
return false;
}
}
/**
* Initalizes the FormGroup for the resource class selection.
* The initial value is set to null.
*/
initForm() {
// build a form for the resource class selection
this.form = this.fb.group({
resourceClass: [null] // resource class selection is optional
});
// store and emit Iri of the resource class when selected
this.form.valueChanges.subscribe((data) => {
this.resourceClassSelected = data.resourceClass;
this.resourceClassSelectedEvent.emit(this.resourceClassSelected);
});
}
ngOnInit() {
this.initForm();
// add form to the parent form group
this.formGroup.addControl('resourceClass', this.form);
}
ngOnChanges() {
if (this.form !== undefined) {
// resource classes have been reinitialized
// reset form
resolvedPromise.then(() => {
// remove this form from the parent form group
this.formGroup.removeControl('resourceClass');
this.initForm();
// add form to the parent form group
this.formGroup.addControl('resourceClass', this.form);
});
}
}
};
SelectResourceClassComponent.ctorParameters = () => [
{ type: FormBuilder, decorators: [{ type: Inject, args: [FormBuilder,] }] }
];
__decorate([
Input(),
__metadata("design:type", FormGroup)
], SelectResourceClassComponent.prototype, "formGroup", void 0);
__decorate([
Input(),
__metadata("design:type", Array),
__metadata("design:paramtypes", [Array])
], SelectResourceClassComponent.prototype, "resourceClasses", null);
__decorate([
Output(),
__metadata("design:type", Object)
], SelectResourceClassComponent.prototype, "resourceClassSelectedEvent", void 0);
SelectResourceClassComponent = __decorate([
Component({
selector: 'kui-select-resource-class',
template: "<mat-form-field *ngIf=\"resourceClasses.length > 0\" class=\"large-field\">\n <mat-select placeholder=\"Select a Resource Class (optional)\" [formControl]=\"form.controls['resourceClass']\">\n <mat-option [value]=\"null\">no selection</mat-option>\n <!-- undo selection of a resource class -->\n <mat-option *ngFor=\"let resourceClass of resourceClasses\" [value]=\"resourceClass.id\">{{ resourceClass.label }}\n </mat-option>\n </mat-select>\n</mat-form-field>\n",
styles: [""]
}),
__param(0, Inject(FormBuilder)),
__metadata("design:paramtypes", [FormBuilder])
], SelectResourceClassComponent);
/**
* The extended search allows you to filter by project, by source type (resource class), or by the metadata (properties) of source types. Each filter can be standalone or combined. The metadata field can be precisely filtered with criteria such as "contains", "like", "equals to", "exists" or in case of a date value with "before" or "after". In addition, for a metadata field that is connected to another source type, it's possible to filter by this second source type. If you are looking for the source type "Photograph" with the metadata field "Photographer", which is connected to source type "Person", you can search for photograph(s) taken by person(s) who is born before February 1970. The result of this request will be an intersection of the two source types.
*/
let ExtendedSearchComponent = class ExtendedSearchComponent {
constructor(fb, knoraApiConnection, _route, _router, _cacheService, _gravSearchService) {
this.fb = fb;
this.knoraApiConnection = knoraApiConnection;
this._route = _route;
this._router = _router;
this._cacheService = _cacheService;
this._gravSearchService = _gravSearchService;
/**
* @param {boolean} toggleExtendedSearchForm Trigger toggle for extended search form.
*/
this.toggleExtendedSearchForm = new EventEmitter();
/**
* @param {string} gravsearch Send the gravsearch query back.
*/
this.gravsearch = new EventEmitter();
// all available ontologies
this.ontologies = [];
// properties specified by the user
this.activeProperties = [];
// resource classes for the selected ontology
this.resourceClasses = [];
this.result = [new ReadResource()];
// form validation status
this.formValid = false;
}
ngOnInit() {
// parent form is empty, it gets passed to the child components
this.form = this.fb.group({});
// if form status changes, re-run validation
this.form.statusChanges.subscribe((data) => {
this.formValid = this.validateForm();
// console.log(this.form);
});
// initialize ontologies to be used for the ontologies selection in the search form
this.initializeOntologies();
}
/**
* @ignore
* Add a property to the search form.
* @returns void
*/
addProperty() {
this.activeProperties.push(true);
}
/**
* @ignore
* Remove the last property from the search form.
* @returns void
*/
removeProperty() {
this.activeProperties.splice(-1, 1);
}
/**
* @ignore
* Gets all available ontologies for the search form.
* @returns void
*/
initializeOntologies() {
this.knoraApiConnection.v2.onto.getOntologiesMetadata().subscribe((response) => {
this.ontologies = response.ontologies;
}, (error) => {
console.error(error);
});
}
/**
* @ignore
* Once an ontology has been selected, gets its classes and properties.
* The classes and properties will be made available to the user for selection.
*
* @param ontologyIri Iri of the ontology chosen by the user.
* @returns void
*/
getResourceClassesAndPropertiesForOntology(ontologyIri) {
// reset active resource class definition
this.activeResourceClass = undefined;
// reset specified properties
this.activeProperties = [];
this.activeOntology = ontologyIri;
this._cacheService.getEntityDefinitionsForOntologies([ontologyIri]).subscribe((ontoInfo) => {
this.resourceClasses = ontoInfo.getResourceClassesAsArray(true);
this.properties = ontoInfo.getProperties();
});
}
/**
* @ignore
* Once a resource class has been selected, gets its properties.
* The properties will be made available to the user for selection.
*
* @param resourceClassIri
* @returns void
*/
getPropertiesForResourceClass(resourceClassIri) {
// reset specified properties
this.activeProperties = [];
// if the client undoes the selection of a resource class, use the active ontology as a fallback
if (resourceClassIri === null) {
this.getResourceClassesAndPropertiesForOntology(this.activeOntology);
}
else {
this._cacheService.getResourceClassDefinitions([resourceClassIri]).subscribe((ontoInfo) => {
this.properties = ontoInfo.getProperties();
this.activeResourceClass = ontoInfo.getResourceClasses()[resourceClassIri];
});
}
}
/**
* @ignore
* Validates form and returns its status (boolean).
*/
validateForm() {
// check that either a resource class is selected or at least one property is specified
return this.form.valid &&
(this.propertyComponents.length > 0 || (this.resourceClassComponent !== undefined && this.resourceClassComponent.getResourceClassSelected() !== false));
}
/**
* @ignore
* Resets the form (selected resource class and specified properties) preserving the active ontology.
*/
resetForm() {
if (this.activeOntology !== undefined) {
this.getResourceClassesAndPropertiesForOntology(this.activeOntology);
}
}
/**
* @ignore
* Creates a GravSearch query with the given form values and calls the extended search route.
*/
submit() {
if (!this.formValid)
return; // check that from is valid
const resClassOption = this.resourceClassComponent.getResourceClassSelected();
let resClass;
if (resClassOption !== false) {
resClass = resClassOption;
}
const properties = this.propertyComponents.map((propComp) => {
return propComp.getPropertySelectedWithValue();
});
const gravsearch = this._gravSearchService.createGravsearchQuery(properties, resClass, 0);
if (this.route) {
this._router.navigate([this.route + '/extended/', gravsearch], { relativeTo: this._route });
}
else {
this.gravsearch.emit(gravsearch);
}
// toggle extended search form
this.toggleExtendedSearchForm.emit(true);
}
};
ExtendedSearchComponent.ctorParameters = () => [
{ type: FormBuilder, decorators: [{ type: Inject, args: [FormBuilder,] }] },
{ type: KnoraApiConnection, decorators: [{ type: Inject, args: [KnoraApiConnectionToken,] }] },
{ type: ActivatedRoute },
{ type: Router },
{ type: OntologyCacheService },
{ type: GravsearchGenerationService }
];
__decorate([
Input(),
__metadata("design:type", Object)
], ExtendedSearchComponent.prototype, "route", void 0);
__decorate([
Output(),
__metadata("design:type", Object)
], ExtendedSearchComponent.prototype, "toggleExtendedSearchForm", void 0);
__decorate([
Output(),
__metadata("design:type", Object)
], ExtendedSearchComponent.prototype, "gravsearch", void 0);
__decorate([
ViewChild('resourceClass', { static: false }),
__metadata("design:type", SelectResourceClassComponent)
], ExtendedSearchComponent.prototype, "resourceClassComponent", void 0);
__decorate([
ViewChildren('property'),
__metadata("design:type", QueryList)
], ExtendedSearchComponent.prototype, "propertyComponents", void 0);
ExtendedSearchComponent = __decorate([
Component({
selector: 'kui-extended-search',
template: "<form [formGroup]=\"form\" (ngSubmit)=\"submit()\" class=\"kui-form-content\">\n\n <div>\n <kui-select-ontology *ngIf=\"ontologies.length > 0\" [formGroup]=\"form\" [ontologies]=\"ontologies\"\n (ontologySelected)=\"getResourceClassesAndPropertiesForOntology($event)\"></kui-select-ontology>\n </div>\n\n <div class=\"select-resource-class\" *ngIf=\"resourceClasses?.length > 0\">\n <kui-select-resource-class #resourceClass [formGroup]=\"form\" [resourceClasses]=\"resourceClasses\"\n (resourceClassSelectedEvent)=\"getPropertiesForResourceClass($event)\">\n </kui-select-resource-class>\n </div>\n\n <div class=\"select-property\" *ngIf=\"properties !== undefined\">\n <div *ngFor=\"let prop of activeProperties; let i = index\">\n\n <kui-select-property #property [activeResourceClass]=\"activeResourceClass\" [formGroup]=\"form\" [index]=\"i\"\n [properties]=\"properties\"></kui-select-property>\n\n </div>\n </div>\n\n\n <div class=\"select-property buttons\">\n <button mat-mini-fab class=\"property-button add-property-button\" color=\"primary\" type=\"button\"\n (click)=\"addProperty()\" [disabled]=\"activeOntology === undefined || activeProperties.length >= 4\">\n <mat-icon aria-label=\"add a property\">add</mat-icon>\n </button>\n\n <button mat-mini-fab class=\"property-button remove-property-button\" color=\"primary\" type=\"button\"\n (click)=\"removeProperty()\" [disabled]=\"activeProperties.length == 0\">\n <mat-icon aria-label=\"remove property\">remove</mat-icon>\n </button>\n </div>\n\n <!-- <div>\n <button mat-icon-button type=\"button\" (click)=\"resetForm()\" [disabled]=\"this.activeOntology === undefined\">\n <mat-icon aria-label=\"reset query form\">clear</mat-icon>\n </button>\n\n <button mat-icon-button type=\"submit\" [disabled]=\"!formValid\">\n <mat-icon aria-label=\"submit query\">send</mat-icon>\n </button>\n </div> -->\n\n <div class=\"kui-form-action\">\n <button class=\"reset\" mat-button type=\"button\" (click)=\"resetForm()\" [disabled]=\"this.activeOntology === undefined\">\n Reset\n </button>\n <span class=\"fill-remaining-space\"></span>\n <button class=\"extended-search-button\" mat-raised-button color=\"primary\" type=\"submit\" [disabled]=\"!formValid\">\n Search\n </button>\n </div>\n\n</form>\n",
styles: [".select-resource-class{margin-left:8px}.select-property{margin-left:16px}.select-property .property-button{margin:0 12px 64px 0}"]
}),
__param(0, Inject(FormBuilder)),
__param(1, Inject(KnoraApiConnectionToken)),
__metadata("design:paramtypes", [FormBuilder,
KnoraApiConnection,
ActivatedRoute,
Router,
OntologyCacheService,
GravsearchGenerationService])
], ExtendedSearchComponent);
let ExpertSearchComponent = class ExpertSearchComponent {
constructor(knoraApiConfig, fb, _route, _router, _searchParamsService) {
this.knoraApiConfig = knoraApiConfig;
this.fb = fb;
this._route = _route;
this._router = _router;
this._searchParamsService = _searchParamsService;
/**
* @param gravsearch Send the gravsearch query back.
*/
this.gravsearch = new EventEmitter();
/**
* @param {boolean} toggleExtendedSearchForm Trigger toggle for extended search form.
*/
this.toggleExpertSearchForm = new EventEmitter();
}
ngOnInit() {
this.initForm();
}
/**
* @ignore
* Initiate the form with predefined Gravsearch query as example.
*/
initForm() {
this.expertSearchForm = this.fb.group({
gravquery: [
`
PREFIX knora-api: <http://api.knora.org/ontology/knora-api/simple/v2#>
PREFIX incunabula: <${this.knoraApiConfig.apiUrl}/ontology/0803/incunabula/simple/v2#>
CONSTRUCT {
?book knora-api:isMainResource true .
?book incunabula:title ?title .
} WHERE {
?book a incunabula:book .
?book incunabula:title ?title .
}
`,
Validators.required
]
});
}
/**
* @ignore
* Send the gravsearch query to the result view, either through the route or by emitting the gravsearch as an output.
*/
submitQuery() {
const gravsearch = this.generateGravsearch(0);
if (this.route) {
this._router.navigate([this.route + '/extended/', gravsearch], { relativeTo: this._route });
}
else {
this.gravsearch.emit(gravsearch);
}
// toggle expert search form
this.toggleExpertSearchForm.emit(true);
}
/**
* @ignore
* Generate the whole gravsearch query matching the query given by the form.
*/
generateGravsearch(offset = 0) {
const queryTemplate = this.expertSearchForm.controls['gravquery'].value;
// offset component of the Gravsearch query
const offsetTemplate = `
OFFSET ${offset}
`;
// function that generates the same Gravsearch query with the given offset
const generateGravsearchWithCustomOffset = (localOffset) => {
const offsetCustomTemplate = `
OFFSET ${localOffset}
`;
return queryTemplate + offsetCustomTemplate;
};
if (offset === 0) {
// store the function so another Gravsearch query can be created with an increased offset
this._searchParamsService.changeSearchParamsMsg(new ExtendedSearchParams(generateGravsearchWithCustomOffset));
}
return queryTemplate + offsetTemplate;
}
/**
* @ignore
* Reset the form to the initial state.
*/
resetForm() {
this.initForm();
}
};
ExpertSearchComponent.ctorParameters = () => [
{ type: KnoraApiConfig, decorators: [{ type: Inject, args: [KnoraApiConfigToken,] }] },
{ type: FormBuilder },
{ type: ActivatedRoute },
{ type: Router },
{ type: SearchParamsService }
];
__decorate([
Input(),
__metadata("design:type", Object)
], ExpertSearchComponent.prototype, "route", void 0);
__decorate([
Output(),
__metadata("design:type", Object)
], ExpertSearchComponent.prototype, "gravsearch", void 0);
__decorate([
Output(),
__metadata("design:type", Object)
], ExpertSearchComponent.prototype, "toggleExpertSearchForm", void 0);
ExpertSearchComponent = __decorate([
Component({
selector: 'kui-expert-search',
template: "<div class=\"expert-search-container\">\n <!-- The integration in container like the accordeon expansion panel should be handled on app side\n <mat-expansion-panel [expanded]=\"true\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n Expert search\n </mat-panel-title>\n <mat-panel-description> </mat-panel-description>\n </mat-expansion-panel-header>\n -->\n <form [formGroup]=\"expertSearchForm\" class=\"expert-search-form kui-form-content\">\n <mat-form-field class=\"textarea-field large-field\">\n <textarea matInput formControlName=\"gravquery\" matTextareaAutosize matAutosizeMinRows=\"12\" matAutosizeMaxRows=\"24\"\n placeholder=\"Write your gravsearch query\"></textarea>\n </mat-form-field>\n\n <div class=\"kui-form-a