UNPKG

@myrmidon/cadmus-graph-ui-ex

Version:

Cadmus - extensions to semantic graph components.

1,504 lines 110 kB
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        &nbsp;\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        &nbsp;\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'\">&#x2b24;</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'\">&#x2b24;</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