@myrmidon/cadmus-graph-ui-ex
Version:
Cadmus - extensions to semantic graph components.
1,504 lines • 110 kB
JavaScript
import * as i0 from '@angular/core';
import { input, model, effect, Component, Pipe, output } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { forkJoin, from, BehaviorSubject, Subject, take as take$1 } from 'rxjs';
import { MatIconButton } from '@angular/material/button';
import { MatTooltip } from '@angular/material/tooltip';
import { MatIcon } from '@angular/material/icon';
import { MatProgressBar } from '@angular/material/progress-bar';
import { MatTabGroup, MatTab, MatTabLabel } from '@angular/material/tabs';
import * as i3$1 from '@swimlane/ngx-graph';
import { GraphModule } from '@swimlane/ngx-graph';
import * as i1 from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatPaginator } from '@angular/material/paginator';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatList, MatListItem } from '@angular/material/list';
import { MatFormField } from '@angular/material/form-field';
import { MatInput } from '@angular/material/input';
import { RefLookupComponent } from '@myrmidon/cadmus-refs-lookup';
import * as i2 from '@myrmidon/cadmus-graph-ui';
import * as i3 from '@myrmidon/cadmus-api';
import { take } from 'rxjs/operators';
import { MatSelect } from '@angular/material/select';
import { MatOption } from '@angular/material/core';
import { MatChipListbox, MatChipOption, MatChipRemove } from '@angular/material/chips';
import * as i2$1 from '@myrmidon/ngx-mat-tools';
/**
* Triples filter.
*/
class TripleFilterComponent {
lookupService;
_graphService;
/**
* True if this component is disabled.
*/
disabled = input(false);
/**
* True if this component should show a pager.
*/
hasPager = input(false);
/**
* The total number of triples returned from the last
* page fetch operation. Used when hasPager is true.
*/
total = input(0);
/**
* The filter.
*/
filter = model({
pageNumber: 1,
pageSize: 10,
});
pageNumber;
pageSize;
litPattern;
litType;
litLanguage;
minLitNumber;
maxLitNumber;
subj;
isNotPred;
preds;
notPreds;
hasLiteralObj;
obj;
sid;
isSidPrefix;
tag;
form;
constructor(formBuilder, lookupService, _graphService) {
this.lookupService = lookupService;
this._graphService = _graphService;
// form
this.pageNumber = formBuilder.control(1, { nonNullable: true });
this.pageSize = formBuilder.control(10, { nonNullable: true });
this.litPattern = formBuilder.control(null);
this.litType = formBuilder.control(null);
this.litLanguage = formBuilder.control(null);
this.minLitNumber = formBuilder.control(null);
this.maxLitNumber = formBuilder.control(null);
this.subj = formBuilder.control(null);
this.preds = formBuilder.control([], { nonNullable: true });
this.isNotPred = formBuilder.control(false, { nonNullable: true });
this.notPreds = formBuilder.control([], { nonNullable: true });
this.hasLiteralObj = formBuilder.control(null);
this.obj = formBuilder.control(null);
this.sid = formBuilder.control(null);
this.isSidPrefix = formBuilder.control(false, { nonNullable: true });
this.tag = formBuilder.control(null);
this.form = formBuilder.group({
pageNumber: this.pageNumber,
pageSize: this.pageSize,
litPattern: this.litPattern,
litType: this.litType,
litLanguage: this.litLanguage,
minLitNumber: this.minLitNumber,
maxLitNumber: this.maxLitNumber,
subj: this.subj,
preds: this.preds,
isNotPred: this.isNotPred,
notPreds: this.notPreds,
hasLiteralObj: this.hasLiteralObj,
obj: this.obj,
sid: this.sid,
isSidPrefix: this.isSidPrefix,
tag: this.tag,
});
effect(() => {
this.updateForm(this.filter());
});
}
updateForm(filter) {
this.pageNumber.setValue(filter.pageNumber);
this.pageSize.setValue(filter.pageSize);
this.litPattern.setValue(filter.literalPattern || null);
this.litType.setValue(filter.literalType || null);
this.litLanguage.setValue(filter.literalLanguage || null);
this.minLitNumber.setValue(filter.minLiteralNumber || null);
this.maxLitNumber.setValue(filter.maxLiteralNumber || null);
this.hasLiteralObj.setValue(filter.hasLiteralObject || null);
this.sid.setValue(filter.sid || null);
this.isSidPrefix.setValue(filter.isSidPrefix || false);
this.tag.setValue(filter.tag || null);
// load the referenced nodes so we can show them by label
forkJoin({
s: filter.subjectId
? this._graphService.getNode(filter.subjectId)
: from([null]),
p: filter.predicateIds?.length
? this._graphService.getNodeSet(filter.predicateIds)
: from([]),
o: filter.objectId
? this._graphService.getNode(filter.objectId)
: from([null]),
}).subscribe((result) => {
this.subj.setValue(result.s);
this.preds.setValue(result.p.filter((n) => n));
this.obj.setValue(result.o);
this.form.markAsPristine();
});
}
getFilter() {
return {
pageNumber: +this.pageNumber.value,
pageSize: +this.pageSize.value,
literalPattern: this.litPattern.value || undefined,
literalType: this.litType.value || undefined,
literalLanguage: this.litLanguage.value || undefined,
minLiteralNumber: this.minLitNumber.value || undefined,
maxLiteralNumber: this.maxLitNumber.value || undefined,
subjectId: this.subj.value?.id || undefined,
predicateIds: this.preds.value?.length
? this.preds.value.map((n) => n.id)
: undefined,
notPredicateIds: this.notPreds.value?.length
? this.notPreds.value.map((n) => n.id)
: undefined,
hasLiteralObject: this.hasLiteralObj.value !== null
? this.hasLiteralObj.value
: undefined,
objectId: this.obj.value?.id || undefined,
sid: this.sid.value || undefined,
isSidPrefix: this.isSidPrefix.value,
tag: this.tag.value || undefined,
};
}
onPageChange(page) {
this.pageNumber.setValue(page.pageIndex + 1);
this.filter.set(this.getFilter());
}
onSubjectNodeChange(node) {
this.subj.setValue(node);
}
onObjectNodeChange(node) {
this.obj.setValue(node);
}
onPredicateNodeChange(node) {
if (!node) {
return;
}
const un = node;
if (this.isNotPred.value) {
const nodes = [...this.notPreds.value];
if (nodes.some((n) => n.id === un.id)) {
return;
}
nodes.push(un);
this.notPreds.setValue(nodes);
this.notPreds.updateValueAndValidity();
this.notPreds.markAsDirty();
}
else {
const nodes = [...this.preds.value];
if (nodes.some((n) => n.id === un.id)) {
return;
}
nodes.push(un);
this.preds.setValue(nodes);
this.preds.updateValueAndValidity();
this.preds.markAsDirty();
}
}
deleteNotPred(node) {
const nodes = [...this.notPreds.value];
const i = nodes.indexOf(node);
nodes.splice(i, 1);
this.notPreds.setValue(nodes);
this.notPreds.updateValueAndValidity();
this.notPreds.markAsDirty();
}
deletePred(node) {
const nodes = [...this.preds.value];
const i = nodes.indexOf(node);
nodes.splice(i, 1);
this.preds.setValue(nodes);
this.preds.updateValueAndValidity();
this.preds.markAsDirty();
}
reset() {
this.form.reset();
this.filter.set(this.getFilter());
}
apply() {
if (this.form.invalid) {
return;
}
this.filter.set(this.getFilter());
this.form.markAsPristine();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TripleFilterComponent, deps: [{ token: i1.FormBuilder }, { token: i2.GraphNodeLookupService }, { token: i3.GraphService }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.0", type: TripleFilterComponent, isStandalone: true, selector: "cadmus-walker-triple-filter", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, hasPager: { classPropertyName: "hasPager", publicName: "hasPager", isSignal: true, isRequired: false, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filter: "filterChange" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"apply()\" [attr.disabled]=\"disabled()\">\r\n <!-- paginator -->\r\n @if (hasPager()) {\r\n <mat-paginator\r\n [length]=\"total\"\r\n [pageSize]=\"pageSize.value || 10\"\r\n [pageSizeOptions]=\"[5, 10, 20]\"\r\n (page)=\"onPageChange($event)\"\r\n aria-label=\"Select page\"\r\n />\r\n }\r\n <mat-tab-group>\r\n <!-- TRIPLE -->\r\n <mat-tab label=\"triple\">\r\n <!-- subject ID -->\r\n <div>\r\n <cadmus-ref-lookup\r\n label=\"subject\"\r\n [service]=\"lookupService\"\r\n (itemChange)=\"onSubjectNodeChange($event)\"\r\n />\r\n </div>\r\n <!-- predicate IDs -->\r\n <div>\r\n <cadmus-ref-lookup\r\n label=\"predicate\"\r\n [service]=\"lookupService\"\r\n [baseFilter]=\"{ tag: 'property' }\"\r\n (itemChange)=\"onPredicateNodeChange($event)\"\r\n />\r\n <mat-checkbox [formControl]=\"isNotPred\">not</mat-checkbox>\r\n <!-- notPreds -->\r\n @if (isNotPred.value) {\r\n <div>\r\n <mat-list dense>\r\n @for (n of notPreds.value; track n.id) {\r\n <mat-list-item>\r\n <span>{{ n.label }}</span>\r\n <button type=\"button\" mat-icon-button (click)=\"deleteNotPred(n)\">\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n </mat-list-item>\r\n }\r\n </mat-list>\r\n </div>\r\n }\r\n <!-- preds -->\r\n @if (!isNotPred.value) {\r\n <div>\r\n <mat-list dense>\r\n @for (n of preds.value; track n.id) {\r\n <mat-list-item>\r\n <span>{{ n.label }}</span>\r\n <button type=\"button\" mat-icon-button (click)=\"deletePred(n)\">\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n </mat-list-item>\r\n }\r\n </mat-list>\r\n </div>\r\n }\r\n </div>\r\n <!-- object ID -->\r\n <div>\r\n <cadmus-ref-lookup\r\n label=\"object\"\r\n [service]=\"lookupService\"\r\n (itemChange)=\"onObjectNodeChange($event)\"\r\n />\r\n </div>\r\n <!-- sid -->\r\n <div>\r\n <mat-form-field>\r\n <input\r\n matInput\r\n [formControl]=\"sid\"\r\n placeholder=\"sid\"\r\n maxlength=\"500\"\r\n />\r\n </mat-form-field>\r\n \r\n <mat-checkbox [formControl]=\"isSidPrefix\">prefix</mat-checkbox>\r\n </div>\r\n <!-- tag -->\r\n <div>\r\n <mat-form-field>\r\n <input\r\n matInput\r\n [formControl]=\"tag\"\r\n placeholder=\"tag\"\r\n maxlength=\"50\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n </mat-tab>\r\n <!-- LITERAL -->\r\n <mat-tab label=\"literal\">\r\n <!-- litPattern -->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"litPattern\" placeholder=\"pattern\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- litType -->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"litType\" placeholder=\"type\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- litLanguage-->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"litLanguage\" placeholder=\"language\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- minLitNumber, maxLitNumber -->\r\n <div>\r\n <mat-form-field style=\"width: 4em\">\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"minLitNumber\"\r\n placeholder=\"min.\"\r\n />\r\n </mat-form-field>\r\n -\r\n <mat-form-field style=\"width: 4em\">\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"maxLitNumber\"\r\n placeholder=\"max.\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n </mat-tab>\r\n </mat-tab-group>\r\n <div\r\n class=\"btn-group\"\r\n role=\"group\"\r\n aria-label=\"toolbar\"\r\n style=\"margin-bottom: 10px\"\r\n >\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n (click)=\"reset()\"\r\n matTooltip=\"Reset filters\"\r\n [disabled]=\"disabled()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"submit\"\r\n mat-icon-button\r\n [disabled]=\"disabled()\"\r\n matTooltip=\"Apply filters\"\r\n >\r\n <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n </button>\r\n </div>\r\n</form>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: RefLookupComponent, selector: "cadmus-ref-lookup", inputs: ["label", "limit", "baseFilter", "service", "item", "required", "hasMore", "linkTemplate", "optDialog", "options"], outputs: ["itemChange", "optionsChange", "moreRequest"] }, { kind: "component", type: MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: TripleFilterComponent, decorators: [{
type: Component,
args: [{ selector: 'cadmus-walker-triple-filter', imports: [
FormsModule,
ReactiveFormsModule,
MatPaginator,
MatTabGroup,
MatTab,
RefLookupComponent,
MatCheckbox,
MatList,
MatListItem,
MatIconButton,
MatIcon,
MatFormField,
MatInput,
MatTooltip,
], template: "<form [formGroup]=\"form\" (submit)=\"apply()\" [attr.disabled]=\"disabled()\">\r\n <!-- paginator -->\r\n @if (hasPager()) {\r\n <mat-paginator\r\n [length]=\"total\"\r\n [pageSize]=\"pageSize.value || 10\"\r\n [pageSizeOptions]=\"[5, 10, 20]\"\r\n (page)=\"onPageChange($event)\"\r\n aria-label=\"Select page\"\r\n />\r\n }\r\n <mat-tab-group>\r\n <!-- TRIPLE -->\r\n <mat-tab label=\"triple\">\r\n <!-- subject ID -->\r\n <div>\r\n <cadmus-ref-lookup\r\n label=\"subject\"\r\n [service]=\"lookupService\"\r\n (itemChange)=\"onSubjectNodeChange($event)\"\r\n />\r\n </div>\r\n <!-- predicate IDs -->\r\n <div>\r\n <cadmus-ref-lookup\r\n label=\"predicate\"\r\n [service]=\"lookupService\"\r\n [baseFilter]=\"{ tag: 'property' }\"\r\n (itemChange)=\"onPredicateNodeChange($event)\"\r\n />\r\n <mat-checkbox [formControl]=\"isNotPred\">not</mat-checkbox>\r\n <!-- notPreds -->\r\n @if (isNotPred.value) {\r\n <div>\r\n <mat-list dense>\r\n @for (n of notPreds.value; track n.id) {\r\n <mat-list-item>\r\n <span>{{ n.label }}</span>\r\n <button type=\"button\" mat-icon-button (click)=\"deleteNotPred(n)\">\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n </mat-list-item>\r\n }\r\n </mat-list>\r\n </div>\r\n }\r\n <!-- preds -->\r\n @if (!isNotPred.value) {\r\n <div>\r\n <mat-list dense>\r\n @for (n of preds.value; track n.id) {\r\n <mat-list-item>\r\n <span>{{ n.label }}</span>\r\n <button type=\"button\" mat-icon-button (click)=\"deletePred(n)\">\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n </mat-list-item>\r\n }\r\n </mat-list>\r\n </div>\r\n }\r\n </div>\r\n <!-- object ID -->\r\n <div>\r\n <cadmus-ref-lookup\r\n label=\"object\"\r\n [service]=\"lookupService\"\r\n (itemChange)=\"onObjectNodeChange($event)\"\r\n />\r\n </div>\r\n <!-- sid -->\r\n <div>\r\n <mat-form-field>\r\n <input\r\n matInput\r\n [formControl]=\"sid\"\r\n placeholder=\"sid\"\r\n maxlength=\"500\"\r\n />\r\n </mat-form-field>\r\n \r\n <mat-checkbox [formControl]=\"isSidPrefix\">prefix</mat-checkbox>\r\n </div>\r\n <!-- tag -->\r\n <div>\r\n <mat-form-field>\r\n <input\r\n matInput\r\n [formControl]=\"tag\"\r\n placeholder=\"tag\"\r\n maxlength=\"50\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n </mat-tab>\r\n <!-- LITERAL -->\r\n <mat-tab label=\"literal\">\r\n <!-- litPattern -->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"litPattern\" placeholder=\"pattern\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- litType -->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"litType\" placeholder=\"type\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- litLanguage-->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"litLanguage\" placeholder=\"language\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- minLitNumber, maxLitNumber -->\r\n <div>\r\n <mat-form-field style=\"width: 4em\">\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"minLitNumber\"\r\n placeholder=\"min.\"\r\n />\r\n </mat-form-field>\r\n -\r\n <mat-form-field style=\"width: 4em\">\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"maxLitNumber\"\r\n placeholder=\"max.\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n </mat-tab>\r\n </mat-tab-group>\r\n <div\r\n class=\"btn-group\"\r\n role=\"group\"\r\n aria-label=\"toolbar\"\r\n style=\"margin-bottom: 10px\"\r\n >\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n (click)=\"reset()\"\r\n matTooltip=\"Reset filters\"\r\n [disabled]=\"disabled()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"submit\"\r\n mat-icon-button\r\n [disabled]=\"disabled()\"\r\n matTooltip=\"Apply filters\"\r\n >\r\n <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n </button>\r\n </div>\r\n</form>\r\n" }]
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.GraphNodeLookupService }, { type: i3.GraphService }] });
/**
* Linked non-literal node filter.
*/
class LinkedNodeFilterComponent {
lookupService;
_graphService;
/**
* True if this component is disabled.
*/
disabled = input();
/**
* True if this component should show a pager.
*/
hasPager = input(false);
/**
* The total number of nodes returned from the last
* page fetch operation. Used when hasPager is true.
*/
total = input(0);
/**
* The filter.
*/
filter = model({
pageNumber: 1,
pageSize: 10,
otherNodeId: 0,
predicateId: 0,
});
otherNodeId;
predicateId;
isObject;
pageNumber;
pageSize;
uid;
isClass;
tag;
label;
sourceType;
sid;
isSidPrefix;
classes;
form;
constructor(formBuilder, lookupService, _graphService) {
this.lookupService = lookupService;
this._graphService = _graphService;
this.otherNodeId = 0;
this.predicateId = 0;
this.isObject = false;
// form
this.pageNumber = formBuilder.control(1, { nonNullable: true });
this.pageSize = formBuilder.control(10, { nonNullable: true });
this.uid = formBuilder.control(null);
this.isClass = formBuilder.control(null);
this.tag = formBuilder.control(null);
this.label = formBuilder.control(null);
this.sourceType = formBuilder.control(null);
this.sid = formBuilder.control(null);
this.isSidPrefix = formBuilder.control(false, { nonNullable: true });
this.classes = formBuilder.control([], { nonNullable: true });
this.form = formBuilder.group({
pageNumber: this.pageNumber,
pageSize: this.pageSize,
uid: this.uid,
isClass: this.isClass,
tag: this.tag,
label: this.label,
sourceType: this.sourceType,
sid: this.sid,
isSidPrefix: this.isSidPrefix,
classes: this.classes,
});
effect(() => {
this.updateForm(this.filter());
});
}
updateForm(filter) {
this.otherNodeId = filter.otherNodeId;
this.predicateId = filter.predicateId;
this.isObject = filter.isObject || false;
this.pageNumber.setValue(filter.pageNumber);
this.pageSize.setValue(filter.pageSize);
this.uid.setValue(filter.uid || null);
this.isClass.setValue(filter.isClass || null);
this.tag.setValue(filter.tag || null);
this.label.setValue(filter.label || null);
this.sourceType.setValue(filter.sourceType || null);
this.sid.setValue(filter.sid || null);
this.isSidPrefix.setValue(filter.isSidPrefix || false);
// load the referenced class nodes so we can show them by label
if (filter.classIds?.length) {
this._graphService
.getNodeSet(filter.classIds)
.pipe(take(1))
.subscribe((nodes) => {
this.classes.setValue(nodes.filter((n) => n));
this.form.markAsPristine();
});
}
else {
this.classes.setValue([]);
this.form.markAsPristine();
}
}
getFilter() {
return {
pageNumber: +this.pageNumber.value,
pageSize: +this.pageSize.value,
uid: this.uid.value || undefined,
isClass: this.isClass.value || undefined,
tag: this.tag.value || undefined,
label: this.label.value || undefined,
sourceType: this.sourceType.value || undefined,
sid: this.sid.value || undefined,
isSidPrefix: this.isSidPrefix.value ? true : undefined,
classIds: this.classes.value.length
? this.classes.value.map((n) => n.id)
: undefined,
otherNodeId: this.otherNodeId,
predicateId: this.predicateId,
isObject: this.isObject,
};
}
onPageChange(page) {
this.pageNumber.setValue(page.pageIndex + 1);
this.filter.set(this.getFilter());
}
onClassAdd(node) {
if (!node) {
return;
}
const nodes = [...this.classes.value];
nodes.push(node);
this.classes.setValue(nodes);
this.classes.updateValueAndValidity();
this.classes.markAsDirty();
}
onClassRemove(node) {
const nodes = [...this.classes.value];
const i = nodes.indexOf(node);
if (i > -1) {
nodes.splice(i, 1);
this.classes.setValue(nodes);
this.classes.updateValueAndValidity();
this.classes.markAsDirty();
}
}
reset() {
this.form.reset();
this.filter.set(this.getFilter());
}
apply() {
if (this.form.invalid) {
return;
}
this.filter.set(this.getFilter());
this.form.markAsPristine();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: LinkedNodeFilterComponent, deps: [{ token: i1.FormBuilder }, { token: i2.GraphNodeLookupService }, { token: i3.GraphService }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.0", type: LinkedNodeFilterComponent, isStandalone: true, selector: "cadmus-walker-linked-node-filter", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, hasPager: { classPropertyName: "hasPager", publicName: "hasPager", isSignal: true, isRequired: false, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filter: "filterChange" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"apply()\" [attr.disabled]=\"disabled()\">\r\n <!-- paginator -->\r\n @if (hasPager()) {\r\n <mat-paginator\r\n [length]=\"total\"\r\n [pageSize]=\"pageSize.value || 10\"\r\n [pageSizeOptions]=\"[5, 10, 20]\"\r\n (page)=\"onPageChange($event)\"\r\n aria-label=\"Select page\"\r\n />\r\n }\r\n\r\n <!-- label -->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"label\" placeholder=\"label\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- uid -->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"uid\" placeholder=\"UID\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- isClass -->\r\n <div>\r\n <mat-form-field>\r\n <mat-select [formControl]=\"isClass\" placeholder=\"class\">\r\n <mat-option [value]=\"0\">(any)</mat-option>\r\n <mat-option [value]=\"1\">not-class</mat-option>\r\n <mat-option [value]=\"2\">class</mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n </div>\r\n <!-- tag -->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"tag\" placeholder=\"tag\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- sourceType -->\r\n <div>\r\n <mat-form-field>\r\n <mat-select [formControl]=\"sourceType\" placeholder=\"source type\">\r\n <mat-option [value]=\"null\">(any)</mat-option>\r\n <mat-option [value]=\"0\">user</mat-option>\r\n <mat-option [value]=\"1\">item</mat-option>\r\n <mat-option [value]=\"2\">part</mat-option>\r\n <mat-option [value]=\"3\">thesaurus</mat-option>\r\n <mat-option [value]=\"4\">implicit</mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n </div>\r\n <div>\r\n <!-- sid -->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"sid\" placeholder=\"SID\" />\r\n </mat-form-field>\r\n\r\n <!-- isSidPrefix -->\r\n <mat-checkbox [formControl]=\"isSidPrefix\">prefix</mat-checkbox>\r\n </div>\r\n </div>\r\n <!-- classes -->\r\n <div>\r\n <fieldset>\r\n <legend>classes</legend>\r\n <cadmus-ref-lookup\r\n label=\"class\"\r\n [service]=\"lookupService\"\r\n [baseFilter]=\"{ isClass: true }\"\r\n (itemChange)=\"onClassAdd($event)\"\r\n />\r\n\r\n @if (classes.value.length) {\r\n <mat-chip-listbox>\r\n @for (node of classes.value; track node.id) {\r\n <mat-chip-option\r\n [removable]=\"true\"\r\n (removed)=\"onClassRemove(node)\"\r\n matTooltip=\"{{ node.uri }}\"\r\n >{{ node.label }}\r\n <button type=\"button\" matChipRemove>\r\n <mat-icon>cancel</mat-icon>\r\n </button>\r\n </mat-chip-option>\r\n }\r\n </mat-chip-listbox>\r\n }\r\n </fieldset>\r\n </div>\r\n\r\n <div\r\n class=\"btn-group\"\r\n role=\"group\"\r\n aria-label=\"toolbar\"\r\n style=\"margin-bottom: 10px\"\r\n >\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n (click)=\"reset()\"\r\n matTooltip=\"Reset filters\"\r\n [disabled]=\"disabled()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"submit\"\r\n mat-icon-button\r\n [disabled]=\"disabled()\"\r\n matTooltip=\"Apply filters\"\r\n >\r\n <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n </button>\r\n </div>\r\n</form>\r\n", styles: ["fieldset{border:1px solid silver;border-radius:4px;padding:4px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: RefLookupComponent, selector: "cadmus-ref-lookup", inputs: ["label", "limit", "baseFilter", "service", "item", "required", "hasMore", "linkTemplate", "optDialog", "options"], outputs: ["itemChange", "optionsChange", "moreRequest"] }, { kind: "component", type: MatChipListbox, selector: "mat-chip-listbox", inputs: ["multiple", "aria-orientation", "selectable", "compareWith", "required", "hideSingleSelectionIndicator", "value"], outputs: ["change"] }, { kind: "component", type: MatChipOption, selector: "mat-basic-chip-option, [mat-basic-chip-option], mat-chip-option, [mat-chip-option]", inputs: ["selectable", "selected"], outputs: ["selectionChange"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: MatChipRemove, selector: "[matChipRemove]" }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: LinkedNodeFilterComponent, decorators: [{
type: Component,
args: [{ selector: 'cadmus-walker-linked-node-filter', imports: [
FormsModule,
ReactiveFormsModule,
MatPaginator,
MatFormField,
MatInput,
MatSelect,
MatOption,
MatCheckbox,
RefLookupComponent,
MatChipListbox,
MatChipOption,
MatTooltip,
MatChipRemove,
MatIcon,
MatIconButton,
], template: "<form [formGroup]=\"form\" (submit)=\"apply()\" [attr.disabled]=\"disabled()\">\r\n <!-- paginator -->\r\n @if (hasPager()) {\r\n <mat-paginator\r\n [length]=\"total\"\r\n [pageSize]=\"pageSize.value || 10\"\r\n [pageSizeOptions]=\"[5, 10, 20]\"\r\n (page)=\"onPageChange($event)\"\r\n aria-label=\"Select page\"\r\n />\r\n }\r\n\r\n <!-- label -->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"label\" placeholder=\"label\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- uid -->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"uid\" placeholder=\"UID\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- isClass -->\r\n <div>\r\n <mat-form-field>\r\n <mat-select [formControl]=\"isClass\" placeholder=\"class\">\r\n <mat-option [value]=\"0\">(any)</mat-option>\r\n <mat-option [value]=\"1\">not-class</mat-option>\r\n <mat-option [value]=\"2\">class</mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n </div>\r\n <!-- tag -->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"tag\" placeholder=\"tag\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- sourceType -->\r\n <div>\r\n <mat-form-field>\r\n <mat-select [formControl]=\"sourceType\" placeholder=\"source type\">\r\n <mat-option [value]=\"null\">(any)</mat-option>\r\n <mat-option [value]=\"0\">user</mat-option>\r\n <mat-option [value]=\"1\">item</mat-option>\r\n <mat-option [value]=\"2\">part</mat-option>\r\n <mat-option [value]=\"3\">thesaurus</mat-option>\r\n <mat-option [value]=\"4\">implicit</mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n </div>\r\n <div>\r\n <!-- sid -->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"sid\" placeholder=\"SID\" />\r\n </mat-form-field>\r\n\r\n <!-- isSidPrefix -->\r\n <mat-checkbox [formControl]=\"isSidPrefix\">prefix</mat-checkbox>\r\n </div>\r\n </div>\r\n <!-- classes -->\r\n <div>\r\n <fieldset>\r\n <legend>classes</legend>\r\n <cadmus-ref-lookup\r\n label=\"class\"\r\n [service]=\"lookupService\"\r\n [baseFilter]=\"{ isClass: true }\"\r\n (itemChange)=\"onClassAdd($event)\"\r\n />\r\n\r\n @if (classes.value.length) {\r\n <mat-chip-listbox>\r\n @for (node of classes.value; track node.id) {\r\n <mat-chip-option\r\n [removable]=\"true\"\r\n (removed)=\"onClassRemove(node)\"\r\n matTooltip=\"{{ node.uri }}\"\r\n >{{ node.label }}\r\n <button type=\"button\" matChipRemove>\r\n <mat-icon>cancel</mat-icon>\r\n </button>\r\n </mat-chip-option>\r\n }\r\n </mat-chip-listbox>\r\n }\r\n </fieldset>\r\n </div>\r\n\r\n <div\r\n class=\"btn-group\"\r\n role=\"group\"\r\n aria-label=\"toolbar\"\r\n style=\"margin-bottom: 10px\"\r\n >\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n (click)=\"reset()\"\r\n matTooltip=\"Reset filters\"\r\n [disabled]=\"disabled()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"submit\"\r\n mat-icon-button\r\n [disabled]=\"disabled()\"\r\n matTooltip=\"Apply filters\"\r\n >\r\n <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n </button>\r\n </div>\r\n</form>\r\n", styles: ["fieldset{border:1px solid silver;border-radius:4px;padding:4px}\n"] }]
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.GraphNodeLookupService }, { type: i3.GraphService }] });
/**
* Linked literal filter.
*/
class LinkedLiteralFilterComponent {
lookupService;
_graphService;
/**
* True if this component is disabled.
*/
disabled = input();
/**
* True if this component should show a pager.
*/
hasPager = input();
/**
* The total number of triples returned from the last
* page fetch operation. Used when hasPager is true.
*/
total = input(0);
/**
* The filter.
*/
filter = model({
pageNumber: 1,
pageSize: 10,
});
pageNumber;
pageSize;
litPattern;
litType;
litLanguage;
minLitNumber;
maxLitNumber;
subj;
pred;
form;
constructor(formBuilder, lookupService, _graphService) {
this.lookupService = lookupService;
this._graphService = _graphService;
this.pageNumber = formBuilder.control(1, { nonNullable: true });
this.pageSize = formBuilder.control(10, { nonNullable: true });
this.litPattern = formBuilder.control(null);
this.litType = formBuilder.control(null);
this.litLanguage = formBuilder.control(null);
this.minLitNumber = formBuilder.control(null);
this.maxLitNumber = formBuilder.control(null);
this.subj = formBuilder.control(null);
this.pred = formBuilder.control(null);
this.form = formBuilder.group({
pageNumber: this.pageNumber,
pageSize: this.pageSize,
litPattern: this.litPattern,
litType: this.litType,
litLanguage: this.litLanguage,
minLitNumber: this.minLitNumber,
maxLitNumber: this.maxLitNumber,
subj: this.subj,
pred: this.pred,
});
effect(() => {
this.updateForm(this.filter());
});
}
updateForm(filter) {
this.pageNumber.setValue(filter.pageNumber);
this.pageSize.setValue(filter.pageSize);
this.litPattern.setValue(filter.literalPattern || null);
this.litType.setValue(filter.literalType || null);
this.litLanguage.setValue(filter.literalLanguage || null);
this.minLitNumber.setValue(filter.minLiteralNumber || null);
this.maxLitNumber.setValue(filter.maxLiteralNumber || null);
// load the referenced triples so we can show them by label
forkJoin({
s: filter.subjectId
? this._graphService.getNode(filter.subjectId)
: from([null]),
p: filter.predicateId
? this._graphService.getNode(filter.predicateId)
: from([]),
}).subscribe((result) => {
this.subj.setValue(result.s);
this.pred.setValue(result.p);
this.form.markAsPristine();
});
}
getFilter() {
return {
pageNumber: +this.pageNumber.value,
pageSize: +this.pageSize.value,
literalPattern: this.litPattern.value || undefined,
literalType: this.litType.value || undefined,
literalLanguage: this.litLanguage.value || undefined,
minLiteralNumber: this.minLitNumber.value || undefined,
maxLiteralNumber: this.maxLitNumber.value || undefined,
subjectId: this.subj.value?.id,
predicateId: this.pred.value?.id,
};
}
onSubjectNodeChange(node) {
this.subj.setValue(node);
}
onPredicateNodeChange(node) {
this.pred.setValue(node);
}
onPageChange(page) {
this.pageNumber.setValue(page.pageIndex + 1);
this.filter.set(this.getFilter());
}
reset() {
this.form.reset();
this.filter.set(this.getFilter());
}
apply() {
if (this.form.invalid) {
return;
}
this.filter.set(this.getFilter());
this.form.markAsPristine();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: LinkedLiteralFilterComponent, deps: [{ token: i1.FormBuilder }, { token: i2.GraphNodeLookupService }, { token: i3.GraphService }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.0", type: LinkedLiteralFilterComponent, isStandalone: true, selector: "cadmus-walker-linked-literal-filter", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, hasPager: { classPropertyName: "hasPager", publicName: "hasPager", isSignal: true, isRequired: false, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filter: "filterChange" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"apply()\" [attr.disabled]=\"disabled()\">\r\n <!-- paginator -->\r\n @if (hasPager()) {\r\n <mat-paginator\r\n [length]=\"total()\"\r\n [pageSize]=\"pageSize.value || 10\"\r\n [pageSizeOptions]=\"[5, 10, 20]\"\r\n (page)=\"onPageChange($event)\"\r\n aria-label=\"Select page\"\r\n />\r\n }\r\n\r\n <!-- subject ID -->\r\n <div>\r\n <cadmus-ref-lookup\r\n label=\"subject\"\r\n [service]=\"lookupService\"\r\n (itemChange)=\"onSubjectNodeChange($event)\"\r\n />\r\n </div>\r\n\r\n <!-- predicate ID -->\r\n <div>\r\n <cadmus-ref-lookup\r\n label=\"predicate\"\r\n [service]=\"lookupService\"\r\n [baseFilter]=\"{ tag: 'property' }\"\r\n (itemChange)=\"onPredicateNodeChange($event)\"\r\n />\r\n </div>\r\n\r\n <!-- LITERAL -->\r\n <!-- litPattern -->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"litPattern\" placeholder=\"pattern\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- litType -->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"litType\" placeholder=\"type\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- litLanguage-->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"litLanguage\" placeholder=\"language\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- minLitNumber, maxLitNumber -->\r\n <div>\r\n <mat-form-field style=\"width: 5em\">\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"minLitNumber\"\r\n placeholder=\"min.\"\r\n />\r\n </mat-form-field>\r\n -\r\n <mat-form-field style=\"width: 5em\">\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"maxLitNumber\"\r\n placeholder=\"max.\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n <div\r\n class=\"btn-group\"\r\n role=\"group\"\r\n aria-label=\"toolbar\"\r\n style=\"margin-bottom: 10px\"\r\n >\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n (click)=\"reset()\"\r\n matTooltip=\"Reset filters\"\r\n [disabled]=\"disabled\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"submit\"\r\n mat-icon-button\r\n [disabled]=\"disabled\"\r\n matTooltip=\"Apply filters\"\r\n >\r\n <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n </button>\r\n </div>\r\n</form>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: RefLookupComponent, selector: "cadmus-ref-lookup", inputs: ["label", "limit", "baseFilter", "service", "item", "required", "hasMore", "linkTemplate", "optDialog", "options"], outputs: ["itemChange", "optionsChange", "moreRequest"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: LinkedLiteralFilterComponent, decorators: [{
type: Component,
args: [{ selector: 'cadmus-walker-linked-literal-filter', imports: [
FormsModule,
ReactiveFormsModule,
MatPaginator,
RefLookupComponent,
MatFormField,
MatInput,
MatIconButton,
MatTooltip,
MatIcon,
], template: "<form [formGroup]=\"form\" (submit)=\"apply()\" [attr.disabled]=\"disabled()\">\r\n <!-- paginator -->\r\n @if (hasPager()) {\r\n <mat-paginator\r\n [length]=\"total()\"\r\n [pageSize]=\"pageSize.value || 10\"\r\n [pageSizeOptions]=\"[5, 10, 20]\"\r\n (page)=\"onPageChange($event)\"\r\n aria-label=\"Select page\"\r\n />\r\n }\r\n\r\n <!-- subject ID -->\r\n <div>\r\n <cadmus-ref-lookup\r\n label=\"subject\"\r\n [service]=\"lookupService\"\r\n (itemChange)=\"onSubjectNodeChange($event)\"\r\n />\r\n </div>\r\n\r\n <!-- predicate ID -->\r\n <div>\r\n <cadmus-ref-lookup\r\n label=\"predicate\"\r\n [service]=\"lookupService\"\r\n [baseFilter]=\"{ tag: 'property' }\"\r\n (itemChange)=\"onPredicateNodeChange($event)\"\r\n />\r\n </div>\r\n\r\n <!-- LITERAL -->\r\n <!-- litPattern -->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"litPattern\" placeholder=\"pattern\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- litType -->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"litType\" placeholder=\"type\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- litLanguage-->\r\n <div>\r\n <mat-form-field>\r\n <input matInput [formControl]=\"litLanguage\" placeholder=\"language\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- minLitNumber, maxLitNumber -->\r\n <div>\r\n <mat-form-field style=\"width: 5em\">\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"minLitNumber\"\r\n placeholder=\"min.\"\r\n />\r\n </mat-form-field>\r\n -\r\n <mat-form-field style=\"width: 5em\">\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"maxLitNumber\"\r\n placeholder=\"max.\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n <div\r\n class=\"btn-group\"\r\n role=\"group\"\r\n aria-label=\"toolbar\"\r\n style=\"margin-bottom: 10px\"\r\n >\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n (click)=\"reset()\"\r\n matTooltip=\"Reset filters\"\r\n [disabled]=\"disabled\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"submit\"\r\n mat-icon-button\r\n [disabled]=\"disabled\"\r\n matTooltip=\"Apply filters\"\r\n >\r\n <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n </button>\r\n </div>\r\n</form>\r\n" }]
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.GraphNodeLookupService }, { type: i3.GraphService }] });
/**
* Get the extended label for the specified graph node. This returns the label
* for N nodes, and the uri + "=" + the label for P nodes. That's because P
* nodes label is just the count of the triples group, so the predicate ID is
* got from the property node's data uri.
*/
class GraphNodeLabelPipe {
transform(value, ...args) {
const node = value;
if (!node?.id || !node?.label) {
return value;
}
if (node.id.startsWith('P') && node.data.uri) {
return `${node.data.uri}=${node.label}`;
}
return node.label;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: GraphNodeLabelPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.0.0", ngImport: i0, type: GraphNodeLabelPipe, isStandalone: true, name: "graphNodeLabel" });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: GraphNodeLabelPipe, decorators: [{
type: Pipe,
args: [{ name: 'graphNodeLabel' }]
}] });
//#endregion
/**
* Graph walker.
* This class encapsulates data used for interactively exploring a graph
* starting from a designated origin node.
*/
class GraphWalker {
_graphService;
_nodes$;
_edges$;
// TODO: add clusters
_loading$;
_error$;
_rootNode;
_selectedNode$;
_pOutFilter$;
_pInFilter$;
_pLitFilter$;
_nOutFilter$;
_nInFilter$;
_childTotals$;
/**
* The page size. Default is 10.
*/
pageSize;
/**
* Max length of literal value to show. Default is 30.
*/
maxLiteralLen;
/**
* The nodes of the walker graph, which can represent nodes, literals,
* or property groups. You can easily determine the type of each node
* by looking at the first character of its id (N=node, L=literal,
* P=property).
*/
get nodes$() {
return this._nodes$.asObservable();
}
/**
* The edges connecting nodes.
*/
get edges$() {
return this._edges$.asObservable();
}
/**
* True if the walker is loading data.
*/
get loading$() {
return this._loading$.asObservable();
}
/**
* The last error occurred in communicating with the server, if any.
*/
get error$() {
return this._error$.asObservable();
}
/**
* The selected node. Only one node at a time can be selected.
*/
get selectedNode$() {
return this._selectedNode$.asObservable();
}
/**
* The outbound linked nodes filter for the selected P node.
*/
get pOutFilter$() {
return this._pOutFilter$.asObservable();
}
/**
* The inbound linked nodes filter for the selected P node.
*/
get pInFilter$() {
return this._pInFilter$.asObservable();
}
/**
* The literal linked nodes filter for the selected P node.
*/
get pLitFilter$() {
return this._pLitFilter$.asObservable();
}
/**
* The outbound triples filter for the selected N node.
*/
get nOutFilter$() {
return this._nOutFilter$.asObservable();
}
/**
* The inbound triples filter for the selected N node.
*/
get nInFilter$() {
return this._nInFilter$.asObservable();
}
/**
* The total items fetched for each filter of the selected node.
*/
get childTotals$() {
return this._childTotals$.asObservable();
}
constructor(_graphService) {
this._graphService = _graphService;
this._nodes$ = new BehaviorSubject([]);
this._edges$ = new BehaviorSubject([]);
this._loading$ = new BehaviorSubject(false);
this._error$ = new BehaviorSubject(null);
this._selectedNode$ = new BehaviorSubject(null);
this._pOutFilter$ = new BehaviorSubject(null);
this._pInFilter$ = new BehaviorSubject(null);
this._pLitFilter$ = new BehaviorSubject(null);
this._nOutFilter$ = new BehaviorSubject(null);
this._nInFilter$ = new BehaviorSubject(null);
this._childTotals$ = new BehaviorSubject({
nOut: 0,
nIn: 0,
pOut: 0,
pIn: 0,
pLit: 0,
});
// defaults
this.pageSize = 10;
this.maxLiteralLen = 30;
}
getSelectedNode() {
return this._selectedNode$.value;
}
toggleLoading(on) {
if (on) {
this._error$.next(null);
this._loading$.next(true);
}
else {
this._loading$.next(false);
}
}
setError(error) {
if (error) {
if (typeof error === 'string') {
this._error$.next(error);
console.error(error);
}
else {
this._error$.next('Walker error');
console.error('Walker error', error);
}
}
else {
this._error$.next('Walker error');
}
}
buildNodeId(id) {
return `N${id}`;
}
/**
* Get the numeric ID of the data node being the source of the graph node
* with the specified ID.
*
* @param id The graph node ID (...N + its data node numeric ID)
* @returns The data node numeric ID.
*/
getNodeNumericId(id) {
const i = id.indexOf('N');
return i === -1 ? 0 : +id.substring(i + 1);
}
getPredicateNumericId(id) {
const i = id.indexOf('P');
const j = id.indexOf('N');
return i === -1 ? 0 : +id.substring(i + 1, j);
}
buildEdgeId(source, target) {
return `E${source}_${target}`;
}
buildPropertyId(predicateId, nodeId) {
return `P${predicateId}N${nodeId}`;
}
buildLiteralId(tripleId) {
return `L${tripleId}`;
}
scale(n, domainMin, domainMax, rangeMin, rangeMax) {
// https://stackoverflow.com/questions/5294955/how-to-scale-down-a-range-of-numbers-with-a-known-min-and-max-value
return (((domainMax - domainMin) * (n - rangeMin)) / (rangeMax - rangeMin) +
domainMin);
}
addEdgeIfAbsent(edge, edges) {
const id = edge.id;
if (edges.some((e) => e.id === id)) {
return;
}
const i = id.indexOf('_');
if (i > -1) {
const invId = 'E' + id.substring(i + 1) + '_' + id.substring(1, i);
if (edges.some((e) => e.id === invId)) {
return;
}
}
edges.push(edge);
}
resetFilters() {
this._nOutFilter$.next(null);
this._nInFilter$.next(null);
this._pOutFilter$.next(null);
this._pInFilter$.next(null);
this._pLitFilter$.next(null);
this._childTotals$.next({
nOut: 0,
nIn: 0,
pOut: 0,
pIn: 0,
pLit: 0,
});
}
/**
* Select the specified node. This also implies updating all the current
* filters, which depend on the selected node.
*
* @param id The node ID or null to deselect the selected node.
*/
selectNode(id) {
const node = id ? this._nodes$.value.find((n) => n.id === id) : null;
if (!node) {
this._selectedNode$.next(null);
this.resetFilters();
return;
}
// deselect
const old = this._nodes$.value.find((n) => n.data?.selected);
if (old) {
old.data.selected = undefined;
}
// select
node.data.selected = true;
this._selectedNode$.next(node);
switch (node.id.charAt(0)) {
case 'N': // non-literal node
const nd = node.data;
this._nOutFilter$.next(nd.outFilter);
this._nInFilter$.next(nd.inFilter);
this._pOutFilter$.next(null);
this._pInFilter$.next(null);
this._pLitFilter$.next(null);
this._childTotals$.next({
nOut: nd.outTotal || 0,
nIn: nd.inTotal || 0,
pOut: 0,
pIn: 0,
pLit: 0,
});
break;
case 'P': // props group
const pd = node.data;
this._nOutFilter$.next(null);
this._nInFilter$.next(null);
this._pOutFilter$.next(pd.outFilter);
this._pInFilter$.next(pd.inFilter);
this._pLitFilter$.next(pd.litFilter);
this._childTotals$.next({
nOut: 0,
nIn: 0,
pOut: pd.outTotal || 0,
pIn: pd.inTotal || 0,
pLit: pd.litTotal || 0,
});
break;
case 'L': // literal node
this.resetFilters();
break;
}
}
/**
* Reset the graph setting its origin to the specified node.
*
* @param id The ID of the origin node to start from ("root origin").
*/
reset(id) {
this.toggleLoading(true);
this._graphService
.getNode(id)
.pipe(take(1))
.subscribe({
next: (node) => {
const nodes = [];
const data = {
uri: node.uri,
sourceType: node.sourceType,
originId: '', // root origin
customColor: '#F89427',
outFilter: {
pageNumber: 1,
pageSize: this.pageSize,
},
inFilter: {
pageNumber: 1,
pageSize: this.pageSize,
},
sid: node.sid,
};
const n = {
id: this.buildNodeId(node.id),
label: node.label || node.uri,
data: data,
};
nodes.push(n);
this._rootNode = n;
this._edges$.next([]);
this._nodes$.next(nodes);
this.expandNode(n);
},
error: (error) => {
this.setError(error);
},
complete: () => {
this.toggleLoading(false);
},
});
}
/**
* Get the source and target graph nodes IDs from the specified edge ID.
*
* @param edgeId The ID of the edge graph node.
* @returns IDs of the source and target graph nodes.
*/
getEdgeEndIds(edgeId) {
const m = new RegExp('^E([^_]+)_(.+)$').exec(edgeId);
return m ? [m[1], m[2]] : null;
}
/**
* Remove all the nodes with the specified origin node ID.
*
* @param originId The ID of the origin graph node.
* @param nodes The nodes array to remove nodes from.
* @param removedIds The IDs of all the removed nodes.
*/
removeDescendantNodes(originId, nodes, removedIds) {
const ids = new Set();
for (let i = nodes.length - 1; i > -1; i--) {
if (nodes[i].data.originId === originId) {
ids.add(nodes[i].id);
nodes.splice(i, 1);
}
}
// recurse for each removed node
// (edges never derive from other edges directly)
ids.forEach((id) => {
this.removeDescendantNodes(id, nodes, removedIds);
});
// update the received IDs set
ids.forEach((id) => {
removedIds.add(id);
});
}
/**
* Remove all the children graph nodes of the specified origin graph node.
*
* @param originId The ID of the origin graph node.
* @param nodes The nodes array to remove nodes from.
* @param edges The edges array to remove edges from.
*/
removeChildren(originId, nodes, edges) {
const selectedId = this._selectedNode$.value?.id;
// remove all the nodes whose origin ID matches,
// collecting their IDs
const removedIds = new Set();
this.removeDescendantNodes(originId, nodes, removedIds);
// remove all the affected edges
for (let i = edges.length - 1; i > -1; i--) {
const endIds = this.getEdgeEndIds(edges[i].id);
if (endIds?.length &&
(removedIds.has(endIds[0]) || removedIds.has(endIds[1]))) {
edges.splice(i, 1);
}
}
// if the removed node was the selected one, select the root origin
if (selectedId && removedIds.has(selectedId)) {
this.selectNode(this._rootNode.id);
}
}
/**
* Build a new graph node representing a property.
*
* @param sourceId The source node ID.
* @param group The triple group emitting this node.
* @returns A new graph node.
*/
buildPropertyNode(sourceId, group) {
const nid = this.getNodeNumericId(sourceId);
const data = {
originId: sourceId,
uri: group.predicateUri,
customColor: '#FF5619',
outFilter: {
pageNumber: 1,
pageSize: this.pageSize,
otherNodeId: nid,
predicateId: group.predicateId,
},
inFilter: {
pageNumber: 1,
pageSize: this.pageSize,
otherNodeId: nid,
predicateId: group.predicateId,
},
litFilter: {
pageNumber: 1,
pageSize: this.pageSize,
predicateId: group.predicateId,
},
};
return {
id: this.buildPropertyId(group.predicateId, nid),
label: group.count.toString(),
data: data,
};
}
/**
* Expand the specified node, by loading its property groups.
*
* @param node The node to expand.
* @param outFilter The properties to update for the output filter.
* @param inFilter The properties to update for the input filter.
*/
expandNode(node, outFilter, inFilter) {
// prepare filters
const nid = this.getNodeNumericId(node.id);
// outbound: node=S
const outf = outFilter
? Object.assign(node.data.outFilter, outFilter, {
subjectId: nid,
})
: {
pageNumber: 1,
pageSize: this.pageSize,
subjectId: nid,
};
// inbound: node=O
const inf = inFilter
? Object.assign(node.data.inFilter, inFilter, {
objectId: nid,
})
: {
pageNumber: 1,
pageSize: this.pageSize,
objectId: nid,
};
// load
this.toggleLoading(true);
const nodes = [...this._nodes$.value];
const edges = [...this._edges$.value];
forkJoin({
outs: this._graphService.getTripleGroups(outf.pageNumber, outf.pageSize, outf),
ins: this._graphService.getTripleGroups(inf.pageNumber, inf.pageSize, inf),
}).subscribe({
next: (result) => {
node.data.expanded = true;
// update origin's filters
node.data.outFilter = outf;
node.data.inFilter = inf;
// remove previous children
this.removeChildren(node.id, nodes, edges);
// add outbound children
node.data.outTotal = result.outs.total;
for (let i = 0; i < result.outs.items.length; i++) {
const group = result.outs.items[i];
const prop = this.buildPropertyNode(node.id, group);
// when expanding a node (e.g. N17) into props, the prop's ID is
// P + predicate ID + N + origin node ID (e.g. P30N17).
// This prop node can then be expanded, too, thus producing
// a node with ID = N + node ID. When this in turn gets expanded,
// it will produce also the prop node it comes from, which
// must not be re-inserted in the graph. This node in the new
// expansion context will get ID from the source node, which
// is different from the node at the other end of the prop node:
// this was e.g. N17, while the new node is e.g. N18. So, the
// prop previously identified as P30N17 would now be identified
// as P30N18, thus producing a duplicate. To avoid this, we
// calculate an alias ID from the origin's origin: for N18,
// its origin being P30N17, this will be N17. This produces an
// alias P30N17, which being already present will avoid duplicates.
// const aliasId = `P${group.predicateId}N${this.getNodeNumericId(
// node.data.originId
// )}`;
// if (!nodes.some((n) => n.id === prop.id || n.id === aliasId)) {
if (!nodes.some((n) => n.id === prop.id)) {
nodes.push(prop);
// edge from origin node to object property
const edge = {
id: this.buildEdgeId(node.id, prop.id),
label: group.predicateUri,
source: node.id,
target: prop.id,
data: {
originId: node.id,
},
};
this.addEdgeIfAbsent(edge, edges);
}
}
// add inbound children
node.data.inTotal = result.ins.total;
for (let i = 0; i < result.ins.items.length; i++) {
const g = result.ins.items[i];
// subject property
const p = this.buildPropertyNode(node.id, g);
// edge from object property to origin node
const edge = {
id: this.buildEdgeId(p.id, node.id),
label: g.predicateUri,
source: p.id,
target: node.id,
data: {
originId: node.id,
},
};
// do not add an edge having the same source P and target N,
// whatever the P's source node
const r = new RegExp('^EP' + g.predicateId + 'N[0-9]+_' + node.id + '$');
if (!edges.some((e) => r.test(e.id))) {
if (!nodes.some((n) => n.id === p.id)) {
nodes.push(p);
}
this.addEdgeIfAbsent(edge, edges);
}
}
// update
this._nodes$.next(nodes);
this._edges$.next(edges);
},
error: (error) => {
node.data.error = 'Error loading properties';
this._nodes$.next(nodes);
this.setError(error);
},
complete: () => {
this.toggleLoading(false);
},
});
}
/**
* Expand the selected node, by loading its property groups.
*
* @param outFilter The properties to update for the output filter.
* @param inFilter The properties to update for the input filter.
*/
expandSelectedNode(outFilter, inFilter) {
if (!this._selectedNode$.value ||
!this._selectedNode$.value.id.startsWith('N')) {
return;
}
const node = this._selectedNode$.value;
this.expandNode(node, outFilter, inFilter);
}
buildLiteralLabel(triple) {
let value = triple.objectLiteral || '';
if (this.maxLiteralLen && value.length > this.maxLiteralLen) {
value = value.substring(0, this.maxLiteralLen) + '\u2026';
}
return value;
}
buildNonLiteralNode(sourceId, node) {
const nid = this.getNodeNumericId(sourceId);
const data = {
originId: sourceId,
customColor: '#80ff95',
uri: node.uri,
sourceType: node.sourceType,
isClass: node.isClass,
sid: node.sid,
tag: node.tag,
outFilter: {
pageNumber: 0,
pageSize: this.pageSize,
subjectId: nid,
},
inFilter: {
pageNumber: 0,
pageSize: this.pageSize,
objectId: nid,
},
};
return {
id: this.buildNodeId(node.id),
label: node.label,
data: data,
};
}
buildLiteralNode(sourceId, triple) {
const data = {
originId: sourceId,
customColor: '#ebe2e0',
value: triple.objectLiteral || '',
type: triple.literalType,
language: triple.literalLanguage,
number: triple.literalNumber,
};
return {
id: this.buildLiteralId(triple.id),
label: this.buildLiteralLabel(triple),
data: data,
};
}
/**
* Expand the currently selected properties group node, by loading its
* outbound nodes, inbound nodes, and literal nodes.
*
* @param node The property group node to expand.
* @param outFilter The properties to update for the outbound nodes filter.
* @param inFilter The properties to update for the inbound nodes filter.
* @param litFilter The properties to update for the literal nodes filter.
*/
expandProperty(node, outFilter, inFilter, litFilter) {
// prepare filters
const nid = this.getNodeNumericId(node.id);
const data = node.data;
const outf = Object.assign(data.outFilter, outFilter || {}, { isObject: true });
const inf = Object.assign(data.inFilter, inFilter || {}, {
isObject: false,
});
const litf = Object.assign(data.litFilter, litFilter || {}, { subjectId: nid, predicateId: this.getPredicateNumericId(node.id) });
// load
this.toggleLoading(true);
const nodes = [...this._nodes$.value];
const edges = [...this._edges$.value];
forkJoin({
outs: this._graphService.getLinkedNodes(outf.pageNumber, outf.pageSize, outf),
ins: this._graphService.getLinkedNodes(inf.pageNumber, inf.pageSize, inf),
lits: this._graphService.getLinkedLiterals(litf.pageNumber, litf.pageSize, litf),
}).subscribe({
next: (result) => {
node.data.expanded = true;
// update origin's filters
node.data.outFilter = outf;
node.data.inFilter = inf;
node.data.litFilter = litf;
// remove previous children
this.removeChildren(node.id, nodes, edges);
// add outbound children
node.data.outTotal = result.outs.total;
for (let i = 0; i < result.outs.items.length; i++) {
const child = result.outs.items[i];
const obj = this.buildNonLiteralNode(node.id, child);
if (!nodes.some((n) => n.id === obj.id)) {
nodes.push(obj);
}
// edge from property to non literal object node
const edge = {
id: this.buildEdgeId(node.id, obj.id),
label: '',
source: node.id,
target: obj.id,
data: {
originId: node.id,
},
};
this.addEdgeIfAbsent(edge, edges);
}
// add inbound children
node.data.inTotal = result.ins.total;
for (let i = 0; i < result.ins.items.length; i++) {
const child = result.ins.items[i];
const subj = this.buildNonLiteralNode(node.id, child);
if (!nodes.some((n) => n.id === subj.id)) {
nodes.push(subj);
}
// edge from subject node to property
const edge = {
id: this.buildEdgeId(subj.id, node.id),
label: '',
source: subj.id,
target: node.id,
data: {
originId: node.id,
},
};
this.addEdgeIfAbsent(edge, edges);
}
// add literal children
node.data.litTotal = result.lits.total;
for (let i = 0; i < result.lits.items.length; i++) {
const triple = result.lits.items[i];
const lit = this.buildLiteralNode(node.id, triple);
if (!nodes.some((n) => n.id === lit.id)) {
nodes.push(lit);
}
// edge from property to literal
const edge = {
id: this.buildEdgeId(node.id, lit.id),
label: lit.data.literalType || '',
source: node.id,
target: lit.id,
data: {
originId: node.id,
},
};
this.addEdgeIfAbsent(edge, edges);
}
// update
this._nodes$.next(nodes);
this._edges$.next(edges);
},
error: (error) => {
node.data.error = 'Error loading nodes';
this._nodes$.next(nodes);
this.setError(error);
},
complete: () => {
this.toggleLoading(false);
},
});
}
/**
* Expand the currently selected properties group node, by loading its
* outbound nodes, inbound nodes, and literal nodes. If there is no
* selection, or the selected node is not a properties group node, nothing
* is done.
*
* @param outFilter The properties to update for the outbound nodes filter.
* @param inFilter The properties to update for the inbound nodes filter.
* @param litFilter The properties to update for the literal nodes filter.
*/
expandSelectedProperty(outFilter, inFilter, litFilter) {
if (!this._selectedNode$.value ||
!this._selectedNode$.value.id.startsWith('P')) {
return;
}
const node = this._selectedNode$.value;
this.expandProperty(node, outFilter, inFilter, litFilter);
}
/**
* Toggle the specified node by expanding or collapsing it.
*
* @param node The node to toggle.
*/
toggleNode(node) {
if (node.data.expanded) {
const nodes = [...this._nodes$.value];
const edges = [...this._edges$.value];
this.removeChildren(node.id, nodes, edges);
node.data.expanded = undefined;
this._nodes$.next(nodes);
this._edges$.next(edges);
}
else {
if (node.id.startsWith('N')) {
this.expandNode(node);
}
else if (node.id.startsWith('P')) {
this.expandProperty(node);
}
}
}
}
/**
* Graph walker component. This starts from a given node, and let users
* walk along edges to discover new nodes.
*/
class GraphWalkerComponent {
_dialog;
_sub;
_walker;
/**
* The root origin node ID.
*/
nodeId = input(0);
/**
* True if user can pick a node from the graph.
*/
canPick = input();
/**
* True if user can move to the source of a picked node when
* shift-clicking it.
*/
canMoveToSource = input();
/**
* Emitted when a graph node is picked by user.
*/
nodePick = output();
/**
* Emitted when the user requests to move to the source of a picked node.
*/
moveToSource = output();
// graph
nodes$;
edges$;
loading$;
error$;
// selected node
selectedNode$;
pOutFilter$;
pInFilter$;
pLitFilter$;
nOutFilter$;
nInFilter$;
childTotals$;
// ngx-graph actions
update$ = new Subject();
center$ = new Subject();
zoomToFit$ = new Subject();
constructor(graphService, _dialog) {
this._dialog = _dialog;
this._walker = new GraphWalker(graphService);
this.nodes$ = this._walker.nodes$;
this.edges$ = this._walker.edges$;
this.loading$ = this._walker.loading$;
this.error$ = this._walker.error$;
this.selectedNode$ = this._walker.selectedNode$;
this.pOutFilter$ = this._walker.pOutFilter$;
this.pInFilter$ = this._walker.pInFilter$;
this.pLitFilter$ = this._walker.pLitFilter$;
this.nOutFilter$ = this._walker.nOutFilter$;
this.nInFilter$ = this._walker.nInFilter$;
this.childTotals$ = this._walker.childTotals$;
effect(() => {
const id = this.nodeId();
if (id) {
this.reset(id);
}
});
}
ngOnInit() {
this._sub = this.update$.subscribe((_) => {
this.onReset();
});
}
ngOnDestroy() {
this._sub?.unsubscribe();
}
onNodeSelect(node) {
this._walker.selectNode(node.id);
}
reset(id) {
this._walker.reset(id);
}
onReset() {
if (!this.nodeId()) {
return;
}
this._dialog
.confirm('Reset', 'Reset the whole graph?')
.pipe(take$1(1))
.subscribe((yes) => {
if (yes) {
this.reset(this.nodeId());
}
});
}
onNodeDblClick(node) {
this._walker.toggleNode(node);
}
onPOutFilterChange(filter) {
this._walker.expandSelectedProperty(filter);
}
onPInFilterChange(filter) {
this._walker.expandSelectedProperty(null, filter);
}
onPLitFilterChange(filter) {
this._walker.expandSelectedProperty(null, null, filter);
}
onNOutFilterChange(filter) {
this._walker.expandSelectedNode(filter);
}
onNInFilterChange(filter) {
this._walker.expandSelectedNode(null, filter);
}
pickSelectedNode(event) {
const node = this._walker.getSelectedNode();
if (!node) {
return;
}
if (this.canMoveToSource() && event.shiftKey) {
if (node.data.sid) {
this.moveToSource.emit(node);
}
}
else {
this.nodePick.emit(node);
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: GraphWalkerComponent, deps: [{ token: i3.GraphService }, { token: i2$1.DialogService }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.0", type: GraphWalkerComponent, isStandalone: true, selector: "cadmus-graph-walker", inputs: { nodeId: { classPropertyName: "nodeId", publicName: "nodeId", isSignal: true, isRequired: false, transformFunction: null }, canPick: { classPropertyName: "canPick", publicName: "canPick", isSignal: true, isRequired: false, transformFunction: null }, canMoveToSource: { classPropertyName: "canMoveToSource", publicName: "canMoveToSource", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { nodePick: "nodePick", moveToSource: "moveToSource" }, ngImport: i0, template: "<div id=\"container\">\r\n <!-- graph -->\r\n <div id=\"graph\">\r\n <ngx-graph\r\n class=\"chart-container\"\r\n [showMiniMap]=\"true\"\r\n [links]=\"(edges$ | async) || []\"\r\n [nodes]=\"(nodes$ | async) || []\"\r\n [update$]=\"update$\"\r\n [center$]=\"center$\"\r\n [zoomToFit$]=\"zoomToFit$\"\r\n layout=\"colaForceDirected\"\r\n (select)=\"onNodeSelect($event)\"\r\n >\r\n <ng-template #defsTemplate>\r\n <svg:marker\r\n id=\"arrow\"\r\n viewBox=\"0 -5 10 10\"\r\n refX=\"8\"\r\n refY=\"0\"\r\n markerWidth=\"4\"\r\n markerHeight=\"4\"\r\n orient=\"auto\"\r\n >\r\n <svg:path d=\"M0,-5L10,0L0,5\" class=\"arrow-head\" />\r\n </svg:marker>\r\n </ng-template>\r\n\r\n <!-- cluster template -->\r\n <ng-template #clusterTemplate let-cluster>\r\n <svg:g class=\"node cluster\">\r\n <svg:rect\r\n rx=\"5\"\r\n ry=\"5\"\r\n [attr.width]=\"cluster.dimension.width\"\r\n [attr.height]=\"cluster.dimension.height\"\r\n [attr.fill]=\"cluster.data.color\"\r\n />\r\n </svg:g>\r\n </ng-template>\r\n\r\n <!-- node template -->\r\n <ng-template #nodeTemplate let-node>\r\n <svg:g class=\"node\" (dblclick)=\"onNodeDblClick(node)\">\r\n <svg:rect\r\n [class.selected]=\"node.data?.selected\"\r\n [attr.width]=\"node.dimension.width\"\r\n [attr.height]=\"node.dimension.height\"\r\n [attr.fill]=\"node.data.customColor || node.data.color\"\r\n />\r\n <svg:text\r\n alignment-baseline=\"central\"\r\n [attr.x]=\"10\"\r\n [attr.y]=\"node.dimension.height / 2\"\r\n >\r\n {{ node.label }}\r\n </svg:text>\r\n </svg:g>\r\n </ng-template>\r\n\r\n <!-- link template -->\r\n <ng-template #linkTemplate let-link>\r\n <svg:g class=\"edge\">\r\n <svg:path\r\n class=\"line\"\r\n stroke-width=\"2\"\r\n marker-end=\"url(#arrow)\"\r\n ></svg:path>\r\n <svg:text class=\"edge-label\" text-anchor=\"middle\">\r\n <textPath\r\n class=\"text-path\"\r\n [attr.href]=\"'#' + link.id\"\r\n [style.dominant-baseline]=\"link.dominantBaseline\"\r\n startOffset=\"50%\"\r\n >\r\n {{ link.label }}\r\n </textPath>\r\n </svg:text>\r\n </svg:g>\r\n </ng-template>\r\n </ngx-graph>\r\n </div>\r\n\r\n <!-- tools -->\r\n <div id=\"tools\">\r\n <div id=\"bar\">\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Center\"\r\n (click)=\"center$.next(true)\"\r\n >\r\n <mat-icon>filter_center_focus</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Zoom to fit\"\r\n (click)=\"zoomToFit$.next({ force: true })\"\r\n >\r\n <mat-icon>fit_screen</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Reset\"\r\n (click)=\"update$.next(true)\"\r\n >\r\n <mat-icon class=\"mat-warn\">restart_alt</mat-icon>\r\n </button>\r\n @if (nodes$ | async; as nodes) {\r\n <span class=\"muted\"\r\n >N:{{ nodes.length }} E:{{ (edges$ | async)?.length }}</span\r\n >\r\n } @if (selectedNode$ | async; as selectedNode) {\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Toggle the selected node\"\r\n [disabled]=\"!selectedNode\"\r\n (click)=\"onNodeDblClick(selectedNode)\"\r\n >\r\n <mat-icon class=\"mat-primary\">unfold_more</mat-icon>\r\n </button>\r\n } @if (canPick()) {\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n [matTooltip]=\"\r\n canMoveToSource()\r\n ? 'Pick selected node or (shift) its source'\r\n : 'Pick the selected node'\r\n \"\r\n [disabled]=\"!(selectedNode$ | async)\"\r\n (click)=\"pickSelectedNode($event)\"\r\n >\r\n <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n </button>\r\n }\r\n </div>\r\n <!-- progress -->\r\n <div id=\"progress\">\r\n @if (loading$ | async) {\r\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\r\n }\r\n </div>\r\n <div id=\"filters\">\r\n @if (selectedNode$ | async; as node) {\r\n <div id=\"filter-head\">\r\n <span [style.color]=\"node.data.color || 'black'\">⬤</span>\r\n <span style=\"margin-left: 6px\" matTooltip=\"{{ node.data.uri }}\">{{\r\n node | graphNodeLabel\r\n }}</span>\r\n <span class=\"muted\" style=\"margin-left: 8px; font-size: 90%\">{{\r\n node.id\r\n }}</span>\r\n </div>\r\n }\r\n <mat-tab-group>\r\n <!-- N-outs -->\r\n @if (nOutFilter$ | async; as nOutFilter) {\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n N<mat-icon>logout</mat-icon>\r\n </ng-template>\r\n <cadmus-walker-triple-filter\r\n [filter]=\"nOutFilter\"\r\n (filterChange)=\"onNOutFilterChange($event)\"\r\n ></cadmus-walker-triple-filter>\r\n </mat-tab>\r\n }\r\n <!-- N-ins -->\r\n @if (nInFilter$ | async; as nInFilter) {\r\n <mat-tab>\r\n <ng-template mat-tab-label> N<mat-icon>login</mat-icon> </ng-template>\r\n <cadmus-walker-triple-filter\r\n [filter]=\"nInFilter\"\r\n (filterChange)=\"onNInFilterChange($event)\"\r\n ></cadmus-walker-triple-filter>\r\n </mat-tab>\r\n }\r\n <!-- P-outs -->\r\n @if (pOutFilter$ | async; as pOutFilter) {\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n P<mat-icon>logout</mat-icon>\r\n </ng-template>\r\n <cadmus-walker-linked-node-filter\r\n [filter]=\"pOutFilter\"\r\n (filterChange)=\"onPOutFilterChange($event)\"\r\n />\r\n </mat-tab>\r\n }\r\n <!-- P-ins -->\r\n @if (pInFilter$ | async; as pInFilter) {\r\n <mat-tab>\r\n <ng-template mat-tab-label> P<mat-icon>login</mat-icon> </ng-template>\r\n <cadmus-walker-linked-node-filter\r\n [filter]=\"pInFilter\"\r\n (filterChange)=\"onPInFilterChange($event)\"\r\n />\r\n </mat-tab>\r\n }\r\n <!-- P-lit -->\r\n @if (pLitFilter$ | async; as pLitFilter) {\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n P<mat-icon>exit_to_app</mat-icon>\r\n </ng-template>\r\n <cadmus-walker-linked-literal-filter\r\n [filter]=\"pLitFilter\"\r\n (filterChange)=\"onPLitFilterChange($event)\"\r\n />\r\n </mat-tab>\r\n }\r\n </mat-tab-group>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: ["div#filter-head{border:1px solid #f8f1ae;border-radius:4px;padding:4px;margin-bottom:4px;background-color:#f8f1ae;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.muted{color:silver}.selected{stroke-width:2px;stroke:#e7d211}#container{width:100%;max-width:100%;height:100%;max-height:100%;display:grid;grid-template-rows:1fr;grid-template-columns:auto 1fr;grid-template-areas:\"tools graph\";gap:8px}#graph{grid-area:graph}#tools{grid-area:tools;padding:8px;border:1px solid silver;height:100%;max-height:100%;min-width:324px;background-color:#fdfdfd}#tools #bar{display:flex;align-items:center;flex-wrap:wrap}@media only screen and (max-width: 959px){#container{grid-template-rows:auto 1fr;grid-template-columns:1fr;grid-template-areas:\"tools\" \"graph\"}}\n"], dependencies: [{ kind: "ngmodule", type: GraphModule }, { kind: "component", type: i3$1.GraphComponent, selector: "ngx-graph", inputs: ["nodes", "clusters", "compoundNodes", "links", "activeEntries", "curve", "draggingEnabled", "nodeHeight", "nodeMaxHeight", "nodeMinHeight", "nodeWidth", "nodeMinWidth", "nodeMaxWidth", "panningEnabled", "panningAxis", "enableZoom", "zoomSpeed", "minZoomLevel", "maxZoomLevel", "autoZoom", "panOnZoom", "animate", "autoCenter", "update$", "center$", "zoomToFit$", "panToNode$", "layout", "layoutSettings", "enableTrackpadSupport", "showMiniMap", "miniMapMaxWidth", "miniMapMaxHeight", "miniMapPosition", "view", "scheme", "customColors", "deferDisplayUntilPosition", "centerNodesOnPositionChange", "enablePreUpdateTransform", "groupResultsBy", "zoomLevel", "panOffsetX", "panOffsetY"], outputs: ["select", "activate", "deactivate", "zoomChange", "clickHandler", "stateChange"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "directive", type: MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: TripleFilterComponent, selector: "cadmus-walker-triple-filter", inputs: ["disabled", "hasPager", "total", "filter"], outputs: ["filterChange"] }, { kind: "component", type: LinkedNodeFilterComponent, selector: "cadmus-walker-linked-node-filter", inputs: ["disabled", "hasPager", "total", "filter"], outputs: ["filterChange"] }, { kind: "component", type: LinkedLiteralFilterComponent, selector: "cadmus-walker-linked-literal-filter", inputs: ["disabled", "hasPager", "total", "filter"], outputs: ["filterChange"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: GraphNodeLabelPipe, name: "graphNodeLabel" }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: GraphWalkerComponent, decorators: [{
type: Component,
args: [{ selector: 'cadmus-graph-walker', imports: [
GraphModule,
MatIconButton,
MatTooltip,
MatIcon,
MatProgressBar,
MatTabGroup,
MatTab,
MatTabLabel,
TripleFilterComponent,
LinkedNodeFilterComponent,
LinkedLiteralFilterComponent,
AsyncPipe,
GraphNodeLabelPipe,
], template: "<div id=\"container\">\r\n <!-- graph -->\r\n <div id=\"graph\">\r\n <ngx-graph\r\n class=\"chart-container\"\r\n [showMiniMap]=\"true\"\r\n [links]=\"(edges$ | async) || []\"\r\n [nodes]=\"(nodes$ | async) || []\"\r\n [update$]=\"update$\"\r\n [center$]=\"center$\"\r\n [zoomToFit$]=\"zoomToFit$\"\r\n layout=\"colaForceDirected\"\r\n (select)=\"onNodeSelect($event)\"\r\n >\r\n <ng-template #defsTemplate>\r\n <svg:marker\r\n id=\"arrow\"\r\n viewBox=\"0 -5 10 10\"\r\n refX=\"8\"\r\n refY=\"0\"\r\n markerWidth=\"4\"\r\n markerHeight=\"4\"\r\n orient=\"auto\"\r\n >\r\n <svg:path d=\"M0,-5L10,0L0,5\" class=\"arrow-head\" />\r\n </svg:marker>\r\n </ng-template>\r\n\r\n <!-- cluster template -->\r\n <ng-template #clusterTemplate let-cluster>\r\n <svg:g class=\"node cluster\">\r\n <svg:rect\r\n rx=\"5\"\r\n ry=\"5\"\r\n [attr.width]=\"cluster.dimension.width\"\r\n [attr.height]=\"cluster.dimension.height\"\r\n [attr.fill]=\"cluster.data.color\"\r\n />\r\n </svg:g>\r\n </ng-template>\r\n\r\n <!-- node template -->\r\n <ng-template #nodeTemplate let-node>\r\n <svg:g class=\"node\" (dblclick)=\"onNodeDblClick(node)\">\r\n <svg:rect\r\n [class.selected]=\"node.data?.selected\"\r\n [attr.width]=\"node.dimension.width\"\r\n [attr.height]=\"node.dimension.height\"\r\n [attr.fill]=\"node.data.customColor || node.data.color\"\r\n />\r\n <svg:text\r\n alignment-baseline=\"central\"\r\n [attr.x]=\"10\"\r\n [attr.y]=\"node.dimension.height / 2\"\r\n >\r\n {{ node.label }}\r\n </svg:text>\r\n </svg:g>\r\n </ng-template>\r\n\r\n <!-- link template -->\r\n <ng-template #linkTemplate let-link>\r\n <svg:g class=\"edge\">\r\n <svg:path\r\n class=\"line\"\r\n stroke-width=\"2\"\r\n marker-end=\"url(#arrow)\"\r\n ></svg:path>\r\n <svg:text class=\"edge-label\" text-anchor=\"middle\">\r\n <textPath\r\n class=\"text-path\"\r\n [attr.href]=\"'#' + link.id\"\r\n [style.dominant-baseline]=\"link.dominantBaseline\"\r\n startOffset=\"50%\"\r\n >\r\n {{ link.label }}\r\n </textPath>\r\n </svg:text>\r\n </svg:g>\r\n </ng-template>\r\n </ngx-graph>\r\n </div>\r\n\r\n <!-- tools -->\r\n <div id=\"tools\">\r\n <div id=\"bar\">\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Center\"\r\n (click)=\"center$.next(true)\"\r\n >\r\n <mat-icon>filter_center_focus</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Zoom to fit\"\r\n (click)=\"zoomToFit$.next({ force: true })\"\r\n >\r\n <mat-icon>fit_screen</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Reset\"\r\n (click)=\"update$.next(true)\"\r\n >\r\n <mat-icon class=\"mat-warn\">restart_alt</mat-icon>\r\n </button>\r\n @if (nodes$ | async; as nodes) {\r\n <span class=\"muted\"\r\n >N:{{ nodes.length }} E:{{ (edges$ | async)?.length }}</span\r\n >\r\n } @if (selectedNode$ | async; as selectedNode) {\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Toggle the selected node\"\r\n [disabled]=\"!selectedNode\"\r\n (click)=\"onNodeDblClick(selectedNode)\"\r\n >\r\n <mat-icon class=\"mat-primary\">unfold_more</mat-icon>\r\n </button>\r\n } @if (canPick()) {\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n [matTooltip]=\"\r\n canMoveToSource()\r\n ? 'Pick selected node or (shift) its source'\r\n : 'Pick the selected node'\r\n \"\r\n [disabled]=\"!(selectedNode$ | async)\"\r\n (click)=\"pickSelectedNode($event)\"\r\n >\r\n <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n </button>\r\n }\r\n </div>\r\n <!-- progress -->\r\n <div id=\"progress\">\r\n @if (loading$ | async) {\r\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\r\n }\r\n </div>\r\n <div id=\"filters\">\r\n @if (selectedNode$ | async; as node) {\r\n <div id=\"filter-head\">\r\n <span [style.color]=\"node.data.color || 'black'\">⬤</span>\r\n <span style=\"margin-left: 6px\" matTooltip=\"{{ node.data.uri }}\">{{\r\n node | graphNodeLabel\r\n }}</span>\r\n <span class=\"muted\" style=\"margin-left: 8px; font-size: 90%\">{{\r\n node.id\r\n }}</span>\r\n </div>\r\n }\r\n <mat-tab-group>\r\n <!-- N-outs -->\r\n @if (nOutFilter$ | async; as nOutFilter) {\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n N<mat-icon>logout</mat-icon>\r\n </ng-template>\r\n <cadmus-walker-triple-filter\r\n [filter]=\"nOutFilter\"\r\n (filterChange)=\"onNOutFilterChange($event)\"\r\n ></cadmus-walker-triple-filter>\r\n </mat-tab>\r\n }\r\n <!-- N-ins -->\r\n @if (nInFilter$ | async; as nInFilter) {\r\n <mat-tab>\r\n <ng-template mat-tab-label> N<mat-icon>login</mat-icon> </ng-template>\r\n <cadmus-walker-triple-filter\r\n [filter]=\"nInFilter\"\r\n (filterChange)=\"onNInFilterChange($event)\"\r\n ></cadmus-walker-triple-filter>\r\n </mat-tab>\r\n }\r\n <!-- P-outs -->\r\n @if (pOutFilter$ | async; as pOutFilter) {\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n P<mat-icon>logout</mat-icon>\r\n </ng-template>\r\n <cadmus-walker-linked-node-filter\r\n [filter]=\"pOutFilter\"\r\n (filterChange)=\"onPOutFilterChange($event)\"\r\n />\r\n </mat-tab>\r\n }\r\n <!-- P-ins -->\r\n @if (pInFilter$ | async; as pInFilter) {\r\n <mat-tab>\r\n <ng-template mat-tab-label> P<mat-icon>login</mat-icon> </ng-template>\r\n <cadmus-walker-linked-node-filter\r\n [filter]=\"pInFilter\"\r\n (filterChange)=\"onPInFilterChange($event)\"\r\n />\r\n </mat-tab>\r\n }\r\n <!-- P-lit -->\r\n @if (pLitFilter$ | async; as pLitFilter) {\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n P<mat-icon>exit_to_app</mat-icon>\r\n </ng-template>\r\n <cadmus-walker-linked-literal-filter\r\n [filter]=\"pLitFilter\"\r\n (filterChange)=\"onPLitFilterChange($event)\"\r\n />\r\n </mat-tab>\r\n }\r\n </mat-tab-group>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: ["div#filter-head{border:1px solid #f8f1ae;border-radius:4px;padding:4px;margin-bottom:4px;background-color:#f8f1ae;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.muted{color:silver}.selected{stroke-width:2px;stroke:#e7d211}#container{width:100%;max-width:100%;height:100%;max-height:100%;display:grid;grid-template-rows:1fr;grid-template-columns:auto 1fr;grid-template-areas:\"tools graph\";gap:8px}#graph{grid-area:graph}#tools{grid-area:tools;padding:8px;border:1px solid silver;height:100%;max-height:100%;min-width:324px;background-color:#fdfdfd}#tools #bar{display:flex;align-items:center;flex-wrap:wrap}@media only screen and (max-width: 959px){#container{grid-template-rows:auto 1fr;grid-template-columns:1fr;grid-template-areas:\"tools\" \"graph\"}}\n"] }]
}], ctorParameters: () => [{ type: i3.GraphService }, { type: i2$1.DialogService }] });
/*
* Public API Surface of cadmus-graph-ui-ex
*/
/**
* Generated bundle index. Do not edit.
*/
export { GraphNodeLabelPipe, GraphWalker, GraphWalkerComponent, LinkedLiteralFilterComponent, LinkedNodeFilterComponent, TripleFilterComponent };
//# sourceMappingURL=myrmidon-cadmus-graph-ui-ex.mjs.map