@myrmidon/cadmus-graph-ui
Version:
Cadmus - semantic graph components.
1,198 lines • 108 kB
JavaScript
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 \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 \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 \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 \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 \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 \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