UNPKG

@myrmidon/cadmus-graph-ui

Version:
1,198 lines 108 kB
import * as i0 from '@angular/core';
import { Injectable, input, Component, model, output, signal, effect } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { take } from 'rxjs/operators';
import { MatPaginator } from '@angular/material/paginator';
import { MatCard, MatCardContent } from '@angular/material/card';
import { MatProgressBar } from '@angular/material/progress-bar';
import { MatIconButton, MatButton } from '@angular/material/button';
import { MatTooltip } from '@angular/material/tooltip';
import { MatIcon } from '@angular/material/icon';
import { MatExpansionPanel, MatExpansionPanelHeader } from '@angular/material/expansion';
import * as i1 from '@myrmidon/cadmus-api';
import { NodeSourceType } from '@myrmidon/cadmus-api';
import * as i1$1 from '@angular/forms';
import { FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { MatFormField, MatLabel, MatError, MatHint } from '@angular/material/form-field';
import { MatInput } from '@angular/material/input';
import { MatSelect } from '@angular/material/select';
import { MatOption } from '@angular/material/core';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatChipListbox, MatChipOption, MatChipRemove } from '@angular/material/chips';
import { RefLookupComponent } from '@myrmidon/cadmus-refs-lookup';
import { of, map, BehaviorSubject, tap, forkJoin } from 'rxjs';
import { PagedListStore } from '@myrmidon/paged-data-browsers';
import * as i3 from '@myrmidon/ngx-mat-tools';
import * as i3$1 from '@angular/material/snack-bar';
import { NgxToolsValidators, deepCopy, EllipsisPipe } from '@myrmidon/ngx-tools';

/**
 * Graph node lookup service.
 */
class GraphNodeLookupService {
    _graphService;
    id = 'graph-node';
    constructor(_graphService) {
        this._graphService = _graphService;
    }
    getById(id) {
        return this._graphService.getNode(parseInt(id, 10));
    }
    lookup(filter, options) {
        if (!filter.text) {
            return of([]);
        }
        return this._graphService
            .getNodes(1, filter.limit || 10, {
            label: filter.text,
            isClass: filter.isClass === undefined || filter.isClass === null
                ? undefined
                : filter.isClass
                    ? true
                    : false,
            tag: filter.tag,
        })
            .pipe(map((p) => p.items));
    }
    getName(item) {
        return item?.label;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphNodeLookupService, deps: [{ token: i1.GraphService }], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphNodeLookupService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphNodeLookupService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }], ctorParameters: () => [{ type: i1.GraphService }] });

/**
 * Graph nodes list repository.
 */
class NodeListRepository {
    _graphService;
    _store;
    _loading$;
    _filter$;
    _linkedNode$;
    _classNodes$;
    get loading$() {
        return this._loading$.asObservable();
    }
    get filter$() {
        return this._filter$.asObservable();
    }
    get page$() {
        return this._store.page$;
    }
    get linkedNode$() {
        return this._linkedNode$.asObservable();
    }
    get classNodes$() {
        return this._classNodes$.asObservable();
    }
    constructor(_graphService) {
        this._graphService = _graphService;
        this._store = new PagedListStore(this);
        this._loading$ = new BehaviorSubject(undefined);
        this._filter$ = new BehaviorSubject({});
        this._linkedNode$ = new BehaviorSubject(undefined);
        this._classNodes$ = new BehaviorSubject([]);
        this._store.reset();
    }
    getLinkedNode() {
        return this._linkedNode$.value;
    }
    getClassNodes() {
        return this._classNodes$.value;
    }
    loadPage(pageNumber, pageSize, filter) {
        this._loading$.next(true);
        return this._graphService.getNodes(pageNumber, pageSize, filter).pipe(tap({
            next: () => this._loading$.next(false),
            error: () => this._loading$.next(false),
        }));
    }
    async reset() {
        this._loading$.next(true);
        try {
            await this._store.reset();
        }
        catch (error) {
            throw error;
        }
        finally {
            this._loading$.next(false);
        }
    }
    async setFilter(filter) {
        this._loading$.next(true);
        try {
            await this._store.setFilter(filter);
        }
        catch (error) {
            throw error;
        }
        finally {
            this._loading$.next(false);
        }
    }
    getFilter() {
        return this._store.getFilter();
    }
    async setPage(pageNumber, pageSize) {
        this._loading$.next(true);
        try {
            await this._store.setPage(pageNumber, pageSize);
        }
        catch (error) {
            throw error;
        }
        finally {
            this._loading$.next(false);
        }
    }
    /**
     * Set the linked node used in filter.
     *
     * @param node The node or undefined.
     */
    setLinkedNode(node) {
        this._linkedNode$.next(node);
    }
    /**
     * Set the linked node used in filter by its ID.
     *
     * @param id The node ID.
     */
    setLinkedNodeId(id) {
        if (!id) {
            this._linkedNode$.next(undefined);
        }
        else {
            this._graphService.getNode(id).subscribe({
                next: (node) => {
                    this._linkedNode$.next(node);
                },
                error: (error) => {
                    console.error(`Node ID ${id} not found`, error);
                    console.warn('Node ID not found: ' + id);
                },
            });
        }
    }
    /**
     * Add the specified node to the filter class nodes.
     * If the node already exists, nothing is done.
     *
     * @param node The node to add.
     */
    addClassNode(node) {
        const nodes = [...this._classNodes$.value];
        if (nodes.some((n) => n.id === node.id)) {
            return;
        }
        nodes.push(node);
        this._classNodes$.next(nodes);
    }
    /**
     * Set the class node IDs in the filter.
     *
     * @param ids The class nodes IDs or undefined.
     */
    setClassNodeIds(ids) {
        if (!ids || !ids.length) {
            this._classNodes$.next([]);
            return;
        }
        const requests = [];
        ids.forEach((id) => {
            requests.push(this._graphService.getNode(id));
        });
        forkJoin(requests).subscribe({
            next: (nodes) => {
                this._classNodes$.next(nodes);
            },
            error: (error) => {
                console.error('Error getting nodes', error);
            },
        });
    }
    /**
     * Delete the specified node from the filter class nodes.
     * If the node does not exist, nothing is done.
     *
     * @param id The node's ID.
     */
    deleteClassNode(id) {
        const nodes = [...this._classNodes$.value];
        const i = nodes.findIndex((n) => n.id === id);
        if (i > -1) {
            nodes.splice(i, 1);
            this._classNodes$.next(nodes);
        }
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: NodeListRepository, deps: [{ token: i1.GraphService }], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: NodeListRepository, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: NodeListRepository, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }], ctorParameters: () => [{ type: i1.GraphService }] });

/**
 * Graph nodes filter used in graph nodes list.
 * Its data are in the graph nodes store, which gets updated when
 * users apply new filters.
 */
class GraphNodeFilterComponent {
    lookupService;
    _repository;
    _sub;
    filter$;
    linkedNode$;
    classNodes$;
    disabled = input(...(ngDevMode ? [undefined, { debugName: "disabled" }] : []));
    label;
    isClass;
    uid;
    tag;
    sourceType;
    sid;
    sidPrefix;
    linkedNodeRole;
    form;
    constructor(formBuilder, lookupService, _repository) {
        this.lookupService = lookupService;
        this._repository = _repository;
        this.filter$ = _repository.filter$;
        this.linkedNode$ = _repository.linkedNode$;
        this.classNodes$ = _repository.classNodes$;
        // form
        this.label = formBuilder.control(null);
        this.isClass = formBuilder.control(0, { nonNullable: true });
        this.uid = formBuilder.control(null);
        this.tag = formBuilder.control(null);
        this.sourceType = formBuilder.control(null);
        this.sid = formBuilder.control(null);
        this.sidPrefix = formBuilder.control(false, { nonNullable: true });
        this.linkedNodeRole = formBuilder.control(null);
        this.form = formBuilder.group({
            label: this.label,
            isClass: this.isClass,
            uid: this.uid,
            tag: this.tag,
            sourceType: this.sourceType,
            sid: this.sid,
            sidPrefix: this.sidPrefix,
            linkedNodeRole: this.linkedNodeRole,
        });
    }
    ngOnInit() {
        this._sub = this.filter$.subscribe((f) => {
            this.updateForm(f);
        });
    }
    ngOnDestroy() {
        this._sub?.unsubscribe();
    }
    updateForm(filter) {
        this.label.setValue(filter.label || null);
        // is-class: 0=unset, 1=class, 2=not-class
        if (filter.isClass !== undefined && filter.isClass !== null) {
            this.isClass.setValue(filter.isClass ? 1 : 2);
        }
        else {
            this.isClass.setValue(0);
        }
        this.uid.setValue(filter.uid || null);
        this.tag.setValue(filter.tag || null);
        if (filter.sourceType === undefined || filter.sourceType === null) {
            this.sourceType.setValue(null);
        }
        else {
            this.sourceType.setValue(filter.sourceType);
        }
        this.sid.setValue(filter.sid || null);
        this.sidPrefix.setValue(filter.isSidPrefix ? true : false);
        this._repository.setLinkedNodeId(filter.linkedNodeId);
        this.linkedNodeRole.setValue(filter.linkedNodeRole || 'S');
        this._repository.setClassNodeIds(filter.classIds);
        this.form.markAsPristine();
    }
    getFilter() {
        return {
            label: this.label.value?.trim(),
            isClass: this.isClass.value === 0 ? undefined : this.isClass.value === 1,
            uid: this.uid.value?.trim(),
            tag: this.tag.value?.trim(),
            sourceType: this.sourceType.value === null ? undefined : this.sourceType.value,
            sid: this.sid.value?.trim(),
            isSidPrefix: this.sidPrefix.value,
            linkedNodeId: this._repository.getLinkedNode()?.id,
            linkedNodeRole: this.linkedNodeRole.value || undefined,
            classIds: this._repository.getClassNodes()?.map((n) => n.id),
        };
    }
    onResetLinkedNode() {
        this._repository.setLinkedNode();
    }
    onLinkedNodeSet(node) {
        this._repository.setLinkedNode(node || undefined);
    }
    clearLinkedNode() {
        this._repository.setLinkedNode();
    }
    onClassAdd(node) {
        if (node) {
            this._repository.addClassNode(node);
        }
    }
    onClassRemove(id) {
        this._repository.deleteClassNode(id);
    }
    reset() {
        this.form.reset();
        this.apply();
    }
    apply() {
        if (this.form.invalid) {
            return;
        }
        const filter = this.getFilter();
        // update filter in state
        this._repository.setFilter(filter);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphNodeFilterComponent, deps: [{ token: i1$1.FormBuilder }, { token: GraphNodeLookupService }, { token: NodeListRepository }], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: GraphNodeFilterComponent, isStandalone: true, selector: "cadmus-graph-node-filter", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<form\r\n  [formGroup]=\"form\"\r\n  (submit)=\"apply()\"\r\n  [attr.disabled]=\"disabled() ? true : null\"\r\n>\r\n  <div class=\"form-row\">\r\n    <!-- label -->\r\n    <div>\r\n      <mat-form-field>\r\n        <mat-label>label</mat-label>\r\n        <input matInput [formControl]=\"label\" />\r\n      </mat-form-field>\r\n    </div>\r\n\r\n    <!-- uid -->\r\n    <div>\r\n      <mat-form-field>\r\n        <mat-label>uid</mat-label>\r\n        <input matInput [formControl]=\"uid\" />\r\n      </mat-form-field>\r\n    </div>\r\n\r\n    <!-- isClass -->\r\n    <div>\r\n      <mat-form-field>\r\n        <mat-label>class</mat-label>\r\n        <mat-select [formControl]=\"isClass\">\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\r\n    <!-- tag -->\r\n    <div>\r\n      <mat-form-field>\r\n        <mat-label>tag</mat-label>\r\n        <input matInput [formControl]=\"tag\" />\r\n      </mat-form-field>\r\n    </div>\r\n\r\n    <!-- sourceType -->\r\n    <div>\r\n      <mat-form-field>\r\n        <mat-label>source type</mat-label>\r\n        <mat-select [formControl]=\"sourceType\">\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\r\n    <!-- sid, sidPrefix -->\r\n    <div>\r\n      <mat-form-field>\r\n        <mat-label>source ID</mat-label>\r\n        <input matInput [formControl]=\"sid\" />\r\n      </mat-form-field>\r\n      &nbsp;\r\n      <mat-checkbox [formControl]=\"sidPrefix\">prefix</mat-checkbox>\r\n    </div>\r\n\r\n    <!-- linkedNode, linkedNodeRole -->\r\n    <div>\r\n      <fieldset>\r\n        <legend>linked node</legend>\r\n        <cadmus-refs-lookup\r\n          label=\"node\"\r\n          [item]=\"linkedNode$ | async\"\r\n          [service]=\"lookupService\"\r\n          (itemChange)=\"onLinkedNodeSet($event)\"\r\n        />\r\n        @if (linkedNode$ | async) {\r\n        <div>\r\n          <mat-form-field>\r\n            <mat-label>role</mat-label>\r\n            <mat-select [formControl]=\"linkedNodeRole\">\r\n              <mat-option value=\"S\">subject</mat-option>\r\n              <mat-option value=\"O\">object</mat-option>\r\n            </mat-select>\r\n          </mat-form-field>\r\n          <button\r\n            type=\"button\"\r\n            mat-icon-button\r\n            matTooltip=\"Reset linked node\"\r\n            (click)=\"clearLinkedNode()\"\r\n          >\r\n            <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n          </button>\r\n        </div>\r\n        }\r\n      </fieldset>\r\n    </div>\r\n\r\n    <!-- classes -->\r\n    <div>\r\n      <fieldset>\r\n        <legend>classes</legend>\r\n        <cadmus-refs-lookup\r\n          label=\"class\"\r\n          [service]=\"lookupService\"\r\n          [baseFilter]=\"{ isClass: true }\"\r\n          (itemChange)=\"onClassAdd($event)\"\r\n        />\r\n        @if (classNodes$ | async; as classNodes) {\r\n        <mat-chip-listbox>\r\n          @for (node of classNodes; track node.id) {\r\n          <mat-chip-option\r\n            [removable]=\"true\"\r\n            (removed)=\"onClassRemove(node.id)\"\r\n            matTooltip=\"{{ node.uri }}\"\r\n            >{{ node.label }}\r\n            <button type=\"button\" matChipRemove>\r\n              <mat-icon>clear</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  </div>\r\n\r\n  <div id=\"toolbar\" class=\"btn-group\" role=\"group\" aria-label=\"toolbar\">\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:8px;padding:8px 16px}#toolbar{margin:10px 0}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { 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-refs-lookup", inputs: ["label", "limit", "baseFilter", "service", "item", "itemId", "required", "hasMore", "linkTemplate", "optDialog", "options", "lookupProviderOptions"], outputs: ["itemChange", "optionsChange", "moreRequest"] }, { 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: 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: MatChipRemove, selector: "[matChipRemove]" }, { kind: "pipe", type: AsyncPipe, name: "async" }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphNodeFilterComponent, decorators: [{
            type: Component,
            args: [{ selector: 'cadmus-graph-node-filter', imports: [
                        FormsModule,
                        ReactiveFormsModule,
                        MatFormField,
                        MatLabel,
                        MatInput,
                        MatSelect,
                        MatOption,
                        MatCheckbox,
                        RefLookupComponent,
                        MatIconButton,
                        MatTooltip,
                        MatIcon,
                        MatChipListbox,
                        MatChipOption,
                        MatChipRemove,
                        AsyncPipe,
                    ], template: "<form\r\n  [formGroup]=\"form\"\r\n  (submit)=\"apply()\"\r\n  [attr.disabled]=\"disabled() ? true : null\"\r\n>\r\n  <div class=\"form-row\">\r\n    <!-- label -->\r\n    <div>\r\n      <mat-form-field>\r\n        <mat-label>label</mat-label>\r\n        <input matInput [formControl]=\"label\" />\r\n      </mat-form-field>\r\n    </div>\r\n\r\n    <!-- uid -->\r\n    <div>\r\n      <mat-form-field>\r\n        <mat-label>uid</mat-label>\r\n        <input matInput [formControl]=\"uid\" />\r\n      </mat-form-field>\r\n    </div>\r\n\r\n    <!-- isClass -->\r\n    <div>\r\n      <mat-form-field>\r\n        <mat-label>class</mat-label>\r\n        <mat-select [formControl]=\"isClass\">\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\r\n    <!-- tag -->\r\n    <div>\r\n      <mat-form-field>\r\n        <mat-label>tag</mat-label>\r\n        <input matInput [formControl]=\"tag\" />\r\n      </mat-form-field>\r\n    </div>\r\n\r\n    <!-- sourceType -->\r\n    <div>\r\n      <mat-form-field>\r\n        <mat-label>source type</mat-label>\r\n        <mat-select [formControl]=\"sourceType\">\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\r\n    <!-- sid, sidPrefix -->\r\n    <div>\r\n      <mat-form-field>\r\n        <mat-label>source ID</mat-label>\r\n        <input matInput [formControl]=\"sid\" />\r\n      </mat-form-field>\r\n      &nbsp;\r\n      <mat-checkbox [formControl]=\"sidPrefix\">prefix</mat-checkbox>\r\n    </div>\r\n\r\n    <!-- linkedNode, linkedNodeRole -->\r\n    <div>\r\n      <fieldset>\r\n        <legend>linked node</legend>\r\n        <cadmus-refs-lookup\r\n          label=\"node\"\r\n          [item]=\"linkedNode$ | async\"\r\n          [service]=\"lookupService\"\r\n          (itemChange)=\"onLinkedNodeSet($event)\"\r\n        />\r\n        @if (linkedNode$ | async) {\r\n        <div>\r\n          <mat-form-field>\r\n            <mat-label>role</mat-label>\r\n            <mat-select [formControl]=\"linkedNodeRole\">\r\n              <mat-option value=\"S\">subject</mat-option>\r\n              <mat-option value=\"O\">object</mat-option>\r\n            </mat-select>\r\n          </mat-form-field>\r\n          <button\r\n            type=\"button\"\r\n            mat-icon-button\r\n            matTooltip=\"Reset linked node\"\r\n            (click)=\"clearLinkedNode()\"\r\n          >\r\n            <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n          </button>\r\n        </div>\r\n        }\r\n      </fieldset>\r\n    </div>\r\n\r\n    <!-- classes -->\r\n    <div>\r\n      <fieldset>\r\n        <legend>classes</legend>\r\n        <cadmus-refs-lookup\r\n          label=\"class\"\r\n          [service]=\"lookupService\"\r\n          [baseFilter]=\"{ isClass: true }\"\r\n          (itemChange)=\"onClassAdd($event)\"\r\n        />\r\n        @if (classNodes$ | async; as classNodes) {\r\n        <mat-chip-listbox>\r\n          @for (node of classNodes; track node.id) {\r\n          <mat-chip-option\r\n            [removable]=\"true\"\r\n            (removed)=\"onClassRemove(node.id)\"\r\n            matTooltip=\"{{ node.uri }}\"\r\n            >{{ node.label }}\r\n            <button type=\"button\" matChipRemove>\r\n              <mat-icon>clear</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  </div>\r\n\r\n  <div id=\"toolbar\" class=\"btn-group\" role=\"group\" aria-label=\"toolbar\">\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:8px;padding:8px 16px}#toolbar{margin:10px 0}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"] }]
        }], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: GraphNodeLookupService }, { type: NodeListRepository }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });

/**
 * Graph node editor.
 */
class GraphNodeEditorComponent {
    /**
     * The node being edited. A new node has ID=0 and no uri.
     */
    node = model(...(ngDevMode ? [undefined, { debugName: "node" }] : []));
    /**
     * The optional set of thesaurus entries for node's tags.
     */
    tagEntries = input(...(ngDevMode ? [undefined, { debugName: "tagEntries" }] : []));
    /**
     * Emitted when the user requested to close the editor.
     */
    editorClose = output();
    isNew = signal(true, ...(ngDevMode ? [{ debugName: "isNew" }] : []));
    uri;
    label;
    isClass;
    tag;
    form;
    constructor(formBuilder) {
        // form
        this.uri = formBuilder.control(null, [
            Validators.required,
            Validators.maxLength(500),
        ]);
        this.label = formBuilder.control(null, [
            Validators.required,
            Validators.maxLength(500),
        ]);
        this.isClass = formBuilder.control(false, { nonNullable: true });
        this.tag = formBuilder.control(null, Validators.maxLength(50));
        this.form = formBuilder.group({
            uri: this.uri,
            label: this.label,
            isClass: this.isClass,
            tag: this.tag,
        });
        effect(() => {
            this.updateForm(this.node());
        });
    }
    updateForm(node) {
        if (!node) {
            this.form.reset();
            this.isNew.set(true);
            return;
        }
        this.uri.setValue(node.uri);
        this.label.setValue(node.label);
        this.isClass.setValue(node.isClass ? true : false);
        this.tag.setValue(node.tag || null);
        this.isNew.set(node.id ? false : true);
        this.form.markAsPristine();
    }
    getNode() {
        return {
            id: this.node()?.id || 0,
            sourceType: this.node()?.sourceType || NodeSourceType.User,
            uri: this.uri.value?.trim() || '',
            label: this.label.value?.trim() || '',
            isClass: this.isClass.value,
            tag: this.tag.value?.trim(),
        };
    }
    cancel() {
        this.editorClose.emit();
    }
    save() {
        if (this.form.invalid) {
            return;
        }
        this.node.set(this.getNode());
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphNodeEditorComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: GraphNodeEditorComponent, isStandalone: true, selector: "cadmus-graph-node-editor", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: false, transformFunction: null }, tagEntries: { classPropertyName: "tagEntries", publicName: "tagEntries", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { node: "nodeChange", editorClose: "editorClose" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n  <!-- uri (readonly if not new) -->\r\n  <div class=\"form-row\">\r\n    @if (isNew()) {\r\n    <mat-form-field>\r\n      <mat-label>uri</mat-label>\r\n      <input matInput [formControl]=\"uri\" />\r\n      @if ($any(uri).errors?.required && (uri.dirty || uri.touched)) {\r\n      <mat-error>uri required</mat-error>\r\n      } @if ($any(uri).errors?.maxLength && (uri.dirty || uri.touched)) {\r\n      <mat-error>uri too long</mat-error>\r\n      }\r\n    </mat-form-field>\r\n    } @else {\r\n    <span style=\"color: silver\">{{ uri.value }} </span>\r\n    }\r\n\r\n    <!-- label -->\r\n    <mat-form-field>\r\n      <mat-label>label</mat-label>\r\n      <input matInput [formControl]=\"label\" />\r\n      @if ($any(label).errors?.required && (label.dirty || label.touched)) {\r\n      <mat-error>label required</mat-error>\r\n      } @if ($any(label).errors?.maxLength && (label.dirty || label.touched)) {\r\n      <mat-error>label too long</mat-error>\r\n      }\r\n    </mat-form-field>\r\n\r\n    <!-- class -->\r\n    <mat-checkbox [formControl]=\"isClass\">is class</mat-checkbox>\r\n\r\n    <!-- tag -->\r\n    @if (tagEntries()?.length) {\r\n    <mat-form-field>\r\n      <mat-label>tag</mat-label>\r\n      <mat-select [formControl]=\"tag\">\r\n        <mat-option [value]=\"null\">(no tag)</mat-option>\r\n        @for (e of tagEntries(); track e.id) {\r\n        <mat-option [value]=\"e.id\">{{ e.value }}</mat-option>\r\n        }\r\n      </mat-select>\r\n    </mat-form-field>\r\n    } @else {\r\n    <mat-form-field>\r\n      <mat-label>tag</mat-label>\r\n      <input matInput [formControl]=\"tag\" />\r\n      @if ($any(tag).errors?.maxLength && (tag.dirty || tag.touched)) {\r\n      <mat-error>tag too long</mat-error>\r\n      }\r\n    </mat-form-field>\r\n    }\r\n  </div>\r\n\r\n  <!-- buttons -->\r\n  <div>\r\n    <button mat-icon-button type=\"button\" (click)=\"cancel()\">\r\n      <mat-icon class=\"mat-warn\">cancel</mat-icon>\r\n    </button>\r\n    &nbsp;\r\n    <button mat-icon-button type=\"submit\" [disabled]=\"form.invalid\">\r\n      <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n    </button>\r\n  </div>\r\n</form>\r\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { 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: MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { 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: 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: 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"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphNodeEditorComponent, decorators: [{
            type: Component,
            args: [{ selector: 'cadmus-graph-node-editor', imports: [
                        FormsModule,
                        ReactiveFormsModule,
                        MatFormField,
                        MatLabel,
                        MatInput,
                        MatError,
                        MatCheckbox,
                        MatSelect,
                        MatOption,
                        MatIconButton,
                        MatIcon,
                    ], template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n  <!-- uri (readonly if not new) -->\r\n  <div class=\"form-row\">\r\n    @if (isNew()) {\r\n    <mat-form-field>\r\n      <mat-label>uri</mat-label>\r\n      <input matInput [formControl]=\"uri\" />\r\n      @if ($any(uri).errors?.required && (uri.dirty || uri.touched)) {\r\n      <mat-error>uri required</mat-error>\r\n      } @if ($any(uri).errors?.maxLength && (uri.dirty || uri.touched)) {\r\n      <mat-error>uri too long</mat-error>\r\n      }\r\n    </mat-form-field>\r\n    } @else {\r\n    <span style=\"color: silver\">{{ uri.value }} </span>\r\n    }\r\n\r\n    <!-- label -->\r\n    <mat-form-field>\r\n      <mat-label>label</mat-label>\r\n      <input matInput [formControl]=\"label\" />\r\n      @if ($any(label).errors?.required && (label.dirty || label.touched)) {\r\n      <mat-error>label required</mat-error>\r\n      } @if ($any(label).errors?.maxLength && (label.dirty || label.touched)) {\r\n      <mat-error>label too long</mat-error>\r\n      }\r\n    </mat-form-field>\r\n\r\n    <!-- class -->\r\n    <mat-checkbox [formControl]=\"isClass\">is class</mat-checkbox>\r\n\r\n    <!-- tag -->\r\n    @if (tagEntries()?.length) {\r\n    <mat-form-field>\r\n      <mat-label>tag</mat-label>\r\n      <mat-select [formControl]=\"tag\">\r\n        <mat-option [value]=\"null\">(no tag)</mat-option>\r\n        @for (e of tagEntries(); track e.id) {\r\n        <mat-option [value]=\"e.id\">{{ e.value }}</mat-option>\r\n        }\r\n      </mat-select>\r\n    </mat-form-field>\r\n    } @else {\r\n    <mat-form-field>\r\n      <mat-label>tag</mat-label>\r\n      <input matInput [formControl]=\"tag\" />\r\n      @if ($any(tag).errors?.maxLength && (tag.dirty || tag.touched)) {\r\n      <mat-error>tag too long</mat-error>\r\n      }\r\n    </mat-form-field>\r\n    }\r\n  </div>\r\n\r\n  <!-- buttons -->\r\n  <div>\r\n    <button mat-icon-button type=\"button\" (click)=\"cancel()\">\r\n      <mat-icon class=\"mat-warn\">cancel</mat-icon>\r\n    </button>\r\n    &nbsp;\r\n    <button mat-icon-button type=\"submit\" [disabled]=\"form.invalid\">\r\n      <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n    </button>\r\n  </div>\r\n</form>\r\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"] }]
        }], ctorParameters: () => [{ type: i1$1.FormBuilder }], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: false }] }, { type: i0.Output, args: ["nodeChange"] }], tagEntries: [{ type: i0.Input, args: [{ isSignal: true, alias: "tagEntries", required: false }] }], editorClose: [{ type: i0.Output, args: ["editorClose"] }] } });

/**
 * List of graph nodes. This includes a graph node filter, a list, and a graph
 * editor.
 */
class GraphNodeListComponent {
    _repository;
    _graphService;
    _dialogService;
    _snackbar;
    loading$;
    page$;
    /**
     * The currently edited node if any.
     */
    editedNode = model(...(ngDevMode ? [undefined, { debugName: "editedNode" }] : []));
    /**
     * The optional set of thesaurus entries for node's tags.
     */
    tagEntries = input(...(ngDevMode ? [undefined, { debugName: "tagEntries" }] : []));
    /**
     * True if this node list should have a walker button for each node.
     */
    hasWalker = input(...(ngDevMode ? [undefined, { debugName: "hasWalker" }] : []));
    /**
     * Emitted when walking a specific node is requested.
     */
    nodeWalk = output();
    constructor(_repository, _graphService, _dialogService, _snackbar) {
        this._repository = _repository;
        this._graphService = _graphService;
        this._dialogService = _dialogService;
        this._snackbar = _snackbar;
        this.loading$ = _repository.loading$;
        this.page$ = _repository.page$;
    }
    onPageChange(event) {
        this._repository.setPage(event.pageIndex + 1, event.pageSize);
    }
    addNode() {
        this.editedNode.set({
            uri: '',
            id: 0,
            sourceType: NodeSourceType.User,
            label: '',
        });
    }
    editNode(node) {
        this.editedNode.set(node);
    }
    onNodeChange(node) {
        this._graphService.addNode(node).subscribe({
            next: (n) => {
                this.editedNode.set(undefined);
                this._repository.reset();
                this._snackbar.open('Node saved', 'OK', {
                    duration: 1500,
                });
            },
            error: (error) => {
                console.error('Error saving node', error);
                this._snackbar.open('Error saving node', 'OK');
            },
        });
    }
    onEditorClose() {
        this.editedNode.set(undefined);
    }
    deleteNode(node) {
        this._dialogService
            .confirm('Delete Node', 'Delete node ' + node.label + '?')
            .pipe(take(1))
            .subscribe((yes) => {
            if (yes) {
                this._graphService
                    .deleteNode(node.id)
                    .pipe(take(1))
                    .subscribe({
                    next: (_) => {
                        this._repository.reset();
                    },
                    error: (error) => {
                        console.error('Error deleting node', error);
                        this._snackbar.open('Error deleting node', 'OK');
                    },
                });
            }
        });
    }
    walkNode(node) {
        this.nodeWalk.emit(node);
    }
    reset() {
        this._repository.reset();
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphNodeListComponent, deps: [{ token: NodeListRepository }, { token: i1.GraphService }, { token: i3.DialogService }, { token: i3$1.MatSnackBar }], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: GraphNodeListComponent, isStandalone: true, selector: "cadmus-graph-node-list", inputs: { editedNode: { classPropertyName: "editedNode", publicName: "editedNode", isSignal: true, isRequired: false, transformFunction: null }, tagEntries: { classPropertyName: "tagEntries", publicName: "tagEntries", isSignal: true, isRequired: false, transformFunction: null }, hasWalker: { classPropertyName: "hasWalker", publicName: "hasWalker", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { editedNode: "editedNodeChange", nodeWalk: "nodeWalk" }, ngImport: i0, template: "<div id=\"container\">\n  <!-- filters -->\n  <div id=\"filters\">\n    <mat-card appearance=\"outlined\">\n      <mat-card-content>\n        <cadmus-graph-node-filter\n          [disabled]=\"(loading$ | async) ? true : false\"\n        />\n      </mat-card-content>\n    </mat-card>\n  </div>\n\n  <!-- list -->\n  @if (page$ | async; as page) {\n  <div id=\"list\">\n    @if (loading$ | async) {\n    <div>\n      <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n    </div>\n    }\n    <!-- list -->\n    @if (page) {\n    <div>\n      <table>\n        <thead>\n          <th></th>\n          <th>id</th>\n          <th>cls</th>\n          <th>label</th>\n          <th>uri</th>\n          <th class=\"noif-lt-md\">srct</th>\n          <th class=\"noif-lt-md\">sid</th>\n          <th class=\"noif-lt-md\">tag</th>\n        </thead>\n        <tbody>\n          @for (d of page.items; track d.id) {\n          <tr [class.selected]=\"d === editedNode()\">\n            <td class=\"fit-width\">\n              @if (hasWalker()) {\n              <button\n                mat-icon-button\n                type=\"button\"\n                matTooltip=\"Walk node\"\n                (click)=\"walkNode(d)\"\n              >\n                <mat-icon class=\"mat-primary\">flare</mat-icon>\n              </button>\n              }\n              <button\n                mat-icon-button\n                type=\"button\"\n                matTooltip=\"Edit node\"\n                (click)=\"editNode(d)\"\n              >\n                <mat-icon class=\"mat-primary\">edit</mat-icon>\n              </button>\n              <button\n                mat-icon-button\n                type=\"button\"\n                matTooltip=\"Delete node\"\n                (click)=\"deleteNode(d)\"\n              >\n                <mat-icon class=\"mat-warn\">delete</mat-icon>\n              </button>\n            </td>\n            <td>{{ d.id }}</td>\n            <td>{{ d.isClass ? \"C\" : \"\" }}</td>\n            <td>{{ d.label }}</td>\n            <td>{{ d.uri }}</td>\n            <td class=\"noif-lt-md\">{{ \"UIPTX\"[d.sourceType] }}</td>\n            <td class=\"noif-lt-md\">{{ d.sid }}</td>\n            <td class=\"noif-lt-md\">{{ d.tag }}</td>\n          </tr>\n          }\n        </tbody>\n      </table>\n      <div>\n        <button\n          class=\"mat-primary\"\n          type=\"button\"\n          mat-flat-button\n          (click)=\"addNode()\"\n        >\n          <mat-icon>add_circle</mat-icon>\n          add node\n        </button>\n      </div>\n      <!-- pagination -->\n      <div class=\"form-row\">\n        <button\n          type=\"button\"\n          mat-icon-button\n          matTooltip=\"Reset list\"\n          (click)=\"reset()\"\n        >\n          <mat-icon class=\"mat-warn\">autorenew</mat-icon>\n        </button>\n        <mat-paginator\n          [length]=\"page.total\"\n          [pageIndex]=\"page.pageNumber - 1\"\n          [pageSize]=\"page.pageSize\"\n          [pageSizeOptions]=\"[5, 10, 20, 50, 100]\"\n          (page)=\"onPageChange($event)\"\n          [showFirstLastButtons]=\"true\"\n        />\n      </div>\n    </div>\n    }\n    <!-- editor -->\n    <mat-expansion-panel\n      [expanded]=\"editedNode() ? true : false\"\n      [disabled]=\"editedNode() ? false : true\"\n      id=\"editor\"\n    >\n      <mat-expansion-panel-header>Node</mat-expansion-panel-header>\n      <cadmus-graph-node-editor\n        [node]=\"editedNode()\"\n        [tagEntries]=\"tagEntries()\"\n        (nodeChange)=\"onNodeChange($event!)\"\n        (editorClose)=\"onEditorClose()\"\n      />\n    </mat-expansion-panel>\n  </div>\n  }\n</div>\n", styles: ["table{width:100%;border-collapse:collapse}tbody tr:nth-child(odd){background-color:#e2e2e2}th{text-align:left;font-weight:400;color:silver}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#d0d0d0!important}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}div#container{display:grid;grid-template-rows:1fr auto;grid-template-columns:auto 1fr;grid-template-areas:\"filters list\" \". editor\";gap:8px}div#filters{grid-area:filters;max-width:350px}div#list{grid-area:list}div#editor{grid-area:editor}@media only screen and (max-width:959px){div#container{grid-template-rows:1fr auto auto;grid-template-columns:1fr;grid-template-areas:\"list\" \"filters\" \"editor\"}div#filters{max-width:none}.noif-lt-md{display:none}}\n"], dependencies: [{ kind: "component", type: MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: MatCardContent, selector: "mat-card-content" }, { kind: "component", type: GraphNodeFilterComponent, selector: "cadmus-graph-node-filter", inputs: ["disabled"] }, { kind: "component", type: MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { 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: MatButton, selector: "    button[matButton], a[matButton], button[mat-button], button[mat-raised-button],    button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button],    a[mat-flat-button], a[mat-stroked-button]  ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "component", type: GraphNodeEditorComponent, selector: "cadmus-graph-node-editor", inputs: ["node", "tagEntries"], outputs: ["nodeChange", "editorClose"] }, { kind: "pipe", type: AsyncPipe, name: "async" }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphNodeListComponent, decorators: [{
            type: Component,
            args: [{ selector: 'cadmus-graph-node-list', imports: [
                        MatCard,
                        MatCardContent,
                        GraphNodeFilterComponent,
                        MatProgressBar,
                        MatIconButton,
                        MatTooltip,
                        MatIcon,
                        MatButton,
                        MatPaginator,
                        MatExpansionPanel,
                        MatExpansionPanelHeader,
                        GraphNodeEditorComponent,
                        AsyncPipe,
                    ], template: "<div id=\"container\">\n  <!-- filters -->\n  <div id=\"filters\">\n    <mat-card appearance=\"outlined\">\n      <mat-card-content>\n        <cadmus-graph-node-filter\n          [disabled]=\"(loading$ | async) ? true : false\"\n        />\n      </mat-card-content>\n    </mat-card>\n  </div>\n\n  <!-- list -->\n  @if (page$ | async; as page) {\n  <div id=\"list\">\n    @if (loading$ | async) {\n    <div>\n      <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n    </div>\n    }\n    <!-- list -->\n    @if (page) {\n    <div>\n      <table>\n        <thead>\n          <th></th>\n          <th>id</th>\n          <th>cls</th>\n          <th>label</th>\n          <th>uri</th>\n          <th class=\"noif-lt-md\">srct</th>\n          <th class=\"noif-lt-md\">sid</th>\n          <th class=\"noif-lt-md\">tag</th>\n        </thead>\n        <tbody>\n          @for (d of page.items; track d.id) {\n          <tr [class.selected]=\"d === editedNode()\">\n            <td class=\"fit-width\">\n              @if (hasWalker()) {\n              <button\n                mat-icon-button\n                type=\"button\"\n                matTooltip=\"Walk node\"\n                (click)=\"walkNode(d)\"\n              >\n                <mat-icon class=\"mat-primary\">flare</mat-icon>\n              </button>\n              }\n              <button\n                mat-icon-button\n                type=\"button\"\n                matTooltip=\"Edit node\"\n                (click)=\"editNode(d)\"\n              >\n                <mat-icon class=\"mat-primary\">edit</mat-icon>\n              </button>\n              <button\n                mat-icon-button\n                type=\"button\"\n                matTooltip=\"Delete node\"\n                (click)=\"deleteNode(d)\"\n              >\n                <mat-icon class=\"mat-warn\">delete</mat-icon>\n              </button>\n            </td>\n            <td>{{ d.id }}</td>\n            <td>{{ d.isClass ? \"C\" : \"\" }}</td>\n            <td>{{ d.label }}</td>\n            <td>{{ d.uri }}</td>\n            <td class=\"noif-lt-md\">{{ \"UIPTX\"[d.sourceType] }}</td>\n            <td class=\"noif-lt-md\">{{ d.sid }}</td>\n            <td class=\"noif-lt-md\">{{ d.tag }}</td>\n          </tr>\n          }\n        </tbody>\n      </table>\n      <div>\n        <button\n          class=\"mat-primary\"\n          type=\"button\"\n          mat-flat-button\n          (click)=\"addNode()\"\n        >\n          <mat-icon>add_circle</mat-icon>\n          add node\n        </button>\n      </div>\n      <!-- pagination -->\n      <div class=\"form-row\">\n        <button\n          type=\"button\"\n          mat-icon-button\n          matTooltip=\"Reset list\"\n          (click)=\"reset()\"\n        >\n          <mat-icon class=\"mat-warn\">autorenew</mat-icon>\n        </button>\n        <mat-paginator\n          [length]=\"page.total\"\n          [pageIndex]=\"page.pageNumber - 1\"\n          [pageSize]=\"page.pageSize\"\n          [pageSizeOptions]=\"[5, 10, 20, 50, 100]\"\n          (page)=\"onPageChange($event)\"\n          [showFirstLastButtons]=\"true\"\n        />\n      </div>\n    </div>\n    }\n    <!-- editor -->\n    <mat-expansion-panel\n      [expanded]=\"editedNode() ? true : false\"\n      [disabled]=\"editedNode() ? false : true\"\n      id=\"editor\"\n    >\n      <mat-expansion-panel-header>Node</mat-expansion-panel-header>\n      <cadmus-graph-node-editor\n        [node]=\"editedNode()\"\n        [tagEntries]=\"tagEntries()\"\n        (nodeChange)=\"onNodeChange($event!)\"\n        (editorClose)=\"onEditorClose()\"\n      />\n    </mat-expansion-panel>\n  </div>\n  }\n</div>\n", styles: ["table{width:100%;border-collapse:collapse}tbody tr:nth-child(odd){background-color:#e2e2e2}th{text-align:left;font-weight:400;color:silver}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#d0d0d0!important}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}div#container{display:grid;grid-template-rows:1fr auto;grid-template-columns:auto 1fr;grid-template-areas:\"filters list\" \". editor\";gap:8px}div#filters{grid-area:filters;max-width:350px}div#list{grid-area:list}div#editor{grid-area:editor}@media only screen and (max-width:959px){div#container{grid-template-rows:1fr auto auto;grid-template-columns:1fr;grid-template-areas:\"list\" \"filters\" \"editor\"}div#filters{max-width:none}.noif-lt-md{display:none}}\n"] }]
        }], ctorParameters: () => [{ type: NodeListRepository }, { type: i1.GraphService }, { type: i3.DialogService }, { type: i3$1.MatSnackBar }], propDecorators: { editedNode: [{ type: i0.Input, args: [{ isSignal: true, alias: "editedNode", required: false }] }, { type: i0.Output, args: ["editedNodeChange"] }], tagEntries: [{ type: i0.Input, args: [{ isSignal: true, alias: "tagEntries", required: false }] }], hasWalker: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasWalker", required: false }] }], nodeWalk: [{ type: i0.Output, args: ["nodeWalk"] }] } });

/**
 * Graph nodes list repository.
 */
class GraphTripleListRepository {
    _graphService;
    _store;
    _loading$;
    _filter$;
    _subjectNode$;
    _predicateNode$;
    _objectNode$;
    get loading$() {
        return this._loading$.asObservable();
    }
    get filter$() {
        return this._filter$.asObservable();
    }
    get page$() {
        return this._store.page$;
    }
    get subjectNode$() {
        return this._subjectNode$.asObservable();
    }
    get predicateNode$() {
        return this._predicateNode$.asObservable();
    }
    get objectNode$() {
        return this._objectNode$.asObservable();
    }
    constructor(_graphService) {
        this._graphService = _graphService;
        this._store = new PagedListStore(this);
        this._filter$ = new BehaviorSubject({});
        this._subjectNode$ = new BehaviorSubject(undefined);
        this._predicateNode$ = new BehaviorSubject(undefined);
        this._objectNode$ = new BehaviorSubject(undefined);
        this._loading$ = new BehaviorSubject(undefined);
        this._store.reset();
    }
    async reset() {
        this._loading$.next(true);
        try {
            await this._store.reset();
        }
        catch (error) {
            throw error;
        }
        finally {
            this._loading$.next(false);
        }
    }
    loadPage(pageNumber, pageSize, filter) {
        this._loading$.next(true);
        return this._graphService.getTriples(pageNumber, pageSize, filter).pipe(tap({
            next: () => this._loading$.next(false),
            error: () => this._loading$.next(false),
        }));
    }
    async setFilter(filter) {
        this._loading$.next(true);
        try {
            await this._store.setFilter(filter);
        }
        catch (error) {
            throw error;
        }
        finally {
            this._loading$.next(false);
        }
    }
    getFilter() {
        return this._store.getFilter();
    }
    async setPage(pageNumber, pageSize) {
        this._loading$.next(true);
        try {
            await this._store.setPage(pageNumber, pageSize);
        }
        catch (error) {
            throw error;
        }
        finally {
            this._loading$.next(false);
        }
    }
    /**
     * Set the node term used in filter.
     *
     * @param node The node or null/undefined.
     * @param type The type: subject, predicate, object.
     */
    setTerm(node, type) {
        switch (type) {
            case 'S':
                this._subjectNode$.next(node || undefined);
                break;
            case 'P':
                this._predicateNode$.next(node || undefined);
                break;
            case 'O':
                this._objectNode$.next(node || undefined);
                break;
        }
    }
    /**
     * Set the node term used in filter by its ID.
     *
     * @param id The node ID or null/undefined.
     * @param type The type: subject, predicate, object.
     */
    setTermId(id, type) {
        if (!id) {
            this.setTerm(null, type);
            return;
        }
        this._graphService.getNode(id).subscribe({
            next: (node) => {
                this.setTerm(node, type);
            },
            error: (error) => {
                console.error(`Node ID ${id} not found`, error);
            },
        });
    }
    selectTerm(type) {
        switch (type) {
            case 'S':
                return this.subjectNode$;
            case 'P':
                return this.predicateNode$;
            case 'O':
                return this.objectNode$;
        }
    }
    getTerm(type) {
        switch (type) {
            case 'S':
                return this._subjectNode$.value;
            case 'P':
                return this._predicateNode$.value;
            case 'O':
                return this._objectNode$.value;
        }
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphTripleListRepository, deps: [{ token: i1.GraphService }], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphTripleListRepository, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphTripleListRepository, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }], ctorParameters: () => [{ type: i1.GraphService }] });

/**
 * Graph triples filter used in graph triples list.
 * Its data are in the graph triples store, which gets updated when
 * users apply new filters.
 */
class GraphTripleFilterComponent {
    lookupService;
    _repository;
    _sub;
    filter$;
    literal;
    objectLit;
    sid;
    sidPrefix;
    tag;
    form;
    subjectNode$;
    predicateNode$;
    objectNode$;
    disabled = input(...(ngDevMode ? [undefined, { debugName: "disabled" }] : []));
    constructor(formBuilder, lookupService, _repository) {
        this.lookupService = lookupService;
        this._repository = _repository;
        this.filter$ = _repository.filter$;
        this.subjectNode$ = _repository.subjectNode$;
        this.predicateNode$ = _repository.predicateNode$;
        this.objectNode$ = _repository.objectNode$;
        // form
        this.literal = formBuilder.control(false, { nonNullable: true });
        this.objectLit = formBuilder.control(null, Validators.maxLength(100));
        this.sid = formBuilder.control(null);
        this.sidPrefix = formBuilder.control(false, { nonNullable: true });
        this.tag = formBuilder.control(null);
        this.form = formBuilder.group({
            literal: this.literal,
            objectLit: this.objectLit,
            sid: this.sid,
            sidPrefix: this.sidPrefix,
            tag: this.tag,
        });
    }
    ngOnInit() {
        this._sub = this.filter$.subscribe((f) => {
            this.updateForm(f);
        });
    }
    ngOnDestroy() {
        this._sub?.unsubscribe();
    }
    updateForm(filter) {
        this._repository.setTermId(filter.subjectId, 'S');
        this._repository.setTermId(filter.predicateIds?.length ? filter.predicateIds[0] : null, 'P');
        this._repository.setTermId(filter.objectId, 'O');
        this.literal.setValue(filter.literalPattern ? true : false);
        this.objectLit.setValue(filter.literalPattern || null);
        this.sid.setValue(filter.sid || null);
        this.tag.setValue(filter.tag || null);
        this.form.markAsPristine();
    }
    getFilter() {
        const pid = this._repository.getTerm('P')?.id;
        return {
            subjectId: this._repository.getTerm('S')?.id,
            predicateIds: pid ? [pid] : undefined,
            objectId: this.literal.value
                ? undefined
                : this._repository.getTerm('O')?.id,
            literalPattern: this.literal.value
                ? this.objectLit.value?.trim()
                : undefined,
            sid: this.sid.value?.trim(),
            tag: this.tag.value?.trim(),
        };
    }
    onSubjectNodeChange(node) {
        this._repository.setTerm(node, 'S');
    }
    clearSubjectNode() {
        this._repository.setTerm(null, 'S');
    }
    onPredicateNodeChange(node) {
        this._repository.setTerm(node, 'P');
    }
    clearPredicateNode() {
        this._repository.setTerm(null, 'P');
    }
    onObjectNodeChange(node) {
        this._repository.setTerm(node, 'O');
    }
    clearObjectNode() {
        this._repository.setTerm(null, 'O');
    }
    reset() {
        this.form.reset();
        this.apply();
    }
    apply() {
        if (this.form.invalid) {
            return;
        }
        const filter = this.getFilter();
        // update filter in state
        this._repository.setFilter(filter);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphTripleFilterComponent, deps: [{ token: i1$1.FormBuilder }, { token: GraphNodeLookupService }, { token: GraphTripleListRepository }], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: GraphTripleFilterComponent, isStandalone: true, selector: "cadmus-graph-triple-filter", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"apply()\" [attr.disabled]=\"disabled()\">\r\n  <!-- subject -->\r\n  <div class=\"form-row\">\r\n    <cadmus-refs-lookup\r\n      [service]=\"lookupService\"\r\n      [item]=\"subjectNode$ | async\"\r\n      label=\"subject\"\r\n      (itemChange)=\"onSubjectNodeChange($event)\"\r\n    />\r\n    <button\r\n      type=\"button\"\r\n      mat-icon-button\r\n      style=\"margin-top: 20px\"\r\n      (click)=\"clearSubjectNode()\"\r\n    >\r\n      <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n    </button>\r\n  </div>\r\n  <!-- predicate -->\r\n  <div class=\"form-row\">\r\n    <cadmus-refs-lookup\r\n      [service]=\"lookupService\"\r\n      label=\"predicate\"\r\n      [item]=\"predicateNode$ | async\"\r\n      [baseFilter]=\"{ tag: 'property' }\"\r\n      (itemChange)=\"onPredicateNodeChange($event)\"\r\n    />\r\n    <button\r\n      type=\"button\"\r\n      mat-icon-button\r\n      style=\"margin-top: 20px\"\r\n      (click)=\"clearPredicateNode()\"\r\n    >\r\n      <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n    </button>\r\n  </div>\r\n  <!-- object, objectLit -->\r\n  <div>\r\n    <mat-checkbox [formControl]=\"literal\">literal</mat-checkbox>\r\n  </div>\r\n  @if (literal.value) {\r\n  <div>\r\n    <mat-label>literal</mat-label>\r\n    <mat-form-field>\r\n      <input matInput [formControl]=\"objectLit\" maxlength=\"500\" />\r\n    </mat-form-field>\r\n  </div>\r\n  } @else {\r\n  <div class=\"form-row\">\r\n    <cadmus-refs-lookup\r\n      [service]=\"lookupService\"\r\n      [item]=\"objectNode$ | async\"\r\n      label=\"object\"\r\n      (itemChange)=\"onObjectNodeChange($event)\"\r\n    />\r\n    <button\r\n      type=\"button\"\r\n      mat-icon-button\r\n      style=\"margin-top: 20px\"\r\n      (click)=\"clearObjectNode()\"\r\n    >\r\n      <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n    </button>\r\n  </div>\r\n  }\r\n  <!-- sid, sidPrefix -->\r\n  <div>\r\n    <mat-form-field>\r\n      <mat-label>sid</mat-label>\r\n      <input matInput [formControl]=\"sid\" maxlength=\"500\" />\r\n    </mat-form-field>\r\n    &nbsp;\r\n    <mat-checkbox [formControl]=\"sidPrefix\">prefix</mat-checkbox>\r\n  </div>\r\n  <!-- tag -->\r\n  <br />\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: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: RefLookupComponent, selector: "cadmus-refs-lookup", inputs: ["label", "limit", "baseFilter", "service", "item", "itemId", "required", "hasMore", "linkTemplate", "optDialog", "options", "lookupProviderOptions"], outputs: ["itemChange", "optionsChange", "moreRequest"] }, { 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: 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: "directive", type: MatLabel, selector: "mat-label" }, { 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"] }, { kind: "pipe", type: AsyncPipe, name: "async" }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphTripleFilterComponent, decorators: [{
            type: Component,
            args: [{ selector: 'cadmus-graph-triple-filter', imports: [
                        FormsModule,
                        ReactiveFormsModule,
                        RefLookupComponent,
                        MatIconButton,
                        MatIcon,
                        MatCheckbox,
                        MatLabel,
                        MatFormField,
                        MatInput,
                        MatTooltip,
                        AsyncPipe,
                    ], template: "<form [formGroup]=\"form\" (submit)=\"apply()\" [attr.disabled]=\"disabled()\">\r\n  <!-- subject -->\r\n  <div class=\"form-row\">\r\n    <cadmus-refs-lookup\r\n      [service]=\"lookupService\"\r\n      [item]=\"subjectNode$ | async\"\r\n      label=\"subject\"\r\n      (itemChange)=\"onSubjectNodeChange($event)\"\r\n    />\r\n    <button\r\n      type=\"button\"\r\n      mat-icon-button\r\n      style=\"margin-top: 20px\"\r\n      (click)=\"clearSubjectNode()\"\r\n    >\r\n      <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n    </button>\r\n  </div>\r\n  <!-- predicate -->\r\n  <div class=\"form-row\">\r\n    <cadmus-refs-lookup\r\n      [service]=\"lookupService\"\r\n      label=\"predicate\"\r\n      [item]=\"predicateNode$ | async\"\r\n      [baseFilter]=\"{ tag: 'property' }\"\r\n      (itemChange)=\"onPredicateNodeChange($event)\"\r\n    />\r\n    <button\r\n      type=\"button\"\r\n      mat-icon-button\r\n      style=\"margin-top: 20px\"\r\n      (click)=\"clearPredicateNode()\"\r\n    >\r\n      <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n    </button>\r\n  </div>\r\n  <!-- object, objectLit -->\r\n  <div>\r\n    <mat-checkbox [formControl]=\"literal\">literal</mat-checkbox>\r\n  </div>\r\n  @if (literal.value) {\r\n  <div>\r\n    <mat-label>literal</mat-label>\r\n    <mat-form-field>\r\n      <input matInput [formControl]=\"objectLit\" maxlength=\"500\" />\r\n    </mat-form-field>\r\n  </div>\r\n  } @else {\r\n  <div class=\"form-row\">\r\n    <cadmus-refs-lookup\r\n      [service]=\"lookupService\"\r\n      [item]=\"objectNode$ | async\"\r\n      label=\"object\"\r\n      (itemChange)=\"onObjectNodeChange($event)\"\r\n    />\r\n    <button\r\n      type=\"button\"\r\n      mat-icon-button\r\n      style=\"margin-top: 20px\"\r\n      (click)=\"clearObjectNode()\"\r\n    >\r\n      <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n    </button>\r\n  </div>\r\n  }\r\n  <!-- sid, sidPrefix -->\r\n  <div>\r\n    <mat-form-field>\r\n      <mat-label>sid</mat-label>\r\n      <input matInput [formControl]=\"sid\" maxlength=\"500\" />\r\n    </mat-form-field>\r\n    &nbsp;\r\n    <mat-checkbox [formControl]=\"sidPrefix\">prefix</mat-checkbox>\r\n  </div>\r\n  <!-- tag -->\r\n  <br />\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: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"] }]
        }], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: GraphNodeLookupService }, { type: GraphTripleListRepository }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });

class GraphTripleEditorComponent {
    lookupService;
    _snackbar;
    _graphService;
    _sub;
    triple = model(...(ngDevMode ? [undefined, { debugName: "triple" }] : []));
    /**
     * Emitted when the user requested to close the editor.
     */
    editorClose = output();
    isNew = signal(true, ...(ngDevMode ? [{ debugName: "isNew" }] : []));
    subjectNode;
    predicateNode;
    objectNode;
    isLiteral;
    literal;
    literalLang;
    literalType;
    tag;
    form;
    constructor(formBuilder, lookupService, _snackbar, _graphService) {
        this.lookupService = lookupService;
        this._snackbar = _snackbar;
        this._graphService = _graphService;
        // form
        this.subjectNode = formBuilder.control(null, Validators.required);
        this.predicateNode = formBuilder.control(null, Validators.required);
        this.objectNode = formBuilder.control(null, {
            validators: NgxToolsValidators.conditionalValidator(() => !this.isLiteral.value, Validators.required),
        });
        this.isLiteral = formBuilder.control(true, { nonNullable: true });
        this.literal = formBuilder.control(null, {
            validators: [Validators.required, Validators.maxLength(15000)],
            updateOn: 'change',
        });
        this.literalLang = formBuilder.control(null, Validators.maxLength(10));
        this.literalType = formBuilder.control(null, Validators.maxLength(100));
        this.tag = formBuilder.control(null, Validators.maxLength(50));
        this.form = formBuilder.group({
            subjectNode: this.subjectNode,
            predicateNode: this.predicateNode,
            objectNode: this.objectNode,
            isLiteral: this.isLiteral,
            literal: this.literal,
            literalLang: this.literalLang,
            literalType: this.literalType,
            tag: this.tag,
        });
        effect(() => {
            this.updateForm(this.triple());
        });
    }
    ngOnInit() {
        this._sub = this.isLiteral.valueChanges.subscribe((value) => {
            if (value) {
                this.objectNode.setValidators(null);
                this.literal.setValidators([
                    Validators.required,
                    Validators.maxLength(15000),
                ]);
            }
            else {
                this.objectNode.setValidators(Validators.required);
                this.literal.setValidators(null);
            }
            this.literal.updateValueAndValidity();
        });
    }
    ngOnDestroy() {
        this._sub?.unsubscribe();
    }
    onSubjectChange(node) {
        this.subjectNode.setValue(node || null);
        this.subjectNode.updateValueAndValidity();
        this.subjectNode.markAsDirty();
    }
    onPredicateChange(node) {
        this.predicateNode.setValue(node || null);
        this.predicateNode.updateValueAndValidity();
        this.predicateNode.markAsDirty();
    }
    onObjectChange(node) {
        this.objectNode.setValue(node || null);
        this.objectNode.updateValueAndValidity();
        this.objectNode.markAsDirty();
        if (node) {
            this.isLiteral.setValue(false);
            this.isLiteral.updateValueAndValidity();
            this.isLiteral.markAsDirty();
        }
    }
    getNode(id) {
        return new Promise((resolve, reject) => {
            this._graphService
                .getNode(id)
                .pipe(take(1))
                .subscribe({
                next: (node) => {
                    resolve(node);
                },
                error: (error) => {
                    console.error('Error loading node', error);
                    this._snackbar.open('Error loading node ' + id, 'OK');
                    reject();
                },
            });
        });
    }
    updateForm(triple) {
        if (!triple) {
            this.form.reset();
            this.isLiteral.setValue(true);
            this.isNew.set(true);
            return;
        }
        if (triple.subjectId) {
            this.getNode(triple.subjectId).then((node) => {
                this.subjectNode.setValue(node || null);
                this.subjectNode.updateValueAndValidity();
                this.subjectNode.markAsDirty();
            });
        }
        else {
            this.subjectNode.reset();
        }
        if (triple.predicateId) {
            this.getNode(triple.predicateId).then((node) => {
                this.predicateNode.setValue(node || null);
                this.predicateNode.updateValueAndValidity();
                this.predicateNode.markAsDirty();
            });
        }
        else {
            this.predicateNode.reset();
        }
        if (triple.objectId) {
            this.isLiteral.setValue(false);
            this.getNode(triple.objectId).then((node) => {
                this.objectNode.setValue(node || null);
                this.objectNode.updateValueAndValidity();
                this.objectNode.markAsDirty();
            });
        }
        else {
            this.isLiteral.setValue(true);
            this.literal.setValue(triple.objectLiteral || null);
            this.literalLang.setValue(triple.literalLanguage || null);
            this.literalType.setValue(triple.literalType || null);
        }
        this.isNew.set(triple.id ? false : true);
        this.form.markAsPristine();
    }
    getTriple() {
        return {
            id: this.triple()?.id || 0,
            subjectId: this.subjectNode.value?.id || 0,
            predicateId: this.predicateNode.value?.id || 0,
            objectId: this.isLiteral.value
                ? undefined
                : this.objectNode.value?.id || 0,
            objectLiteral: this.isLiteral.value ? this.literal.value : undefined,
            literalLanguage: this.isLiteral.value
                ? this.literalLang.value || undefined
                : undefined,
            literalType: this.isLiteral.value
                ? this.literalType.value || undefined
                : undefined,
            subjectUri: this.subjectNode.value?.uri || '',
            predicateUri: this.predicateNode.value?.uri || '',
            objectUri: this.isLiteral.value
                ? undefined
                : this.objectNode.value?.uri || '',
        };
    }
    cancel() {
        this.editorClose.emit();
    }
    save() {
        if (this.form.invalid) {
            return;
        }
        this.triple.set(this.getTriple());
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphTripleEditorComponent, deps: [{ token: i1$1.FormBuilder }, { token: GraphNodeLookupService }, { token: i3$1.MatSnackBar }, { token: i1.GraphService }], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: GraphTripleEditorComponent, isStandalone: true, selector: "cadmus-graph-triple-editor", inputs: { triple: { classPropertyName: "triple", publicName: "triple", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { triple: "tripleChange", editorClose: "editorClose" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n  <div id=\"row\">\r\n    <!-- S -->\r\n    <fieldset id=\"s\">\r\n      <legend>subject</legend>\r\n      <!-- picker -->\r\n      <cadmus-refs-lookup\r\n        [service]=\"lookupService\"\r\n        [required]=\"true\"\r\n        [item]=\"subjectNode.value\"\r\n        label=\"subject\"\r\n        (itemChange)=\"onSubjectChange($event)\"\r\n      />\r\n    </fieldset>\r\n    <!-- P -->\r\n    <fieldset id=\"p\">\r\n      <legend>predicate</legend>\r\n      <!-- picker -->\r\n      <cadmus-refs-lookup\r\n        [service]=\"lookupService\"\r\n        [required]=\"true\"\r\n        [item]=\"predicateNode.value\"\r\n        label=\"predicate\"\r\n        [baseFilter]=\"{ tag: 'property' }\"\r\n        (itemChange)=\"onPredicateChange($event)\"\r\n      />\r\n    </fieldset>\r\n    <!-- O -->\r\n    <fieldset id=\"o\">\r\n      <legend>object</legend>\r\n      <mat-checkbox [formControl]=\"isLiteral\">literal</mat-checkbox>\r\n      <!-- object -->\r\n      @if (!isLiteral.value) {\r\n      <cadmus-refs-lookup\r\n        [required]=\"true\"\r\n        [service]=\"lookupService\"\r\n        [item]=\"objectNode.value\"\r\n        label=\"object\"\r\n        (itemChange)=\"onObjectChange($event)\"\r\n      />\r\n      }\r\n      <!-- literal value -->\r\n      @if (isLiteral.value) {\r\n      <mat-form-field style=\"width: 100%\">\r\n        <mat-label>object</mat-label>\r\n        <textarea matInput [formControl]=\"literal\"></textarea>\r\n        @if ( $any(literal).errors?.required && (literal.dirty ||\r\n        literal.touched) ) {\r\n        <mat-error>literal required</mat-error>\r\n        } @if ( $any(literal).errors?.maxLength && (literal.dirty ||\r\n        literal.touched) ) {\r\n        <mat-error>literal too long</mat-error>\r\n        }\r\n      </mat-form-field>\r\n      }\r\n      <div class=\"form-row\">\r\n        <!-- literal language -->\r\n        @if (isLiteral.value) {\r\n        <mat-form-field>\r\n          <mat-label>language</mat-label>\r\n          <input matInput [formControl]=\"literalLang\" />\r\n          <mat-hint>ISO-639 etc.</mat-hint>\r\n          @if ( $any(literalLang).errors?.maxLength && (literalLang.dirty ||\r\n          literalLang.touched) ) {\r\n          <mat-error>language too long</mat-error>\r\n          }\r\n        </mat-form-field>\r\n\r\n        <!-- literal type -->\r\n        <mat-form-field>\r\n          <mat-label>type</mat-label>\r\n          <input matInput [formControl]=\"literalType\" />\r\n          <mat-hint>XML data type (xs:...)</mat-hint>\r\n          @if ( $any(literalType).errors?.maxLength && (literalType.dirty ||\r\n          literalType.touched) ) {\r\n          <mat-error>type too long</mat-error>\r\n          }\r\n        </mat-form-field>\r\n        }\r\n      </div>\r\n    </fieldset>\r\n  </div>\r\n\r\n  <!-- buttons -->\r\n  <div>\r\n    <button mat-icon-button type=\"button\" (click)=\"cancel()\">\r\n      <mat-icon class=\"mat-warn\">cancel</mat-icon>\r\n    </button>\r\n    <button mat-icon-button type=\"submit\" [disabled]=\"form.invalid\">\r\n      <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n    </button>\r\n  </div>\r\n</form>\r\n", styles: ["#row{display:flex;flex-wrap:wrap;gap:16px}#row fieldset{flex:1 1 auto;border:1px solid;border-radius:8px;padding:8px 16px}fieldset#s{border-color:green;color:green}fieldset#p{border-color:orange;color:orange}fieldset#o{border-color:#4169e1;color:#4169e1}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: RefLookupComponent, selector: "cadmus-refs-lookup", inputs: ["label", "limit", "baseFilter", "service", "item", "itemId", "required", "hasMore", "linkTemplate", "optDialog", "options", "lookupProviderOptions"], 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: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { 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: MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { 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"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphTripleEditorComponent, decorators: [{
            type: Component,
            args: [{ selector: 'cadmus-graph-triple-editor', imports: [
                        FormsModule,
                        ReactiveFormsModule,
                        RefLookupComponent,
                        MatCheckbox,
                        MatFormField,
                        MatLabel,
                        MatInput,
                        MatError,
                        MatHint,
                        MatIconButton,
                        MatIcon,
                    ], template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n  <div id=\"row\">\r\n    <!-- S -->\r\n    <fieldset id=\"s\">\r\n      <legend>subject</legend>\r\n      <!-- picker -->\r\n      <cadmus-refs-lookup\r\n        [service]=\"lookupService\"\r\n        [required]=\"true\"\r\n        [item]=\"subjectNode.value\"\r\n        label=\"subject\"\r\n        (itemChange)=\"onSubjectChange($event)\"\r\n      />\r\n    </fieldset>\r\n    <!-- P -->\r\n    <fieldset id=\"p\">\r\n      <legend>predicate</legend>\r\n      <!-- picker -->\r\n      <cadmus-refs-lookup\r\n        [service]=\"lookupService\"\r\n        [required]=\"true\"\r\n        [item]=\"predicateNode.value\"\r\n        label=\"predicate\"\r\n        [baseFilter]=\"{ tag: 'property' }\"\r\n        (itemChange)=\"onPredicateChange($event)\"\r\n      />\r\n    </fieldset>\r\n    <!-- O -->\r\n    <fieldset id=\"o\">\r\n      <legend>object</legend>\r\n      <mat-checkbox [formControl]=\"isLiteral\">literal</mat-checkbox>\r\n      <!-- object -->\r\n      @if (!isLiteral.value) {\r\n      <cadmus-refs-lookup\r\n        [required]=\"true\"\r\n        [service]=\"lookupService\"\r\n        [item]=\"objectNode.value\"\r\n        label=\"object\"\r\n        (itemChange)=\"onObjectChange($event)\"\r\n      />\r\n      }\r\n      <!-- literal value -->\r\n      @if (isLiteral.value) {\r\n      <mat-form-field style=\"width: 100%\">\r\n        <mat-label>object</mat-label>\r\n        <textarea matInput [formControl]=\"literal\"></textarea>\r\n        @if ( $any(literal).errors?.required && (literal.dirty ||\r\n        literal.touched) ) {\r\n        <mat-error>literal required</mat-error>\r\n        } @if ( $any(literal).errors?.maxLength && (literal.dirty ||\r\n        literal.touched) ) {\r\n        <mat-error>literal too long</mat-error>\r\n        }\r\n      </mat-form-field>\r\n      }\r\n      <div class=\"form-row\">\r\n        <!-- literal language -->\r\n        @if (isLiteral.value) {\r\n        <mat-form-field>\r\n          <mat-label>language</mat-label>\r\n          <input matInput [formControl]=\"literalLang\" />\r\n          <mat-hint>ISO-639 etc.</mat-hint>\r\n          @if ( $any(literalLang).errors?.maxLength && (literalLang.dirty ||\r\n          literalLang.touched) ) {\r\n          <mat-error>language too long</mat-error>\r\n          }\r\n        </mat-form-field>\r\n\r\n        <!-- literal type -->\r\n        <mat-form-field>\r\n          <mat-label>type</mat-label>\r\n          <input matInput [formControl]=\"literalType\" />\r\n          <mat-hint>XML data type (xs:...)</mat-hint>\r\n          @if ( $any(literalType).errors?.maxLength && (literalType.dirty ||\r\n          literalType.touched) ) {\r\n          <mat-error>type too long</mat-error>\r\n          }\r\n        </mat-form-field>\r\n        }\r\n      </div>\r\n    </fieldset>\r\n  </div>\r\n\r\n  <!-- buttons -->\r\n  <div>\r\n    <button mat-icon-button type=\"button\" (click)=\"cancel()\">\r\n      <mat-icon class=\"mat-warn\">cancel</mat-icon>\r\n    </button>\r\n    <button mat-icon-button type=\"submit\" [disabled]=\"form.invalid\">\r\n      <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n    </button>\r\n  </div>\r\n</form>\r\n", styles: ["#row{display:flex;flex-wrap:wrap;gap:16px}#row fieldset{flex:1 1 auto;border:1px solid;border-radius:8px;padding:8px 16px}fieldset#s{border-color:green;color:green}fieldset#p{border-color:orange;color:orange}fieldset#o{border-color:#4169e1;color:#4169e1}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"] }]
        }], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: GraphNodeLookupService }, { type: i3$1.MatSnackBar }, { type: i1.GraphService }], propDecorators: { triple: [{ type: i0.Input, args: [{ isSignal: true, alias: "triple", required: false }] }, { type: i0.Output, args: ["tripleChange"] }], editorClose: [{ type: i0.Output, args: ["editorClose"] }] } });

class GraphTripleListComponent {
    _graphService;
    _dialogService;
    _snackbar;
    _repository;
    page$;
    loading$;
    editedTriple = signal(undefined, ...(ngDevMode ? [{ debugName: "editedTriple" }] : []));
    /**
     * The optional set of thesaurus entries for triple's tags.
     */
    // public readonly tagEntries = input<ThesaurusEntry[]>();
    constructor(_graphService, _dialogService, _snackbar, _repository) {
        this._graphService = _graphService;
        this._dialogService = _dialogService;
        this._snackbar = _snackbar;
        this._repository = _repository;
        this.page$ = _repository.page$;
        this.loading$ = _repository.loading$;
    }
    onPageChange(event) {
        this._repository.setPage(event.pageIndex + 1, event.pageSize);
    }
    addTriple() {
        this.editedTriple.set({
            id: 0,
            subjectId: 0,
            predicateId: 0,
            objectId: 0,
            subjectUri: '',
            predicateUri: '',
        });
    }
    editTriple(triple) {
        this.editedTriple.set(deepCopy(triple));
    }
    onTripleChange(triple) {
        this._graphService
            .addTriple(triple)
            .pipe(take(1))
            .subscribe({
            next: (n) => {
                this.editedTriple.set(undefined);
                this._repository.reset();
                this._snackbar.open('Triple saved', 'OK', {
                    duration: 1500,
                });
            },
            error: (error) => {
                console.error('Error saving triple', error);
                this._snackbar.open('Error saving triple', 'OK');
            },
        });
    }
    onEditorClose() {
        this.editedTriple.set(undefined);
    }
    deleteTriple(triple) {
        this._dialogService
            .confirm('Delete Triple', 'Delete triple?')
            .pipe(take(1))
            .subscribe((yes) => {
            if (yes) {
                this._graphService
                    .deleteTriple(triple.id)
                    .pipe(take(1))
                    .subscribe({
                    next: (_) => {
                        this._repository.reset();
                    },
                    error: (error) => {
                        console.error('Error deleting triple', error);
                        this._snackbar.open('Error deleting triple', 'OK');
                    },
                });
            }
        });
    }
    reset() {
        this._repository.reset();
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphTripleListComponent, deps: [{ token: i1.GraphService }, { token: i3.DialogService }, { token: i3$1.MatSnackBar }, { token: GraphTripleListRepository }], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: GraphTripleListComponent, isStandalone: true, selector: "cadmus-graph-triple-list", ngImport: i0, template: "<div id=\"container\">\r\n  <!-- filters -->\r\n  <div id=\"filters\">\r\n    <mat-card appearance=\"outlined\">\r\n      <mat-card-content>\r\n        <cadmus-graph-triple-filter [disabled]=\"(loading$ | async) === true\" />\r\n      </mat-card-content>\r\n    </mat-card>\r\n  </div>\r\n\r\n  <!-- list -->\r\n  @if (page$ | async; as page) {\r\n  <div id=\"list\">\r\n    @if (loading$ | async) {\r\n    <div>\r\n      <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\r\n    </div>\r\n    }\r\n    <!-- list -->\r\n    <div>\r\n      <table>\r\n        <thead>\r\n          <th></th>\r\n          <th>ID</th>\r\n          <th>S</th>\r\n          <th>P</th>\r\n          <th>O</th>\r\n          <th class=\"noif-lt-md\">sid</th>\r\n          <th class=\"noif-lt-md\">tag</th>\r\n        </thead>\r\n        <tbody>\r\n          @for (d of page.items; track d.id) {\r\n          <tr [class.selected]=\"d === editedTriple()\">\r\n            <td class=\"fit-width\">\r\n              <button\r\n                mat-icon-button\r\n                type=\"button\"\r\n                matTooltip=\"Edit triple\"\r\n                (click)=\"editTriple(d)\"\r\n              >\r\n                <mat-icon class=\"mat-primary\">edit</mat-icon>\r\n              </button>\r\n              <button\r\n                mat-icon-button\r\n                type=\"button\"\r\n                matTooltip=\"Delete triple\"\r\n                (click)=\"deleteTriple(d)\"\r\n              >\r\n                <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n              </button>\r\n            </td>\r\n            <td>{{ d.id }}</td>\r\n            <td>{{ d.subjectUri }}</td>\r\n            <td>{{ d.predicateUri }}</td>\r\n            <td>{{ d.objectUri ?? d.objectLiteral | ellipsis }}</td>\r\n            <td class=\"noif-lt-md\">{{ d.sid }}</td>\r\n            <td class=\"noif-lt-md\">{{ d.tag }}</td>\r\n          </tr>\r\n          }\r\n        </tbody>\r\n      </table>\r\n      <div>\r\n        <button\r\n          class=\"mat-primary\"\r\n          type=\"button\"\r\n          mat-flat-button\r\n          (click)=\"addTriple()\"\r\n        >\r\n          <mat-icon>add_circle</mat-icon>\r\n          triple\r\n        </button>\r\n      </div>\r\n      <!-- pagination -->\r\n      <div id=\"paginator\" class=\"form-row\">\r\n        <button\r\n          type=\"button\"\r\n          mat-icon-button\r\n          matTooltip=\"Reset list\"\r\n          (click)=\"reset()\"\r\n        >\r\n          <mat-icon class=\"mat-warn\">autorenew</mat-icon>\r\n        </button>\r\n        <mat-paginator\r\n          [length]=\"page.total\"\r\n          [pageIndex]=\"page.pageNumber - 1\"\r\n          [pageSize]=\"page.pageSize\"\r\n          [pageSizeOptions]=\"[5, 10, 20, 50, 100]\"\r\n          (page)=\"onPageChange($event)\"\r\n          [showFirstLastButtons]=\"true\"\r\n        />\r\n      </div>\r\n    </div>\r\n    <!-- editor -->\r\n    <mat-expansion-panel\r\n      id=\"editor\"\r\n      [expanded]=\"editedTriple()\"\r\n      [disabled]=\"!editedTriple()\"\r\n    >\r\n      <mat-expansion-panel-header>Triple</mat-expansion-panel-header>\r\n      <cadmus-graph-triple-editor\r\n        [triple]=\"editedTriple()\"\r\n        (tripleChange)=\"onTripleChange($event!)\"\r\n        (editorClose)=\"onEditorClose()\"\r\n      />\r\n    </mat-expansion-panel>\r\n  </div>\r\n  }\r\n</div>\r\n", styles: ["table{width:100%;border-collapse:collapse}tbody tr:nth-child(odd){background-color:#e2e2e2}th{text-align:left;font-weight:400;color:silver}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#d0d0d0!important}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}div#container{display:grid;grid-template-rows:1fr auto auto;grid-template-columns:auto 1fr;grid-template-areas:\"filters list\" \". paginator\" \". editor\";gap:8px}div#filters{grid-area:filters}div#list{grid-area:list}div#paginator{grid-area:paginator;justify-content:end}div#editor{grid-area:editor}@media only screen and (max-width:959px){div#container{grid-template-columns:1fr;grid-template-areas:\"list\" \"paginator\" \"filters\" \"editor\"}.noif-lt-md{display:none}}\n"], dependencies: [{ kind: "component", type: MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: MatCardContent, selector: "mat-card-content" }, { kind: "component", type: GraphTripleFilterComponent, selector: "cadmus-graph-triple-filter", inputs: ["disabled"] }, { kind: "component", type: MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { 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: MatButton, selector: "    button[matButton], a[matButton], button[mat-button], button[mat-raised-button],    button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button],    a[mat-flat-button], a[mat-stroked-button]  ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "component", type: GraphTripleEditorComponent, selector: "cadmus-graph-triple-editor", inputs: ["triple"], outputs: ["tripleChange", "editorClose"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: EllipsisPipe, name: "ellipsis" }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GraphTripleListComponent, decorators: [{
            type: Component,
            args: [{ selector: 'cadmus-graph-triple-list', imports: [
                        MatCard,
                        MatCardContent,
                        GraphTripleFilterComponent,
                        MatProgressBar,
                        MatIconButton,
                        MatTooltip,
                        MatIcon,
                        MatButton,
                        MatPaginator,
                        MatExpansionPanel,
                        MatExpansionPanelHeader,
                        GraphTripleEditorComponent,
                        AsyncPipe,
                        EllipsisPipe,
                    ], template: "<div id=\"container\">\r\n  <!-- filters -->\r\n  <div id=\"filters\">\r\n    <mat-card appearance=\"outlined\">\r\n      <mat-card-content>\r\n        <cadmus-graph-triple-filter [disabled]=\"(loading$ | async) === true\" />\r\n      </mat-card-content>\r\n    </mat-card>\r\n  </div>\r\n\r\n  <!-- list -->\r\n  @if (page$ | async; as page) {\r\n  <div id=\"list\">\r\n    @if (loading$ | async) {\r\n    <div>\r\n      <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\r\n    </div>\r\n    }\r\n    <!-- list -->\r\n    <div>\r\n      <table>\r\n        <thead>\r\n          <th></th>\r\n          <th>ID</th>\r\n          <th>S</th>\r\n          <th>P</th>\r\n          <th>O</th>\r\n          <th class=\"noif-lt-md\">sid</th>\r\n          <th class=\"noif-lt-md\">tag</th>\r\n        </thead>\r\n        <tbody>\r\n          @for (d of page.items; track d.id) {\r\n          <tr [class.selected]=\"d === editedTriple()\">\r\n            <td class=\"fit-width\">\r\n              <button\r\n                mat-icon-button\r\n                type=\"button\"\r\n                matTooltip=\"Edit triple\"\r\n                (click)=\"editTriple(d)\"\r\n              >\r\n                <mat-icon class=\"mat-primary\">edit</mat-icon>\r\n              </button>\r\n              <button\r\n                mat-icon-button\r\n                type=\"button\"\r\n                matTooltip=\"Delete triple\"\r\n                (click)=\"deleteTriple(d)\"\r\n              >\r\n                <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n              </button>\r\n            </td>\r\n            <td>{{ d.id }}</td>\r\n            <td>{{ d.subjectUri }}</td>\r\n            <td>{{ d.predicateUri }}</td>\r\n            <td>{{ d.objectUri ?? d.objectLiteral | ellipsis }}</td>\r\n            <td class=\"noif-lt-md\">{{ d.sid }}</td>\r\n            <td class=\"noif-lt-md\">{{ d.tag }}</td>\r\n          </tr>\r\n          }\r\n        </tbody>\r\n      </table>\r\n      <div>\r\n        <button\r\n          class=\"mat-primary\"\r\n          type=\"button\"\r\n          mat-flat-button\r\n          (click)=\"addTriple()\"\r\n        >\r\n          <mat-icon>add_circle</mat-icon>\r\n          triple\r\n        </button>\r\n      </div>\r\n      <!-- pagination -->\r\n      <div id=\"paginator\" class=\"form-row\">\r\n        <button\r\n          type=\"button\"\r\n          mat-icon-button\r\n          matTooltip=\"Reset list\"\r\n          (click)=\"reset()\"\r\n        >\r\n          <mat-icon class=\"mat-warn\">autorenew</mat-icon>\r\n        </button>\r\n        <mat-paginator\r\n          [length]=\"page.total\"\r\n          [pageIndex]=\"page.pageNumber - 1\"\r\n          [pageSize]=\"page.pageSize\"\r\n          [pageSizeOptions]=\"[5, 10, 20, 50, 100]\"\r\n          (page)=\"onPageChange($event)\"\r\n          [showFirstLastButtons]=\"true\"\r\n        />\r\n      </div>\r\n    </div>\r\n    <!-- editor -->\r\n    <mat-expansion-panel\r\n      id=\"editor\"\r\n      [expanded]=\"editedTriple()\"\r\n      [disabled]=\"!editedTriple()\"\r\n    >\r\n      <mat-expansion-panel-header>Triple</mat-expansion-panel-header>\r\n      <cadmus-graph-triple-editor\r\n        [triple]=\"editedTriple()\"\r\n        (tripleChange)=\"onTripleChange($event!)\"\r\n        (editorClose)=\"onEditorClose()\"\r\n      />\r\n    </mat-expansion-panel>\r\n  </div>\r\n  }\r\n</div>\r\n", styles: ["table{width:100%;border-collapse:collapse}tbody tr:nth-child(odd){background-color:#e2e2e2}th{text-align:left;font-weight:400;color:silver}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#d0d0d0!important}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}div#container{display:grid;grid-template-rows:1fr auto auto;grid-template-columns:auto 1fr;grid-template-areas:\"filters list\" \". paginator\" \". editor\";gap:8px}div#filters{grid-area:filters}div#list{grid-area:list}div#paginator{grid-area:paginator;justify-content:end}div#editor{grid-area:editor}@media only screen and (max-width:959px){div#container{grid-template-columns:1fr;grid-template-areas:\"list\" \"paginator\" \"filters\" \"editor\"}.noif-lt-md{display:none}}\n"] }]
        }], ctorParameters: () => [{ type: i1.GraphService }, { type: i3.DialogService }, { type: i3$1.MatSnackBar }, { type: GraphTripleListRepository }] });

/*
 * Public API Surface of cadmus-graph-ui
 */
// export * from './lib/components/graph-node-filter/graph-node-filter.component';

/**
 * Generated bundle index. Do not edit.
 */

export { GraphNodeEditorComponent, GraphNodeListComponent, GraphNodeLookupService, GraphTripleEditorComponent, GraphTripleListComponent, GraphTripleListRepository, NodeListRepository };
//# sourceMappingURL=myrmidon-cadmus-graph-ui.mjs.map