@myrmidon/gve-core 6.1.6 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ import { input, output, ChangeDetectionStrategy, Component, inject, DestroyRef,
3
3
  import * as i1 from '@angular/material/core';
4
4
  import { MatRippleModule } from '@angular/material/core';
5
5
  import * as i2$1 from '@myrmidon/ngx-tools';
6
- import { ColorToContrastPipe, FlatLookupPipe } from '@myrmidon/ngx-tools';
6
+ import { ColorToContrastPipe, FlatLookupPipe, NgxToolsValidators } from '@myrmidon/ngx-tools';
7
7
  import * as i1$1 from '@angular/forms';
8
8
  import { FormsModule, Validators, ReactiveFormsModule, FormControl, FormGroup } from '@angular/forms';
9
9
  import * as i2 from '@angular/material/button';
@@ -33,14 +33,14 @@ import * as i3$2 from '@angular/material/dialog';
33
33
  import { MAT_DIALOG_DATA } from '@angular/material/dialog';
34
34
  import * as i1$2 from '@angular/common/http';
35
35
  import { filter, debounceTime as debounceTime$1 } from 'rxjs/operators';
36
- import { MatBadgeModule } from '@angular/material/badge';
37
- import * as i10 from '@angular/material/tabs';
36
+ import { MatBadgeModule, MatBadge } from '@angular/material/badge';
37
+ import * as i12 from '@angular/material/tabs';
38
38
  import { MatTabsModule } from '@angular/material/tabs';
39
39
  import { NgeMonacoModule } from '@cisstech/nge/monaco';
40
40
  import { customAlphabet } from 'nanoid';
41
41
  import { CommonModule } from '@angular/common';
42
42
  import { MatButtonToggleModule } from '@angular/material/button-toggle';
43
- import * as i9 from '@angular/material/progress-bar';
43
+ import * as i11 from '@angular/material/progress-bar';
44
44
  import { MatProgressBarModule } from '@angular/material/progress-bar';
45
45
  import { MatSliderModule } from '@angular/material/slider';
46
46
  import { MatSlideToggle } from '@angular/material/slide-toggle';
@@ -100,10 +100,10 @@ class BaseTextCharComponent {
100
100
  onCharDblClick(event) {
101
101
  this.charDblPick.emit({ char: this.char(), event });
102
102
  }
103
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: BaseTextCharComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
104
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: BaseTextCharComponent, isStandalone: true, selector: "gve-base-text-char", inputs: { char: { classPropertyName: "char", publicName: "char", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { charPick: "charPick", charDblPick: "charDblPick" }, ngImport: i0, template: "@if (char()) {\r\n<div matRipple id=\"container\" (click)=\"onCharClick($event)\" (dblclick)=\"onCharDblClick($event)\">\r\n <div\r\n id=\"c-label\"\r\n [style.fontSize]=\"char()!.emSize + 'em'\"\r\n [style.borderColor]=\"char()!.borderColor\"\r\n >\r\n {{ char()!.label }}\r\n </div>\r\n <div\r\n id=\"c-id\"\r\n [style.fontSize]=\"char()!.emSize / 2 + 'em'\"\r\n [style.color]=\"char()!.color | colorToContrast\"\r\n [style.borderColor]=\"char()!.borderColor\"\r\n [style.backgroundColor]=\"char()!.color\"\r\n >\r\n {{ char()!.id }}\r\n </div>\r\n</div>\r\n}\r\n", styles: ["div#container{cursor:pointer;flex-direction:column;align-items:center}div#c-label{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:6px;height:1.5em;align-items:center;justify-content:center;text-align:center}div#c-id{margin-top:4px;margin-bottom:16px;border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:6px;align-items:center;justify-content:center;text-align:center}\n"], dependencies: [{ kind: "ngmodule", type: MatRippleModule }, { kind: "directive", type: i1.MatRipple, selector: "[mat-ripple], [matRipple]", inputs: ["matRippleColor", "matRippleUnbounded", "matRippleCentered", "matRippleRadius", "matRippleAnimation", "matRippleDisabled", "matRippleTrigger"], exportAs: ["matRipple"] }, { kind: "pipe", type: ColorToContrastPipe, name: "colorToContrast" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
103
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BaseTextCharComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
104
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: BaseTextCharComponent, isStandalone: true, selector: "gve-base-text-char", inputs: { char: { classPropertyName: "char", publicName: "char", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { charPick: "charPick", charDblPick: "charDblPick" }, ngImport: i0, template: "@if (char()) {\r\n<div matRipple id=\"container\" (click)=\"onCharClick($event)\" (dblclick)=\"onCharDblClick($event)\">\r\n <div\r\n id=\"c-label\"\r\n [style.fontSize]=\"char()!.emSize + 'em'\"\r\n [style.borderColor]=\"char()!.borderColor\"\r\n >\r\n {{ char()!.label }}\r\n </div>\r\n <div\r\n id=\"c-id\"\r\n [style.fontSize]=\"char()!.emSize / 2 + 'em'\"\r\n [style.color]=\"char()!.color | colorToContrast\"\r\n [style.borderColor]=\"char()!.borderColor\"\r\n [style.backgroundColor]=\"char()!.color\"\r\n >\r\n {{ char()!.id }}\r\n </div>\r\n</div>\r\n}\r\n", styles: ["div#container{cursor:pointer;flex-direction:column;align-items:center}div#c-label{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:6px;height:1.5em;align-items:center;justify-content:center;text-align:center}div#c-id{margin-top:4px;margin-bottom:16px;border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:6px;align-items:center;justify-content:center;text-align:center}\n"], dependencies: [{ kind: "ngmodule", type: MatRippleModule }, { kind: "directive", type: i1.MatRipple, selector: "[mat-ripple], [matRipple]", inputs: ["matRippleColor", "matRippleUnbounded", "matRippleCentered", "matRippleRadius", "matRippleAnimation", "matRippleDisabled", "matRippleTrigger"], exportAs: ["matRipple"] }, { kind: "pipe", type: ColorToContrastPipe, name: "colorToContrast" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
105
105
  }
106
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: BaseTextCharComponent, decorators: [{
106
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BaseTextCharComponent, decorators: [{
107
107
  type: Component,
108
108
  args: [{ selector: 'gve-base-text-char', imports: [MatRippleModule, ColorToContrastPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (char()) {\r\n<div matRipple id=\"container\" (click)=\"onCharClick($event)\" (dblclick)=\"onCharDblClick($event)\">\r\n <div\r\n id=\"c-label\"\r\n [style.fontSize]=\"char()!.emSize + 'em'\"\r\n [style.borderColor]=\"char()!.borderColor\"\r\n >\r\n {{ char()!.label }}\r\n </div>\r\n <div\r\n id=\"c-id\"\r\n [style.fontSize]=\"char()!.emSize / 2 + 'em'\"\r\n [style.color]=\"char()!.color | colorToContrast\"\r\n [style.borderColor]=\"char()!.borderColor\"\r\n [style.backgroundColor]=\"char()!.color\"\r\n >\r\n {{ char()!.id }}\r\n </div>\r\n</div>\r\n}\r\n", styles: ["div#container{cursor:pointer;flex-direction:column;align-items:center}div#c-label{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:6px;height:1.5em;align-items:center;justify-content:center;text-align:center}div#c-id{margin-top:4px;margin-bottom:16px;border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:6px;align-items:center;justify-content:center;text-align:center}\n"] }]
109
109
  }], propDecorators: { char: [{ type: i0.Input, args: [{ isSignal: true, alias: "char", required: false }] }], charPick: [{ type: i0.Output, args: ["charPick"] }], charDblPick: [{ type: i0.Output, args: ["charDblPick"] }] } });
@@ -633,10 +633,10 @@ class BaseTextViewComponent {
633
633
  this._clipboard.copy(ids);
634
634
  this._snackBar.open('IDs copied', 'OK', { duration: 2000 });
635
635
  }
636
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: BaseTextViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
637
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: BaseTextViewComponent, isStandalone: true, selector: "gve-base-text-view", inputs: { defaultColor: { classPropertyName: "defaultColor", publicName: "defaultColor", isSignal: true, isRequired: false, transformFunction: null }, defaultBorderColor: { classPropertyName: "defaultBorderColor", publicName: "defaultBorderColor", isSignal: true, isRequired: false, transformFunction: null }, selectionColor: { classPropertyName: "selectionColor", publicName: "selectionColor", isSignal: true, isRequired: false, transformFunction: null }, searchHighlightColor: { classPropertyName: "searchHighlightColor", publicName: "searchHighlightColor", isSignal: true, isRequired: false, transformFunction: null }, hasLineNumber: { classPropertyName: "hasLineNumber", publicName: "hasLineNumber", isSignal: true, isRequired: false, transformFunction: null }, text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null }, colorCallback: { classPropertyName: "colorCallback", publicName: "colorCallback", isSignal: true, isRequired: false, transformFunction: null }, borderColorCallback: { classPropertyName: "borderColorCallback", publicName: "borderColorCallback", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { charPick: "charPick", rangePick: "rangePick" }, ngImport: i0, template: "<!-- Toolbar -->\r\n<div class=\"toolbar\">\r\n <mat-form-field appearance=\"outline\" class=\"search-field\">\r\n <input\r\n matInput\r\n [ngModel]=\"searchQuery()\"\r\n (ngModelChange)=\"onSearchInput($event)\"\r\n placeholder=\"search\"\r\n />\r\n @if (searchQuery()) {\r\n <button\r\n type=\"button\"\r\n matSuffix\r\n mat-icon-button\r\n matTooltip=\"Clear search\"\r\n (click)=\"clearSearch()\"\r\n >\r\n <mat-icon class=\"mat-warn\">close</mat-icon>\r\n </button>\r\n }\r\n </mat-form-field>\r\n\r\n @if (isSearchActive()) {\r\n <span class=\"match-count\" [class.no-matches]=\"matchCount() === 0\">\r\n {{ matchCount() }}\r\n </span>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n mat-stroked-button\r\n matTooltip=\"Copy selection text to clipboard\"\r\n [disabled]=\"!hasSelection()\"\r\n (click)=\"copySelection()\"\r\n >\r\n <mat-icon>content_copy</mat-icon>\r\n copy\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n mat-stroked-button\r\n matTooltip=\"Copy selection coordinates to clipboard\"\r\n [disabled]=\"!hasSelection()\"\r\n (click)=\"copyCoordinates()\"\r\n >\r\n <mat-icon>pin_drop</mat-icon>\r\n copy coords\r\n </button>\r\n\r\n @if (pickedIds()) {\r\n <span class=\"picked-ids\" matTooltip=\"Double-clicked character IDs\">{{ pickedIds() }}</span>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Clear collected IDs\"\r\n (click)=\"clearPickedIds()\"\r\n >\r\n <mat-icon>clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Copy collected IDs to clipboard\"\r\n (click)=\"copyPickedIds()\"\r\n >\r\n <mat-icon>content_copy</mat-icon>\r\n </button>\r\n }\r\n</div>\r\n\r\n<!-- Text display -->\r\n<div id=\"text\" tabindex=\"0\" (keydown)=\"onKeyDown($event)\">\r\n @for (line of lines(); track $index) {\r\n <div class=\"line\">\r\n @if (hasLineNumber()) {\r\n <div class=\"nr\">\r\n {{ lines().indexOf(line) + 1 }}\r\n </div>\r\n }\r\n @for (c of line; track c.id) {\r\n <gve-base-text-char [char]=\"c\" (charPick)=\"onCharPick($event)\" (charDblPick)=\"onCharDblPick($event)\" />\r\n }\r\n </div>\r\n }\r\n</div>\r\n", styles: [".toolbar{display:flex;align-items:center;gap:8px;margin-bottom:8px;flex-wrap:wrap}.search-field{flex:0 0 auto;min-width:200px;max-width:300px}.match-count{font-weight:700;padding:4px 8px;border-radius:4px;color:var(--mat-sys-success)}.match-count.no-matches{background-color:var(--mat-sys-error-container);color:var(--mat-sys-on-error-container)}.line{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.line *{flex:0 0 auto}.nr{font-size:.8em;font-weight:700;color:var(--mat-sys-on-surface-variant);margin:0 4px}.picked-ids{font-family:monospace;font-size:.85em;padding:4px 8px;border-radius:4px;background-color:var(--mat-sys-primary-container);color:var(--mat-sys-on-primary-container);border:1px solid var(--mat-sys-primary);max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: BaseTextCharComponent, selector: "gve-base-text-char", inputs: ["char"], outputs: ["charPick", "charDblPick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
636
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BaseTextViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
637
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: BaseTextViewComponent, isStandalone: true, selector: "gve-base-text-view", inputs: { defaultColor: { classPropertyName: "defaultColor", publicName: "defaultColor", isSignal: true, isRequired: false, transformFunction: null }, defaultBorderColor: { classPropertyName: "defaultBorderColor", publicName: "defaultBorderColor", isSignal: true, isRequired: false, transformFunction: null }, selectionColor: { classPropertyName: "selectionColor", publicName: "selectionColor", isSignal: true, isRequired: false, transformFunction: null }, searchHighlightColor: { classPropertyName: "searchHighlightColor", publicName: "searchHighlightColor", isSignal: true, isRequired: false, transformFunction: null }, hasLineNumber: { classPropertyName: "hasLineNumber", publicName: "hasLineNumber", isSignal: true, isRequired: false, transformFunction: null }, text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null }, colorCallback: { classPropertyName: "colorCallback", publicName: "colorCallback", isSignal: true, isRequired: false, transformFunction: null }, borderColorCallback: { classPropertyName: "borderColorCallback", publicName: "borderColorCallback", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { charPick: "charPick", rangePick: "rangePick" }, ngImport: i0, template: "<!-- Toolbar -->\r\n<div class=\"toolbar\">\r\n <mat-form-field appearance=\"outline\" class=\"search-field\">\r\n <input\r\n matInput\r\n [ngModel]=\"searchQuery()\"\r\n (ngModelChange)=\"onSearchInput($event)\"\r\n placeholder=\"search\"\r\n />\r\n @if (searchQuery()) {\r\n <button\r\n type=\"button\"\r\n matSuffix\r\n mat-icon-button\r\n matTooltip=\"Clear search\"\r\n (click)=\"clearSearch()\"\r\n >\r\n <mat-icon class=\"mat-warn\">close</mat-icon>\r\n </button>\r\n }\r\n </mat-form-field>\r\n\r\n @if (isSearchActive()) {\r\n <span class=\"match-count\" [class.no-matches]=\"matchCount() === 0\">\r\n {{ matchCount() }}\r\n </span>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n mat-stroked-button\r\n matTooltip=\"Copy selection text to clipboard\"\r\n [disabled]=\"!hasSelection()\"\r\n (click)=\"copySelection()\"\r\n >\r\n <mat-icon>content_copy</mat-icon>\r\n copy\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n mat-stroked-button\r\n matTooltip=\"Copy selection coordinates to clipboard\"\r\n [disabled]=\"!hasSelection()\"\r\n (click)=\"copyCoordinates()\"\r\n >\r\n <mat-icon>pin_drop</mat-icon>\r\n copy coords\r\n </button>\r\n\r\n @if (pickedIds()) {\r\n <span class=\"picked-ids\" matTooltip=\"Double-clicked character IDs\">{{ pickedIds() }}</span>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Clear collected IDs\"\r\n (click)=\"clearPickedIds()\"\r\n >\r\n <mat-icon>clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Copy collected IDs to clipboard\"\r\n (click)=\"copyPickedIds()\"\r\n >\r\n <mat-icon>content_copy</mat-icon>\r\n </button>\r\n }\r\n</div>\r\n\r\n<!-- Text display -->\r\n<div id=\"text\" tabindex=\"0\" (keydown)=\"onKeyDown($event)\">\r\n @for (line of lines(); track $index) {\r\n <div class=\"line\">\r\n @if (hasLineNumber()) {\r\n <div class=\"nr\">\r\n {{ lines().indexOf(line) + 1 }}\r\n </div>\r\n }\r\n @for (c of line; track c.id) {\r\n <gve-base-text-char [char]=\"c\" (charPick)=\"onCharPick($event)\" (charDblPick)=\"onCharDblPick($event)\" />\r\n }\r\n </div>\r\n }\r\n</div>\r\n", styles: [".toolbar{display:flex;align-items:center;gap:8px;margin-bottom:8px;flex-wrap:wrap}.search-field{flex:0 0 auto;min-width:200px;max-width:300px}.match-count{font-weight:700;padding:4px 8px;border-radius:4px;color:var(--mat-sys-success)}.match-count.no-matches{background-color:var(--mat-sys-error-container);color:var(--mat-sys-on-error-container)}.line{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.line *{flex:0 0 auto}.nr{font-size:.8em;font-weight:700;color:var(--mat-sys-on-surface-variant);margin:0 4px}.picked-ids{font-family:monospace;font-size:.85em;padding:4px 8px;border-radius:4px;background-color:var(--mat-sys-primary-container);color:var(--mat-sys-on-primary-container);border:1px solid var(--mat-sys-primary);max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: BaseTextCharComponent, selector: "gve-base-text-char", inputs: ["char"], outputs: ["charPick", "charDblPick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
638
638
  }
639
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: BaseTextViewComponent, decorators: [{
639
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BaseTextViewComponent, decorators: [{
640
640
  type: Component,
641
641
  args: [{ selector: 'gve-base-text-view', imports: [
642
642
  FormsModule,
@@ -852,10 +852,10 @@ class FeatureEditorComponent {
852
852
  setPolicy: this.setPolicy.value,
853
853
  });
854
854
  }
855
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FeatureEditorComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
856
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: FeatureEditorComponent, isStandalone: true, selector: "gve-feature-editor", inputs: { featNames: { classPropertyName: "featNames", publicName: "featNames", isSignal: true, isRequired: false, transformFunction: null }, featValues: { classPropertyName: "featValues", publicName: "featValues", isSignal: true, isRequired: false, transformFunction: null }, feature: { classPropertyName: "feature", publicName: "feature", isSignal: true, isRequired: false, transformFunction: null }, isVar: { classPropertyName: "isVar", publicName: "isVar", isSignal: true, isRequired: false, transformFunction: null }, multiValuedFeatureIds: { classPropertyName: "multiValuedFeatureIds", publicName: "multiValuedFeatureIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { feature: "featureChange", featureCancel: "featureCancel" }, viewQueries: [{ propertyName: "nameControl", first: true, predicate: ["nameCtl"], descendants: true, isSignal: true }], ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <div class=\"form-row\">\r\n @if (featNames()?.length) {\r\n <!-- name (bound) -->\r\n <mat-form-field>\r\n <mat-label>name</mat-label>\r\n <mat-select #nameCtl [formControl]=\"name\">\r\n @for (i of featNames(); track i) {\r\n <mat-option [value]=\"i.id\">{{ i.label }}</mat-option>\r\n }\r\n </mat-select>\r\n @if ($any(name).errors?.required && (name.dirty || name.touched)) {\r\n <mat-error>name required</mat-error>\r\n }\r\n </mat-form-field>\r\n } @else {\r\n <!-- name (free) -->\r\n <mat-form-field>\r\n <mat-label>name</mat-label>\r\n <input #nameCtl matInput [formControl]=\"name\" />\r\n @if ($any(name).errors?.required && (name.dirty || name.touched)) {\r\n <mat-error>name required</mat-error>\r\n }\r\n @if ($any(name).errors?.maxLength && (name.dirty || name.touched)) {\r\n <mat-error>name too long</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n\r\n <!-- value (bound) -->\r\n @if (nameIds()?.length) {\r\n @if (isMultiValued()) {\r\n <!-- multi-valued: select for picking + text for composite -->\r\n <mat-form-field>\r\n <mat-label>select value</mat-label>\r\n <mat-select [formControl]=\"selectedValue\">\r\n @for (e of nameIds(); track e) {\r\n <mat-option [value]=\"e.id\">{{ e.label }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n <button\r\n type=\"button\"\r\n color=\"primary\"\r\n mat-icon-button\r\n matTooltip=\"Add selected value\"\r\n (click)=\"addValueToComposite()\"\r\n [disabled]=\"!selectedValue.value\"\r\n >\r\n <mat-icon class=\"mat-primary\">add</mat-icon>\r\n </button>\r\n <mat-form-field>\r\n <mat-label>composite value</mat-label>\r\n <input matInput [formControl]=\"value\" />\r\n @if (\r\n $any(value).errors?.maxLength && (value.dirty || value.touched)\r\n ) {\r\n <mat-error>value too long</mat-error>\r\n }\r\n </mat-form-field>\r\n } @else {\r\n <!-- single-valued: just select -->\r\n <mat-form-field>\r\n <mat-label>value</mat-label>\r\n <mat-select [formControl]=\"value\">\r\n @for (e of nameIds(); track e) {\r\n <mat-option [value]=\"e.id\">{{ e.label }}</mat-option>\r\n }\r\n </mat-select>\r\n @if ($any(value).errors?.required && (value.dirty || value.touched)) {\r\n <mat-error>value required</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n } @else {\r\n <!-- value (free) -->\r\n <mat-form-field>\r\n <mat-label>value</mat-label>\r\n <input matInput [formControl]=\"value\" />\r\n @if ($any(value).errors?.required && (value.dirty || value.touched)) {\r\n <mat-error>value required</mat-error>\r\n }\r\n @if ($any(value).errors?.maxLength && (value.dirty || value.touched)) {\r\n <mat-error>value too long</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n\r\n <!-- setPolicy -->\r\n <mat-form-field>\r\n <mat-label>set policy</mat-label>\r\n <mat-select [formControl]=\"setPolicy\">\r\n <mat-option [value]=\"0\">multiple</mat-option>\r\n <mat-option [value]=\"1\">single</mat-option>\r\n <mat-option [value]=\"2\">single first</mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n @if (isVar()) {\r\n <!-- isNegated -->\r\n <mat-checkbox [formControl]=\"isNegated\"\r\n ><mat-icon class=\"mat-warn\">remove_circle</mat-icon>\r\n negated</mat-checkbox\r\n >\r\n\r\n <!-- isShortLived -->\r\n <mat-checkbox [formControl]=\"isShortLived\"\r\n ><mat-icon class=\"mat-warn\">timer</mat-icon> short-lived</mat-checkbox\r\n >\r\n\r\n <!-- isGlobal -->\r\n <mat-checkbox [formControl]=\"isGlobal\"\r\n ><mat-icon class=\"mat-primary\">public</mat-icon> global</mat-checkbox\r\n >\r\n }\r\n </div>\r\n\r\n <!-- buttons -->\r\n <div class=\"form-row\">\r\n <button\r\n type=\"button\"\r\n color=\"warn\"\r\n mat-icon-button\r\n matTooltip=\"Discard changes\"\r\n (click)=\"cancel()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"submit\"\r\n color=\"primary\"\r\n mat-icon-button\r\n matTooltip=\"Accept changes\"\r\n [disabled]=\"form.invalid || form.pristine\"\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: ReactiveFormsModule }, { 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.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: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i3$1.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: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.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: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
855
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: FeatureEditorComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
856
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: FeatureEditorComponent, isStandalone: true, selector: "gve-feature-editor", inputs: { featNames: { classPropertyName: "featNames", publicName: "featNames", isSignal: true, isRequired: false, transformFunction: null }, featValues: { classPropertyName: "featValues", publicName: "featValues", isSignal: true, isRequired: false, transformFunction: null }, feature: { classPropertyName: "feature", publicName: "feature", isSignal: true, isRequired: false, transformFunction: null }, isVar: { classPropertyName: "isVar", publicName: "isVar", isSignal: true, isRequired: false, transformFunction: null }, multiValuedFeatureIds: { classPropertyName: "multiValuedFeatureIds", publicName: "multiValuedFeatureIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { feature: "featureChange", featureCancel: "featureCancel" }, viewQueries: [{ propertyName: "nameControl", first: true, predicate: ["nameCtl"], descendants: true, isSignal: true }], ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <div class=\"form-row\">\r\n @if (featNames()?.length) {\r\n <!-- name (bound) -->\r\n <mat-form-field>\r\n <mat-label>name</mat-label>\r\n <mat-select #nameCtl [formControl]=\"name\">\r\n @for (i of featNames(); track i) {\r\n <mat-option [value]=\"i.id\">{{ i.label }}</mat-option>\r\n }\r\n </mat-select>\r\n @if ($any(name).errors?.required && (name.dirty || name.touched)) {\r\n <mat-error>name required</mat-error>\r\n }\r\n </mat-form-field>\r\n } @else {\r\n <!-- name (free) -->\r\n <mat-form-field>\r\n <mat-label>name</mat-label>\r\n <input #nameCtl matInput [formControl]=\"name\" />\r\n @if ($any(name).errors?.required && (name.dirty || name.touched)) {\r\n <mat-error>name required</mat-error>\r\n }\r\n @if ($any(name).errors?.maxLength && (name.dirty || name.touched)) {\r\n <mat-error>name too long</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n\r\n <!-- value (bound) -->\r\n @if (nameIds()?.length) {\r\n @if (isMultiValued()) {\r\n <!-- multi-valued: select for picking + text for composite -->\r\n <mat-form-field>\r\n <mat-label>select value</mat-label>\r\n <mat-select [formControl]=\"selectedValue\">\r\n @for (e of nameIds(); track e) {\r\n <mat-option [value]=\"e.id\">{{ e.label }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n <button\r\n type=\"button\"\r\n color=\"primary\"\r\n mat-icon-button\r\n matTooltip=\"Add selected value\"\r\n (click)=\"addValueToComposite()\"\r\n [disabled]=\"!selectedValue.value\"\r\n >\r\n <mat-icon class=\"mat-primary\">add</mat-icon>\r\n </button>\r\n <mat-form-field>\r\n <mat-label>composite value</mat-label>\r\n <input matInput [formControl]=\"value\" />\r\n @if (\r\n $any(value).errors?.maxLength && (value.dirty || value.touched)\r\n ) {\r\n <mat-error>value too long</mat-error>\r\n }\r\n </mat-form-field>\r\n } @else {\r\n <!-- single-valued: just select -->\r\n <mat-form-field>\r\n <mat-label>value</mat-label>\r\n <mat-select [formControl]=\"value\">\r\n @for (e of nameIds(); track e) {\r\n <mat-option [value]=\"e.id\">{{ e.label }}</mat-option>\r\n }\r\n </mat-select>\r\n @if ($any(value).errors?.required && (value.dirty || value.touched)) {\r\n <mat-error>value required</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n } @else {\r\n <!-- value (free) -->\r\n <mat-form-field>\r\n <mat-label>value</mat-label>\r\n <input matInput [formControl]=\"value\" />\r\n @if ($any(value).errors?.required && (value.dirty || value.touched)) {\r\n <mat-error>value required</mat-error>\r\n }\r\n @if ($any(value).errors?.maxLength && (value.dirty || value.touched)) {\r\n <mat-error>value too long</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n\r\n <!-- setPolicy -->\r\n <mat-form-field>\r\n <mat-label>set policy</mat-label>\r\n <mat-select [formControl]=\"setPolicy\">\r\n <mat-option [value]=\"0\">multiple</mat-option>\r\n <mat-option [value]=\"1\">single</mat-option>\r\n <mat-option [value]=\"2\">single first</mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n @if (isVar()) {\r\n <!-- isNegated -->\r\n <mat-checkbox [formControl]=\"isNegated\"\r\n ><mat-icon class=\"mat-warn\">remove_circle</mat-icon>\r\n negated</mat-checkbox\r\n >\r\n\r\n <!-- isShortLived -->\r\n <mat-checkbox [formControl]=\"isShortLived\"\r\n ><mat-icon class=\"mat-warn\">timer</mat-icon> short-lived</mat-checkbox\r\n >\r\n\r\n <!-- isGlobal -->\r\n <mat-checkbox [formControl]=\"isGlobal\"\r\n ><mat-icon class=\"mat-primary\">public</mat-icon> global</mat-checkbox\r\n >\r\n }\r\n </div>\r\n\r\n <!-- buttons -->\r\n <div class=\"form-row\">\r\n <button\r\n type=\"button\"\r\n color=\"warn\"\r\n mat-icon-button\r\n matTooltip=\"Discard changes\"\r\n (click)=\"cancel()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"submit\"\r\n color=\"primary\"\r\n mat-icon-button\r\n matTooltip=\"Accept changes\"\r\n [disabled]=\"form.invalid || form.pristine\"\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: ReactiveFormsModule }, { 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.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: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i3$1.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: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.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: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
857
857
  }
858
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FeatureEditorComponent, decorators: [{
858
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: FeatureEditorComponent, decorators: [{
859
859
  type: Component,
860
860
  args: [{ selector: 'gve-feature-editor', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
861
861
  ReactiveFormsModule,
@@ -892,6 +892,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
892
892
  * will allow adding multiple values to it.
893
893
  * - ▶️ `isVar`: true if the feature is a variant operation feature, which
894
894
  * has additional properties like negation, global, and short-lived.
895
+ * - ▶️ `hasCloseButton`: true if a close button should be shown to allow closing
896
+ * the editor.
897
+ * - 🔥 `close`: emitted when the user clicks the close button.
895
898
  * - 🔥 `featuresChange` (`Feature[]`): emitted when features have changed.
896
899
  */
897
900
  class FeatureSetEditorComponent {
@@ -928,6 +931,15 @@ class FeatureSetEditorComponent {
928
931
  this.multiValuedFeatureIds = input([
929
932
  'r_hints',
930
933
  ], ...(ngDevMode ? [{ debugName: "multiValuedFeatureIds" }] : /* istanbul ignore next */ []));
934
+ /**
935
+ * True if a close button should be shown to allow closing the editor
936
+ * without saving.
937
+ */
938
+ this.hasCloseButton = input(false, ...(ngDevMode ? [{ debugName: "hasCloseButton" }] : /* istanbul ignore next */ []));
939
+ /**
940
+ * Emitted when the user clicks the close button.
941
+ */
942
+ this.close = output();
931
943
  /**
932
944
  * The features to edit.
933
945
  */
@@ -1010,10 +1022,10 @@ class FeatureSetEditorComponent {
1010
1022
  this.editedFeature.set(undefined);
1011
1023
  this.editedFeatureIndex.set(-1);
1012
1024
  }
1013
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FeatureSetEditorComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
1014
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: FeatureSetEditorComponent, isStandalone: true, selector: "gve-feature-set-editor", inputs: { isVar: { classPropertyName: "isVar", publicName: "isVar", isSignal: true, isRequired: false, transformFunction: null }, featNames: { classPropertyName: "featNames", publicName: "featNames", isSignal: true, isRequired: false, transformFunction: null }, featValues: { classPropertyName: "featValues", publicName: "featValues", isSignal: true, isRequired: false, transformFunction: null }, filterThreshold: { classPropertyName: "filterThreshold", publicName: "filterThreshold", isSignal: true, isRequired: false, transformFunction: null }, multiValuedFeatureIds: { classPropertyName: "multiValuedFeatureIds", publicName: "multiValuedFeatureIds", isSignal: true, isRequired: false, transformFunction: null }, features: { classPropertyName: "features", publicName: "features", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { features: "featuresChange" }, ngImport: i0, template: "<div>\r\n <div class=\"form-row\">\r\n <!-- filter -->\r\n @if (filterThreshold() === 0 || features().length >= filterThreshold()) {\r\n <mat-form-field>\r\n <label>filter</label>\r\n <input matInput [formControl]=\"filter\" />\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matSuffix\r\n (click)=\"filter.reset()\"\r\n [disabled]=\"!filter.value\"\r\n [attr.aria-label]=\"'Reset filter'\"\r\n >\r\n <mat-icon color=\"warn\" class=\"mat-warn\">cancel</mat-icon>\r\n </button>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- add feature button -->\r\n <button\r\n type=\"button\"\r\n color=\"primary\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Add a new feature\"\r\n (click)=\"addFeature()\"\r\n [class.in-row-button]=\"\r\n filterThreshold() === 0 || features()!.length >= filterThreshold()\r\n \"\r\n >\r\n <mat-icon>add</mat-icon>\r\n feature\r\n </button>\r\n </div>\r\n\r\n <!-- list -->\r\n @if (features().length) {\r\n <table>\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>feature</th>\r\n <th>value</th>\r\n <th>policy</th>\r\n @if (isVar()) {\r\n <th>flags</th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (feature of filteredFeatures(); track $index) {\r\n <tr [class.selected]=\"$index === editedFeatureIndex()\">\r\n <td class=\"fit-width\">\r\n <!-- edit -->\r\n <button\r\n type=\"button\"\r\n color=\"primary\"\r\n mat-icon-button\r\n matTooltip=\"Edit this feature\"\r\n (click)=\"editFeature(feature)\"\r\n >\r\n <mat-icon class=\"mat-primary\">edit</mat-icon>\r\n </button>\r\n <!-- delete -->\r\n <button\r\n type=\"button\"\r\n color=\"warn\"\r\n mat-icon-button\r\n matTooltip=\"Delete this feature\"\r\n (click)=\"deleteFeature(feature)\"\r\n >\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n </td>\r\n <td>\r\n @if (featNames()?.length) {\r\n <span>{{\r\n feature.name | flatLookup: featNames() : \"id\" : \"label\"\r\n }}</span>\r\n } @else {\r\n <span>{{ feature.name }}</span>\r\n }\r\n </td>\r\n <td>\r\n @if (featValues()) {\r\n <span>{{\r\n feature.value | flatLookup: featValues()![feature.name]\r\n }}</span>\r\n } @else {\r\n <span>{{ feature.value }}</span>\r\n }\r\n </td>\r\n <td>\r\n <span>{{ POLICIES[feature.setPolicy] }}</span>\r\n </td>\r\n @if (isVar()) {\r\n <td>\r\n @if ($any(feature).isNegated) {\r\n <span class=\"icon\" matTooltip=\"Negated: remove if present\">\r\n <mat-icon class=\"mat-warn\">remove_circle</mat-icon>\r\n </span>\r\n }\r\n @if ($any(feature).isGlobal) {\r\n <span class=\"icon\" matTooltip=\"Global: apply to whole output\">\r\n <mat-icon class=\"mat-primary\">public</mat-icon>\r\n </span>\r\n }\r\n @if ($any(feature).isShortLived) {\r\n <span\r\n class=\"icon\"\r\n matTooltip=\"Short-lived: removed on next operation\"\r\n >\r\n <mat-icon class=\"mat-accent\">timer</mat-icon>\r\n </span>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n }\r\n\r\n <!-- editor -->\r\n @if (editedFeature()) {\r\n <mat-expansion-panel\r\n [disabled]=\"!editedFeature()\"\r\n [expanded]=\"editedFeature()\"\r\n >\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>\r\n <span>{{ editedFeature()?.name || \"feature\" }}</span>\r\n </mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <gve-feature-editor\r\n [isVar]=\"isVar()\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n [featNames]=\"featNames()\"\r\n [featValues]=\"featValues()\"\r\n [feature]=\"editedFeature()\"\r\n (featureChange)=\"onFeatureChange($event)\"\r\n (featureCancel)=\"onFeatureCancel()\"\r\n />\r\n </mat-expansion-panel>\r\n }\r\n</div>\r\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}.in-row-button{margin-top:-16px}table{margin-top:8px;width:100%;border-collapse:collapse;color:var(--mat-sys-on-surface-variant)}tbody tr:nth-child(odd){background-color:var(--mat-sys-surface-container)}th{text-align:left;font-weight:400;color:var(--mat-sys-on-surface-variant)}tbody tr:hover{background-color:var(--mat-sys-surface-container-high)}td.fit-width{width:1px;white-space:nowrap}tbody tr.selected{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i7$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i7$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i7$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "ngmodule", type: MatSelectModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: FeatureEditorComponent, selector: "gve-feature-editor", inputs: ["featNames", "featValues", "feature", "isVar", "multiValuedFeatureIds"], outputs: ["featureChange", "featureCancel"] }, { kind: "pipe", type: FlatLookupPipe, name: "flatLookup" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1025
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: FeatureSetEditorComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
1026
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: FeatureSetEditorComponent, isStandalone: true, selector: "gve-feature-set-editor", inputs: { isVar: { classPropertyName: "isVar", publicName: "isVar", isSignal: true, isRequired: false, transformFunction: null }, featNames: { classPropertyName: "featNames", publicName: "featNames", isSignal: true, isRequired: false, transformFunction: null }, featValues: { classPropertyName: "featValues", publicName: "featValues", isSignal: true, isRequired: false, transformFunction: null }, filterThreshold: { classPropertyName: "filterThreshold", publicName: "filterThreshold", isSignal: true, isRequired: false, transformFunction: null }, multiValuedFeatureIds: { classPropertyName: "multiValuedFeatureIds", publicName: "multiValuedFeatureIds", isSignal: true, isRequired: false, transformFunction: null }, hasCloseButton: { classPropertyName: "hasCloseButton", publicName: "hasCloseButton", isSignal: true, isRequired: false, transformFunction: null }, features: { classPropertyName: "features", publicName: "features", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { close: "close", features: "featuresChange" }, ngImport: i0, template: "<div>\r\n <div class=\"form-row\">\r\n <!-- filter -->\r\n @if (filterThreshold() === 0 || features().length >= filterThreshold()) {\r\n <mat-form-field>\r\n <label>filter</label>\r\n <input matInput [formControl]=\"filter\" />\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matSuffix\r\n (click)=\"filter.reset()\"\r\n [disabled]=\"!filter.value\"\r\n [attr.aria-label]=\"'Reset filter'\"\r\n >\r\n <mat-icon color=\"warn\" class=\"mat-warn\">cancel</mat-icon>\r\n </button>\r\n </mat-form-field>\r\n }\r\n\r\n <div class=\"form-row\">\r\n @if (hasCloseButton()) {\r\n <!-- close editor button -->\r\n <button\r\n type=\"button\"\r\n color=\"warn\"\r\n class=\"mat-warn\"\r\n mat-flat-button\r\n matTooltip=\"Close features editor\"\r\n (click)=\"close.emit()\"\r\n >\r\n <mat-icon>close</mat-icon>\r\n close\r\n </button>\r\n }\r\n <!-- add feature button -->\r\n <button\r\n type=\"button\"\r\n color=\"primary\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Add a new feature\"\r\n (click)=\"addFeature()\"\r\n [class.in-row-button]=\"\r\n filterThreshold() === 0 || features()!.length >= filterThreshold()\r\n \"\r\n >\r\n <mat-icon>add</mat-icon>\r\n feature\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- list -->\r\n @if (features().length) {\r\n <table>\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>feature</th>\r\n <th>value</th>\r\n <th>policy</th>\r\n @if (isVar()) {\r\n <th>flags</th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (feature of filteredFeatures(); track $index) {\r\n <tr [class.selected]=\"$index === editedFeatureIndex()\">\r\n <td class=\"fit-width\">\r\n <!-- edit -->\r\n <button\r\n type=\"button\"\r\n color=\"primary\"\r\n mat-icon-button\r\n matTooltip=\"Edit this feature\"\r\n (click)=\"editFeature(feature)\"\r\n >\r\n <mat-icon class=\"mat-primary\">edit</mat-icon>\r\n </button>\r\n <!-- delete -->\r\n <button\r\n type=\"button\"\r\n color=\"warn\"\r\n mat-icon-button\r\n matTooltip=\"Delete this feature\"\r\n (click)=\"deleteFeature(feature)\"\r\n >\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n </td>\r\n <td>\r\n @if (featNames()?.length) {\r\n <span>{{\r\n feature.name | flatLookup: featNames() : \"id\" : \"label\"\r\n }}</span>\r\n } @else {\r\n <span>{{ feature.name }}</span>\r\n }\r\n </td>\r\n <td>\r\n @if (featValues()) {\r\n <span>{{\r\n feature.value | flatLookup: featValues()![feature.name]\r\n }}</span>\r\n } @else {\r\n <span>{{ feature.value }}</span>\r\n }\r\n </td>\r\n <td>\r\n <span>{{ POLICIES[feature.setPolicy] }}</span>\r\n </td>\r\n @if (isVar()) {\r\n <td>\r\n @if ($any(feature).isNegated) {\r\n <span class=\"icon\" matTooltip=\"Negated: remove if present\">\r\n <mat-icon class=\"mat-warn\">remove_circle</mat-icon>\r\n </span>\r\n }\r\n @if ($any(feature).isGlobal) {\r\n <span class=\"icon\" matTooltip=\"Global: apply to whole output\">\r\n <mat-icon class=\"mat-primary\">public</mat-icon>\r\n </span>\r\n }\r\n @if ($any(feature).isShortLived) {\r\n <span\r\n class=\"icon\"\r\n matTooltip=\"Short-lived: removed on next operation\"\r\n >\r\n <mat-icon class=\"mat-accent\">timer</mat-icon>\r\n </span>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n }\r\n\r\n <!-- editor -->\r\n @if (editedFeature()) {\r\n <mat-expansion-panel\r\n [disabled]=\"!editedFeature()\"\r\n [expanded]=\"editedFeature()\"\r\n >\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>\r\n <span>{{ editedFeature()?.name || \"feature\" }}</span>\r\n </mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <gve-feature-editor\r\n [isVar]=\"isVar()\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n [featNames]=\"featNames()\"\r\n [featValues]=\"featValues()\"\r\n [feature]=\"editedFeature()\"\r\n (featureChange)=\"onFeatureChange($event)\"\r\n (featureCancel)=\"onFeatureCancel()\"\r\n />\r\n </mat-expansion-panel>\r\n }\r\n</div>\r\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}.in-row-button{margin-top:-16px}table{margin-top:8px;width:100%;border-collapse:collapse;color:var(--mat-sys-on-surface-variant)}tbody tr:nth-child(odd){background-color:var(--mat-sys-surface-container)}th{text-align:left;font-weight:400;color:var(--mat-sys-on-surface-variant)}tbody tr:hover{background-color:var(--mat-sys-surface-container-high)}td.fit-width{width:1px;white-space:nowrap}tbody tr.selected{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i7$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i7$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i7$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "ngmodule", type: MatSelectModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: FeatureEditorComponent, selector: "gve-feature-editor", inputs: ["featNames", "featValues", "feature", "isVar", "multiValuedFeatureIds"], outputs: ["featureChange", "featureCancel"] }, { kind: "pipe", type: FlatLookupPipe, name: "flatLookup" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1015
1027
  }
1016
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FeatureSetEditorComponent, decorators: [{
1028
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: FeatureSetEditorComponent, decorators: [{
1017
1029
  type: Component,
1018
1030
  args: [{ selector: 'gve-feature-set-editor', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
1019
1031
  ReactiveFormsModule,
@@ -1026,8 +1038,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
1026
1038
  MatTooltipModule,
1027
1039
  FlatLookupPipe,
1028
1040
  FeatureEditorComponent,
1029
- ], template: "<div>\r\n <div class=\"form-row\">\r\n <!-- filter -->\r\n @if (filterThreshold() === 0 || features().length >= filterThreshold()) {\r\n <mat-form-field>\r\n <label>filter</label>\r\n <input matInput [formControl]=\"filter\" />\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matSuffix\r\n (click)=\"filter.reset()\"\r\n [disabled]=\"!filter.value\"\r\n [attr.aria-label]=\"'Reset filter'\"\r\n >\r\n <mat-icon color=\"warn\" class=\"mat-warn\">cancel</mat-icon>\r\n </button>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- add feature button -->\r\n <button\r\n type=\"button\"\r\n color=\"primary\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Add a new feature\"\r\n (click)=\"addFeature()\"\r\n [class.in-row-button]=\"\r\n filterThreshold() === 0 || features()!.length >= filterThreshold()\r\n \"\r\n >\r\n <mat-icon>add</mat-icon>\r\n feature\r\n </button>\r\n </div>\r\n\r\n <!-- list -->\r\n @if (features().length) {\r\n <table>\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>feature</th>\r\n <th>value</th>\r\n <th>policy</th>\r\n @if (isVar()) {\r\n <th>flags</th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (feature of filteredFeatures(); track $index) {\r\n <tr [class.selected]=\"$index === editedFeatureIndex()\">\r\n <td class=\"fit-width\">\r\n <!-- edit -->\r\n <button\r\n type=\"button\"\r\n color=\"primary\"\r\n mat-icon-button\r\n matTooltip=\"Edit this feature\"\r\n (click)=\"editFeature(feature)\"\r\n >\r\n <mat-icon class=\"mat-primary\">edit</mat-icon>\r\n </button>\r\n <!-- delete -->\r\n <button\r\n type=\"button\"\r\n color=\"warn\"\r\n mat-icon-button\r\n matTooltip=\"Delete this feature\"\r\n (click)=\"deleteFeature(feature)\"\r\n >\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n </td>\r\n <td>\r\n @if (featNames()?.length) {\r\n <span>{{\r\n feature.name | flatLookup: featNames() : \"id\" : \"label\"\r\n }}</span>\r\n } @else {\r\n <span>{{ feature.name }}</span>\r\n }\r\n </td>\r\n <td>\r\n @if (featValues()) {\r\n <span>{{\r\n feature.value | flatLookup: featValues()![feature.name]\r\n }}</span>\r\n } @else {\r\n <span>{{ feature.value }}</span>\r\n }\r\n </td>\r\n <td>\r\n <span>{{ POLICIES[feature.setPolicy] }}</span>\r\n </td>\r\n @if (isVar()) {\r\n <td>\r\n @if ($any(feature).isNegated) {\r\n <span class=\"icon\" matTooltip=\"Negated: remove if present\">\r\n <mat-icon class=\"mat-warn\">remove_circle</mat-icon>\r\n </span>\r\n }\r\n @if ($any(feature).isGlobal) {\r\n <span class=\"icon\" matTooltip=\"Global: apply to whole output\">\r\n <mat-icon class=\"mat-primary\">public</mat-icon>\r\n </span>\r\n }\r\n @if ($any(feature).isShortLived) {\r\n <span\r\n class=\"icon\"\r\n matTooltip=\"Short-lived: removed on next operation\"\r\n >\r\n <mat-icon class=\"mat-accent\">timer</mat-icon>\r\n </span>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n }\r\n\r\n <!-- editor -->\r\n @if (editedFeature()) {\r\n <mat-expansion-panel\r\n [disabled]=\"!editedFeature()\"\r\n [expanded]=\"editedFeature()\"\r\n >\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>\r\n <span>{{ editedFeature()?.name || \"feature\" }}</span>\r\n </mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <gve-feature-editor\r\n [isVar]=\"isVar()\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n [featNames]=\"featNames()\"\r\n [featValues]=\"featValues()\"\r\n [feature]=\"editedFeature()\"\r\n (featureChange)=\"onFeatureChange($event)\"\r\n (featureCancel)=\"onFeatureCancel()\"\r\n />\r\n </mat-expansion-panel>\r\n }\r\n</div>\r\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}.in-row-button{margin-top:-16px}table{margin-top:8px;width:100%;border-collapse:collapse;color:var(--mat-sys-on-surface-variant)}tbody tr:nth-child(odd){background-color:var(--mat-sys-surface-container)}th{text-align:left;font-weight:400;color:var(--mat-sys-on-surface-variant)}tbody tr:hover{background-color:var(--mat-sys-surface-container-high)}td.fit-width{width:1px;white-space:nowrap}tbody tr.selected{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}\n"] }]
1030
- }], ctorParameters: () => [{ type: i1$1.FormBuilder }], propDecorators: { isVar: [{ type: i0.Input, args: [{ isSignal: true, alias: "isVar", required: false }] }], featNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "featNames", required: false }] }], featValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "featValues", required: false }] }], filterThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterThreshold", required: false }] }], multiValuedFeatureIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiValuedFeatureIds", required: false }] }], features: [{ type: i0.Input, args: [{ isSignal: true, alias: "features", required: false }] }, { type: i0.Output, args: ["featuresChange"] }] } });
1041
+ ], template: "<div>\r\n <div class=\"form-row\">\r\n <!-- filter -->\r\n @if (filterThreshold() === 0 || features().length >= filterThreshold()) {\r\n <mat-form-field>\r\n <label>filter</label>\r\n <input matInput [formControl]=\"filter\" />\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matSuffix\r\n (click)=\"filter.reset()\"\r\n [disabled]=\"!filter.value\"\r\n [attr.aria-label]=\"'Reset filter'\"\r\n >\r\n <mat-icon color=\"warn\" class=\"mat-warn\">cancel</mat-icon>\r\n </button>\r\n </mat-form-field>\r\n }\r\n\r\n <div class=\"form-row\">\r\n @if (hasCloseButton()) {\r\n <!-- close editor button -->\r\n <button\r\n type=\"button\"\r\n color=\"warn\"\r\n class=\"mat-warn\"\r\n mat-flat-button\r\n matTooltip=\"Close features editor\"\r\n (click)=\"close.emit()\"\r\n >\r\n <mat-icon>close</mat-icon>\r\n close\r\n </button>\r\n }\r\n <!-- add feature button -->\r\n <button\r\n type=\"button\"\r\n color=\"primary\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Add a new feature\"\r\n (click)=\"addFeature()\"\r\n [class.in-row-button]=\"\r\n filterThreshold() === 0 || features()!.length >= filterThreshold()\r\n \"\r\n >\r\n <mat-icon>add</mat-icon>\r\n feature\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- list -->\r\n @if (features().length) {\r\n <table>\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>feature</th>\r\n <th>value</th>\r\n <th>policy</th>\r\n @if (isVar()) {\r\n <th>flags</th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (feature of filteredFeatures(); track $index) {\r\n <tr [class.selected]=\"$index === editedFeatureIndex()\">\r\n <td class=\"fit-width\">\r\n <!-- edit -->\r\n <button\r\n type=\"button\"\r\n color=\"primary\"\r\n mat-icon-button\r\n matTooltip=\"Edit this feature\"\r\n (click)=\"editFeature(feature)\"\r\n >\r\n <mat-icon class=\"mat-primary\">edit</mat-icon>\r\n </button>\r\n <!-- delete -->\r\n <button\r\n type=\"button\"\r\n color=\"warn\"\r\n mat-icon-button\r\n matTooltip=\"Delete this feature\"\r\n (click)=\"deleteFeature(feature)\"\r\n >\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n </td>\r\n <td>\r\n @if (featNames()?.length) {\r\n <span>{{\r\n feature.name | flatLookup: featNames() : \"id\" : \"label\"\r\n }}</span>\r\n } @else {\r\n <span>{{ feature.name }}</span>\r\n }\r\n </td>\r\n <td>\r\n @if (featValues()) {\r\n <span>{{\r\n feature.value | flatLookup: featValues()![feature.name]\r\n }}</span>\r\n } @else {\r\n <span>{{ feature.value }}</span>\r\n }\r\n </td>\r\n <td>\r\n <span>{{ POLICIES[feature.setPolicy] }}</span>\r\n </td>\r\n @if (isVar()) {\r\n <td>\r\n @if ($any(feature).isNegated) {\r\n <span class=\"icon\" matTooltip=\"Negated: remove if present\">\r\n <mat-icon class=\"mat-warn\">remove_circle</mat-icon>\r\n </span>\r\n }\r\n @if ($any(feature).isGlobal) {\r\n <span class=\"icon\" matTooltip=\"Global: apply to whole output\">\r\n <mat-icon class=\"mat-primary\">public</mat-icon>\r\n </span>\r\n }\r\n @if ($any(feature).isShortLived) {\r\n <span\r\n class=\"icon\"\r\n matTooltip=\"Short-lived: removed on next operation\"\r\n >\r\n <mat-icon class=\"mat-accent\">timer</mat-icon>\r\n </span>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n }\r\n\r\n <!-- editor -->\r\n @if (editedFeature()) {\r\n <mat-expansion-panel\r\n [disabled]=\"!editedFeature()\"\r\n [expanded]=\"editedFeature()\"\r\n >\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>\r\n <span>{{ editedFeature()?.name || \"feature\" }}</span>\r\n </mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <gve-feature-editor\r\n [isVar]=\"isVar()\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n [featNames]=\"featNames()\"\r\n [featValues]=\"featValues()\"\r\n [feature]=\"editedFeature()\"\r\n (featureChange)=\"onFeatureChange($event)\"\r\n (featureCancel)=\"onFeatureCancel()\"\r\n />\r\n </mat-expansion-panel>\r\n }\r\n</div>\r\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}.in-row-button{margin-top:-16px}table{margin-top:8px;width:100%;border-collapse:collapse;color:var(--mat-sys-on-surface-variant)}tbody tr:nth-child(odd){background-color:var(--mat-sys-surface-container)}th{text-align:left;font-weight:400;color:var(--mat-sys-on-surface-variant)}tbody tr:hover{background-color:var(--mat-sys-surface-container-high)}td.fit-width{width:1px;white-space:nowrap}tbody tr.selected{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}\n"] }]
1042
+ }], ctorParameters: () => [{ type: i1$1.FormBuilder }], propDecorators: { isVar: [{ type: i0.Input, args: [{ isSignal: true, alias: "isVar", required: false }] }], featNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "featNames", required: false }] }], featValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "featValues", required: false }] }], filterThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterThreshold", required: false }] }], multiValuedFeatureIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiValuedFeatureIds", required: false }] }], hasCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasCloseButton", required: false }] }], close: [{ type: i0.Output, args: ["close"] }], features: [{ type: i0.Input, args: [{ isSignal: true, alias: "features", required: false }] }, { type: i0.Output, args: ["featuresChange"] }] } });
1031
1043
 
1032
1044
  /**
1033
1045
  * Service for base text operations.
@@ -1156,8 +1168,12 @@ class BaseTextEditorComponent {
1156
1168
  if (!char) {
1157
1169
  return;
1158
1170
  }
1159
- const chars = this.text().map((c) => c.id === char.id ? { ...c, features } : c);
1160
- this.selectedChar.set({ ...char, features });
1171
+ // base text node features are stored under the 'v0' version tag
1172
+ const updatedFeatures = features.length
1173
+ ? { v0: features }
1174
+ : null;
1175
+ const chars = this.text().map((c) => c.id === char.id ? { ...c, features: updatedFeatures } : c);
1176
+ this.selectedChar.set({ ...char, features: updatedFeatures });
1161
1177
  this.textChange.emit(chars);
1162
1178
  }
1163
1179
  onRangePick(range) {
@@ -1172,10 +1188,10 @@ class BaseTextEditorComponent {
1172
1188
  }
1173
1189
  });
1174
1190
  }
1175
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: BaseTextEditorComponent, deps: [{ token: i1$1.FormBuilder }, { token: i4$1.DialogService }], target: i0.ɵɵFactoryTarget.Component }); }
1176
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: BaseTextEditorComponent, isStandalone: true, selector: "gve-base-text-editor", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: true, transformFunction: null }, multiValuedFeatureIds: { classPropertyName: "multiValuedFeatureIds", publicName: "multiValuedFeatureIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { textChange: "textChange" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"setTextFromUser()\">\r\n <!-- text -->\r\n <div>\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>text</mat-label>\r\n <textarea matInput [formControl]=\"userText\" rows=\"5\"></textarea>\r\n @if ( $any(userText).errors?.required && (userText.dirty ||\r\n userText.touched) ) {\r\n <mat-error>text required</mat-error>\r\n } @if ( $any(userText).errors?.maxLength && (userText.dirty ||\r\n userText.touched) ) {\r\n <mat-error>text too long</mat-error>\r\n }\r\n </mat-form-field>\r\n </div>\r\n <!-- buttons -->\r\n <div class=\"form-row\">\r\n <!-- set -->\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n class=\"mat-warn\"\r\n matTooltip=\"Reset characters to newly entered text\"\r\n [disabled]=\"!userText.value\"\r\n (click)=\"setTextFromUser()\"\r\n >\r\n set\r\n </button>\r\n </div>\r\n\r\n <!-- base text -->\r\n <div id=\"text-view\">\r\n <gve-base-text-view\r\n [text]=\"text() || []\"\r\n (charPick)=\"onSelectedChar($event)\"\r\n (rangePick)=\"textRange.set($event)\"\r\n />\r\n\r\n <!-- text range -->\r\n @if (textRange()) {\r\n <div id=\"text-range\">{{ textRange()!.at }} \u00D7 {{ textRange()!.run }}</div>\r\n }\r\n </div>\r\n\r\n <!-- char features -->\r\n @if (selectedChar()) {\r\n <fieldset>\r\n <legend>features</legend>\r\n <gve-feature-set-editor\r\n [features]=\"selectedChar()!.features || []\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n (featuresChange)=\"onFeaturesChange($event)\"\r\n />\r\n </fieldset>\r\n }\r\n</form>\r\n", styles: [".full-width{width:100%}#text-view{margin:8px 0}fieldset{padding:10px;margin:10px 0;border-radius:5px;border:1px solid var(--mat-sys-on-surface-variant)}legend{color:var(--mat-sys-on-surface-variant)}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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.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: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: BaseTextViewComponent, selector: "gve-base-text-view", inputs: ["defaultColor", "defaultBorderColor", "selectionColor", "searchHighlightColor", "hasLineNumber", "text", "colorCallback", "borderColorCallback"], outputs: ["charPick", "rangePick"] }, { kind: "component", type: FeatureSetEditorComponent, selector: "gve-feature-set-editor", inputs: ["isVar", "featNames", "featValues", "filterThreshold", "multiValuedFeatureIds", "features"], outputs: ["featuresChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1191
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BaseTextEditorComponent, deps: [{ token: i1$1.FormBuilder }, { token: i4$1.DialogService }], target: i0.ɵɵFactoryTarget.Component }); }
1192
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: BaseTextEditorComponent, isStandalone: true, selector: "gve-base-text-editor", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: true, transformFunction: null }, multiValuedFeatureIds: { classPropertyName: "multiValuedFeatureIds", publicName: "multiValuedFeatureIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { textChange: "textChange" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"setTextFromUser()\">\r\n <!-- text -->\r\n <div>\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>text</mat-label>\r\n <textarea matInput [formControl]=\"userText\" rows=\"5\"></textarea>\r\n @if ( $any(userText).errors?.required && (userText.dirty ||\r\n userText.touched) ) {\r\n <mat-error>text required</mat-error>\r\n } @if ( $any(userText).errors?.maxLength && (userText.dirty ||\r\n userText.touched) ) {\r\n <mat-error>text too long</mat-error>\r\n }\r\n </mat-form-field>\r\n </div>\r\n <!-- buttons -->\r\n <div class=\"form-row\">\r\n <!-- set -->\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n class=\"mat-warn\"\r\n matTooltip=\"Reset characters to newly entered text\"\r\n [disabled]=\"!userText.value\"\r\n (click)=\"setTextFromUser()\"\r\n >\r\n set\r\n </button>\r\n </div>\r\n\r\n <!-- base text -->\r\n <div id=\"text-view\">\r\n <gve-base-text-view\r\n [text]=\"text() || []\"\r\n (charPick)=\"onSelectedChar($event)\"\r\n (rangePick)=\"textRange.set($event)\"\r\n />\r\n\r\n <!-- text range -->\r\n @if (textRange()) {\r\n <div id=\"text-range\">{{ textRange()!.at }} \u00D7 {{ textRange()!.run }}</div>\r\n }\r\n </div>\r\n\r\n <!-- char features -->\r\n @if (selectedChar()) {\r\n <fieldset>\r\n <legend>features</legend>\r\n <gve-feature-set-editor\r\n [features]=\"selectedChar()!.features?.['v0'] ?? []\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n (featuresChange)=\"onFeaturesChange($event)\"\r\n />\r\n </fieldset>\r\n }\r\n</form>\r\n", styles: [".full-width{width:100%}#text-view{margin:8px 0}fieldset{padding:10px;margin:10px 0;border-radius:5px;border:1px solid var(--mat-sys-on-surface-variant)}legend{color:var(--mat-sys-on-surface-variant)}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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.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: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: BaseTextViewComponent, selector: "gve-base-text-view", inputs: ["defaultColor", "defaultBorderColor", "selectionColor", "searchHighlightColor", "hasLineNumber", "text", "colorCallback", "borderColorCallback"], outputs: ["charPick", "rangePick"] }, { kind: "component", type: FeatureSetEditorComponent, selector: "gve-feature-set-editor", inputs: ["isVar", "featNames", "featValues", "filterThreshold", "multiValuedFeatureIds", "hasCloseButton", "features"], outputs: ["close", "featuresChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1177
1193
  }
1178
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: BaseTextEditorComponent, decorators: [{
1194
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BaseTextEditorComponent, decorators: [{
1179
1195
  type: Component,
1180
1196
  args: [{ selector: 'gve-base-text-editor', imports: [
1181
1197
  ReactiveFormsModule,
@@ -1186,7 +1202,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
1186
1202
  MatTooltipModule,
1187
1203
  BaseTextViewComponent,
1188
1204
  FeatureSetEditorComponent,
1189
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form [formGroup]=\"form\" (submit)=\"setTextFromUser()\">\r\n <!-- text -->\r\n <div>\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>text</mat-label>\r\n <textarea matInput [formControl]=\"userText\" rows=\"5\"></textarea>\r\n @if ( $any(userText).errors?.required && (userText.dirty ||\r\n userText.touched) ) {\r\n <mat-error>text required</mat-error>\r\n } @if ( $any(userText).errors?.maxLength && (userText.dirty ||\r\n userText.touched) ) {\r\n <mat-error>text too long</mat-error>\r\n }\r\n </mat-form-field>\r\n </div>\r\n <!-- buttons -->\r\n <div class=\"form-row\">\r\n <!-- set -->\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n class=\"mat-warn\"\r\n matTooltip=\"Reset characters to newly entered text\"\r\n [disabled]=\"!userText.value\"\r\n (click)=\"setTextFromUser()\"\r\n >\r\n set\r\n </button>\r\n </div>\r\n\r\n <!-- base text -->\r\n <div id=\"text-view\">\r\n <gve-base-text-view\r\n [text]=\"text() || []\"\r\n (charPick)=\"onSelectedChar($event)\"\r\n (rangePick)=\"textRange.set($event)\"\r\n />\r\n\r\n <!-- text range -->\r\n @if (textRange()) {\r\n <div id=\"text-range\">{{ textRange()!.at }} \u00D7 {{ textRange()!.run }}</div>\r\n }\r\n </div>\r\n\r\n <!-- char features -->\r\n @if (selectedChar()) {\r\n <fieldset>\r\n <legend>features</legend>\r\n <gve-feature-set-editor\r\n [features]=\"selectedChar()!.features || []\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n (featuresChange)=\"onFeaturesChange($event)\"\r\n />\r\n </fieldset>\r\n }\r\n</form>\r\n", styles: [".full-width{width:100%}#text-view{margin:8px 0}fieldset{padding:10px;margin:10px 0;border-radius:5px;border:1px solid var(--mat-sys-on-surface-variant)}legend{color:var(--mat-sys-on-surface-variant)}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"] }]
1205
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form [formGroup]=\"form\" (submit)=\"setTextFromUser()\">\r\n <!-- text -->\r\n <div>\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>text</mat-label>\r\n <textarea matInput [formControl]=\"userText\" rows=\"5\"></textarea>\r\n @if ( $any(userText).errors?.required && (userText.dirty ||\r\n userText.touched) ) {\r\n <mat-error>text required</mat-error>\r\n } @if ( $any(userText).errors?.maxLength && (userText.dirty ||\r\n userText.touched) ) {\r\n <mat-error>text too long</mat-error>\r\n }\r\n </mat-form-field>\r\n </div>\r\n <!-- buttons -->\r\n <div class=\"form-row\">\r\n <!-- set -->\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n class=\"mat-warn\"\r\n matTooltip=\"Reset characters to newly entered text\"\r\n [disabled]=\"!userText.value\"\r\n (click)=\"setTextFromUser()\"\r\n >\r\n set\r\n </button>\r\n </div>\r\n\r\n <!-- base text -->\r\n <div id=\"text-view\">\r\n <gve-base-text-view\r\n [text]=\"text() || []\"\r\n (charPick)=\"onSelectedChar($event)\"\r\n (rangePick)=\"textRange.set($event)\"\r\n />\r\n\r\n <!-- text range -->\r\n @if (textRange()) {\r\n <div id=\"text-range\">{{ textRange()!.at }} \u00D7 {{ textRange()!.run }}</div>\r\n }\r\n </div>\r\n\r\n <!-- char features -->\r\n @if (selectedChar()) {\r\n <fieldset>\r\n <legend>features</legend>\r\n <gve-feature-set-editor\r\n [features]=\"selectedChar()!.features?.['v0'] ?? []\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n (featuresChange)=\"onFeaturesChange($event)\"\r\n />\r\n </fieldset>\r\n }\r\n</form>\r\n", styles: [".full-width{width:100%}#text-view{margin:8px 0}fieldset{padding:10px;margin:10px 0;border-radius:5px;border:1px solid var(--mat-sys-on-surface-variant)}legend{color:var(--mat-sys-on-surface-variant)}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"] }]
1190
1206
  }], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: i4$1.DialogService }], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: true }] }], multiValuedFeatureIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiValuedFeatureIds", required: false }] }], textChange: [{ type: i0.Output, args: ["textChange"] }] } });
1191
1207
 
1192
1208
  /**
@@ -1268,10 +1284,10 @@ class GveApiService {
1268
1284
  })
1269
1285
  .pipe(catchError(this._error.handleError));
1270
1286
  }
1271
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GveApiService, deps: [{ token: i1$2.HttpClient }, { token: i2$1.ErrorService }, { token: i2$1.EnvService }], target: i0.ɵɵFactoryTarget.Injectable }); }
1272
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GveApiService, providedIn: 'root' }); }
1287
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: GveApiService, deps: [{ token: i1$2.HttpClient }, { token: i2$1.ErrorService }, { token: i2$1.EnvService }], target: i0.ɵɵFactoryTarget.Injectable }); }
1288
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: GveApiService, providedIn: 'root' }); }
1273
1289
  }
1274
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GveApiService, decorators: [{
1290
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: GveApiService, decorators: [{
1275
1291
  type: Injectable,
1276
1292
  args: [{
1277
1293
  providedIn: 'root',
@@ -1354,10 +1370,10 @@ class BatchOperationEditorComponent {
1354
1370
  close() {
1355
1371
  this.dialogRef?.close();
1356
1372
  }
1357
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: BatchOperationEditorComponent, deps: [{ token: i1$1.FormBuilder }, { token: GveApiService }, { token: i3$2.MatDialogRef, optional: true }, { token: MAT_DIALOG_DATA, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
1358
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: BatchOperationEditorComponent, isStandalone: true, selector: "gve-batch-operation-editor", inputs: { preset: { classPropertyName: "preset", publicName: "preset", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { operationsChange: "operationsChange" }, ngImport: i0, template: "<div [style.padding]=\"dialogRef ? '8px' : '0'\">\r\n @if (dialogRef) {\r\n <div id=\"heading\">Add Operations</div>\r\n }\r\n <fieldset>\r\n <div id=\"batch-input\">\r\n <div>\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>operations</mat-label>\r\n <textarea\r\n class=\"code\"\r\n matInput\r\n [formControl]=\"text\"\r\n rows=\"8\"\r\n spellcheck=\"false\"\r\n ></textarea>\r\n </mat-form-field>\r\n @if (parseError()) {\r\n <div class=\"error\">{{ parseError() }}</div>\r\n }\r\n </div>\r\n </div>\r\n <div id=\"batch-help\">\r\n <ul>\r\n <li>\r\n <strong>replace</strong>:\r\n <code>ATxRUN<strong>=</strong>\"VALUE\"</code>\r\n </li>\r\n <li>\r\n <strong>delete</strong>:\r\n <code>ATxRUN<strong>-</strong></code>\r\n </li>\r\n <li>\r\n <strong>add-before</strong>:\r\n <code>ATxRUN<strong>+[</strong>\"VALUE\"</code>\r\n </li>\r\n <li>\r\n <strong>add-after</strong>:\r\n <code>ATxRUN<strong>+]</strong>\"VALUE\"</code>\r\n </li>\r\n <li>\r\n <strong>move-before</strong>:\r\n <code>ATxRUN<strong>&gt;[</strong>TO</code>\r\n </li>\r\n <li>\r\n <strong>move-after</strong>:\r\n <code>ATxRUN<strong>&gt;]</strong>TO</code>\r\n </li>\r\n <li>\r\n <strong>swap</strong>:\r\n <code>ATxRUN<strong>&lt;&gt;</strong>TOxRUN</code>\r\n </li>\r\n <li>\r\n <strong>annotate</strong>:\r\n <code>ATxRUN<strong>:</strong></code>\r\n </li>\r\n </ul>\r\n <p>For all the operations:</p>\r\n <ul>\r\n <li>\r\n prefix <code>(ITAG:OTAG)</code> to define input and/or output tags,\r\n separated by colon.\r\n </li>\r\n <li>\r\n prepend <code>&#x40;</code> to AT to use character indexes (0-N)\r\n rather than IDs.\r\n </li>\r\n <li>RUN (where applicable) defaults to 1.</li>\r\n <li>\r\n append features like <code>[NAME OPERATOR VALUE]</code>, separated by\r\n space, where:\r\n </li>\r\n <li>\r\n <ol>\r\n <li>\r\n NAME is an arbitrary string. Prefixes:\r\n <code>*</code>=global features, <code>!</code>=remove feature (no\r\n value). Suffixes: <code>^</code>=short-lived (like\r\n <code>*version^=alpha</code>).\r\n </li>\r\n <li>\r\n OPERATOR is <code>=</code> (multiple), <code>:=</code> (single),\r\n <code>==</code> (first-single).\r\n </li>\r\n <li>\r\n VALUE is a string. If it includes spaces, wrap it in\r\n <code>\"\"</code>.\r\n </li>\r\n </ol>\r\n </li>\r\n </ul>\r\n </div>\r\n <div class=\"form-row-center\">\r\n @if (dialogRef) {\r\n <button\r\n type=\"button\"\r\n class=\"mat-warn\"\r\n mat-flat-button\r\n matTooltip=\"Close dialog\"\r\n (click)=\"close()\"\r\n >\r\n close\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Parse text into operations\"\r\n [disabled]=\"!text.value || busy()\"\r\n (click)=\"parseOperations(text.value!)\"\r\n >\r\n batch add\r\n </button>\r\n </div>\r\n </fieldset>\r\n</div>\r\n", styles: [".full-width{width:100%}.error{color:var(--mat-sys-error)}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row-center{display:flex;gap:8px;align-items:center;justify-content:center;flex-wrap:wrap}.form-row,.form-row-center *{flex:0 0 auto}div#heading{margin:8px;text-align:center;font-weight:700}div#batch-help{font-size:.9em}div#batch-help strong{color:var(--mat-sys-primary)}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1373
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BatchOperationEditorComponent, deps: [{ token: i1$1.FormBuilder }, { token: GveApiService }, { token: i3$2.MatDialogRef, optional: true }, { token: MAT_DIALOG_DATA, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
1374
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: BatchOperationEditorComponent, isStandalone: true, selector: "gve-batch-operation-editor", inputs: { preset: { classPropertyName: "preset", publicName: "preset", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { operationsChange: "operationsChange" }, ngImport: i0, template: "<div [style.padding]=\"dialogRef ? '8px' : '0'\">\r\n @if (dialogRef) {\r\n <div id=\"heading\">Add Operations</div>\r\n }\r\n <fieldset>\r\n <div id=\"batch-input\">\r\n <div>\r\n <mat-form-field class=\"full-width\">\r\n <mat-label>operations</mat-label>\r\n <textarea\r\n class=\"code\"\r\n matInput\r\n [formControl]=\"text\"\r\n rows=\"8\"\r\n spellcheck=\"false\"\r\n ></textarea>\r\n </mat-form-field>\r\n @if (parseError()) {\r\n <div class=\"error\">{{ parseError() }}</div>\r\n }\r\n </div>\r\n </div>\r\n <div id=\"batch-help\">\r\n <ul>\r\n <li>\r\n <strong>replace</strong>:\r\n <code>ATxRUN<strong>=</strong>\"VALUE\"</code>\r\n </li>\r\n <li>\r\n <strong>delete</strong>:\r\n <code>ATxRUN<strong>-</strong></code>\r\n </li>\r\n <li>\r\n <strong>add-before</strong>:\r\n <code>ATxRUN<strong>+[</strong>\"VALUE\"</code>\r\n </li>\r\n <li>\r\n <strong>add-after</strong>:\r\n <code>ATxRUN<strong>+]</strong>\"VALUE\"</code>\r\n </li>\r\n <li>\r\n <strong>move-before</strong>:\r\n <code>ATxRUN<strong>&gt;[</strong>TO</code>\r\n </li>\r\n <li>\r\n <strong>move-after</strong>:\r\n <code>ATxRUN<strong>&gt;]</strong>TO</code>\r\n </li>\r\n <li>\r\n <strong>swap</strong>:\r\n <code>ATxRUN<strong>&lt;&gt;</strong>TOxRUN</code>\r\n </li>\r\n <li>\r\n <strong>annotate</strong>:\r\n <code>ATxRUN<strong>:</strong></code>\r\n </li>\r\n </ul>\r\n <p>For all the operations:</p>\r\n <ul>\r\n <li>\r\n prefix <code>(ITAG:OTAG)</code> to define input and/or output tags,\r\n separated by colon.\r\n </li>\r\n <li>\r\n prepend <code>&#x40;</code> to AT to use character indexes (0-N)\r\n rather than IDs.\r\n </li>\r\n <li>RUN (where applicable) defaults to 1.</li>\r\n <li>\r\n append features like <code>[NAME OPERATOR VALUE]</code>, separated by\r\n space, where:\r\n </li>\r\n <li>\r\n <ol>\r\n <li>\r\n NAME is an arbitrary string. Prefixes:\r\n <code>*</code>=global features, <code>!</code>=remove feature (no\r\n value). Suffixes: <code>^</code>=short-lived (like\r\n <code>*version^=alpha</code>).\r\n </li>\r\n <li>\r\n OPERATOR is <code>=</code> (multiple), <code>:=</code> (single),\r\n <code>==</code> (first-single).\r\n </li>\r\n <li>\r\n VALUE is a string. If it includes spaces, wrap it in\r\n <code>\"\"</code>.\r\n </li>\r\n </ol>\r\n </li>\r\n </ul>\r\n </div>\r\n <div class=\"form-row-center\">\r\n @if (dialogRef) {\r\n <button\r\n type=\"button\"\r\n class=\"mat-warn\"\r\n mat-flat-button\r\n matTooltip=\"Close dialog\"\r\n (click)=\"close()\"\r\n >\r\n close\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Parse text into operations\"\r\n [disabled]=\"!text.value || busy()\"\r\n (click)=\"parseOperations(text.value!)\"\r\n >\r\n batch add\r\n </button>\r\n </div>\r\n </fieldset>\r\n</div>\r\n", styles: [".full-width{width:100%}.error{color:var(--mat-sys-error)}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row-center{display:flex;gap:8px;align-items:center;justify-content:center;flex-wrap:wrap}.form-row,.form-row-center *{flex:0 0 auto}div#heading{margin:8px;text-align:center;font-weight:700}div#batch-help{font-size:.9em}div#batch-help strong{color:var(--mat-sys-primary)}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1359
1375
  }
1360
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: BatchOperationEditorComponent, decorators: [{
1376
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BatchOperationEditorComponent, decorators: [{
1361
1377
  type: Component,
1362
1378
  args: [{ selector: 'gve-batch-operation-editor', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
1363
1379
  ReactiveFormsModule,
@@ -1459,10 +1475,10 @@ class OperationSourceEditorComponent {
1459
1475
  note: this.note.value || undefined,
1460
1476
  });
1461
1477
  }
1462
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OperationSourceEditorComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
1463
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OperationSourceEditorComponent, isStandalone: true, selector: "gve-operation-source-editor", inputs: { source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null }, ids: { classPropertyName: "ids", publicName: "ids", isSignal: true, isRequired: false, transformFunction: null }, types: { classPropertyName: "types", publicName: "types", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { source: "sourceChange", sourceCancel: "sourceCancel" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <div class=\"form-row\">\r\n <!-- id -->\r\n <!-- id (bound) -->\r\n @if (ids()?.length) {\r\n <mat-form-field>\r\n <mat-label>id</mat-label>\r\n <mat-select [formControl]=\"id\">\r\n @for (i of ids(); track i.id) {\r\n <mat-option [value]=\"i.id\">{{ i.label }}</mat-option>\r\n }\r\n </mat-select>\r\n @if ($any(id.errors)?.required && (id.dirty || id.touched)) {\r\n <mat-error>ID required</mat-error>\r\n }\r\n </mat-form-field>\r\n } @else {\r\n <!-- id (free) -->\r\n <mat-form-field>\r\n <mat-label>id</mat-label>\r\n <input matInput [formControl]=\"id\" />\r\n @if ($any(id.errors)?.required && (id.dirty || id.touched)) {\r\n <mat-error>ID required</mat-error>\r\n } @if ($any(id.errors)?.maxLength && (id.dirty || id.touched)) {\r\n <mat-error>id too long</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n\r\n <!-- type (bound) -->\r\n @if (types()?.length) {\r\n <mat-form-field>\r\n <mat-label>type</mat-label>\r\n <mat-select [formControl]=\"type\">\r\n @for (i of types(); track i.id) {\r\n <mat-option [value]=\"i.id\">{{ i.label }}</mat-option>\r\n }\r\n </mat-select>\r\n @if ($any(type.errors)?.required && (type.dirty || type.touched)) {\r\n <mat-error>type required</mat-error>\r\n }\r\n </mat-form-field>\r\n } @else {\r\n <!-- type (free) -->\r\n <mat-form-field>\r\n <mat-label>type</mat-label>\r\n <input matInput [formControl]=\"type\" />\r\n @if ($any(type.errors)?.required && (type.dirty || type.touched)) {\r\n <mat-error>type required</mat-error>\r\n } @if ($any(type.errors)?.maxLength && (type.dirty || type.touched)) {\r\n <mat-error>type too long</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n\r\n <!-- rank -->\r\n <mat-form-field class=\"nr\">\r\n <mat-label>rank</mat-label>\r\n <input matInput [formControl]=\"rank\" type=\"number\" min=\"0\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- note -->\r\n <div>\r\n <mat-form-field class=\"long-text\">\r\n <mat-label>note</mat-label>\r\n <textarea matInput [formControl]=\"note\" class=\"long-text\"></textarea>\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- buttons -->\r\n <div class=\"form-row\">\r\n <button\r\n type=\"button\"\r\n color=\"warn\"\r\n mat-icon-button\r\n matTooltip=\"Discard changes\"\r\n (click)=\"cancel()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"submit\"\r\n color=\"primary\"\r\n mat-icon-button\r\n matTooltip=\"Accept changes\"\r\n [disabled]=\"form.invalid\"\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: [".long-text{width:100%;max-width:800px}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}.nr{width:5em}table{width:100%;border-collapse:collapse;color:var(--mat-sys-on-surface-variant)}tbody tr:nth-child(odd){background-color:var(--mat-sys-surface-container)}th{text-align:left;font-weight:400;color:var(--mat-sys-on-surface-variant)}tbody tr:hover{background-color:var(--mat-sys-surface-container-high)}td.fit-width{width:1px;white-space:nowrap}tbody tr.selected{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { 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.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { 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: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.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: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1478
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OperationSourceEditorComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
1479
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: OperationSourceEditorComponent, isStandalone: true, selector: "gve-operation-source-editor", inputs: { source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null }, ids: { classPropertyName: "ids", publicName: "ids", isSignal: true, isRequired: false, transformFunction: null }, types: { classPropertyName: "types", publicName: "types", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { source: "sourceChange", sourceCancel: "sourceCancel" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <div class=\"form-row\">\r\n <!-- id -->\r\n <!-- id (bound) -->\r\n @if (ids()?.length) {\r\n <mat-form-field>\r\n <mat-label>id</mat-label>\r\n <mat-select [formControl]=\"id\">\r\n @for (i of ids(); track i.id) {\r\n <mat-option [value]=\"i.id\">{{ i.label }}</mat-option>\r\n }\r\n </mat-select>\r\n @if ($any(id.errors)?.required && (id.dirty || id.touched)) {\r\n <mat-error>ID required</mat-error>\r\n }\r\n </mat-form-field>\r\n } @else {\r\n <!-- id (free) -->\r\n <mat-form-field>\r\n <mat-label>id</mat-label>\r\n <input matInput [formControl]=\"id\" />\r\n @if ($any(id.errors)?.required && (id.dirty || id.touched)) {\r\n <mat-error>ID required</mat-error>\r\n } @if ($any(id.errors)?.maxLength && (id.dirty || id.touched)) {\r\n <mat-error>id too long</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n\r\n <!-- type (bound) -->\r\n @if (types()?.length) {\r\n <mat-form-field>\r\n <mat-label>type</mat-label>\r\n <mat-select [formControl]=\"type\">\r\n @for (i of types(); track i.id) {\r\n <mat-option [value]=\"i.id\">{{ i.label }}</mat-option>\r\n }\r\n </mat-select>\r\n @if ($any(type.errors)?.required && (type.dirty || type.touched)) {\r\n <mat-error>type required</mat-error>\r\n }\r\n </mat-form-field>\r\n } @else {\r\n <!-- type (free) -->\r\n <mat-form-field>\r\n <mat-label>type</mat-label>\r\n <input matInput [formControl]=\"type\" />\r\n @if ($any(type.errors)?.required && (type.dirty || type.touched)) {\r\n <mat-error>type required</mat-error>\r\n } @if ($any(type.errors)?.maxLength && (type.dirty || type.touched)) {\r\n <mat-error>type too long</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n\r\n <!-- rank -->\r\n <mat-form-field class=\"nr\">\r\n <mat-label>rank</mat-label>\r\n <input matInput [formControl]=\"rank\" type=\"number\" min=\"0\" />\r\n </mat-form-field>\r\n </div>\r\n <!-- note -->\r\n <div>\r\n <mat-form-field class=\"long-text\">\r\n <mat-label>note</mat-label>\r\n <textarea matInput [formControl]=\"note\" class=\"long-text\"></textarea>\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- buttons -->\r\n <div class=\"form-row\">\r\n <button\r\n type=\"button\"\r\n color=\"warn\"\r\n mat-icon-button\r\n matTooltip=\"Discard changes\"\r\n (click)=\"cancel()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"submit\"\r\n color=\"primary\"\r\n mat-icon-button\r\n matTooltip=\"Accept changes\"\r\n [disabled]=\"form.invalid\"\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: [".long-text{width:100%;max-width:800px}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}.nr{width:5em}table{width:100%;border-collapse:collapse;color:var(--mat-sys-on-surface-variant)}tbody tr:nth-child(odd){background-color:var(--mat-sys-surface-container)}th{text-align:left;font-weight:400;color:var(--mat-sys-on-surface-variant)}tbody tr:hover{background-color:var(--mat-sys-surface-container-high)}td.fit-width{width:1px;white-space:nowrap}tbody tr.selected{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { 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.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { 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: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.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: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1464
1480
  }
1465
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OperationSourceEditorComponent, decorators: [{
1481
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OperationSourceEditorComponent, decorators: [{
1466
1482
  type: Component,
1467
1483
  args: [{ selector: 'gve-operation-source-editor', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
1468
1484
  ReactiveFormsModule,
@@ -1691,10 +1707,10 @@ class SettingsService {
1691
1707
  // for when the cache was not in sync with the local storage
1692
1708
  removedKeys.forEach((key) => this._subject.next(key));
1693
1709
  }
1694
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SettingsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1695
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SettingsService, providedIn: 'root' }); }
1710
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: SettingsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1711
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: SettingsService, providedIn: 'root' }); }
1696
1712
  }
1697
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SettingsService, decorators: [{
1713
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: SettingsService, decorators: [{
1698
1714
  type: Injectable,
1699
1715
  args: [{
1700
1716
  providedIn: 'root',
@@ -1836,11 +1852,16 @@ class ChainOperationEditorComponent {
1836
1852
  nonNullable: true,
1837
1853
  });
1838
1854
  this.at = formBuilder.control(0, {
1839
- validators: Validators.required,
1855
+ validators: [
1856
+ Validators.required,
1857
+ NgxToolsValidators.conditionalValidator(() => !this.atAsIndex.value, Validators.min(0)),
1858
+ ],
1840
1859
  nonNullable: true,
1841
1860
  });
1842
1861
  this.atAsIndex = formBuilder.control(false, { nonNullable: true });
1843
- this.to = formBuilder.control(0, { nonNullable: true });
1862
+ this.to = formBuilder.control(0, {
1863
+ nonNullable: true,
1864
+ });
1844
1865
  this.toAsIndex = formBuilder.control(false, { nonNullable: true });
1845
1866
  this.inputTag = formBuilder.control(null);
1846
1867
  this.outputTag = formBuilder.control(null);
@@ -1849,7 +1870,12 @@ class ChainOperationEditorComponent {
1849
1870
  nonNullable: true,
1850
1871
  });
1851
1872
  this.toRun = formBuilder.control(0, { nonNullable: true });
1852
- this.value = formBuilder.control(null);
1873
+ this.value = formBuilder.control(null, {
1874
+ validators: NgxToolsValidators.conditionalValidator(() => this.type.value === OperationType.addBefore ||
1875
+ this.type.value === OperationType.addAfter ||
1876
+ this.type.value === OperationType.replace, Validators.required),
1877
+ nonNullable: true,
1878
+ });
1853
1879
  // svg
1854
1880
  this.svg = formBuilder.control('', {
1855
1881
  validators: [SvgValidators.rootGValidator()],
@@ -2058,10 +2084,10 @@ class ChainOperationEditorComponent {
2058
2084
  this.operation.set(this.getOperation());
2059
2085
  this.operationChange.emit(this.operation());
2060
2086
  }
2061
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ChainOperationEditorComponent, deps: [{ token: i1$1.FormBuilder }, { token: i2$2.Clipboard }, { token: SettingsService }, { token: i4$1.DialogService }], target: i0.ɵɵFactoryTarget.Component }); }
2062
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ChainOperationEditorComponent, isStandalone: true, selector: "gve-chain-operation-editor", inputs: { operation: { classPropertyName: "operation", publicName: "operation", isSignal: true, isRequired: false, transformFunction: null }, snapshot: { classPropertyName: "snapshot", publicName: "snapshot", isSignal: true, isRequired: false, transformFunction: null }, hidePreview: { classPropertyName: "hidePreview", publicName: "hidePreview", isSignal: true, isRequired: false, transformFunction: null }, featureDefs: { classPropertyName: "featureDefs", publicName: "featureDefs", isSignal: true, isRequired: false, transformFunction: null }, rangePatch: { classPropertyName: "rangePatch", publicName: "rangePatch", isSignal: true, isRequired: false, transformFunction: null }, multiValuedFeatureIds: { classPropertyName: "multiValuedFeatureIds", publicName: "multiValuedFeatureIds", isSignal: true, isRequired: false, transformFunction: null }, sourceIds: { classPropertyName: "sourceIds", publicName: "sourceIds", isSignal: true, isRequired: false, transformFunction: null }, sourceTypes: { classPropertyName: "sourceTypes", publicName: "sourceTypes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { operation: "operationChange", operationChange: "operationChange", operationPreview: "operationPreview", operationCancel: "operationCancel" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <!-- tabs -->\r\n <mat-tab-group [selectedIndex]=\"tabIndex()\">\r\n <!-- GENERAL -->\r\n <mat-tab label=\"general\">\r\n <div class=\"form-row\">\r\n <!-- id -->\r\n <div id=\"id\" class=\"muted\">{{ id() }}</div>\r\n\r\n <!-- type -->\r\n <mat-form-field>\r\n <mat-label>type</mat-label>\r\n <mat-select [formControl]=\"type\">\r\n <mat-option [value]=\"7\"\r\n ><mat-icon>edit_note</mat-icon>annotate</mat-option\r\n >\r\n <mat-option [value]=\"0\"\r\n ><mat-icon>layers</mat-icon> replace</mat-option\r\n >\r\n <mat-option [value]=\"1\"\r\n ><mat-icon>close</mat-icon>delete</mat-option\r\n >\r\n <mat-option [value]=\"2\"\r\n ><mat-icon>last_page</mat-icon>add-before</mat-option\r\n >\r\n <mat-option [value]=\"3\"\r\n ><mat-icon>first_page</mat-icon>add-after</mat-option\r\n >\r\n <mat-option [value]=\"4\"\r\n ><mat-icon>login</mat-icon>move-before</mat-option\r\n >\r\n <mat-option [value]=\"5\"\r\n ><mat-icon>logout</mat-icon>move-after</mat-option\r\n >\r\n <mat-option [value]=\"6\"\r\n ><mat-icon>compare_arrows</mat-icon>swap</mat-option\r\n >\r\n </mat-select>\r\n @if ($any(type).errors?.required && (type.dirty || type.touched)) {\r\n <mat-error>type required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- at -->\r\n <mat-form-field class=\"nr\">\r\n <mat-label>at</mat-label>\r\n <input matInput [formControl]=\"at\" type=\"number\" min=\"0\" />\r\n @if ($any(at).errors?.required && (at.dirty || at.touched)) {\r\n <mat-error>at required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- atAsIndex -->\r\n <mat-checkbox [formControl]=\"atAsIndex\">idx</mat-checkbox>\r\n\r\n <!-- run -->\r\n <mat-form-field class=\"nr\">\r\n <mat-label>run</mat-label>\r\n <input matInput [formControl]=\"run\" type=\"number\" min=\"0\" />\r\n @if ($any(run).errors?.required && (run.dirty || run.touched)) {\r\n <mat-error>run required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- to -->\r\n @if (hasTo()) {\r\n <mat-form-field class=\"nr\">\r\n <mat-label>to</mat-label>\r\n <input matInput [formControl]=\"to\" type=\"number\" min=\"0\" />\r\n @if ($any(to).errors?.required && (to.dirty || to.touched)) {\r\n <mat-error>to required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- toAsIndex -->\r\n <mat-checkbox [formControl]=\"toAsIndex\">idx</mat-checkbox>\r\n\r\n <!-- toRun -->\r\n @if (hasToRun()) {\r\n <mat-form-field class=\"nr\">\r\n <mat-label>to run</mat-label>\r\n <input matInput [formControl]=\"toRun\" type=\"number\" min=\"0\" />\r\n @if (\r\n $any(toRun).errors?.required && (toRun.dirty || toRun.touched)\r\n ) {\r\n <mat-error>to run required</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n }\r\n\r\n <!-- value -->\r\n @if (hasValue()) {\r\n <mat-form-field>\r\n <mat-label>value</mat-label>\r\n <input matInput [formControl]=\"value\" />\r\n </mat-form-field>\r\n }\r\n </div>\r\n <div class=\"form-row\">\r\n <!-- rank -->\r\n <mat-form-field class=\"nr\">\r\n <mat-label>rank</mat-label>\r\n <input matInput [formControl]=\"rank\" type=\"number\" min=\"0\" />\r\n @if ($any(rank).errors?.required && (rank.dirty || rank.touched)) {\r\n <mat-error>rank required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- groupId -->\r\n <mat-form-field>\r\n <mat-label>group ID</mat-label>\r\n <input matInput [formControl]=\"groupId\" />\r\n @if (\r\n $any(groupId).errors?.required && (groupId.dirty || groupId.touched)\r\n ) {\r\n <mat-error>group ID required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- inputTag -->\r\n <mat-form-field>\r\n <mat-label>input tag</mat-label>\r\n <input matInput [formControl]=\"inputTag\" />\r\n <mat-hint>blank=latest</mat-hint>\r\n </mat-form-field>\r\n\r\n <!-- outputTag -->\r\n <mat-form-field>\r\n <mat-label>output tag</mat-label>\r\n <input matInput [formControl]=\"outputTag\" />\r\n <mat-hint>blank=auto (vN)</mat-hint>\r\n </mat-form-field>\r\n </div>\r\n <div>\r\n <!-- features -->\r\n <fieldset>\r\n <legend>operation features</legend>\r\n <gve-feature-set-editor\r\n [isVar]=\"true\"\r\n [featNames]=\"featureDefs()?.names\"\r\n [featValues]=\"featureDefs()?.values\"\r\n [features]=\"features.value\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n (featuresChange)=\"onFeaturesChange($event)\"\r\n />\r\n </fieldset>\r\n </div>\r\n </mat-tab>\r\n\r\n <!-- SOURCES -->\r\n <mat-tab label=\"sources\">\r\n <div>\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n color=\"primary\"\r\n class=\"mat-primary\"\r\n (click)=\"addSource()\"\r\n >\r\n <mat-icon>add_circle</mat-icon>\r\n source\r\n </button>\r\n </div>\r\n <table>\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>type</th>\r\n <th>id</th>\r\n <th>rank</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (s of sources.value; track s) {\r\n <tr [class.selected]=\"s === editedSource()\">\r\n <td class=\"fit-width\">\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n color=\"primary\"\r\n (click)=\"editSource($index)\"\r\n >\r\n <mat-icon class=\"mat-primary\">edit</mat-icon>\r\n </button>\r\n <button type=\"button\" mat-icon-button color=\"warn\">\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n </td>\r\n <td>{{ s.type | flatLookup: sourceTypes() : \"id\" : \"label\" }}</td>\r\n <td>{{ s.id | flatLookup: sourceIds() : \"id\" : \"label\" }}</td>\r\n <td>{{ s.rank }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n @if (editedSource()) {\r\n <!-- source editor -->\r\n <mat-expansion-panel\r\n [disabled]=\"!editedSource()\"\r\n [expanded]=\"editedSource()\"\r\n >\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>source</mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <gve-operation-source-editor\r\n [source]=\"editedSource()\"\r\n [ids]=\"sourceIds()\"\r\n [types]=\"sourceTypes()\"\r\n (sourceChange)=\"onSourceChange($event!)\"\r\n (sourceCancel)=\"closeSource()\"\r\n />\r\n </mat-expansion-panel>\r\n }\r\n </mat-tab>\r\n </mat-tab-group>\r\n\r\n <!-- buttons -->\r\n <div id=\"submit-row\">\r\n <button\r\n type=\"button\"\r\n color=\"warn\"\r\n mat-icon-button\r\n matTooltip=\"Discard operation\"\r\n (click)=\"cancel()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n @if (!hidePreview()) {\r\n <button\r\n type=\"button\"\r\n color=\"primary\"\r\n mat-icon-button\r\n matTooltip=\"Preview operation\"\r\n (click)=\"requestPreview()\"\r\n >\r\n <mat-icon class=\"mat-primary\">preview</mat-icon>\r\n </button>\r\n }\r\n <button\r\n type=\"submit\"\r\n color=\"primary\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Save operation\"\r\n [disabled]=\"form.invalid || form.pristine\"\r\n >\r\n <mat-icon>check_circle</mat-icon>\r\n save\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}#submit-row{display:flex;gap:8px;align-items:center;justify-content:center;flex-wrap:wrap;margin-top:8px;border-top:1px solid var(--mat-sys-on-surface-variant)}.toolbar-row{display:flex;align-items:center;flex-wrap:wrap}.nr{width:6em}.long-text{width:100%;max-width:800px}#id{border-radius:6px;padding:4px;background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container);margin-top:-16px}fieldset{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:8px;margin:8px 0}legend{color:var(--mat-sys-on-surface-variant)}table{width:100%;border-collapse:collapse;color:var(--mat-sys-on-surface-variant)}tbody tr:nth-child(odd){background-color:var(--mat-sys-surface-container)}th{text-align:left;font-weight:400;color:var(--mat-sys-on-surface-variant)}tbody tr:hover{background-color:var(--mat-sys-surface-container-high)}td.fit-width{width:1px;white-space:nowrap}tbody tr.selected{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}#code{height:500px;border:1px solid var(--mat-sys-on-surface-variant)}#monaco{height:100%}#preview{box-sizing:border-box;width:100%;border:1px solid var(--mat-sys-on-surface-variant);border-radius:4px;padding:8px}.tree-invisible{display:none}.tree ul,.tree li{margin-top:0;margin-bottom:0;list-style-type:none}.selected-node{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}.child-title{font-weight:700;margin:0;background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container);padding:8px}#tree-container{display:grid;grid-template-rows:auto;grid-template-columns:auto 1fr;grid-template-areas:\"nav ed\";gap:0 16px;align-items:stretch}#tree-nav{grid-area:nav;border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;margin:8px 0;padding-right:8px}#tree-ed{grid-area:ed;border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;margin:8px 0}@media only screen and (max-width:959px){div#container{grid-template-columns:1fr;grid-template-areas:\"nav\" \"ed\";gap:16px 0;align-items:start}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { 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.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { 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: "ngmodule", type: ClipboardModule }, { kind: "ngmodule", type: MatBadgeModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i3$1.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: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i7$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i7$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i7$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.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: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i10.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i10.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: NgeMonacoModule }, { kind: "component", type: FeatureSetEditorComponent, selector: "gve-feature-set-editor", inputs: ["isVar", "featNames", "featValues", "filterThreshold", "multiValuedFeatureIds", "features"], outputs: ["featuresChange"] }, { kind: "component", type: OperationSourceEditorComponent, selector: "gve-operation-source-editor", inputs: ["source", "ids", "types"], outputs: ["sourceChange", "sourceCancel"] }, { kind: "pipe", type: FlatLookupPipe, name: "flatLookup" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2087
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: ChainOperationEditorComponent, deps: [{ token: i1$1.FormBuilder }, { token: i2$2.Clipboard }, { token: SettingsService }, { token: i4$1.DialogService }], target: i0.ɵɵFactoryTarget.Component }); }
2088
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: ChainOperationEditorComponent, isStandalone: true, selector: "gve-chain-operation-editor", inputs: { operation: { classPropertyName: "operation", publicName: "operation", isSignal: true, isRequired: false, transformFunction: null }, snapshot: { classPropertyName: "snapshot", publicName: "snapshot", isSignal: true, isRequired: false, transformFunction: null }, hidePreview: { classPropertyName: "hidePreview", publicName: "hidePreview", isSignal: true, isRequired: false, transformFunction: null }, featureDefs: { classPropertyName: "featureDefs", publicName: "featureDefs", isSignal: true, isRequired: false, transformFunction: null }, rangePatch: { classPropertyName: "rangePatch", publicName: "rangePatch", isSignal: true, isRequired: false, transformFunction: null }, multiValuedFeatureIds: { classPropertyName: "multiValuedFeatureIds", publicName: "multiValuedFeatureIds", isSignal: true, isRequired: false, transformFunction: null }, sourceIds: { classPropertyName: "sourceIds", publicName: "sourceIds", isSignal: true, isRequired: false, transformFunction: null }, sourceTypes: { classPropertyName: "sourceTypes", publicName: "sourceTypes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { operation: "operationChange", operationChange: "operationChange", operationPreview: "operationPreview", operationCancel: "operationCancel" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <!-- tabs -->\r\n <mat-tab-group [selectedIndex]=\"tabIndex()\">\r\n <!-- GENERAL -->\r\n <mat-tab label=\"general\">\r\n <div class=\"form-row\">\r\n <!-- id -->\r\n <div id=\"id\" class=\"muted\">{{ id() }}</div>\r\n\r\n <!-- type -->\r\n <mat-form-field>\r\n <mat-label>type</mat-label>\r\n <mat-select [formControl]=\"type\">\r\n <mat-option [value]=\"7\"\r\n ><mat-icon>edit_note</mat-icon>annotate</mat-option\r\n >\r\n <mat-option [value]=\"0\"\r\n ><mat-icon>layers</mat-icon> replace</mat-option\r\n >\r\n <mat-option [value]=\"1\"\r\n ><mat-icon>close</mat-icon>delete</mat-option\r\n >\r\n <mat-option [value]=\"2\"\r\n ><mat-icon>last_page</mat-icon>add-before</mat-option\r\n >\r\n <mat-option [value]=\"3\"\r\n ><mat-icon>first_page</mat-icon>add-after</mat-option\r\n >\r\n <mat-option [value]=\"4\"\r\n ><mat-icon>login</mat-icon>move-before</mat-option\r\n >\r\n <mat-option [value]=\"5\"\r\n ><mat-icon>logout</mat-icon>move-after</mat-option\r\n >\r\n <mat-option [value]=\"6\"\r\n ><mat-icon>compare_arrows</mat-icon>swap</mat-option\r\n >\r\n </mat-select>\r\n @if ($any(type).errors?.required && (type.dirty || type.touched)) {\r\n <mat-error>type required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- at -->\r\n <mat-form-field class=\"nr\">\r\n <mat-label>at</mat-label>\r\n <input matInput [formControl]=\"at\" type=\"number\" min=\"0\" />\r\n @if ($any(at).errors?.required && (at.dirty || at.touched)) {\r\n <mat-error>at required</mat-error>\r\n }\r\n @if ($any(at).errors?.min && (at.dirty || at.touched)) {\r\n <mat-error>at must be at least 0</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- atAsIndex -->\r\n <mat-checkbox [formControl]=\"atAsIndex\">idx</mat-checkbox>\r\n\r\n <!-- run -->\r\n <mat-form-field class=\"nr\">\r\n <mat-label>run</mat-label>\r\n <input matInput [formControl]=\"run\" type=\"number\" min=\"0\" />\r\n @if ($any(run).errors?.required && (run.dirty || run.touched)) {\r\n <mat-error>run required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- to -->\r\n @if (hasTo()) {\r\n <mat-form-field class=\"nr\">\r\n <mat-label>to</mat-label>\r\n <input matInput [formControl]=\"to\" type=\"number\" min=\"0\" />\r\n @if ($any(to).errors?.required && (to.dirty || to.touched)) {\r\n <mat-error>to required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- toAsIndex -->\r\n <mat-checkbox [formControl]=\"toAsIndex\">idx</mat-checkbox>\r\n\r\n <!-- toRun -->\r\n @if (hasToRun()) {\r\n <mat-form-field class=\"nr\">\r\n <mat-label>to run</mat-label>\r\n <input matInput [formControl]=\"toRun\" type=\"number\" min=\"0\" />\r\n @if (\r\n $any(toRun).errors?.required && (toRun.dirty || toRun.touched)\r\n ) {\r\n <mat-error>to run required</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n }\r\n\r\n <!-- value -->\r\n @if (hasValue()) {\r\n <mat-form-field>\r\n <mat-label>value</mat-label>\r\n <input matInput [formControl]=\"value\" />\r\n @if (\r\n $any(value).errors?.required && (value.dirty || value.touched)\r\n ) {\r\n <mat-error>value required</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n </div>\r\n <div class=\"form-row\">\r\n <!-- rank -->\r\n <mat-form-field class=\"nr\">\r\n <mat-label>rank</mat-label>\r\n <input matInput [formControl]=\"rank\" type=\"number\" min=\"0\" />\r\n @if ($any(rank).errors?.required && (rank.dirty || rank.touched)) {\r\n <mat-error>rank required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- groupId -->\r\n <mat-form-field>\r\n <mat-label>group ID</mat-label>\r\n <input matInput [formControl]=\"groupId\" />\r\n @if (\r\n $any(groupId).errors?.required && (groupId.dirty || groupId.touched)\r\n ) {\r\n <mat-error>group ID required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- inputTag -->\r\n <mat-form-field>\r\n <mat-label>input tag</mat-label>\r\n <input matInput [formControl]=\"inputTag\" />\r\n <mat-hint>blank=latest</mat-hint>\r\n </mat-form-field>\r\n\r\n <!-- outputTag -->\r\n <mat-form-field>\r\n <mat-label>output tag</mat-label>\r\n <input matInput [formControl]=\"outputTag\" />\r\n <mat-hint>blank=auto (vN)</mat-hint>\r\n </mat-form-field>\r\n </div>\r\n <div>\r\n <!-- features -->\r\n <fieldset>\r\n <legend>operation features</legend>\r\n <gve-feature-set-editor\r\n [isVar]=\"true\"\r\n [featNames]=\"featureDefs()?.names\"\r\n [featValues]=\"featureDefs()?.values\"\r\n [features]=\"features.value\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n (featuresChange)=\"onFeaturesChange($event)\"\r\n />\r\n </fieldset>\r\n </div>\r\n </mat-tab>\r\n\r\n <!-- SOURCES -->\r\n <mat-tab label=\"sources\">\r\n <div>\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n color=\"primary\"\r\n class=\"mat-primary\"\r\n (click)=\"addSource()\"\r\n >\r\n <mat-icon>add_circle</mat-icon>\r\n source\r\n </button>\r\n </div>\r\n <table>\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>type</th>\r\n <th>id</th>\r\n <th>rank</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (s of sources.value; track s) {\r\n <tr [class.selected]=\"s === editedSource()\">\r\n <td class=\"fit-width\">\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n color=\"primary\"\r\n (click)=\"editSource($index)\"\r\n >\r\n <mat-icon class=\"mat-primary\">edit</mat-icon>\r\n </button>\r\n <button type=\"button\" mat-icon-button color=\"warn\">\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n </td>\r\n <td>{{ s.type | flatLookup: sourceTypes() : \"id\" : \"label\" }}</td>\r\n <td>{{ s.id | flatLookup: sourceIds() : \"id\" : \"label\" }}</td>\r\n <td>{{ s.rank }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n @if (editedSource()) {\r\n <!-- source editor -->\r\n <mat-expansion-panel\r\n [disabled]=\"!editedSource()\"\r\n [expanded]=\"editedSource()\"\r\n >\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>source</mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <gve-operation-source-editor\r\n [source]=\"editedSource()\"\r\n [ids]=\"sourceIds()\"\r\n [types]=\"sourceTypes()\"\r\n (sourceChange)=\"onSourceChange($event!)\"\r\n (sourceCancel)=\"closeSource()\"\r\n />\r\n </mat-expansion-panel>\r\n }\r\n </mat-tab>\r\n </mat-tab-group>\r\n\r\n <!-- buttons -->\r\n <div id=\"submit-row\">\r\n <button\r\n type=\"button\"\r\n color=\"warn\"\r\n mat-icon-button\r\n matTooltip=\"Discard operation\"\r\n (click)=\"cancel()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n @if (!hidePreview()) {\r\n <button\r\n type=\"button\"\r\n color=\"primary\"\r\n mat-icon-button\r\n matTooltip=\"Preview operation\"\r\n (click)=\"requestPreview()\"\r\n >\r\n <mat-icon class=\"mat-primary\">preview</mat-icon>\r\n </button>\r\n }\r\n <button\r\n type=\"submit\"\r\n color=\"primary\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Save operation\"\r\n [disabled]=\"form.invalid || form.pristine\"\r\n >\r\n <mat-icon>check_circle</mat-icon>\r\n save\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}#submit-row{display:flex;gap:8px;align-items:center;justify-content:center;flex-wrap:wrap;margin-top:8px;border-top:1px solid var(--mat-sys-on-surface-variant)}.toolbar-row{display:flex;align-items:center;flex-wrap:wrap}.nr{width:6em}.long-text{width:100%;max-width:800px}#id{border-radius:6px;padding:4px;background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container);margin-top:-16px}fieldset{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:8px;margin:8px 0}legend{color:var(--mat-sys-on-surface-variant)}table{width:100%;border-collapse:collapse;color:var(--mat-sys-on-surface-variant)}tbody tr:nth-child(odd){background-color:var(--mat-sys-surface-container)}th{text-align:left;font-weight:400;color:var(--mat-sys-on-surface-variant)}tbody tr:hover{background-color:var(--mat-sys-surface-container-high)}td.fit-width{width:1px;white-space:nowrap}tbody tr.selected{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}#code{height:500px;border:1px solid var(--mat-sys-on-surface-variant)}#monaco{height:100%}#preview{box-sizing:border-box;width:100%;border:1px solid var(--mat-sys-on-surface-variant);border-radius:4px;padding:8px}.tree-invisible{display:none}.tree ul,.tree li{margin-top:0;margin-bottom:0;list-style-type:none}.selected-node{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}.child-title{font-weight:700;margin:0;background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container);padding:8px}#tree-container{display:grid;grid-template-rows:auto;grid-template-columns:auto 1fr;grid-template-areas:\"nav ed\";gap:0 16px;align-items:stretch}#tree-nav{grid-area:nav;border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;margin:8px 0;padding-right:8px}#tree-ed{grid-area:ed;border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;margin:8px 0}@media only screen and (max-width:959px){div#container{grid-template-columns:1fr;grid-template-areas:\"nav\" \"ed\";gap:16px 0;align-items:start}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { 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.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { 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: "ngmodule", type: ClipboardModule }, { kind: "ngmodule", type: MatBadgeModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i3$1.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: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i7$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i7$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i7$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.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: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i12.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i12.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: NgeMonacoModule }, { kind: "component", type: FeatureSetEditorComponent, selector: "gve-feature-set-editor", inputs: ["isVar", "featNames", "featValues", "filterThreshold", "multiValuedFeatureIds", "hasCloseButton", "features"], outputs: ["close", "featuresChange"] }, { kind: "component", type: OperationSourceEditorComponent, selector: "gve-operation-source-editor", inputs: ["source", "ids", "types"], outputs: ["sourceChange", "sourceCancel"] }, { kind: "pipe", type: FlatLookupPipe, name: "flatLookup" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2063
2089
  }
2064
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ChainOperationEditorComponent, decorators: [{
2090
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: ChainOperationEditorComponent, decorators: [{
2065
2091
  type: Component,
2066
2092
  args: [{ selector: 'gve-chain-operation-editor', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
2067
2093
  ReactiveFormsModule,
@@ -2080,7 +2106,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
2080
2106
  FeatureSetEditorComponent,
2081
2107
  OperationSourceEditorComponent,
2082
2108
  FlatLookupPipe,
2083
- ], template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <!-- tabs -->\r\n <mat-tab-group [selectedIndex]=\"tabIndex()\">\r\n <!-- GENERAL -->\r\n <mat-tab label=\"general\">\r\n <div class=\"form-row\">\r\n <!-- id -->\r\n <div id=\"id\" class=\"muted\">{{ id() }}</div>\r\n\r\n <!-- type -->\r\n <mat-form-field>\r\n <mat-label>type</mat-label>\r\n <mat-select [formControl]=\"type\">\r\n <mat-option [value]=\"7\"\r\n ><mat-icon>edit_note</mat-icon>annotate</mat-option\r\n >\r\n <mat-option [value]=\"0\"\r\n ><mat-icon>layers</mat-icon> replace</mat-option\r\n >\r\n <mat-option [value]=\"1\"\r\n ><mat-icon>close</mat-icon>delete</mat-option\r\n >\r\n <mat-option [value]=\"2\"\r\n ><mat-icon>last_page</mat-icon>add-before</mat-option\r\n >\r\n <mat-option [value]=\"3\"\r\n ><mat-icon>first_page</mat-icon>add-after</mat-option\r\n >\r\n <mat-option [value]=\"4\"\r\n ><mat-icon>login</mat-icon>move-before</mat-option\r\n >\r\n <mat-option [value]=\"5\"\r\n ><mat-icon>logout</mat-icon>move-after</mat-option\r\n >\r\n <mat-option [value]=\"6\"\r\n ><mat-icon>compare_arrows</mat-icon>swap</mat-option\r\n >\r\n </mat-select>\r\n @if ($any(type).errors?.required && (type.dirty || type.touched)) {\r\n <mat-error>type required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- at -->\r\n <mat-form-field class=\"nr\">\r\n <mat-label>at</mat-label>\r\n <input matInput [formControl]=\"at\" type=\"number\" min=\"0\" />\r\n @if ($any(at).errors?.required && (at.dirty || at.touched)) {\r\n <mat-error>at required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- atAsIndex -->\r\n <mat-checkbox [formControl]=\"atAsIndex\">idx</mat-checkbox>\r\n\r\n <!-- run -->\r\n <mat-form-field class=\"nr\">\r\n <mat-label>run</mat-label>\r\n <input matInput [formControl]=\"run\" type=\"number\" min=\"0\" />\r\n @if ($any(run).errors?.required && (run.dirty || run.touched)) {\r\n <mat-error>run required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- to -->\r\n @if (hasTo()) {\r\n <mat-form-field class=\"nr\">\r\n <mat-label>to</mat-label>\r\n <input matInput [formControl]=\"to\" type=\"number\" min=\"0\" />\r\n @if ($any(to).errors?.required && (to.dirty || to.touched)) {\r\n <mat-error>to required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- toAsIndex -->\r\n <mat-checkbox [formControl]=\"toAsIndex\">idx</mat-checkbox>\r\n\r\n <!-- toRun -->\r\n @if (hasToRun()) {\r\n <mat-form-field class=\"nr\">\r\n <mat-label>to run</mat-label>\r\n <input matInput [formControl]=\"toRun\" type=\"number\" min=\"0\" />\r\n @if (\r\n $any(toRun).errors?.required && (toRun.dirty || toRun.touched)\r\n ) {\r\n <mat-error>to run required</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n }\r\n\r\n <!-- value -->\r\n @if (hasValue()) {\r\n <mat-form-field>\r\n <mat-label>value</mat-label>\r\n <input matInput [formControl]=\"value\" />\r\n </mat-form-field>\r\n }\r\n </div>\r\n <div class=\"form-row\">\r\n <!-- rank -->\r\n <mat-form-field class=\"nr\">\r\n <mat-label>rank</mat-label>\r\n <input matInput [formControl]=\"rank\" type=\"number\" min=\"0\" />\r\n @if ($any(rank).errors?.required && (rank.dirty || rank.touched)) {\r\n <mat-error>rank required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- groupId -->\r\n <mat-form-field>\r\n <mat-label>group ID</mat-label>\r\n <input matInput [formControl]=\"groupId\" />\r\n @if (\r\n $any(groupId).errors?.required && (groupId.dirty || groupId.touched)\r\n ) {\r\n <mat-error>group ID required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- inputTag -->\r\n <mat-form-field>\r\n <mat-label>input tag</mat-label>\r\n <input matInput [formControl]=\"inputTag\" />\r\n <mat-hint>blank=latest</mat-hint>\r\n </mat-form-field>\r\n\r\n <!-- outputTag -->\r\n <mat-form-field>\r\n <mat-label>output tag</mat-label>\r\n <input matInput [formControl]=\"outputTag\" />\r\n <mat-hint>blank=auto (vN)</mat-hint>\r\n </mat-form-field>\r\n </div>\r\n <div>\r\n <!-- features -->\r\n <fieldset>\r\n <legend>operation features</legend>\r\n <gve-feature-set-editor\r\n [isVar]=\"true\"\r\n [featNames]=\"featureDefs()?.names\"\r\n [featValues]=\"featureDefs()?.values\"\r\n [features]=\"features.value\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n (featuresChange)=\"onFeaturesChange($event)\"\r\n />\r\n </fieldset>\r\n </div>\r\n </mat-tab>\r\n\r\n <!-- SOURCES -->\r\n <mat-tab label=\"sources\">\r\n <div>\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n color=\"primary\"\r\n class=\"mat-primary\"\r\n (click)=\"addSource()\"\r\n >\r\n <mat-icon>add_circle</mat-icon>\r\n source\r\n </button>\r\n </div>\r\n <table>\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>type</th>\r\n <th>id</th>\r\n <th>rank</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (s of sources.value; track s) {\r\n <tr [class.selected]=\"s === editedSource()\">\r\n <td class=\"fit-width\">\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n color=\"primary\"\r\n (click)=\"editSource($index)\"\r\n >\r\n <mat-icon class=\"mat-primary\">edit</mat-icon>\r\n </button>\r\n <button type=\"button\" mat-icon-button color=\"warn\">\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n </td>\r\n <td>{{ s.type | flatLookup: sourceTypes() : \"id\" : \"label\" }}</td>\r\n <td>{{ s.id | flatLookup: sourceIds() : \"id\" : \"label\" }}</td>\r\n <td>{{ s.rank }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n @if (editedSource()) {\r\n <!-- source editor -->\r\n <mat-expansion-panel\r\n [disabled]=\"!editedSource()\"\r\n [expanded]=\"editedSource()\"\r\n >\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>source</mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <gve-operation-source-editor\r\n [source]=\"editedSource()\"\r\n [ids]=\"sourceIds()\"\r\n [types]=\"sourceTypes()\"\r\n (sourceChange)=\"onSourceChange($event!)\"\r\n (sourceCancel)=\"closeSource()\"\r\n />\r\n </mat-expansion-panel>\r\n }\r\n </mat-tab>\r\n </mat-tab-group>\r\n\r\n <!-- buttons -->\r\n <div id=\"submit-row\">\r\n <button\r\n type=\"button\"\r\n color=\"warn\"\r\n mat-icon-button\r\n matTooltip=\"Discard operation\"\r\n (click)=\"cancel()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n @if (!hidePreview()) {\r\n <button\r\n type=\"button\"\r\n color=\"primary\"\r\n mat-icon-button\r\n matTooltip=\"Preview operation\"\r\n (click)=\"requestPreview()\"\r\n >\r\n <mat-icon class=\"mat-primary\">preview</mat-icon>\r\n </button>\r\n }\r\n <button\r\n type=\"submit\"\r\n color=\"primary\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Save operation\"\r\n [disabled]=\"form.invalid || form.pristine\"\r\n >\r\n <mat-icon>check_circle</mat-icon>\r\n save\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}#submit-row{display:flex;gap:8px;align-items:center;justify-content:center;flex-wrap:wrap;margin-top:8px;border-top:1px solid var(--mat-sys-on-surface-variant)}.toolbar-row{display:flex;align-items:center;flex-wrap:wrap}.nr{width:6em}.long-text{width:100%;max-width:800px}#id{border-radius:6px;padding:4px;background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container);margin-top:-16px}fieldset{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:8px;margin:8px 0}legend{color:var(--mat-sys-on-surface-variant)}table{width:100%;border-collapse:collapse;color:var(--mat-sys-on-surface-variant)}tbody tr:nth-child(odd){background-color:var(--mat-sys-surface-container)}th{text-align:left;font-weight:400;color:var(--mat-sys-on-surface-variant)}tbody tr:hover{background-color:var(--mat-sys-surface-container-high)}td.fit-width{width:1px;white-space:nowrap}tbody tr.selected{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}#code{height:500px;border:1px solid var(--mat-sys-on-surface-variant)}#monaco{height:100%}#preview{box-sizing:border-box;width:100%;border:1px solid var(--mat-sys-on-surface-variant);border-radius:4px;padding:8px}.tree-invisible{display:none}.tree ul,.tree li{margin-top:0;margin-bottom:0;list-style-type:none}.selected-node{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}.child-title{font-weight:700;margin:0;background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container);padding:8px}#tree-container{display:grid;grid-template-rows:auto;grid-template-columns:auto 1fr;grid-template-areas:\"nav ed\";gap:0 16px;align-items:stretch}#tree-nav{grid-area:nav;border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;margin:8px 0;padding-right:8px}#tree-ed{grid-area:ed;border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;margin:8px 0}@media only screen and (max-width:959px){div#container{grid-template-columns:1fr;grid-template-areas:\"nav\" \"ed\";gap:16px 0;align-items:start}}\n"] }]
2109
+ ], template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <!-- tabs -->\r\n <mat-tab-group [selectedIndex]=\"tabIndex()\">\r\n <!-- GENERAL -->\r\n <mat-tab label=\"general\">\r\n <div class=\"form-row\">\r\n <!-- id -->\r\n <div id=\"id\" class=\"muted\">{{ id() }}</div>\r\n\r\n <!-- type -->\r\n <mat-form-field>\r\n <mat-label>type</mat-label>\r\n <mat-select [formControl]=\"type\">\r\n <mat-option [value]=\"7\"\r\n ><mat-icon>edit_note</mat-icon>annotate</mat-option\r\n >\r\n <mat-option [value]=\"0\"\r\n ><mat-icon>layers</mat-icon> replace</mat-option\r\n >\r\n <mat-option [value]=\"1\"\r\n ><mat-icon>close</mat-icon>delete</mat-option\r\n >\r\n <mat-option [value]=\"2\"\r\n ><mat-icon>last_page</mat-icon>add-before</mat-option\r\n >\r\n <mat-option [value]=\"3\"\r\n ><mat-icon>first_page</mat-icon>add-after</mat-option\r\n >\r\n <mat-option [value]=\"4\"\r\n ><mat-icon>login</mat-icon>move-before</mat-option\r\n >\r\n <mat-option [value]=\"5\"\r\n ><mat-icon>logout</mat-icon>move-after</mat-option\r\n >\r\n <mat-option [value]=\"6\"\r\n ><mat-icon>compare_arrows</mat-icon>swap</mat-option\r\n >\r\n </mat-select>\r\n @if ($any(type).errors?.required && (type.dirty || type.touched)) {\r\n <mat-error>type required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- at -->\r\n <mat-form-field class=\"nr\">\r\n <mat-label>at</mat-label>\r\n <input matInput [formControl]=\"at\" type=\"number\" min=\"0\" />\r\n @if ($any(at).errors?.required && (at.dirty || at.touched)) {\r\n <mat-error>at required</mat-error>\r\n }\r\n @if ($any(at).errors?.min && (at.dirty || at.touched)) {\r\n <mat-error>at must be at least 0</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- atAsIndex -->\r\n <mat-checkbox [formControl]=\"atAsIndex\">idx</mat-checkbox>\r\n\r\n <!-- run -->\r\n <mat-form-field class=\"nr\">\r\n <mat-label>run</mat-label>\r\n <input matInput [formControl]=\"run\" type=\"number\" min=\"0\" />\r\n @if ($any(run).errors?.required && (run.dirty || run.touched)) {\r\n <mat-error>run required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- to -->\r\n @if (hasTo()) {\r\n <mat-form-field class=\"nr\">\r\n <mat-label>to</mat-label>\r\n <input matInput [formControl]=\"to\" type=\"number\" min=\"0\" />\r\n @if ($any(to).errors?.required && (to.dirty || to.touched)) {\r\n <mat-error>to required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- toAsIndex -->\r\n <mat-checkbox [formControl]=\"toAsIndex\">idx</mat-checkbox>\r\n\r\n <!-- toRun -->\r\n @if (hasToRun()) {\r\n <mat-form-field class=\"nr\">\r\n <mat-label>to run</mat-label>\r\n <input matInput [formControl]=\"toRun\" type=\"number\" min=\"0\" />\r\n @if (\r\n $any(toRun).errors?.required && (toRun.dirty || toRun.touched)\r\n ) {\r\n <mat-error>to run required</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n }\r\n\r\n <!-- value -->\r\n @if (hasValue()) {\r\n <mat-form-field>\r\n <mat-label>value</mat-label>\r\n <input matInput [formControl]=\"value\" />\r\n @if (\r\n $any(value).errors?.required && (value.dirty || value.touched)\r\n ) {\r\n <mat-error>value required</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n </div>\r\n <div class=\"form-row\">\r\n <!-- rank -->\r\n <mat-form-field class=\"nr\">\r\n <mat-label>rank</mat-label>\r\n <input matInput [formControl]=\"rank\" type=\"number\" min=\"0\" />\r\n @if ($any(rank).errors?.required && (rank.dirty || rank.touched)) {\r\n <mat-error>rank required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- groupId -->\r\n <mat-form-field>\r\n <mat-label>group ID</mat-label>\r\n <input matInput [formControl]=\"groupId\" />\r\n @if (\r\n $any(groupId).errors?.required && (groupId.dirty || groupId.touched)\r\n ) {\r\n <mat-error>group ID required</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- inputTag -->\r\n <mat-form-field>\r\n <mat-label>input tag</mat-label>\r\n <input matInput [formControl]=\"inputTag\" />\r\n <mat-hint>blank=latest</mat-hint>\r\n </mat-form-field>\r\n\r\n <!-- outputTag -->\r\n <mat-form-field>\r\n <mat-label>output tag</mat-label>\r\n <input matInput [formControl]=\"outputTag\" />\r\n <mat-hint>blank=auto (vN)</mat-hint>\r\n </mat-form-field>\r\n </div>\r\n <div>\r\n <!-- features -->\r\n <fieldset>\r\n <legend>operation features</legend>\r\n <gve-feature-set-editor\r\n [isVar]=\"true\"\r\n [featNames]=\"featureDefs()?.names\"\r\n [featValues]=\"featureDefs()?.values\"\r\n [features]=\"features.value\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n (featuresChange)=\"onFeaturesChange($event)\"\r\n />\r\n </fieldset>\r\n </div>\r\n </mat-tab>\r\n\r\n <!-- SOURCES -->\r\n <mat-tab label=\"sources\">\r\n <div>\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n color=\"primary\"\r\n class=\"mat-primary\"\r\n (click)=\"addSource()\"\r\n >\r\n <mat-icon>add_circle</mat-icon>\r\n source\r\n </button>\r\n </div>\r\n <table>\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>type</th>\r\n <th>id</th>\r\n <th>rank</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (s of sources.value; track s) {\r\n <tr [class.selected]=\"s === editedSource()\">\r\n <td class=\"fit-width\">\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n color=\"primary\"\r\n (click)=\"editSource($index)\"\r\n >\r\n <mat-icon class=\"mat-primary\">edit</mat-icon>\r\n </button>\r\n <button type=\"button\" mat-icon-button color=\"warn\">\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n </td>\r\n <td>{{ s.type | flatLookup: sourceTypes() : \"id\" : \"label\" }}</td>\r\n <td>{{ s.id | flatLookup: sourceIds() : \"id\" : \"label\" }}</td>\r\n <td>{{ s.rank }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n @if (editedSource()) {\r\n <!-- source editor -->\r\n <mat-expansion-panel\r\n [disabled]=\"!editedSource()\"\r\n [expanded]=\"editedSource()\"\r\n >\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>source</mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <gve-operation-source-editor\r\n [source]=\"editedSource()\"\r\n [ids]=\"sourceIds()\"\r\n [types]=\"sourceTypes()\"\r\n (sourceChange)=\"onSourceChange($event!)\"\r\n (sourceCancel)=\"closeSource()\"\r\n />\r\n </mat-expansion-panel>\r\n }\r\n </mat-tab>\r\n </mat-tab-group>\r\n\r\n <!-- buttons -->\r\n <div id=\"submit-row\">\r\n <button\r\n type=\"button\"\r\n color=\"warn\"\r\n mat-icon-button\r\n matTooltip=\"Discard operation\"\r\n (click)=\"cancel()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n @if (!hidePreview()) {\r\n <button\r\n type=\"button\"\r\n color=\"primary\"\r\n mat-icon-button\r\n matTooltip=\"Preview operation\"\r\n (click)=\"requestPreview()\"\r\n >\r\n <mat-icon class=\"mat-primary\">preview</mat-icon>\r\n </button>\r\n }\r\n <button\r\n type=\"submit\"\r\n color=\"primary\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Save operation\"\r\n [disabled]=\"form.invalid || form.pristine\"\r\n >\r\n <mat-icon>check_circle</mat-icon>\r\n save\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}#submit-row{display:flex;gap:8px;align-items:center;justify-content:center;flex-wrap:wrap;margin-top:8px;border-top:1px solid var(--mat-sys-on-surface-variant)}.toolbar-row{display:flex;align-items:center;flex-wrap:wrap}.nr{width:6em}.long-text{width:100%;max-width:800px}#id{border-radius:6px;padding:4px;background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container);margin-top:-16px}fieldset{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:8px;margin:8px 0}legend{color:var(--mat-sys-on-surface-variant)}table{width:100%;border-collapse:collapse;color:var(--mat-sys-on-surface-variant)}tbody tr:nth-child(odd){background-color:var(--mat-sys-surface-container)}th{text-align:left;font-weight:400;color:var(--mat-sys-on-surface-variant)}tbody tr:hover{background-color:var(--mat-sys-surface-container-high)}td.fit-width{width:1px;white-space:nowrap}tbody tr.selected{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}#code{height:500px;border:1px solid var(--mat-sys-on-surface-variant)}#monaco{height:100%}#preview{box-sizing:border-box;width:100%;border:1px solid var(--mat-sys-on-surface-variant);border-radius:4px;padding:8px}.tree-invisible{display:none}.tree ul,.tree li{margin-top:0;margin-bottom:0;list-style-type:none}.selected-node{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}.child-title{font-weight:700;margin:0;background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container);padding:8px}#tree-container{display:grid;grid-template-rows:auto;grid-template-columns:auto 1fr;grid-template-areas:\"nav ed\";gap:0 16px;align-items:stretch}#tree-nav{grid-area:nav;border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;margin:8px 0;padding-right:8px}#tree-ed{grid-area:ed;border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;margin:8px 0}@media only screen and (max-width:959px){div#container{grid-template-columns:1fr;grid-template-areas:\"nav\" \"ed\";gap:16px 0;align-items:start}}\n"] }]
2084
2110
  }], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: i2$2.Clipboard }, { type: SettingsService }, { type: i4$1.DialogService }], propDecorators: { operation: [{ type: i0.Input, args: [{ isSignal: true, alias: "operation", required: false }] }, { type: i0.Output, args: ["operationChange"] }], snapshot: [{ type: i0.Input, args: [{ isSignal: true, alias: "snapshot", required: false }] }], hidePreview: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidePreview", required: false }] }], featureDefs: [{ type: i0.Input, args: [{ isSignal: true, alias: "featureDefs", required: false }] }], rangePatch: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangePatch", required: false }] }], multiValuedFeatureIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiValuedFeatureIds", required: false }] }], sourceIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceIds", required: false }] }], sourceTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceTypes", required: false }] }], operationChange: [{ type: i0.Output, args: ["operationChange"] }], operationPreview: [{ type: i0.Output, args: ["operationPreview"] }], operationCancel: [{ type: i0.Output, args: ["operationCancel"] }] } });
2085
2111
 
2086
2112
  /**
@@ -2149,10 +2175,10 @@ class FeatureSetViewComponent {
2149
2175
  ngOnDestroy() {
2150
2176
  this._sub?.unsubscribe();
2151
2177
  }
2152
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FeatureSetViewComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
2153
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: FeatureSetViewComponent, isStandalone: true, selector: "gve-feature-set-view", inputs: { features: { classPropertyName: "features", publicName: "features", isSignal: true, isRequired: false, transformFunction: null }, featNames: { classPropertyName: "featNames", publicName: "featNames", isSignal: true, isRequired: false, transformFunction: null }, featValues: { classPropertyName: "featValues", publicName: "featValues", isSignal: true, isRequired: false, transformFunction: null }, filterThreshold: { classPropertyName: "filterThreshold", publicName: "filterThreshold", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div>\r\n <!-- filter -->\r\n <div>\r\n @if (filterThreshold() === 0 || features()!.length > filterThreshold()) {\r\n <div class=\"form-row\">\r\n <mat-form-field id=\"filter\">\r\n <mat-label>filter</mat-label>\r\n <input matInput [formControl]=\"filter\" />\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matSuffix\r\n (click)=\"filter.reset()\"\r\n [disabled]=\"!filter.value\"\r\n [attr.aria-label]=\"'Reset filter'\"\r\n >\r\n <mat-icon color=\"warn\" class=\"mat-warn\">cancel</mat-icon>\r\n </button>\r\n </mat-form-field>\r\n <span id=\"badge\">{{ filteredFeatures().length }}</span>\r\n </div>\r\n }\r\n\r\n <!-- list -->\r\n @if (filteredFeatures().length) {\r\n <table>\r\n <tbody>\r\n @for (feature of filteredFeatures(); track $index) {\r\n <tr>\r\n <th>\r\n @if (featNames()?.length) {\r\n <span>{{\r\n feature.name | flatLookup: featNames() : \"id\" : \"label\"\r\n }}</span>\r\n } @else {\r\n <span>{{ feature.name }}</span>\r\n }\r\n </th>\r\n <td>\r\n @if (featValues()) {\r\n <span>{{\r\n feature.value | flatLookup: featValues()![feature.name]\r\n }}</span>\r\n } @else {\r\n <span>{{ feature.value }}</span>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n }\r\n </div>\r\n</div>\r\n", styles: [":host{display:block;width:100%;min-width:0}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}.in-row-button{margin-top:-16px}table{width:100%;border-collapse:collapse;color:var(--mat-sys-on-surface-variant)}tbody tr:nth-child(odd){background-color:var(--mat-sys-surface-container)}th{text-align:left;font-weight:400;color:var(--mat-sys-on-surface-variant)}tbody tr:hover{background-color:var(--mat-sys-surface-container-high)}td.fit-width{width:1px;white-space:nowrap}tbody tr.selected{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}#filter{width:7em}#badge{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:4px;margin-top:-18px}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "pipe", type: FlatLookupPipe, name: "flatLookup" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2178
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: FeatureSetViewComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
2179
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: FeatureSetViewComponent, isStandalone: true, selector: "gve-feature-set-view", inputs: { features: { classPropertyName: "features", publicName: "features", isSignal: true, isRequired: false, transformFunction: null }, featNames: { classPropertyName: "featNames", publicName: "featNames", isSignal: true, isRequired: false, transformFunction: null }, featValues: { classPropertyName: "featValues", publicName: "featValues", isSignal: true, isRequired: false, transformFunction: null }, filterThreshold: { classPropertyName: "filterThreshold", publicName: "filterThreshold", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div>\r\n <!-- filter -->\r\n <div>\r\n @if (filterThreshold() === 0 || features()!.length > filterThreshold()) {\r\n <div class=\"form-row\">\r\n <mat-form-field id=\"filter\">\r\n <mat-label>filter</mat-label>\r\n <input matInput [formControl]=\"filter\" />\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matSuffix\r\n (click)=\"filter.reset()\"\r\n [disabled]=\"!filter.value\"\r\n [attr.aria-label]=\"'Reset filter'\"\r\n >\r\n <mat-icon color=\"warn\" class=\"mat-warn\">cancel</mat-icon>\r\n </button>\r\n </mat-form-field>\r\n <span id=\"badge\">{{ filteredFeatures().length }}</span>\r\n </div>\r\n }\r\n\r\n <!-- list -->\r\n @if (filteredFeatures().length) {\r\n <table>\r\n <tbody>\r\n @for (feature of filteredFeatures(); track $index) {\r\n <tr>\r\n <th>\r\n @if (featNames()?.length) {\r\n <span>{{\r\n feature.name | flatLookup: featNames() : \"id\" : \"label\"\r\n }}</span>\r\n } @else {\r\n <span>{{ feature.name }}</span>\r\n }\r\n </th>\r\n <td>\r\n @if (featValues()) {\r\n <span>{{\r\n feature.value | flatLookup: featValues()![feature.name]\r\n }}</span>\r\n } @else {\r\n <span>{{ feature.value }}</span>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n }\r\n </div>\r\n</div>\r\n", styles: [":host{display:block;width:100%;min-width:0}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}.in-row-button{margin-top:-16px}table{width:100%;border-collapse:collapse;color:var(--mat-sys-on-surface-variant)}tbody tr:nth-child(odd){background-color:var(--mat-sys-surface-container)}th{text-align:left;font-weight:400;color:var(--mat-sys-on-surface-variant)}tbody tr:hover{background-color:var(--mat-sys-surface-container-high)}td.fit-width{width:1px;white-space:nowrap}tbody tr.selected{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}#filter{width:7em}#badge{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:4px;margin-top:-18px}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "pipe", type: FlatLookupPipe, name: "flatLookup" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2154
2180
  }
2155
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FeatureSetViewComponent, decorators: [{
2181
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: FeatureSetViewComponent, decorators: [{
2156
2182
  type: Component,
2157
2183
  args: [{ selector: 'gve-feature-set-view', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
2158
2184
  ReactiveFormsModule,
@@ -2272,10 +2298,10 @@ class StepsMapComponent {
2272
2298
  onStepClick(step) {
2273
2299
  this.selectedStep.set(step);
2274
2300
  }
2275
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: StepsMapComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2276
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: StepsMapComponent, isStandalone: true, selector: "gve-steps-map", inputs: { steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: false, transformFunction: null }, selectedStep: { classPropertyName: "selectedStep", publicName: "selectedStep", isSignal: true, isRequired: false, transformFunction: null }, textFontSize: { classPropertyName: "textFontSize", publicName: "textFontSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedStep: "selectedStepChange" }, ngImport: i0, template: "<div>\r\n @if (steps().length) {\r\n @for (step of steps(); track step.outputTag) {\r\n <div\r\n matRipple\r\n class=\"step form-row\"\r\n [class.selected]=\"step === selectedStep()\"\r\n (click)=\"onStepClick(step)\"\r\n >\r\n <!-- tag -->\r\n <div class=\"tag\">\r\n <span class=\"muted-tag\">{{ step.inputTag }} &#x25b6; </span>\r\n {{ step.outputTag }}\r\n </div>\r\n <!-- text lines -->\r\n <div class=\"text\" [style.fontSize]=\"textFontSize()\">\r\n @for (line of lines()[step.outputTag]; track $index) {\r\n <p class=\"line\">{{ line }}</p>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n</div>\r\n", styles: [".form-row{display:flex;gap:8px;align-items:start;flex-wrap:wrap}.form-row *{flex:0 0 auto}.form-row .text{flex:1 1 auto;max-width:100%}.selected{background-color:var(--mat-sys-surface-bright)}.step{border:1px solid var(--mat-sys-outline);border-radius:6px;margin:8px 0}.tag{background-color:var(--mat-sys-surface-container);color:var(--mat-sys-on-surface-variant);border:1px solid var(--mat-sys-outline);border-radius:6px;padding:4px;cursor:pointer}.muted-tag{color:var(--mat-sys-on-surface-variant)}.text{padding:0 4px;font-size:.5em;white-space:normal;overflow-wrap:break-word;-webkit-hyphens:auto;hyphens:auto;min-width:0;max-height:200px;overflow-y:auto}.text p{margin:0;white-space:normal}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatRippleModule }, { kind: "directive", type: i1.MatRipple, selector: "[mat-ripple], [matRipple]", inputs: ["matRippleColor", "matRippleUnbounded", "matRippleCentered", "matRippleRadius", "matRippleAnimation", "matRippleDisabled", "matRippleTrigger"], exportAs: ["matRipple"] }, { kind: "ngmodule", type: MatTooltipModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2301
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: StepsMapComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2302
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: StepsMapComponent, isStandalone: true, selector: "gve-steps-map", inputs: { steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: false, transformFunction: null }, selectedStep: { classPropertyName: "selectedStep", publicName: "selectedStep", isSignal: true, isRequired: false, transformFunction: null }, textFontSize: { classPropertyName: "textFontSize", publicName: "textFontSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedStep: "selectedStepChange" }, ngImport: i0, template: "<div>\r\n @if (steps().length) {\r\n @for (step of steps(); track step.outputTag) {\r\n <div\r\n matRipple\r\n class=\"step form-row\"\r\n [class.selected]=\"step === selectedStep()\"\r\n (click)=\"onStepClick(step)\"\r\n >\r\n <!-- tag -->\r\n <div class=\"tag\">\r\n <span class=\"muted-tag\">{{ step.inputTag }} &#x25b6; </span>\r\n {{ step.outputTag }}\r\n </div>\r\n <!-- text lines -->\r\n <div class=\"text\" [style.fontSize]=\"textFontSize()\">\r\n @for (line of lines()[step.outputTag]; track $index) {\r\n <p class=\"line\">{{ line }}</p>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n</div>\r\n", styles: [".form-row{display:flex;gap:8px;align-items:start;flex-wrap:wrap}.form-row *{flex:0 0 auto}.form-row .text{flex:1 1 auto;max-width:100%}.selected{background-color:var(--mat-sys-surface-bright)}.step{border:1px solid var(--mat-sys-outline);border-radius:6px;margin:8px 0}.tag{background-color:var(--mat-sys-surface-container);color:var(--mat-sys-on-surface-variant);border:1px solid var(--mat-sys-outline);border-radius:6px;padding:4px;cursor:pointer}.muted-tag{color:var(--mat-sys-on-surface-variant)}.text{padding:0 4px;font-size:.5em;white-space:normal;overflow-wrap:break-word;-webkit-hyphens:auto;hyphens:auto;min-width:0;max-height:200px;overflow-y:auto}.text p{margin:0;white-space:normal}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatRippleModule }, { kind: "directive", type: i1.MatRipple, selector: "[mat-ripple], [matRipple]", inputs: ["matRippleColor", "matRippleUnbounded", "matRippleCentered", "matRippleRadius", "matRippleAnimation", "matRippleDisabled", "matRippleTrigger"], exportAs: ["matRipple"] }, { kind: "ngmodule", type: MatTooltipModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2277
2303
  }
2278
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: StepsMapComponent, decorators: [{
2304
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: StepsMapComponent, decorators: [{
2279
2305
  type: Component,
2280
2306
  args: [{ selector: 'gve-steps-map', imports: [MatButtonModule, MatRippleModule, MatTooltipModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div>\r\n @if (steps().length) {\r\n @for (step of steps(); track step.outputTag) {\r\n <div\r\n matRipple\r\n class=\"step form-row\"\r\n [class.selected]=\"step === selectedStep()\"\r\n (click)=\"onStepClick(step)\"\r\n >\r\n <!-- tag -->\r\n <div class=\"tag\">\r\n <span class=\"muted-tag\">{{ step.inputTag }} &#x25b6; </span>\r\n {{ step.outputTag }}\r\n </div>\r\n <!-- text lines -->\r\n <div class=\"text\" [style.fontSize]=\"textFontSize()\">\r\n @for (line of lines()[step.outputTag]; track $index) {\r\n <p class=\"line\">{{ line }}</p>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n</div>\r\n", styles: [".form-row{display:flex;gap:8px;align-items:start;flex-wrap:wrap}.form-row *{flex:0 0 auto}.form-row .text{flex:1 1 auto;max-width:100%}.selected{background-color:var(--mat-sys-surface-bright)}.step{border:1px solid var(--mat-sys-outline);border-radius:6px;margin:8px 0}.tag{background-color:var(--mat-sys-surface-container);color:var(--mat-sys-on-surface-variant);border:1px solid var(--mat-sys-outline);border-radius:6px;padding:4px;cursor:pointer}.muted-tag{color:var(--mat-sys-on-surface-variant)}.text{padding:0 4px;font-size:.5em;white-space:normal;overflow-wrap:break-word;-webkit-hyphens:auto;hyphens:auto;min-width:0;max-height:200px;overflow-y:auto}.text p{margin:0;white-space:normal}\n"] }]
2281
2307
  }], ctorParameters: () => [], propDecorators: { steps: [{ type: i0.Input, args: [{ isSignal: true, alias: "steps", required: false }] }], selectedStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedStep", required: false }] }, { type: i0.Output, args: ["selectedStepChange"] }], textFontSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "textFontSize", required: false }] }] } });
@@ -2318,6 +2344,31 @@ class ChainResultViewComponent {
2318
2344
  this.selection = signal(undefined, ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
2319
2345
  this.selectionFeatures = signal([], ...(ngDevMode ? [{ debugName: "selectionFeatures" }] : /* istanbul ignore next */ []));
2320
2346
  this.selectionRange = signal(undefined, ...(ngDevMode ? [{ debugName: "selectionRange" }] : /* istanbul ignore next */ []));
2347
+ /**
2348
+ * Ordered nodes for the current step's version, resolved from
2349
+ * `result.versionNodeIds` and `result.nodes`.
2350
+ */
2351
+ this.stepNodes = computed(() => {
2352
+ const result = this.result();
2353
+ const step = this.step();
2354
+ if (!result || !step) {
2355
+ return [];
2356
+ }
2357
+ const ids = result.versionNodeIds[step.outputTag] ?? [];
2358
+ const nodeMap = new Map(result.nodes.map((n) => [n.id, n]));
2359
+ return ids.map((id) => nodeMap.get(id)).filter(Boolean);
2360
+ }, ...(ngDevMode ? [{ debugName: "stepNodes" }] : /* istanbul ignore next */ []));
2361
+ /**
2362
+ * Global features for the current step's output version.
2363
+ */
2364
+ this.stepGlobalFeatures = computed(() => {
2365
+ const result = this.result();
2366
+ const step = this.step();
2367
+ if (!result || !step) {
2368
+ return [];
2369
+ }
2370
+ return result.globalFeatures[step.outputTag] ?? [];
2371
+ }, ...(ngDevMode ? [{ debugName: "stepGlobalFeatures" }] : /* istanbul ignore next */ []));
2321
2372
  /**
2322
2373
  * True to disable range picking. For instance the snapshot editor,
2323
2374
  * which uses rangePick to set the edited operation's range, disables
@@ -2418,10 +2469,10 @@ class ChainResultViewComponent {
2418
2469
  this.versionTags.set([]);
2419
2470
  return;
2420
2471
  }
2421
- // extract version tags from result steps features named 'version'
2472
+ // extract version labels from global features named 'version'
2422
2473
  const tags = new Set();
2423
- for (const step of this.result().steps) {
2424
- for (const feature of step.featureSet.features) {
2474
+ for (const features of Object.values(this.result().globalFeatures)) {
2475
+ for (const feature of features) {
2425
2476
  if (feature.name === 'version') {
2426
2477
  tags.add(feature.value);
2427
2478
  }
@@ -2433,11 +2484,9 @@ class ChainResultViewComponent {
2433
2484
  if (!this.result() || !version) {
2434
2485
  return undefined;
2435
2486
  }
2436
- for (const step of this.result().steps) {
2437
- for (const feature of step.featureSet.features) {
2438
- if (feature.name === 'version' && feature.value === version) {
2439
- return step.outputTag;
2440
- }
2487
+ for (const [tag, features] of Object.entries(this.result().globalFeatures)) {
2488
+ if (features.some((f) => f.name === 'version' && f.value === version)) {
2489
+ return tag;
2441
2490
  }
2442
2491
  }
2443
2492
  return undefined;
@@ -2458,23 +2507,16 @@ class ChainResultViewComponent {
2458
2507
  this._stepSelectionDone.set(false);
2459
2508
  }
2460
2509
  getNodeFeatures(id) {
2461
- if (!this.step()) {
2510
+ const result = this.result();
2511
+ const step = this.step();
2512
+ if (!result || !step) {
2462
2513
  return [];
2463
2514
  }
2464
- return (this.step().featureSet.nodeFeatures[this.step().outputTag + '_' + id] ||
2465
- []);
2515
+ const node = result.nodes.find((n) => n.id === id);
2516
+ return node?.features?.[step.outputTag] ?? [];
2466
2517
  }
2467
2518
  findNode(id) {
2468
- if (!this.step()) {
2469
- return null;
2470
- }
2471
- for (let key of Object.keys(this.result().taggedNodes)) {
2472
- const node = this.result().taggedNodes[key].find((n) => n.id === id);
2473
- if (node) {
2474
- return node;
2475
- }
2476
- }
2477
- return null;
2519
+ return this.result()?.nodes.find((n) => n.id === id) ?? null;
2478
2520
  }
2479
2521
  onTextCharPick(event) {
2480
2522
  const features = this.getNodeFeatures(event.char.id);
@@ -2485,25 +2527,30 @@ class ChainResultViewComponent {
2485
2527
  }
2486
2528
  onTextRangePick(range) {
2487
2529
  if (range.run === 1) {
2488
- // already handled by onStepCharPick
2530
+ // already handled by onTextCharPick
2489
2531
  return;
2490
2532
  }
2491
2533
  this.selection.set(range.at + '×' + range.run);
2492
2534
  this.selectionRange.set(range);
2493
2535
  const end = range.at + range.run;
2494
2536
  const features = [];
2495
- const tagNodes = this.result().taggedNodes[this.tag.value];
2496
- for (let node of tagNodes) {
2497
- // if in range, intersect features
2537
+ const result = this.result();
2538
+ const tag = this.tag.value;
2539
+ const nodeIds = result.versionNodeIds[tag] ?? [];
2540
+ const nodeMap = new Map(result.nodes.map((n) => [n.id, n]));
2541
+ for (const nodeId of nodeIds) {
2542
+ const node = nodeMap.get(nodeId);
2543
+ if (!node)
2544
+ continue;
2498
2545
  if (node.id >= range.at && node.id < end) {
2499
2546
  const nodeFeatures = this.getNodeFeatures(node.id);
2500
2547
  if (node.id === range.at) {
2501
- for (let f of nodeFeatures) {
2548
+ for (const f of nodeFeatures) {
2502
2549
  features.push(f);
2503
2550
  }
2504
2551
  }
2505
2552
  else {
2506
- // features = intersection of features and charFeatures
2553
+ // features = intersection of features and nodeFeatures
2507
2554
  features.splice(0, features.length, ...features.filter((f) => nodeFeatures.some((cf) => cf.name === f.name)));
2508
2555
  }
2509
2556
  }
@@ -2522,10 +2569,10 @@ class ChainResultViewComponent {
2522
2569
  this.rangePick.emit(this.selectionRange());
2523
2570
  }
2524
2571
  }
2525
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ChainResultViewComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
2526
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ChainResultViewComponent, isStandalone: true, selector: "gve-chain-result-view", inputs: { result: { classPropertyName: "result", publicName: "result", isSignal: true, isRequired: false, transformFunction: null }, initialStepIndex: { classPropertyName: "initialStepIndex", publicName: "initialStepIndex", isSignal: true, isRequired: false, transformFunction: null }, disabledRangePick: { classPropertyName: "disabledRangePick", publicName: "disabledRangePick", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { stepPick: "stepPick", rangePick: "rangePick" }, ngImport: i0, template: "@if (result()) {\r\n<div id=\"container\">\r\n <div id=\"bar\" class=\"form-row\">\r\n <!-- version -->\r\n @if (versionTags().length) {\r\n <mat-form-field>\r\n <mat-label>stage</mat-label>\r\n <mat-select [formControl]=\"versionTag\">\r\n <mat-option [value]=\"null\">-</mat-option>\r\n @for (t of versionTags(); track t) {\r\n <mat-option [value]=\"t\">{{ t }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n }\r\n <!-- tag -->\r\n @if (tags().length) {\r\n <mat-form-field>\r\n <mat-label>tag</mat-label>\r\n <mat-select [formControl]=\"tag\">\r\n @for (t of tags(); track t) {\r\n <mat-option [value]=\"t\">{{ t }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n }\r\n <!-- range picker -->\r\n @if (selectionRange()) {\r\n <div class=\"range\">\r\n {{ selectionRange()!.at }}\u00D7{{ selectionRange()!.run }}\r\n </div>\r\n @if (!disabledRangePick()) {\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Pick this range for the edited operation\"\r\n (click)=\"pickRange()\"\r\n [disabled]=\"disabledRangePick()\"\r\n >\r\n <mat-icon class=\"mat-warn\">file_open</mat-icon>\r\n </button>\r\n } }\r\n </div>\r\n\r\n <!-- step -->\r\n @if (step()) {\r\n <div id=\"step\">\r\n <!-- text -->\r\n <div id=\"text\">\r\n <gve-base-text-view\r\n [text]=\"result()!.taggedNodes[step()!.outputTag]\"\r\n [colorCallback]=\"getCharColor\"\r\n [hasLineNumber]=\"true\"\r\n (charPick)=\"onTextCharPick($event)\"\r\n (rangePick)=\"onTextRangePick($event)\"\r\n />\r\n </div>\r\n <!-- features -->\r\n <div id=\"feats\" class=\"form-row-top\">\r\n <div id=\"g-feats\">\r\n <div class=\"feat-header\">{{ step()!.outputTag }}</div>\r\n <gve-feature-set-view [features]=\"step()!.featureSet.features\" />\r\n </div>\r\n <div id=\"n-feats\">\r\n @if (selectionFeatures().length) {\r\n <div class=\"feat-header\">{{ selection() }}</div>\r\n <gve-feature-set-view [features]=\"selectionFeatures()\" />\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- steps map -->\r\n <div id=\"map\">\r\n <gve-steps-map\r\n [steps]=\"result()!.steps\"\r\n [selectedStep]=\"step()\"\r\n (selectedStepChange)=\"onStepChange($event)\"\r\n />\r\n </div>\r\n</div>\r\n}\r\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}.form-row-top{display:flex;gap:8px;align-items:start;flex-wrap:wrap;width:100%;box-sizing:border-box}.form-row-top>div{flex:1 1 0%;min-width:0}.range{color:var(--mat-sys-on-surface-variant);border:1px solid var(--mat-sys-on-surface-variant);border-radius:4px;padding:4px}div#container{display:grid;grid-template-rows:auto 1fr;grid-template-columns:3fr 1fr;grid-template-areas:\"bar map\" \"step map\";gap:8px;height:100%}div#bar{grid-area:bar}div#map{grid-area:map;min-height:0;overflow-y:auto}div#step{grid-area:step;min-height:0;display:grid;grid-template-rows:1fr auto;gap:8px}div#text{min-height:0;overflow-y:auto}div#feats{width:100%}.feat-header{background-color:var(--mat-sys-primary);color:var(--mat-sys-on-primary);text-align:center;border:1px solid var(--mat-sys-primary);border-top-left-radius:4px;border-top-right-radius:4px}@media only screen and (max-width:959px){div#container{grid-template-columns:1fr;grid-template-areas:\"bar\" \"step\"}#map{display:none}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.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: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: FeatureSetViewComponent, selector: "gve-feature-set-view", inputs: ["features", "featNames", "featValues", "filterThreshold"] }, { kind: "component", type: StepsMapComponent, selector: "gve-steps-map", inputs: ["steps", "selectedStep", "textFontSize"], outputs: ["selectedStepChange"] }, { kind: "component", type: BaseTextViewComponent, selector: "gve-base-text-view", inputs: ["defaultColor", "defaultBorderColor", "selectionColor", "searchHighlightColor", "hasLineNumber", "text", "colorCallback", "borderColorCallback"], outputs: ["charPick", "rangePick"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2572
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: ChainResultViewComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
2573
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: ChainResultViewComponent, isStandalone: true, selector: "gve-chain-result-view", inputs: { result: { classPropertyName: "result", publicName: "result", isSignal: true, isRequired: false, transformFunction: null }, initialStepIndex: { classPropertyName: "initialStepIndex", publicName: "initialStepIndex", isSignal: true, isRequired: false, transformFunction: null }, disabledRangePick: { classPropertyName: "disabledRangePick", publicName: "disabledRangePick", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { stepPick: "stepPick", rangePick: "rangePick" }, ngImport: i0, template: "@if (result()) {\r\n<div id=\"container\">\r\n <div id=\"bar\" class=\"form-row\">\r\n <!-- version -->\r\n @if (versionTags().length) {\r\n <mat-form-field>\r\n <mat-label>stage</mat-label>\r\n <mat-select [formControl]=\"versionTag\">\r\n <mat-option [value]=\"null\">-</mat-option>\r\n @for (t of versionTags(); track t) {\r\n <mat-option [value]=\"t\">{{ t }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n }\r\n <!-- tag -->\r\n @if (tags().length) {\r\n <mat-form-field>\r\n <mat-label>tag</mat-label>\r\n <mat-select [formControl]=\"tag\">\r\n @for (t of tags(); track t) {\r\n <mat-option [value]=\"t\">{{ t }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n }\r\n <!-- range picker -->\r\n @if (selectionRange()) {\r\n <div class=\"range\">\r\n {{ selectionRange()!.at }}\u00D7{{ selectionRange()!.run }}\r\n </div>\r\n @if (!disabledRangePick()) {\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Pick this range for the edited operation\"\r\n (click)=\"pickRange()\"\r\n [disabled]=\"disabledRangePick()\"\r\n >\r\n <mat-icon class=\"mat-warn\">file_open</mat-icon>\r\n </button>\r\n } }\r\n </div>\r\n\r\n <!-- step -->\r\n @if (step()) {\r\n <div id=\"step\">\r\n <!-- text -->\r\n <div id=\"text\">\r\n <gve-base-text-view\r\n [text]=\"stepNodes()\"\r\n [colorCallback]=\"getCharColor\"\r\n [hasLineNumber]=\"true\"\r\n (charPick)=\"onTextCharPick($event)\"\r\n (rangePick)=\"onTextRangePick($event)\"\r\n />\r\n </div>\r\n <!-- features -->\r\n <div id=\"feats\" class=\"form-row-top\">\r\n <div id=\"g-feats\">\r\n <div class=\"feat-header\">{{ step()!.outputTag }}</div>\r\n <gve-feature-set-view [features]=\"stepGlobalFeatures()\" />\r\n </div>\r\n <div id=\"n-feats\">\r\n @if (selectionFeatures().length) {\r\n <div class=\"feat-header\">{{ selection() }}</div>\r\n <gve-feature-set-view [features]=\"selectionFeatures()\" />\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- steps map -->\r\n <div id=\"map\">\r\n <gve-steps-map\r\n [steps]=\"result()!.steps\"\r\n [selectedStep]=\"step()\"\r\n (selectedStepChange)=\"onStepChange($event)\"\r\n />\r\n </div>\r\n</div>\r\n}\r\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}.form-row-top{display:flex;gap:8px;align-items:start;flex-wrap:wrap;width:100%;box-sizing:border-box}.form-row-top>div{flex:1 1 0%;min-width:0}.range{color:var(--mat-sys-on-surface-variant);border:1px solid var(--mat-sys-on-surface-variant);border-radius:4px;padding:4px}div#container{display:grid;grid-template-rows:auto 1fr;grid-template-columns:3fr 1fr;grid-template-areas:\"bar map\" \"step map\";gap:8px;height:100%}div#bar{grid-area:bar}div#map{grid-area:map;min-height:0;overflow-y:auto}div#step{grid-area:step;min-height:0;display:grid;grid-template-rows:1fr auto;gap:8px}div#text{min-height:0;overflow-y:auto}div#feats{width:100%}.feat-header{background-color:var(--mat-sys-primary);color:var(--mat-sys-on-primary);text-align:center;border:1px solid var(--mat-sys-primary);border-top-left-radius:4px;border-top-right-radius:4px}@media only screen and (max-width:959px){div#container{grid-template-columns:1fr;grid-template-areas:\"bar\" \"step\"}#map{display:none}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.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: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: FeatureSetViewComponent, selector: "gve-feature-set-view", inputs: ["features", "featNames", "featValues", "filterThreshold"] }, { kind: "component", type: StepsMapComponent, selector: "gve-steps-map", inputs: ["steps", "selectedStep", "textFontSize"], outputs: ["selectedStepChange"] }, { kind: "component", type: BaseTextViewComponent, selector: "gve-base-text-view", inputs: ["defaultColor", "defaultBorderColor", "selectionColor", "searchHighlightColor", "hasLineNumber", "text", "colorCallback", "borderColorCallback"], outputs: ["charPick", "rangePick"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2527
2574
  }
2528
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ChainResultViewComponent, decorators: [{
2575
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: ChainResultViewComponent, decorators: [{
2529
2576
  type: Component,
2530
2577
  args: [{ selector: 'gve-chain-result-view', imports: [
2531
2578
  ReactiveFormsModule,
@@ -2537,7 +2584,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
2537
2584
  StepsMapComponent,
2538
2585
  BaseTextViewComponent,
2539
2586
  MatTooltip
2540
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (result()) {\r\n<div id=\"container\">\r\n <div id=\"bar\" class=\"form-row\">\r\n <!-- version -->\r\n @if (versionTags().length) {\r\n <mat-form-field>\r\n <mat-label>stage</mat-label>\r\n <mat-select [formControl]=\"versionTag\">\r\n <mat-option [value]=\"null\">-</mat-option>\r\n @for (t of versionTags(); track t) {\r\n <mat-option [value]=\"t\">{{ t }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n }\r\n <!-- tag -->\r\n @if (tags().length) {\r\n <mat-form-field>\r\n <mat-label>tag</mat-label>\r\n <mat-select [formControl]=\"tag\">\r\n @for (t of tags(); track t) {\r\n <mat-option [value]=\"t\">{{ t }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n }\r\n <!-- range picker -->\r\n @if (selectionRange()) {\r\n <div class=\"range\">\r\n {{ selectionRange()!.at }}\u00D7{{ selectionRange()!.run }}\r\n </div>\r\n @if (!disabledRangePick()) {\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Pick this range for the edited operation\"\r\n (click)=\"pickRange()\"\r\n [disabled]=\"disabledRangePick()\"\r\n >\r\n <mat-icon class=\"mat-warn\">file_open</mat-icon>\r\n </button>\r\n } }\r\n </div>\r\n\r\n <!-- step -->\r\n @if (step()) {\r\n <div id=\"step\">\r\n <!-- text -->\r\n <div id=\"text\">\r\n <gve-base-text-view\r\n [text]=\"result()!.taggedNodes[step()!.outputTag]\"\r\n [colorCallback]=\"getCharColor\"\r\n [hasLineNumber]=\"true\"\r\n (charPick)=\"onTextCharPick($event)\"\r\n (rangePick)=\"onTextRangePick($event)\"\r\n />\r\n </div>\r\n <!-- features -->\r\n <div id=\"feats\" class=\"form-row-top\">\r\n <div id=\"g-feats\">\r\n <div class=\"feat-header\">{{ step()!.outputTag }}</div>\r\n <gve-feature-set-view [features]=\"step()!.featureSet.features\" />\r\n </div>\r\n <div id=\"n-feats\">\r\n @if (selectionFeatures().length) {\r\n <div class=\"feat-header\">{{ selection() }}</div>\r\n <gve-feature-set-view [features]=\"selectionFeatures()\" />\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- steps map -->\r\n <div id=\"map\">\r\n <gve-steps-map\r\n [steps]=\"result()!.steps\"\r\n [selectedStep]=\"step()\"\r\n (selectedStepChange)=\"onStepChange($event)\"\r\n />\r\n </div>\r\n</div>\r\n}\r\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}.form-row-top{display:flex;gap:8px;align-items:start;flex-wrap:wrap;width:100%;box-sizing:border-box}.form-row-top>div{flex:1 1 0%;min-width:0}.range{color:var(--mat-sys-on-surface-variant);border:1px solid var(--mat-sys-on-surface-variant);border-radius:4px;padding:4px}div#container{display:grid;grid-template-rows:auto 1fr;grid-template-columns:3fr 1fr;grid-template-areas:\"bar map\" \"step map\";gap:8px;height:100%}div#bar{grid-area:bar}div#map{grid-area:map;min-height:0;overflow-y:auto}div#step{grid-area:step;min-height:0;display:grid;grid-template-rows:1fr auto;gap:8px}div#text{min-height:0;overflow-y:auto}div#feats{width:100%}.feat-header{background-color:var(--mat-sys-primary);color:var(--mat-sys-on-primary);text-align:center;border:1px solid var(--mat-sys-primary);border-top-left-radius:4px;border-top-right-radius:4px}@media only screen and (max-width:959px){div#container{grid-template-columns:1fr;grid-template-areas:\"bar\" \"step\"}#map{display:none}}\n"] }]
2587
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (result()) {\r\n<div id=\"container\">\r\n <div id=\"bar\" class=\"form-row\">\r\n <!-- version -->\r\n @if (versionTags().length) {\r\n <mat-form-field>\r\n <mat-label>stage</mat-label>\r\n <mat-select [formControl]=\"versionTag\">\r\n <mat-option [value]=\"null\">-</mat-option>\r\n @for (t of versionTags(); track t) {\r\n <mat-option [value]=\"t\">{{ t }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n }\r\n <!-- tag -->\r\n @if (tags().length) {\r\n <mat-form-field>\r\n <mat-label>tag</mat-label>\r\n <mat-select [formControl]=\"tag\">\r\n @for (t of tags(); track t) {\r\n <mat-option [value]=\"t\">{{ t }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n }\r\n <!-- range picker -->\r\n @if (selectionRange()) {\r\n <div class=\"range\">\r\n {{ selectionRange()!.at }}\u00D7{{ selectionRange()!.run }}\r\n </div>\r\n @if (!disabledRangePick()) {\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Pick this range for the edited operation\"\r\n (click)=\"pickRange()\"\r\n [disabled]=\"disabledRangePick()\"\r\n >\r\n <mat-icon class=\"mat-warn\">file_open</mat-icon>\r\n </button>\r\n } }\r\n </div>\r\n\r\n <!-- step -->\r\n @if (step()) {\r\n <div id=\"step\">\r\n <!-- text -->\r\n <div id=\"text\">\r\n <gve-base-text-view\r\n [text]=\"stepNodes()\"\r\n [colorCallback]=\"getCharColor\"\r\n [hasLineNumber]=\"true\"\r\n (charPick)=\"onTextCharPick($event)\"\r\n (rangePick)=\"onTextRangePick($event)\"\r\n />\r\n </div>\r\n <!-- features -->\r\n <div id=\"feats\" class=\"form-row-top\">\r\n <div id=\"g-feats\">\r\n <div class=\"feat-header\">{{ step()!.outputTag }}</div>\r\n <gve-feature-set-view [features]=\"stepGlobalFeatures()\" />\r\n </div>\r\n <div id=\"n-feats\">\r\n @if (selectionFeatures().length) {\r\n <div class=\"feat-header\">{{ selection() }}</div>\r\n <gve-feature-set-view [features]=\"selectionFeatures()\" />\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- steps map -->\r\n <div id=\"map\">\r\n <gve-steps-map\r\n [steps]=\"result()!.steps\"\r\n [selectedStep]=\"step()\"\r\n (selectedStepChange)=\"onStepChange($event)\"\r\n />\r\n </div>\r\n</div>\r\n}\r\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}.form-row-top{display:flex;gap:8px;align-items:start;flex-wrap:wrap;width:100%;box-sizing:border-box}.form-row-top>div{flex:1 1 0%;min-width:0}.range{color:var(--mat-sys-on-surface-variant);border:1px solid var(--mat-sys-on-surface-variant);border-radius:4px;padding:4px}div#container{display:grid;grid-template-rows:auto 1fr;grid-template-columns:3fr 1fr;grid-template-areas:\"bar map\" \"step map\";gap:8px;height:100%}div#bar{grid-area:bar}div#map{grid-area:map;min-height:0;overflow-y:auto}div#step{grid-area:step;min-height:0;display:grid;grid-template-rows:1fr auto;gap:8px}div#text{min-height:0;overflow-y:auto}div#feats{width:100%}.feat-header{background-color:var(--mat-sys-primary);color:var(--mat-sys-on-primary);text-align:center;border:1px solid var(--mat-sys-primary);border-top-left-radius:4px;border-top-right-radius:4px}@media only screen and (max-width:959px){div#container{grid-template-columns:1fr;grid-template-areas:\"bar\" \"step\"}#map{display:none}}\n"] }]
2541
2588
  }], ctorParameters: () => [{ type: i1$1.FormBuilder }], propDecorators: { result: [{ type: i0.Input, args: [{ isSignal: true, alias: "result", required: false }] }], initialStepIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialStepIndex", required: false }] }], stepPick: [{ type: i0.Output, args: ["stepPick"] }], disabledRangePick: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledRangePick", required: false }] }], rangePick: [{ type: i0.Output, args: ["rangePick"] }] } });
2542
2589
 
2543
2590
  /**
@@ -2606,10 +2653,10 @@ class SnapshotTextEditorComponent {
2606
2653
  this.text.set(GveBaseTextService.stringToBaseChars(this.userText.value));
2607
2654
  this.dialogRef?.close(this.text());
2608
2655
  }
2609
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SnapshotTextEditorComponent, deps: [{ token: i1$1.FormBuilder }, { token: i2$3.MatSnackBar }, { token: i3$2.MatDialogRef, optional: true }, { token: MAT_DIALOG_DATA, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
2610
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: SnapshotTextEditorComponent, isStandalone: true, selector: "gve-snapshot-text-editor", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { text: "textChange" }, ngImport: i0, template: "<div [style.padding]=\"dialogRef ? '8px' : '0'\">\r\n @if (dialogRef) {\r\n <div id=\"heading\">Set Base Text</div>\r\n }\r\n <form [formGroup]=\"form\" (submit)=\"save()\">\r\n <fieldset>\r\n <div id=\"batch-input\">\r\n <div>\r\n <mat-form-field class=\"full-width\">\r\n <label>base text</label>\r\n <textarea matInput [formControl]=\"userText\" rows=\"8\"></textarea>\r\n @if (\r\n $any(userText).errors?.required &&\r\n (userText.dirty || userText.touched)\r\n ) {\r\n <mat-error>text required</mat-error>\r\n }\r\n @if (\r\n $any(userText).errors?.maxLength &&\r\n (userText.dirty || userText.touched)\r\n ) {\r\n <mat-error>text too long</mat-error>\r\n }\r\n </mat-form-field>\r\n </div>\r\n </div>\r\n <div class=\"form-row-center\">\r\n @if (dialogRef) {\r\n <button\r\n type=\"button\"\r\n class=\"mat-warn\"\r\n mat-flat-button\r\n matTooltip=\"Close dialog\"\r\n (click)=\"close()\"\r\n >\r\n close\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Copy JSON to clipboard\"\r\n [disabled]=\"form.invalid\"\r\n (click)=\"copyToClipboard()\"\r\n >\r\n JSON\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Set base text\"\r\n [disabled]=\"form.invalid\"\r\n (click)=\"save()\"\r\n >\r\n set\r\n </button>\r\n </div>\r\n </fieldset>\r\n </form>\r\n</div>\r\n", styles: [".full-width{width:100%}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row-center{display:flex;gap:8px;align-items:center;justify-content:center;flex-wrap:wrap}.form-row,.form-row-center *{flex:0 0 auto}div#heading{margin:8px;text-align:center;font-weight:700}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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.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: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2656
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: SnapshotTextEditorComponent, deps: [{ token: i1$1.FormBuilder }, { token: i2$3.MatSnackBar }, { token: i3$2.MatDialogRef, optional: true }, { token: MAT_DIALOG_DATA, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
2657
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: SnapshotTextEditorComponent, isStandalone: true, selector: "gve-snapshot-text-editor", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { text: "textChange" }, ngImport: i0, template: "<div [style.padding]=\"dialogRef ? '8px' : '0'\">\r\n @if (dialogRef) {\r\n <div id=\"heading\">Set Base Text</div>\r\n }\r\n <form [formGroup]=\"form\" (submit)=\"save()\">\r\n <fieldset>\r\n <div id=\"batch-input\">\r\n <div>\r\n <mat-form-field class=\"full-width\">\r\n <label>base text</label>\r\n <textarea matInput [formControl]=\"userText\" rows=\"8\"></textarea>\r\n @if (\r\n $any(userText).errors?.required &&\r\n (userText.dirty || userText.touched)\r\n ) {\r\n <mat-error>text required</mat-error>\r\n }\r\n @if (\r\n $any(userText).errors?.maxLength &&\r\n (userText.dirty || userText.touched)\r\n ) {\r\n <mat-error>text too long</mat-error>\r\n }\r\n </mat-form-field>\r\n </div>\r\n </div>\r\n <div class=\"form-row-center\">\r\n @if (dialogRef) {\r\n <button\r\n type=\"button\"\r\n class=\"mat-warn\"\r\n mat-flat-button\r\n matTooltip=\"Close dialog\"\r\n (click)=\"close()\"\r\n >\r\n close\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Copy JSON to clipboard\"\r\n [disabled]=\"form.invalid\"\r\n (click)=\"copyToClipboard()\"\r\n >\r\n JSON\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Set base text\"\r\n [disabled]=\"form.invalid\"\r\n (click)=\"save()\"\r\n >\r\n set\r\n </button>\r\n </div>\r\n </fieldset>\r\n </form>\r\n</div>\r\n", styles: [".full-width{width:100%}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row-center{display:flex;gap:8px;align-items:center;justify-content:center;flex-wrap:wrap}.form-row,.form-row-center *{flex:0 0 auto}div#heading{margin:8px;text-align:center;font-weight:700}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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.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: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2611
2658
  }
2612
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SnapshotTextEditorComponent, decorators: [{
2659
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: SnapshotTextEditorComponent, decorators: [{
2613
2660
  type: Component,
2614
2661
  args: [{ selector: 'gve-snapshot-text-editor', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
2615
2662
  ReactiveFormsModule,
@@ -2640,6 +2687,14 @@ const SVG_TRANSP_SUFFIX = '_t';
2640
2687
  * - ▶️ `snapshot` (`Snapshot`): the snapshot to edit.
2641
2688
  * - ▶️ `batchOps` (`string`): the batch operations text to set for the editor.
2642
2689
  * - ▶️ `noSave` (`boolean`): true to disable saving.
2690
+ * - ▶️ `debug` (`boolean`): true to enable debug mode for view rendition.
2691
+ * - ▶️ `noDecoration` (`boolean`): true to not show character decoration according to the trace features in nodes.
2692
+ * - ▶️ `featureDefs` (`FeatureDefinitions`): definitions for features, including names and values.
2693
+ * - ▶️ `multiValuedFeatureIds` (`string[]`): the IDs of the features that are multi-valued. If a feature being edited is in this list, the feature editor will allow adding multiple values to it. Passed down to feature editors.
2694
+ * - ▶️ `sourceIds` (`LabeledId[]`): the list of source IDs when it's a closed set. Passed down to the operation source editor, which will show a dropdown to select from these IDs. When not specified, the source ID can be freely typed in the source editor.
2695
+ * - ▶️ `sourceTypes` (`LabeledId[]`): the list of source types when it's a closed set. Passed down to the operation source editor, which will show a dropdown to select from these types. When not specified, the source type can be freely typed in the source editor.
2696
+ * - ▶️ `renditionSettings` (`GveRenditionSettings`): settings for rendering the snapshot. These are passed to the snapshot rendition component.
2697
+ * - ▶️ `opListMaxHeight` (`string`): optional maximum height for the operations list. When set, the operations list will have a vertical scrollbar if its content exceeds this height.
2643
2698
  * - 🔥 `snapshotChange` (`Snapshot`): emitted when the user saves the edited snapshot.
2644
2699
  * - 🔥 `snapshotCancel` (`void`): emitted when the user cancels the snapshot editing.
2645
2700
  */
@@ -2749,6 +2804,14 @@ class SnapshotEditorComponent {
2749
2804
  this.result = signal(undefined, ...(ngDevMode ? [{ debugName: "result" }] : /* istanbul ignore next */ []));
2750
2805
  this.resultOperationId = signal(undefined, ...(ngDevMode ? [{ debugName: "resultOperationId" }] : /* istanbul ignore next */ []));
2751
2806
  this.initialStepIndex = signal(-1, ...(ngDevMode ? [{ debugName: "initialStepIndex" }] : /* istanbul ignore next */ []));
2807
+ // the edited operations features (set when editing features of multiple
2808
+ // operations at once)
2809
+ this.operationFeats = signal(undefined, ...(ngDevMode ? [{ debugName: "operationFeats" }] : /* istanbul ignore next */ []));
2810
+ // the ordinal numbers of all the operations whose features are being
2811
+ // edited at once
2812
+ this.featsOpNumbers = signal(undefined, ...(ngDevMode ? [{ debugName: "featsOpNumbers" }] : /* istanbul ignore next */ []));
2813
+ // the display label for the current feats range (preserves original input)
2814
+ this.featsRangeLabel = signal(undefined, ...(ngDevMode ? [{ debugName: "featsRangeLabel" }] : /* istanbul ignore next */ []));
2752
2815
  // base text
2753
2816
  this.baseText = new FormControl([], {
2754
2817
  nonNullable: true,
@@ -2763,6 +2826,16 @@ class SnapshotEditorComponent {
2763
2826
  baseText: this.baseText,
2764
2827
  operations: this.operations,
2765
2828
  });
2829
+ // feats form — accepts comma-separated N or N-M segments, e.g. "2, 5-7, 9"
2830
+ this.editFeatsRange = formBuilder.control(null, {
2831
+ validators: [
2832
+ Validators.required,
2833
+ Validators.pattern(/^(\d+(?:-\d+)?)(\s*,\s*\d+(?:-\d+)?)*$/),
2834
+ ],
2835
+ });
2836
+ this.editFeatsForm = formBuilder.group({
2837
+ editFeatsRange: this.editFeatsRange,
2838
+ });
2766
2839
  // when snapshot changes, update the form and run all operations.
2767
2840
  // untracked prevents busy() (read inside runToLast → runTo) from being
2768
2841
  // tracked as a dependency, which would cause re-runs each time the API
@@ -2835,7 +2908,9 @@ class SnapshotEditorComponent {
2835
2908
  this.applyRenditionSettings();
2836
2909
  // supply the base text (not returned by API, must be provided by caller)
2837
2910
  result.text = this.baseText.value;
2838
- // set data
2911
+ // gve-snapshot-rendition@0.0.8 still types data with the old
2912
+ // CharChainResult (taggedNodes). Cast until the package is updated.
2913
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2839
2914
  rendition.data = result;
2840
2915
  }
2841
2916
  }
@@ -2941,6 +3016,68 @@ class SnapshotEditorComponent {
2941
3016
  this.editedOpIndex.set(index);
2942
3017
  this.editedOp.set(structuredClone(this.operations.value[index]));
2943
3018
  }
3019
+ /**
3020
+ * Parse a range string into a sorted array of 1-based operation ordinal
3021
+ * numbers. Accepts comma-separated segments where each segment is either
3022
+ * a single number N or a range N-M (N ≤ M). Returns null when any segment
3023
+ * is syntactically invalid or out of bounds.
3024
+ */
3025
+ parseOpRange(rangeStr) {
3026
+ const total = this.operations.value.length;
3027
+ const parts = rangeStr.split(',').map((p) => p.trim());
3028
+ const numbers = new Set();
3029
+ for (const part of parts) {
3030
+ const m = part.match(/^(\d+)(?:-(\d+))?$/);
3031
+ if (!m)
3032
+ return null;
3033
+ const from = parseInt(m[1], 10);
3034
+ const to = m[2] ? parseInt(m[2], 10) : from;
3035
+ if (from < 1 || to > total || from > to)
3036
+ return null;
3037
+ for (let n = from; n <= to; n++)
3038
+ numbers.add(n);
3039
+ }
3040
+ return [...numbers].sort((a, b) => a - b);
3041
+ }
3042
+ /**
3043
+ * Open a feature set editor to edit all features of the operations
3044
+ * specified by the editFeatsRange form control value.
3045
+ * Supports comma-separated segments like "2, 5-7, 9".
3046
+ */
3047
+ editOperationsFeats() {
3048
+ if (this.editFeatsForm.invalid) {
3049
+ this.editFeatsRange.markAsTouched();
3050
+ return;
3051
+ }
3052
+ const rangeValue = this.editFeatsRange.value;
3053
+ const opNumbers = this.parseOpRange(rangeValue);
3054
+ if (!opNumbers) {
3055
+ return;
3056
+ }
3057
+ this.featsOpNumbers.set(opNumbers);
3058
+ this.featsRangeLabel.set(rangeValue.replace(/\s+/g, ''));
3059
+ this.operationFeats.set([]);
3060
+ }
3061
+ saveOperationsFeats(features) {
3062
+ const opNumbers = this.featsOpNumbers();
3063
+ if (!opNumbers) {
3064
+ return;
3065
+ }
3066
+ const operations = [...this.operations.value];
3067
+ for (const opNumber of opNumbers) {
3068
+ const op = operations[opNumber - 1];
3069
+ // just add features to the existing ones, without touching existing features
3070
+ op.features = [...(op.features || []), ...features];
3071
+ }
3072
+ this.operations.setValue(operations);
3073
+ this.operations.markAsDirty();
3074
+ this.operations.updateValueAndValidity();
3075
+ }
3076
+ closeOperationFeats() {
3077
+ this.featsOpNumbers.set(undefined);
3078
+ this.operationFeats.set(undefined);
3079
+ this.featsRangeLabel.set(undefined);
3080
+ }
2944
3081
  /**
2945
3082
  * Close the currently edited operation.
2946
3083
  */
@@ -3187,25 +3324,29 @@ class SnapshotEditorComponent {
3187
3324
  // get the original nodes from the snapshot base text
3188
3325
  const nodes = snapshot.text;
3189
3326
  // find the max ID in the original nodes, so that any ID greater than it
3190
- // belongs to new nodes
3327
+ // belongs to new nodes added by operations
3191
3328
  const maxOriginalId = nodes.reduce((max, n) => Math.max(max, n.id), 0);
3192
3329
  const stepIndex = result.steps.findIndex((s) => s.outputTag === tag);
3330
+ // build a map from node ID to the result node for O(1) lookup
3331
+ const nodeMap = new Map(result.nodes.map((n) => [n.id, n]));
3193
3332
  // for each step up to the specified one, add the nodes introduced by the step
3194
3333
  // and update the features from each step node that is already present
3195
3334
  for (let i = 0; i <= stepIndex; i++) {
3196
- // get the nodes of the step
3197
- const stepNodes = result.taggedNodes[result.steps[i].outputTag];
3198
- for (const stepNode of stepNodes) {
3199
- // if the node was added, add it
3200
- if (stepNode.id > maxOriginalId &&
3201
- !nodes.find((n) => n.id === stepNode.id)) {
3202
- nodes.push(stepNode);
3335
+ const versionTag = result.steps[i].outputTag;
3336
+ const versionNodeIds = result.versionNodeIds[versionTag] ?? [];
3337
+ for (const nodeId of versionNodeIds) {
3338
+ const resultNode = nodeMap.get(nodeId);
3339
+ if (!resultNode)
3340
+ continue;
3341
+ if (nodeId > maxOriginalId && !nodes.find((n) => n.id === nodeId)) {
3342
+ // node was added by an operation — append it
3343
+ nodes.push(resultNode);
3203
3344
  }
3204
3345
  else {
3205
- // if the node was already in the original nodes, update its features
3206
- const existing = nodes.findIndex((n) => n.id === stepNode.id);
3346
+ // node already exists update its features
3347
+ const existing = nodes.findIndex((n) => n.id === nodeId);
3207
3348
  if (existing > -1) {
3208
- nodes[existing].features = stepNode.features;
3349
+ nodes[existing].features = resultNode.features;
3209
3350
  }
3210
3351
  }
3211
3352
  }
@@ -3374,15 +3515,16 @@ class SnapshotEditorComponent {
3374
3515
  }
3375
3516
  this.snapshot.set(this.getSnapshot());
3376
3517
  }
3377
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SnapshotEditorComponent, deps: [{ token: i1$1.FormBuilder }, { token: GveApiService }, { token: i3$2.MatDialog }, { token: i4$1.DialogService }, { token: i2$3.MatSnackBar }], target: i0.ɵɵFactoryTarget.Component }); }
3378
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: SnapshotEditorComponent, isStandalone: true, selector: "gve-snapshot-editor", inputs: { snapshot: { classPropertyName: "snapshot", publicName: "snapshot", isSignal: true, isRequired: false, transformFunction: null }, batchOps: { classPropertyName: "batchOps", publicName: "batchOps", isSignal: true, isRequired: false, transformFunction: null }, noSave: { classPropertyName: "noSave", publicName: "noSave", isSignal: true, isRequired: false, transformFunction: null }, debug: { classPropertyName: "debug", publicName: "debug", isSignal: true, isRequired: false, transformFunction: null }, noDecoration: { classPropertyName: "noDecoration", publicName: "noDecoration", isSignal: true, isRequired: false, transformFunction: null }, featureDefs: { classPropertyName: "featureDefs", publicName: "featureDefs", isSignal: true, isRequired: false, transformFunction: null }, multiValuedFeatureIds: { classPropertyName: "multiValuedFeatureIds", publicName: "multiValuedFeatureIds", isSignal: true, isRequired: false, transformFunction: null }, sourceIds: { classPropertyName: "sourceIds", publicName: "sourceIds", isSignal: true, isRequired: false, transformFunction: null }, sourceTypes: { classPropertyName: "sourceTypes", publicName: "sourceTypes", isSignal: true, isRequired: false, transformFunction: null }, renditionSettings: { classPropertyName: "renditionSettings", publicName: "renditionSettings", isSignal: true, isRequired: false, transformFunction: null }, opListMaxHeight: { classPropertyName: "opListMaxHeight", publicName: "opListMaxHeight", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { snapshot: "snapshotChange", snapshotCancel: "snapshotCancel" }, viewQueries: [{ propertyName: "renditionRef", first: true, predicate: ["rendition"], descendants: true, isSignal: true }, { propertyName: "operationEditorPanelRef", first: true, predicate: ["operationEditorPanel"], descendants: true, isSignal: true }], ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <mat-tab-group>\r\n <!-- text -->\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n <mat-icon>article</mat-icon> <span class=\"label\">text</span>\r\n </ng-template>\r\n <mat-expansion-panel>\r\n <mat-expansion-panel-header>\r\n <mat-panel-title> base text </mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <!-- base text -->\r\n <div class=\"form-row\">\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n color=\"mat-warn\"\r\n matTooltip=\"Enter base text\"\r\n (click)=\"inputBaseText()\"\r\n [disabled]=\"busy()\"\r\n >\r\n <mat-icon>edit</mat-icon> base text\r\n </button>\r\n\r\n @if (textRange()) {\r\n <span id=\"text-range\"\r\n >{{ textRange()!.at }}\u00D7{{ textRange()!.run }}</span\r\n >\r\n }\r\n </div>\r\n\r\n <!-- text characters -->\r\n <gve-base-text-view\r\n [text]=\"baseText.value\"\r\n [hasLineNumber]=\"true\"\r\n (rangePick)=\"onRangePick($event)\"\r\n />\r\n </mat-expansion-panel>\r\n </mat-tab>\r\n\r\n <!-- operations -->\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n <mat-icon>edit</mat-icon> <span class=\"label\">operations</span>\r\n </ng-template>\r\n\r\n <div id=\"snapshot-container\">\r\n <div id=\"general\">\r\n <div class=\"form-row\">\r\n <!-- feat details -->\r\n <mat-slide-toggle\r\n [formControl]=\"featDetails\"\r\n matTooltip=\"Toggle feature details\"\r\n >feat. details</mat-slide-toggle\r\n >\r\n <!-- auto run on edit -->\r\n <mat-slide-toggle\r\n [formControl]=\"autoRun\"\r\n matTooltip=\"Auto-run operations on edit\"\r\n >auto-run</mat-slide-toggle\r\n >\r\n\r\n <div class=\"form-row right\">\r\n <!-- copy ops DSL -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Copy operations as DSL to clipboard\"\r\n [disabled]=\"!operations.value.length || busy()\"\r\n (click)=\"copyOperationsDsl()\"\r\n >\r\n <mat-icon>content_copy</mat-icon>\r\n </button>\r\n\r\n <!-- copy ops JSON -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Copy operations as JSON to clipboard\"\r\n [disabled]=\"!operations.value.length || busy()\"\r\n (click)=\"copyOperationsJson()\"\r\n >\r\n <mat-icon>copy_all</mat-icon>\r\n </button>\r\n\r\n <!-- remove ops -->\r\n <button\r\n type=\"button\"\r\n class=\"mat-warn\"\r\n mat-flat-button\r\n matTooltip=\"Remove all the operations\"\r\n [disabled]=\"!operations.value.length || busy()\"\r\n (click)=\"clearOperations()\"\r\n >\r\n <mat-icon>delete_forever</mat-icon> clear\r\n </button>\r\n\r\n <!-- batch add ops -->\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n matTooltip=\"Add a batch of operations\"\r\n class=\"mat-primary\"\r\n [disabled]=\"busy()\"\r\n (click)=\"parseOperations()\"\r\n >\r\n <mat-icon>post_add</mat-icon> batch\r\n </button>\r\n\r\n <!-- add op -->\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n class=\"mat-primary\"\r\n [disabled]=\"!baseText.value\"\r\n (click)=\"editNewOperation()\"\r\n matTooltip=\"Add a new operation\"\r\n >\r\n <mat-icon>add_circle</mat-icon> operation\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- operation editor -->\r\n @if (editedOp()) {\r\n <mat-expansion-panel\r\n #operationEditorPanel\r\n [expanded]=\"editedOp()\"\r\n [disabled]=\"!editedOp()\"\r\n >\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>operation {{ editedOp()?.id }}</mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <fieldset>\r\n <gve-chain-operation-editor\r\n [featureDefs]=\"featureDefs()\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n [hidePreview]=\"editedOpIndex() === -1\"\r\n [operation]=\"editedOp()\"\r\n [rangePatch]=\"editedOpRangePatch()\"\r\n [sourceIds]=\"sourceIds()\"\r\n [sourceTypes]=\"sourceTypes()\"\r\n (operationCancel)=\"onOperationCancel()\"\r\n (operationChange)=\"onOperationChange($event)\"\r\n (operationPreview)=\"onOperationPreview($event)\"\r\n />\r\n </fieldset>\r\n </mat-expansion-panel>\r\n }\r\n\r\n <!-- ops -->\r\n <div\r\n id=\"ops\"\r\n [style.max-height]=\"opListMaxHeight()\"\r\n [style.overflow-y]=\"opListMaxHeight() ? 'auto' : null\"\r\n >\r\n <!-- operations list -->\r\n @if (operations.value.length) {\r\n <table id=\"list\">\r\n <thead>\r\n <tr>\r\n <th>nr.</th>\r\n <th></th>\r\n <th>ID</th>\r\n <th>type</th>\r\n <th>at</th>\r\n <th>run</th>\r\n <th>value</th>\r\n <th>itag</th>\r\n <th>otag</th>\r\n <th>gid</th>\r\n <th>feats</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (\r\n operation of operations.value;\r\n track operation.id;\r\n let index = $index\r\n ) {\r\n <tr [class.selected]=\"operation.id === editedOp()?.id\">\r\n <td class=\"fit-width\">{{ index + 1 }}.</td>\r\n <td class=\"fit-width\">\r\n <!-- edit -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n class=\"mat-primary\"\r\n (click)=\"editOperation(index)\"\r\n matTooltip=\"Edit operation\"\r\n >\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n <!-- delete -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n class=\"mat-warn\"\r\n (click)=\"deleteOperation(index)\"\r\n matTooltip=\"Delete operation\"\r\n >\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n <!-- run to -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n class=\"mat-accent\"\r\n (click)=\"runTo(index)\"\r\n matTooltip=\"Run to this operation\"\r\n >\r\n <mat-icon class=\"mat-accent\">subscriptions</mat-icon>\r\n </button>\r\n <!-- duplicate -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n (click)=\"duplicateOperation(index)\"\r\n matTooltip=\"Duplicate operation\"\r\n >\r\n <mat-icon>control_point_duplicate</mat-icon>\r\n </button>\r\n <!-- move up -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n (click)=\"moveOperationUp(index)\"\r\n matTooltip=\"Move operation up\"\r\n [disabled]=\"index === 0\"\r\n >\r\n <mat-icon>arrow_circle_up</mat-icon>\r\n </button>\r\n <!-- move down -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n (click)=\"moveOperationDown(index)\"\r\n matTooltip=\"Move operation down\"\r\n [disabled]=\"index === operations.value.length - 1\"\r\n >\r\n <mat-icon>arrow_circle_down</mat-icon>\r\n </button>\r\n </td>\r\n <td>{{ operation.id }}</td>\r\n <td>{{ operation.type | flatLookup: opTypeMap }}</td>\r\n <td>{{ operation.at }}</td>\r\n <td>{{ operation.run }}</td>\r\n <td>{{ operation.value }}</td>\r\n <td>{{ operation.inputTag }}</td>\r\n <td>{{ operation.outputTag }}</td>\r\n <td>{{ operation.groupId }}</td>\r\n <td>\r\n @if (operation.features?.length) {\r\n @if (featDetails.value) {\r\n <div class=\"form-row\">\r\n @for (\r\n feat of operation.features;\r\n track $index;\r\n let i = $index\r\n ) {\r\n <div class=\"feature\">\r\n <span class=\"fname\">{{\r\n feat.name\r\n | flatLookup\r\n : featureDefs()?.names\r\n : \"id\"\r\n : \"label\"\r\n }}</span\r\n >=<span class=\"fvalue\">{{\r\n feat.value\r\n | flatLookup\r\n : featureDefs()?.values?.[feat.name]\r\n : \"id\"\r\n : \"label\"\r\n }}</span>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n {{ operation.features?.length }}\r\n }\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n }\r\n </div>\r\n </div>\r\n </mat-tab>\r\n </mat-tab-group>\r\n\r\n <!-- progress -->\r\n <div class=\"margin-bottom\">\r\n @if (busy()) {\r\n <mat-progress-bar mode=\"indeterminate\" />\r\n }\r\n </div>\r\n\r\n <!-- result -->\r\n @if (result()) {\r\n <fieldset id=\"result\">\r\n <legend>result</legend>\r\n <gve-chain-result-view\r\n [result]=\"result()!\"\r\n [initialStepIndex]=\"initialStepIndex()\"\r\n [disabledRangePick]=\"editedOp() ? false : true\"\r\n (stepPick)=\"onStepPick($event)\"\r\n (rangePick)=\"onRangePick($event, true)\"\r\n />\r\n </fieldset>\r\n }\r\n\r\n <!-- snapshot view -->\r\n @if (result()) {\r\n <div id=\"preview\">\r\n <gve-snapshot-rendition #rendition class=\"rendition\">\r\n </gve-snapshot-rendition>\r\n </div>\r\n }\r\n\r\n <!--buttons -->\r\n <div class=\"form-row-center\">\r\n <button\r\n type=\"button\"\r\n class=\"mat-warn\"\r\n mat-flat-button\r\n matTooltip=\"Discard changes\"\r\n (click)=\"close()\"\r\n >\r\n <mat-icon>clear</mat-icon>\r\n close\r\n </button>\r\n @if (!noSave()) {\r\n <button\r\n type=\"submit\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Save changes\"\r\n [disabled]=\"form.invalid\"\r\n >\r\n <mat-icon>check_circle</mat-icon>\r\n save\r\n </button>\r\n }\r\n </div>\r\n</form>\r\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row-center{display:flex;gap:8px;align-items:center;justify-content:center;flex-wrap:wrap}.form-row,.form-row-center *{flex:0 0 auto}.form-row .right{margin-left:auto}.button-row{display:flex;align-items:center;flex-wrap:wrap}.button-row *{flex:0 0 auto}.long-text{width:100%;max-width:800px}.margin-bottom{margin-bottom:8px}#text-range{margin:8px;border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:6px}mat-expansion-panel{margin:8px 0}div#visual-info{font-size:95%;color:var(--mat-sys-on-surface-variant);margin:8px}#result{max-height:800px;overflow:auto}#snapshot-container{margin-top:6px}#list{margin:8px 0}#opStyle{margin-top:8px}table{width:100%;border-collapse:collapse;color:var(--mat-sys-on-surface-variant)}tbody tr:nth-child(odd){background-color:var(--mat-sys-surface-container)}th{text-align:left;font-weight:400;color:var(--mat-sys-on-surface-variant)}tbody tr:hover{background-color:var(--mat-sys-surface-container-high)}td.fit-width{width:1px;white-space:nowrap}tbody tr.selected{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}fieldset{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:8px;margin:8px 0}legend{color:var(--mat-sys-on-surface-variant)}.error{color:var(--mat-sys-error)}.input-nr{width:6em}.full-width{width:100%}.code{font-family:Courier New,Courier,monospace}.boxed{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:8px;margin:8px 0}span.label{margin-left:8px}.feature{color:var(--mat-sys-on-surface-variant)}.fname{font-size:.9em;color:var(--mat-sys-on-surface-variant)}.fvalue{font-size:.9em;color:var(--mat-sys-on-surface-variant);background-color:var(--mat-sys-surface-container);padding:2px 4px;border-radius:4px}gve-animation-timeline-set{margin:8px 0}div#image{display:grid;gap:8px;grid-template-rows:auto;grid-template-columns:1fr auto;grid-template-areas:\"image-ctl image-view\"}div#image-ctl{grid-area:image-ctl}div#image-view{grid-area:image-view}div#chain-view{margin-bottom:8px}#preview{margin:8px 0}.rendition{display:block;width:100%;min-height:600px;max-height:80vh;border:1px solid var(--mat-sys-on-surface-variant);overflow:auto}@media only screen and (max-width:959px){div#image{grid-template-columns:1fr;grid-template-areas:\"image-ctl\" \"image-view\"}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i7$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i7$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i7$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "component", type: i9.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatSliderModule }, { kind: "component", type: MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i10.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i10.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i10.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: ChainOperationEditorComponent, selector: "gve-chain-operation-editor", inputs: ["operation", "snapshot", "hidePreview", "featureDefs", "rangePatch", "multiValuedFeatureIds", "sourceIds", "sourceTypes"], outputs: ["operationChange", "operationPreview", "operationCancel"] }, { kind: "component", type: ChainResultViewComponent, selector: "gve-chain-result-view", inputs: ["result", "initialStepIndex", "disabledRangePick"], outputs: ["stepPick", "rangePick"] }, { kind: "component", type: BaseTextViewComponent, selector: "gve-base-text-view", inputs: ["defaultColor", "defaultBorderColor", "selectionColor", "searchHighlightColor", "hasLineNumber", "text", "colorCallback", "borderColorCallback"], outputs: ["charPick", "rangePick"] }, { kind: "pipe", type: FlatLookupPipe, name: "flatLookup" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3518
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: SnapshotEditorComponent, deps: [{ token: i1$1.FormBuilder }, { token: GveApiService }, { token: i3$2.MatDialog }, { token: i4$1.DialogService }, { token: i2$3.MatSnackBar }], target: i0.ɵɵFactoryTarget.Component }); }
3519
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: SnapshotEditorComponent, isStandalone: true, selector: "gve-snapshot-editor", inputs: { snapshot: { classPropertyName: "snapshot", publicName: "snapshot", isSignal: true, isRequired: false, transformFunction: null }, batchOps: { classPropertyName: "batchOps", publicName: "batchOps", isSignal: true, isRequired: false, transformFunction: null }, noSave: { classPropertyName: "noSave", publicName: "noSave", isSignal: true, isRequired: false, transformFunction: null }, debug: { classPropertyName: "debug", publicName: "debug", isSignal: true, isRequired: false, transformFunction: null }, noDecoration: { classPropertyName: "noDecoration", publicName: "noDecoration", isSignal: true, isRequired: false, transformFunction: null }, featureDefs: { classPropertyName: "featureDefs", publicName: "featureDefs", isSignal: true, isRequired: false, transformFunction: null }, multiValuedFeatureIds: { classPropertyName: "multiValuedFeatureIds", publicName: "multiValuedFeatureIds", isSignal: true, isRequired: false, transformFunction: null }, sourceIds: { classPropertyName: "sourceIds", publicName: "sourceIds", isSignal: true, isRequired: false, transformFunction: null }, sourceTypes: { classPropertyName: "sourceTypes", publicName: "sourceTypes", isSignal: true, isRequired: false, transformFunction: null }, renditionSettings: { classPropertyName: "renditionSettings", publicName: "renditionSettings", isSignal: true, isRequired: false, transformFunction: null }, opListMaxHeight: { classPropertyName: "opListMaxHeight", publicName: "opListMaxHeight", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { snapshot: "snapshotChange", snapshotCancel: "snapshotCancel" }, viewQueries: [{ propertyName: "renditionRef", first: true, predicate: ["rendition"], descendants: true, isSignal: true }, { propertyName: "operationEditorPanelRef", first: true, predicate: ["operationEditorPanel"], descendants: true, isSignal: true }], ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <mat-tab-group>\r\n <!-- text -->\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n <mat-icon>article</mat-icon> <span class=\"label\">text</span>\r\n </ng-template>\r\n <mat-expansion-panel>\r\n <mat-expansion-panel-header>\r\n <mat-panel-title> base text </mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <!-- base text -->\r\n <div class=\"form-row\">\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n color=\"mat-warn\"\r\n matTooltip=\"Enter base text\"\r\n (click)=\"inputBaseText()\"\r\n [disabled]=\"busy()\"\r\n >\r\n <mat-icon>edit</mat-icon> base text\r\n </button>\r\n\r\n @if (textRange()) {\r\n <span id=\"text-range\"\r\n >{{ textRange()!.at }}\u00D7{{ textRange()!.run }}</span\r\n >\r\n }\r\n </div>\r\n\r\n <!-- text characters -->\r\n <gve-base-text-view\r\n [text]=\"baseText.value\"\r\n [hasLineNumber]=\"true\"\r\n (rangePick)=\"onRangePick($event)\"\r\n />\r\n </mat-expansion-panel>\r\n </mat-tab>\r\n\r\n <!-- operations -->\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n <mat-icon>edit</mat-icon> <span class=\"label\">operations</span>\r\n </ng-template>\r\n\r\n <div id=\"snapshot-container\">\r\n <div id=\"general\">\r\n <div class=\"form-row\">\r\n <!-- feat details -->\r\n <mat-slide-toggle\r\n [formControl]=\"featDetails\"\r\n matTooltip=\"Toggle feature details\"\r\n >feat. details</mat-slide-toggle\r\n >\r\n <!-- auto run on edit -->\r\n <mat-slide-toggle\r\n [formControl]=\"autoRun\"\r\n matTooltip=\"Auto-run operations on edit\"\r\n >auto-run</mat-slide-toggle\r\n >\r\n\r\n <div class=\"form-row right\">\r\n <!-- copy ops DSL -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Copy operations as DSL to clipboard\"\r\n [disabled]=\"!operations.value.length || busy()\"\r\n (click)=\"copyOperationsDsl()\"\r\n >\r\n <mat-icon>content_copy</mat-icon>\r\n </button>\r\n\r\n <!-- copy ops JSON -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Copy operations as JSON to clipboard\"\r\n [disabled]=\"!operations.value.length || busy()\"\r\n (click)=\"copyOperationsJson()\"\r\n >\r\n <mat-icon>copy_all</mat-icon>\r\n </button>\r\n\r\n <!-- remove ops -->\r\n <button\r\n type=\"button\"\r\n class=\"mat-warn\"\r\n mat-flat-button\r\n matTooltip=\"Remove all the operations\"\r\n [disabled]=\"!operations.value.length || busy()\"\r\n (click)=\"clearOperations()\"\r\n >\r\n <mat-icon>delete_forever</mat-icon> clear\r\n </button>\r\n\r\n <!-- batch add ops -->\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n matTooltip=\"Add a batch of operations\"\r\n class=\"mat-primary\"\r\n [disabled]=\"busy()\"\r\n (click)=\"parseOperations()\"\r\n >\r\n <mat-icon>post_add</mat-icon> batch\r\n </button>\r\n\r\n <!-- edit operations feats -->\r\n <form\r\n (submit)=\"editOperationsFeats()\"\r\n [formGroup]=\"editFeatsForm\"\r\n >\r\n <mat-form-field>\r\n <mat-label>operations (e.g. 2, 5-7, 9)</mat-label>\r\n <input matInput [formControl]=\"editFeatsRange\" />\r\n @if (\r\n $any(editFeatsRange).errors?.required &&\r\n (editFeatsRange.dirty || editFeatsRange.touched)\r\n ) {\r\n <mat-error>range required</mat-error>\r\n }\r\n @if (\r\n $any(editFeatsRange).errors?.pattern &&\r\n (editFeatsRange.dirty || editFeatsRange.touched)\r\n ) {\r\n <mat-error>invalid range (use e.g. 2, 5-7, 9)</mat-error>\r\n }\r\n <button type=\"submit\" matIconButton matSuffix>\r\n <mat-icon>edit_note</mat-icon>\r\n </button>\r\n </mat-form-field>\r\n </form>\r\n\r\n <!-- add op -->\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n class=\"mat-primary\"\r\n [disabled]=\"\r\n busy() || !operations.value.length || !baseText.value\r\n \"\r\n (click)=\"editNewOperation()\"\r\n matTooltip=\"Add a new operation\"\r\n >\r\n <mat-icon>add_circle</mat-icon> operation\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- feature set editor -->\r\n @if (featsOpNumbers()?.length) {\r\n <mat-expansion-panel\r\n #featsEditorPanel\r\n [expanded]=\"featsOpNumbers()!.length > 0\"\r\n >\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>\r\n Features of {{ featsRangeLabel() }}\r\n </mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <fieldset>\r\n <gve-feature-set-editor\r\n [hasCloseButton]=\"true\"\r\n [features]=\"operationFeats() || []\"\r\n [featureDefs]=\"featureDefs()\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n [operations]=\"operations.value\"\r\n [opNumbers]=\"featsOpNumbers()!\"\r\n (close)=\"closeOperationFeats()\"\r\n (featuresChange)=\"saveOperationsFeats($event)\"\r\n />\r\n </fieldset>\r\n </mat-expansion-panel>\r\n }\r\n\r\n <!-- operation editor -->\r\n @if (editedOp()) {\r\n <mat-expansion-panel\r\n #operationEditorPanel\r\n [expanded]=\"editedOp()\"\r\n [disabled]=\"!editedOp()\"\r\n >\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>operation {{ editedOp()?.id }}</mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <fieldset>\r\n <gve-chain-operation-editor\r\n [featureDefs]=\"featureDefs()\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n [hidePreview]=\"editedOpIndex() === -1\"\r\n [operation]=\"editedOp()\"\r\n [rangePatch]=\"editedOpRangePatch()\"\r\n [sourceIds]=\"sourceIds()\"\r\n [sourceTypes]=\"sourceTypes()\"\r\n (operationCancel)=\"onOperationCancel()\"\r\n (operationChange)=\"onOperationChange($event)\"\r\n (operationPreview)=\"onOperationPreview($event)\"\r\n />\r\n </fieldset>\r\n </mat-expansion-panel>\r\n }\r\n\r\n <!-- ops -->\r\n <div\r\n id=\"ops\"\r\n [style.max-height]=\"opListMaxHeight()\"\r\n [style.overflow-y]=\"opListMaxHeight() ? 'auto' : null\"\r\n >\r\n <!-- operations list -->\r\n @if (operations.value.length) {\r\n <table id=\"list\">\r\n <thead>\r\n <tr>\r\n <th>nr.</th>\r\n <th></th>\r\n <th>ID</th>\r\n <th>type</th>\r\n <th>at</th>\r\n <th>run</th>\r\n <th>value</th>\r\n <th>itag</th>\r\n <th>otag</th>\r\n <th>gid</th>\r\n <th>feats</th>\r\n <th>src</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (\r\n operation of operations.value;\r\n track operation.id;\r\n let index = $index\r\n ) {\r\n <tr\r\n [class.selected]=\"operation.id === editedOp()?.id\"\r\n [class.feat-editing]=\"featsOpNumbers()?.includes(index + 1)\"\r\n >\r\n <td class=\"fit-width\">{{ index + 1 }}.</td>\r\n <td class=\"fit-width\">\r\n <!-- edit -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n class=\"mat-primary\"\r\n (click)=\"editOperation(index)\"\r\n matTooltip=\"Edit operation\"\r\n >\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n <!-- delete -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n class=\"mat-warn\"\r\n (click)=\"deleteOperation(index)\"\r\n matTooltip=\"Delete operation\"\r\n >\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n <!-- run to -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n class=\"mat-accent\"\r\n (click)=\"runTo(index)\"\r\n matTooltip=\"Run to this operation\"\r\n >\r\n <mat-icon class=\"mat-accent\">subscriptions</mat-icon>\r\n </button>\r\n <!-- duplicate -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n (click)=\"duplicateOperation(index)\"\r\n matTooltip=\"Duplicate operation\"\r\n >\r\n <mat-icon>control_point_duplicate</mat-icon>\r\n </button>\r\n <!-- move up -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n (click)=\"moveOperationUp(index)\"\r\n matTooltip=\"Move operation up\"\r\n [disabled]=\"index === 0\"\r\n >\r\n <mat-icon>arrow_circle_up</mat-icon>\r\n </button>\r\n <!-- move down -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n (click)=\"moveOperationDown(index)\"\r\n matTooltip=\"Move operation down\"\r\n [disabled]=\"index === operations.value.length - 1\"\r\n >\r\n <mat-icon>arrow_circle_down</mat-icon>\r\n </button>\r\n </td>\r\n <td>{{ operation.id }}</td>\r\n <td>{{ operation.type | flatLookup: opTypeMap }}</td>\r\n <td>{{ operation.at }}</td>\r\n <td>{{ operation.run }}</td>\r\n <td>{{ operation.value }}</td>\r\n <td>{{ operation.inputTag }}</td>\r\n <td>{{ operation.outputTag }}</td>\r\n <td>{{ operation.groupId }}</td>\r\n <td>\r\n @if (operation.features?.length) {\r\n @if (featDetails.value) {\r\n <div class=\"form-row\">\r\n @for (\r\n feat of operation.features;\r\n track $index;\r\n let i = $index\r\n ) {\r\n <div class=\"feature\">\r\n <span class=\"fname\">{{\r\n feat.name\r\n | flatLookup\r\n : featureDefs()?.names\r\n : \"id\"\r\n : \"label\"\r\n }}</span\r\n >=<span class=\"fvalue\">{{\r\n feat.value\r\n | flatLookup\r\n : featureDefs()?.values?.[feat.name]\r\n : \"id\"\r\n : \"label\"\r\n }}</span>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- version badge -->\r\n @if (\r\n operation.features?.some(f => f.name === 'version')\r\n ) {\r\n <span\r\n matBadge=\"{{ operation.features?.find(f => f.name === 'version')?.value }}\"\r\n matBadgeOverlap=\"false\"\r\n >{{ operation.features?.length }}</span>\r\n }\r\n }\r\n }\r\n </td>\r\n <td>{{ operation.sources?.length || \"\" }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n }\r\n </div>\r\n </div>\r\n </mat-tab>\r\n </mat-tab-group>\r\n\r\n <!-- progress -->\r\n <div class=\"margin-bottom\">\r\n @if (busy()) {\r\n <mat-progress-bar mode=\"indeterminate\" />\r\n }\r\n </div>\r\n\r\n <!-- result -->\r\n @if (result()) {\r\n <fieldset id=\"result\">\r\n <legend>result</legend>\r\n <gve-chain-result-view\r\n [result]=\"result()!\"\r\n [initialStepIndex]=\"initialStepIndex()\"\r\n [disabledRangePick]=\"editedOp() ? false : true\"\r\n (stepPick)=\"onStepPick($event)\"\r\n (rangePick)=\"onRangePick($event, true)\"\r\n />\r\n </fieldset>\r\n }\r\n\r\n <!-- snapshot view -->\r\n @if (result()) {\r\n <div id=\"preview\">\r\n <gve-snapshot-rendition #rendition class=\"rendition\">\r\n </gve-snapshot-rendition>\r\n </div>\r\n }\r\n\r\n <!--buttons -->\r\n <div class=\"form-row-center\">\r\n <button\r\n type=\"button\"\r\n class=\"mat-warn\"\r\n mat-flat-button\r\n matTooltip=\"Discard changes\"\r\n (click)=\"close()\"\r\n >\r\n <mat-icon>clear</mat-icon>\r\n close\r\n </button>\r\n @if (!noSave()) {\r\n <button\r\n type=\"submit\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Save changes\"\r\n [disabled]=\"form.invalid\"\r\n >\r\n <mat-icon>check_circle</mat-icon>\r\n save\r\n </button>\r\n }\r\n </div>\r\n</form>\r\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row-center{display:flex;gap:8px;align-items:center;justify-content:center;flex-wrap:wrap}.form-row,.form-row-center *{flex:0 0 auto}.form-row .right{margin-left:auto}.button-row{display:flex;align-items:center;flex-wrap:wrap}.button-row *{flex:0 0 auto}.long-text{width:100%;max-width:800px}.margin-bottom{margin-bottom:8px}#text-range{margin:8px;border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:6px}mat-expansion-panel{margin:8px 0}div#visual-info{font-size:95%;color:var(--mat-sys-on-surface-variant);margin:8px}#result{max-height:800px;overflow:auto}#snapshot-container{margin-top:6px}#list{margin:8px 0}#opStyle{margin-top:8px}table{width:100%;border-collapse:collapse;color:var(--mat-sys-on-surface-variant)}tbody tr:nth-child(odd){background-color:var(--mat-sys-surface-container)}th{text-align:left;font-weight:400;color:var(--mat-sys-on-surface-variant)}tbody tr:hover{background-color:var(--mat-sys-surface-container-high)}td.fit-width{width:1px;white-space:nowrap}tbody tr.feat-editing{background-color:var(--mat-sys-tertiary-container);color:var(--mat-sys-on-tertiary-container)}tbody tr.selected{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}fieldset{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:8px;margin:8px 0}legend{color:var(--mat-sys-on-surface-variant)}.error{color:var(--mat-sys-error)}.input-nr{width:6em}.full-width{width:100%}.code{font-family:Courier New,Courier,monospace}.boxed{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:8px;margin:8px 0}span.label{margin-left:8px}.feature{color:var(--mat-sys-on-surface-variant)}.fname{font-size:.9em;color:var(--mat-sys-on-surface-variant)}.fvalue{font-size:.9em;color:var(--mat-sys-on-surface-variant);background-color:var(--mat-sys-surface-container);padding:2px 4px;border-radius:4px}gve-animation-timeline-set{margin:8px 0}div#image{display:grid;gap:8px;grid-template-rows:auto;grid-template-columns:1fr auto;grid-template-areas:\"image-ctl image-view\"}div#image-ctl{grid-area:image-ctl}div#image-view{grid-area:image-view}div#chain-view{margin-bottom:8px}#preview{margin:8px 0}.rendition{display:block;width:100%;min-height:600px;max-height:80vh;border:1px solid var(--mat-sys-on-surface-variant);overflow:auto}@media only screen and (max-width:959px){div#image{grid-template-columns:1fr;grid-template-areas:\"image-ctl\" \"image-view\"}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: "directive", type: MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i7$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i7$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i7$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "ngmodule", type: MatProgressBarModule }, { kind: "component", type: i11.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatSliderModule }, { kind: "component", type: MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i12.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i12.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i12.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: ChainOperationEditorComponent, selector: "gve-chain-operation-editor", inputs: ["operation", "snapshot", "hidePreview", "featureDefs", "rangePatch", "multiValuedFeatureIds", "sourceIds", "sourceTypes"], outputs: ["operationChange", "operationPreview", "operationCancel"] }, { kind: "component", type: ChainResultViewComponent, selector: "gve-chain-result-view", inputs: ["result", "initialStepIndex", "disabledRangePick"], outputs: ["stepPick", "rangePick"] }, { kind: "component", type: BaseTextViewComponent, selector: "gve-base-text-view", inputs: ["defaultColor", "defaultBorderColor", "selectionColor", "searchHighlightColor", "hasLineNumber", "text", "colorCallback", "borderColorCallback"], outputs: ["charPick", "rangePick"] }, { kind: "component", type: FeatureSetEditorComponent, selector: "gve-feature-set-editor", inputs: ["isVar", "featNames", "featValues", "filterThreshold", "multiValuedFeatureIds", "hasCloseButton", "features"], outputs: ["close", "featuresChange"] }, { kind: "pipe", type: FlatLookupPipe, name: "flatLookup" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3379
3520
  }
3380
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SnapshotEditorComponent, decorators: [{
3521
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: SnapshotEditorComponent, decorators: [{
3381
3522
  type: Component,
3382
3523
  args: [{ selector: 'gve-snapshot-editor', imports: [
3383
3524
  CommonModule,
3384
3525
  FormsModule,
3385
3526
  ReactiveFormsModule,
3527
+ MatBadge,
3386
3528
  MatButtonModule,
3387
3529
  MatButtonToggleModule,
3388
3530
  MatCheckboxModule,
@@ -3399,8 +3541,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
3399
3541
  ChainOperationEditorComponent,
3400
3542
  ChainResultViewComponent,
3401
3543
  BaseTextViewComponent,
3544
+ FeatureSetEditorComponent,
3402
3545
  FlatLookupPipe,
3403
- ], schemas: [CUSTOM_ELEMENTS_SCHEMA], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <mat-tab-group>\r\n <!-- text -->\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n <mat-icon>article</mat-icon> <span class=\"label\">text</span>\r\n </ng-template>\r\n <mat-expansion-panel>\r\n <mat-expansion-panel-header>\r\n <mat-panel-title> base text </mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <!-- base text -->\r\n <div class=\"form-row\">\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n color=\"mat-warn\"\r\n matTooltip=\"Enter base text\"\r\n (click)=\"inputBaseText()\"\r\n [disabled]=\"busy()\"\r\n >\r\n <mat-icon>edit</mat-icon> base text\r\n </button>\r\n\r\n @if (textRange()) {\r\n <span id=\"text-range\"\r\n >{{ textRange()!.at }}\u00D7{{ textRange()!.run }}</span\r\n >\r\n }\r\n </div>\r\n\r\n <!-- text characters -->\r\n <gve-base-text-view\r\n [text]=\"baseText.value\"\r\n [hasLineNumber]=\"true\"\r\n (rangePick)=\"onRangePick($event)\"\r\n />\r\n </mat-expansion-panel>\r\n </mat-tab>\r\n\r\n <!-- operations -->\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n <mat-icon>edit</mat-icon> <span class=\"label\">operations</span>\r\n </ng-template>\r\n\r\n <div id=\"snapshot-container\">\r\n <div id=\"general\">\r\n <div class=\"form-row\">\r\n <!-- feat details -->\r\n <mat-slide-toggle\r\n [formControl]=\"featDetails\"\r\n matTooltip=\"Toggle feature details\"\r\n >feat. details</mat-slide-toggle\r\n >\r\n <!-- auto run on edit -->\r\n <mat-slide-toggle\r\n [formControl]=\"autoRun\"\r\n matTooltip=\"Auto-run operations on edit\"\r\n >auto-run</mat-slide-toggle\r\n >\r\n\r\n <div class=\"form-row right\">\r\n <!-- copy ops DSL -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Copy operations as DSL to clipboard\"\r\n [disabled]=\"!operations.value.length || busy()\"\r\n (click)=\"copyOperationsDsl()\"\r\n >\r\n <mat-icon>content_copy</mat-icon>\r\n </button>\r\n\r\n <!-- copy ops JSON -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Copy operations as JSON to clipboard\"\r\n [disabled]=\"!operations.value.length || busy()\"\r\n (click)=\"copyOperationsJson()\"\r\n >\r\n <mat-icon>copy_all</mat-icon>\r\n </button>\r\n\r\n <!-- remove ops -->\r\n <button\r\n type=\"button\"\r\n class=\"mat-warn\"\r\n mat-flat-button\r\n matTooltip=\"Remove all the operations\"\r\n [disabled]=\"!operations.value.length || busy()\"\r\n (click)=\"clearOperations()\"\r\n >\r\n <mat-icon>delete_forever</mat-icon> clear\r\n </button>\r\n\r\n <!-- batch add ops -->\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n matTooltip=\"Add a batch of operations\"\r\n class=\"mat-primary\"\r\n [disabled]=\"busy()\"\r\n (click)=\"parseOperations()\"\r\n >\r\n <mat-icon>post_add</mat-icon> batch\r\n </button>\r\n\r\n <!-- add op -->\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n class=\"mat-primary\"\r\n [disabled]=\"!baseText.value\"\r\n (click)=\"editNewOperation()\"\r\n matTooltip=\"Add a new operation\"\r\n >\r\n <mat-icon>add_circle</mat-icon> operation\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- operation editor -->\r\n @if (editedOp()) {\r\n <mat-expansion-panel\r\n #operationEditorPanel\r\n [expanded]=\"editedOp()\"\r\n [disabled]=\"!editedOp()\"\r\n >\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>operation {{ editedOp()?.id }}</mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <fieldset>\r\n <gve-chain-operation-editor\r\n [featureDefs]=\"featureDefs()\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n [hidePreview]=\"editedOpIndex() === -1\"\r\n [operation]=\"editedOp()\"\r\n [rangePatch]=\"editedOpRangePatch()\"\r\n [sourceIds]=\"sourceIds()\"\r\n [sourceTypes]=\"sourceTypes()\"\r\n (operationCancel)=\"onOperationCancel()\"\r\n (operationChange)=\"onOperationChange($event)\"\r\n (operationPreview)=\"onOperationPreview($event)\"\r\n />\r\n </fieldset>\r\n </mat-expansion-panel>\r\n }\r\n\r\n <!-- ops -->\r\n <div\r\n id=\"ops\"\r\n [style.max-height]=\"opListMaxHeight()\"\r\n [style.overflow-y]=\"opListMaxHeight() ? 'auto' : null\"\r\n >\r\n <!-- operations list -->\r\n @if (operations.value.length) {\r\n <table id=\"list\">\r\n <thead>\r\n <tr>\r\n <th>nr.</th>\r\n <th></th>\r\n <th>ID</th>\r\n <th>type</th>\r\n <th>at</th>\r\n <th>run</th>\r\n <th>value</th>\r\n <th>itag</th>\r\n <th>otag</th>\r\n <th>gid</th>\r\n <th>feats</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (\r\n operation of operations.value;\r\n track operation.id;\r\n let index = $index\r\n ) {\r\n <tr [class.selected]=\"operation.id === editedOp()?.id\">\r\n <td class=\"fit-width\">{{ index + 1 }}.</td>\r\n <td class=\"fit-width\">\r\n <!-- edit -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n class=\"mat-primary\"\r\n (click)=\"editOperation(index)\"\r\n matTooltip=\"Edit operation\"\r\n >\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n <!-- delete -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n class=\"mat-warn\"\r\n (click)=\"deleteOperation(index)\"\r\n matTooltip=\"Delete operation\"\r\n >\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n <!-- run to -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n class=\"mat-accent\"\r\n (click)=\"runTo(index)\"\r\n matTooltip=\"Run to this operation\"\r\n >\r\n <mat-icon class=\"mat-accent\">subscriptions</mat-icon>\r\n </button>\r\n <!-- duplicate -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n (click)=\"duplicateOperation(index)\"\r\n matTooltip=\"Duplicate operation\"\r\n >\r\n <mat-icon>control_point_duplicate</mat-icon>\r\n </button>\r\n <!-- move up -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n (click)=\"moveOperationUp(index)\"\r\n matTooltip=\"Move operation up\"\r\n [disabled]=\"index === 0\"\r\n >\r\n <mat-icon>arrow_circle_up</mat-icon>\r\n </button>\r\n <!-- move down -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n (click)=\"moveOperationDown(index)\"\r\n matTooltip=\"Move operation down\"\r\n [disabled]=\"index === operations.value.length - 1\"\r\n >\r\n <mat-icon>arrow_circle_down</mat-icon>\r\n </button>\r\n </td>\r\n <td>{{ operation.id }}</td>\r\n <td>{{ operation.type | flatLookup: opTypeMap }}</td>\r\n <td>{{ operation.at }}</td>\r\n <td>{{ operation.run }}</td>\r\n <td>{{ operation.value }}</td>\r\n <td>{{ operation.inputTag }}</td>\r\n <td>{{ operation.outputTag }}</td>\r\n <td>{{ operation.groupId }}</td>\r\n <td>\r\n @if (operation.features?.length) {\r\n @if (featDetails.value) {\r\n <div class=\"form-row\">\r\n @for (\r\n feat of operation.features;\r\n track $index;\r\n let i = $index\r\n ) {\r\n <div class=\"feature\">\r\n <span class=\"fname\">{{\r\n feat.name\r\n | flatLookup\r\n : featureDefs()?.names\r\n : \"id\"\r\n : \"label\"\r\n }}</span\r\n >=<span class=\"fvalue\">{{\r\n feat.value\r\n | flatLookup\r\n : featureDefs()?.values?.[feat.name]\r\n : \"id\"\r\n : \"label\"\r\n }}</span>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n {{ operation.features?.length }}\r\n }\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n }\r\n </div>\r\n </div>\r\n </mat-tab>\r\n </mat-tab-group>\r\n\r\n <!-- progress -->\r\n <div class=\"margin-bottom\">\r\n @if (busy()) {\r\n <mat-progress-bar mode=\"indeterminate\" />\r\n }\r\n </div>\r\n\r\n <!-- result -->\r\n @if (result()) {\r\n <fieldset id=\"result\">\r\n <legend>result</legend>\r\n <gve-chain-result-view\r\n [result]=\"result()!\"\r\n [initialStepIndex]=\"initialStepIndex()\"\r\n [disabledRangePick]=\"editedOp() ? false : true\"\r\n (stepPick)=\"onStepPick($event)\"\r\n (rangePick)=\"onRangePick($event, true)\"\r\n />\r\n </fieldset>\r\n }\r\n\r\n <!-- snapshot view -->\r\n @if (result()) {\r\n <div id=\"preview\">\r\n <gve-snapshot-rendition #rendition class=\"rendition\">\r\n </gve-snapshot-rendition>\r\n </div>\r\n }\r\n\r\n <!--buttons -->\r\n <div class=\"form-row-center\">\r\n <button\r\n type=\"button\"\r\n class=\"mat-warn\"\r\n mat-flat-button\r\n matTooltip=\"Discard changes\"\r\n (click)=\"close()\"\r\n >\r\n <mat-icon>clear</mat-icon>\r\n close\r\n </button>\r\n @if (!noSave()) {\r\n <button\r\n type=\"submit\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Save changes\"\r\n [disabled]=\"form.invalid\"\r\n >\r\n <mat-icon>check_circle</mat-icon>\r\n save\r\n </button>\r\n }\r\n </div>\r\n</form>\r\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row-center{display:flex;gap:8px;align-items:center;justify-content:center;flex-wrap:wrap}.form-row,.form-row-center *{flex:0 0 auto}.form-row .right{margin-left:auto}.button-row{display:flex;align-items:center;flex-wrap:wrap}.button-row *{flex:0 0 auto}.long-text{width:100%;max-width:800px}.margin-bottom{margin-bottom:8px}#text-range{margin:8px;border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:6px}mat-expansion-panel{margin:8px 0}div#visual-info{font-size:95%;color:var(--mat-sys-on-surface-variant);margin:8px}#result{max-height:800px;overflow:auto}#snapshot-container{margin-top:6px}#list{margin:8px 0}#opStyle{margin-top:8px}table{width:100%;border-collapse:collapse;color:var(--mat-sys-on-surface-variant)}tbody tr:nth-child(odd){background-color:var(--mat-sys-surface-container)}th{text-align:left;font-weight:400;color:var(--mat-sys-on-surface-variant)}tbody tr:hover{background-color:var(--mat-sys-surface-container-high)}td.fit-width{width:1px;white-space:nowrap}tbody tr.selected{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}fieldset{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:8px;margin:8px 0}legend{color:var(--mat-sys-on-surface-variant)}.error{color:var(--mat-sys-error)}.input-nr{width:6em}.full-width{width:100%}.code{font-family:Courier New,Courier,monospace}.boxed{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:8px;margin:8px 0}span.label{margin-left:8px}.feature{color:var(--mat-sys-on-surface-variant)}.fname{font-size:.9em;color:var(--mat-sys-on-surface-variant)}.fvalue{font-size:.9em;color:var(--mat-sys-on-surface-variant);background-color:var(--mat-sys-surface-container);padding:2px 4px;border-radius:4px}gve-animation-timeline-set{margin:8px 0}div#image{display:grid;gap:8px;grid-template-rows:auto;grid-template-columns:1fr auto;grid-template-areas:\"image-ctl image-view\"}div#image-ctl{grid-area:image-ctl}div#image-view{grid-area:image-view}div#chain-view{margin-bottom:8px}#preview{margin:8px 0}.rendition{display:block;width:100%;min-height:600px;max-height:80vh;border:1px solid var(--mat-sys-on-surface-variant);overflow:auto}@media only screen and (max-width:959px){div#image{grid-template-columns:1fr;grid-template-areas:\"image-ctl\" \"image-view\"}}\n"] }]
3546
+ ], schemas: [CUSTOM_ELEMENTS_SCHEMA], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <mat-tab-group>\r\n <!-- text -->\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n <mat-icon>article</mat-icon> <span class=\"label\">text</span>\r\n </ng-template>\r\n <mat-expansion-panel>\r\n <mat-expansion-panel-header>\r\n <mat-panel-title> base text </mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <!-- base text -->\r\n <div class=\"form-row\">\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n color=\"mat-warn\"\r\n matTooltip=\"Enter base text\"\r\n (click)=\"inputBaseText()\"\r\n [disabled]=\"busy()\"\r\n >\r\n <mat-icon>edit</mat-icon> base text\r\n </button>\r\n\r\n @if (textRange()) {\r\n <span id=\"text-range\"\r\n >{{ textRange()!.at }}\u00D7{{ textRange()!.run }}</span\r\n >\r\n }\r\n </div>\r\n\r\n <!-- text characters -->\r\n <gve-base-text-view\r\n [text]=\"baseText.value\"\r\n [hasLineNumber]=\"true\"\r\n (rangePick)=\"onRangePick($event)\"\r\n />\r\n </mat-expansion-panel>\r\n </mat-tab>\r\n\r\n <!-- operations -->\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n <mat-icon>edit</mat-icon> <span class=\"label\">operations</span>\r\n </ng-template>\r\n\r\n <div id=\"snapshot-container\">\r\n <div id=\"general\">\r\n <div class=\"form-row\">\r\n <!-- feat details -->\r\n <mat-slide-toggle\r\n [formControl]=\"featDetails\"\r\n matTooltip=\"Toggle feature details\"\r\n >feat. details</mat-slide-toggle\r\n >\r\n <!-- auto run on edit -->\r\n <mat-slide-toggle\r\n [formControl]=\"autoRun\"\r\n matTooltip=\"Auto-run operations on edit\"\r\n >auto-run</mat-slide-toggle\r\n >\r\n\r\n <div class=\"form-row right\">\r\n <!-- copy ops DSL -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Copy operations as DSL to clipboard\"\r\n [disabled]=\"!operations.value.length || busy()\"\r\n (click)=\"copyOperationsDsl()\"\r\n >\r\n <mat-icon>content_copy</mat-icon>\r\n </button>\r\n\r\n <!-- copy ops JSON -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Copy operations as JSON to clipboard\"\r\n [disabled]=\"!operations.value.length || busy()\"\r\n (click)=\"copyOperationsJson()\"\r\n >\r\n <mat-icon>copy_all</mat-icon>\r\n </button>\r\n\r\n <!-- remove ops -->\r\n <button\r\n type=\"button\"\r\n class=\"mat-warn\"\r\n mat-flat-button\r\n matTooltip=\"Remove all the operations\"\r\n [disabled]=\"!operations.value.length || busy()\"\r\n (click)=\"clearOperations()\"\r\n >\r\n <mat-icon>delete_forever</mat-icon> clear\r\n </button>\r\n\r\n <!-- batch add ops -->\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n matTooltip=\"Add a batch of operations\"\r\n class=\"mat-primary\"\r\n [disabled]=\"busy()\"\r\n (click)=\"parseOperations()\"\r\n >\r\n <mat-icon>post_add</mat-icon> batch\r\n </button>\r\n\r\n <!-- edit operations feats -->\r\n <form\r\n (submit)=\"editOperationsFeats()\"\r\n [formGroup]=\"editFeatsForm\"\r\n >\r\n <mat-form-field>\r\n <mat-label>operations (e.g. 2, 5-7, 9)</mat-label>\r\n <input matInput [formControl]=\"editFeatsRange\" />\r\n @if (\r\n $any(editFeatsRange).errors?.required &&\r\n (editFeatsRange.dirty || editFeatsRange.touched)\r\n ) {\r\n <mat-error>range required</mat-error>\r\n }\r\n @if (\r\n $any(editFeatsRange).errors?.pattern &&\r\n (editFeatsRange.dirty || editFeatsRange.touched)\r\n ) {\r\n <mat-error>invalid range (use e.g. 2, 5-7, 9)</mat-error>\r\n }\r\n <button type=\"submit\" matIconButton matSuffix>\r\n <mat-icon>edit_note</mat-icon>\r\n </button>\r\n </mat-form-field>\r\n </form>\r\n\r\n <!-- add op -->\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n class=\"mat-primary\"\r\n [disabled]=\"\r\n busy() || !operations.value.length || !baseText.value\r\n \"\r\n (click)=\"editNewOperation()\"\r\n matTooltip=\"Add a new operation\"\r\n >\r\n <mat-icon>add_circle</mat-icon> operation\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- feature set editor -->\r\n @if (featsOpNumbers()?.length) {\r\n <mat-expansion-panel\r\n #featsEditorPanel\r\n [expanded]=\"featsOpNumbers()!.length > 0\"\r\n >\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>\r\n Features of {{ featsRangeLabel() }}\r\n </mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <fieldset>\r\n <gve-feature-set-editor\r\n [hasCloseButton]=\"true\"\r\n [features]=\"operationFeats() || []\"\r\n [featureDefs]=\"featureDefs()\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n [operations]=\"operations.value\"\r\n [opNumbers]=\"featsOpNumbers()!\"\r\n (close)=\"closeOperationFeats()\"\r\n (featuresChange)=\"saveOperationsFeats($event)\"\r\n />\r\n </fieldset>\r\n </mat-expansion-panel>\r\n }\r\n\r\n <!-- operation editor -->\r\n @if (editedOp()) {\r\n <mat-expansion-panel\r\n #operationEditorPanel\r\n [expanded]=\"editedOp()\"\r\n [disabled]=\"!editedOp()\"\r\n >\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>operation {{ editedOp()?.id }}</mat-panel-title>\r\n </mat-expansion-panel-header>\r\n <fieldset>\r\n <gve-chain-operation-editor\r\n [featureDefs]=\"featureDefs()\"\r\n [multiValuedFeatureIds]=\"multiValuedFeatureIds()\"\r\n [hidePreview]=\"editedOpIndex() === -1\"\r\n [operation]=\"editedOp()\"\r\n [rangePatch]=\"editedOpRangePatch()\"\r\n [sourceIds]=\"sourceIds()\"\r\n [sourceTypes]=\"sourceTypes()\"\r\n (operationCancel)=\"onOperationCancel()\"\r\n (operationChange)=\"onOperationChange($event)\"\r\n (operationPreview)=\"onOperationPreview($event)\"\r\n />\r\n </fieldset>\r\n </mat-expansion-panel>\r\n }\r\n\r\n <!-- ops -->\r\n <div\r\n id=\"ops\"\r\n [style.max-height]=\"opListMaxHeight()\"\r\n [style.overflow-y]=\"opListMaxHeight() ? 'auto' : null\"\r\n >\r\n <!-- operations list -->\r\n @if (operations.value.length) {\r\n <table id=\"list\">\r\n <thead>\r\n <tr>\r\n <th>nr.</th>\r\n <th></th>\r\n <th>ID</th>\r\n <th>type</th>\r\n <th>at</th>\r\n <th>run</th>\r\n <th>value</th>\r\n <th>itag</th>\r\n <th>otag</th>\r\n <th>gid</th>\r\n <th>feats</th>\r\n <th>src</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (\r\n operation of operations.value;\r\n track operation.id;\r\n let index = $index\r\n ) {\r\n <tr\r\n [class.selected]=\"operation.id === editedOp()?.id\"\r\n [class.feat-editing]=\"featsOpNumbers()?.includes(index + 1)\"\r\n >\r\n <td class=\"fit-width\">{{ index + 1 }}.</td>\r\n <td class=\"fit-width\">\r\n <!-- edit -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n class=\"mat-primary\"\r\n (click)=\"editOperation(index)\"\r\n matTooltip=\"Edit operation\"\r\n >\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n <!-- delete -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n class=\"mat-warn\"\r\n (click)=\"deleteOperation(index)\"\r\n matTooltip=\"Delete operation\"\r\n >\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n <!-- run to -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n class=\"mat-accent\"\r\n (click)=\"runTo(index)\"\r\n matTooltip=\"Run to this operation\"\r\n >\r\n <mat-icon class=\"mat-accent\">subscriptions</mat-icon>\r\n </button>\r\n <!-- duplicate -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n (click)=\"duplicateOperation(index)\"\r\n matTooltip=\"Duplicate operation\"\r\n >\r\n <mat-icon>control_point_duplicate</mat-icon>\r\n </button>\r\n <!-- move up -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n (click)=\"moveOperationUp(index)\"\r\n matTooltip=\"Move operation up\"\r\n [disabled]=\"index === 0\"\r\n >\r\n <mat-icon>arrow_circle_up</mat-icon>\r\n </button>\r\n <!-- move down -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n (click)=\"moveOperationDown(index)\"\r\n matTooltip=\"Move operation down\"\r\n [disabled]=\"index === operations.value.length - 1\"\r\n >\r\n <mat-icon>arrow_circle_down</mat-icon>\r\n </button>\r\n </td>\r\n <td>{{ operation.id }}</td>\r\n <td>{{ operation.type | flatLookup: opTypeMap }}</td>\r\n <td>{{ operation.at }}</td>\r\n <td>{{ operation.run }}</td>\r\n <td>{{ operation.value }}</td>\r\n <td>{{ operation.inputTag }}</td>\r\n <td>{{ operation.outputTag }}</td>\r\n <td>{{ operation.groupId }}</td>\r\n <td>\r\n @if (operation.features?.length) {\r\n @if (featDetails.value) {\r\n <div class=\"form-row\">\r\n @for (\r\n feat of operation.features;\r\n track $index;\r\n let i = $index\r\n ) {\r\n <div class=\"feature\">\r\n <span class=\"fname\">{{\r\n feat.name\r\n | flatLookup\r\n : featureDefs()?.names\r\n : \"id\"\r\n : \"label\"\r\n }}</span\r\n >=<span class=\"fvalue\">{{\r\n feat.value\r\n | flatLookup\r\n : featureDefs()?.values?.[feat.name]\r\n : \"id\"\r\n : \"label\"\r\n }}</span>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- version badge -->\r\n @if (\r\n operation.features?.some(f => f.name === 'version')\r\n ) {\r\n <span\r\n matBadge=\"{{ operation.features?.find(f => f.name === 'version')?.value }}\"\r\n matBadgeOverlap=\"false\"\r\n >{{ operation.features?.length }}</span>\r\n }\r\n }\r\n }\r\n </td>\r\n <td>{{ operation.sources?.length || \"\" }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n }\r\n </div>\r\n </div>\r\n </mat-tab>\r\n </mat-tab-group>\r\n\r\n <!-- progress -->\r\n <div class=\"margin-bottom\">\r\n @if (busy()) {\r\n <mat-progress-bar mode=\"indeterminate\" />\r\n }\r\n </div>\r\n\r\n <!-- result -->\r\n @if (result()) {\r\n <fieldset id=\"result\">\r\n <legend>result</legend>\r\n <gve-chain-result-view\r\n [result]=\"result()!\"\r\n [initialStepIndex]=\"initialStepIndex()\"\r\n [disabledRangePick]=\"editedOp() ? false : true\"\r\n (stepPick)=\"onStepPick($event)\"\r\n (rangePick)=\"onRangePick($event, true)\"\r\n />\r\n </fieldset>\r\n }\r\n\r\n <!-- snapshot view -->\r\n @if (result()) {\r\n <div id=\"preview\">\r\n <gve-snapshot-rendition #rendition class=\"rendition\">\r\n </gve-snapshot-rendition>\r\n </div>\r\n }\r\n\r\n <!--buttons -->\r\n <div class=\"form-row-center\">\r\n <button\r\n type=\"button\"\r\n class=\"mat-warn\"\r\n mat-flat-button\r\n matTooltip=\"Discard changes\"\r\n (click)=\"close()\"\r\n >\r\n <mat-icon>clear</mat-icon>\r\n close\r\n </button>\r\n @if (!noSave()) {\r\n <button\r\n type=\"submit\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Save changes\"\r\n [disabled]=\"form.invalid\"\r\n >\r\n <mat-icon>check_circle</mat-icon>\r\n save\r\n </button>\r\n }\r\n </div>\r\n</form>\r\n", styles: [".form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row-center{display:flex;gap:8px;align-items:center;justify-content:center;flex-wrap:wrap}.form-row,.form-row-center *{flex:0 0 auto}.form-row .right{margin-left:auto}.button-row{display:flex;align-items:center;flex-wrap:wrap}.button-row *{flex:0 0 auto}.long-text{width:100%;max-width:800px}.margin-bottom{margin-bottom:8px}#text-range{margin:8px;border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:6px}mat-expansion-panel{margin:8px 0}div#visual-info{font-size:95%;color:var(--mat-sys-on-surface-variant);margin:8px}#result{max-height:800px;overflow:auto}#snapshot-container{margin-top:6px}#list{margin:8px 0}#opStyle{margin-top:8px}table{width:100%;border-collapse:collapse;color:var(--mat-sys-on-surface-variant)}tbody tr:nth-child(odd){background-color:var(--mat-sys-surface-container)}th{text-align:left;font-weight:400;color:var(--mat-sys-on-surface-variant)}tbody tr:hover{background-color:var(--mat-sys-surface-container-high)}td.fit-width{width:1px;white-space:nowrap}tbody tr.feat-editing{background-color:var(--mat-sys-tertiary-container);color:var(--mat-sys-on-tertiary-container)}tbody tr.selected{background-color:var(--mat-sys-secondary-container);color:var(--mat-sys-on-secondary-container)}fieldset{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:8px;margin:8px 0}legend{color:var(--mat-sys-on-surface-variant)}.error{color:var(--mat-sys-error)}.input-nr{width:6em}.full-width{width:100%}.code{font-family:Courier New,Courier,monospace}.boxed{border:1px solid var(--mat-sys-on-surface-variant);border-radius:6px;padding:8px;margin:8px 0}span.label{margin-left:8px}.feature{color:var(--mat-sys-on-surface-variant)}.fname{font-size:.9em;color:var(--mat-sys-on-surface-variant)}.fvalue{font-size:.9em;color:var(--mat-sys-on-surface-variant);background-color:var(--mat-sys-surface-container);padding:2px 4px;border-radius:4px}gve-animation-timeline-set{margin:8px 0}div#image{display:grid;gap:8px;grid-template-rows:auto;grid-template-columns:1fr auto;grid-template-areas:\"image-ctl image-view\"}div#image-ctl{grid-area:image-ctl}div#image-view{grid-area:image-view}div#chain-view{margin-bottom:8px}#preview{margin:8px 0}.rendition{display:block;width:100%;min-height:600px;max-height:80vh;border:1px solid var(--mat-sys-on-surface-variant);overflow:auto}@media only screen and (max-width:959px){div#image{grid-template-columns:1fr;grid-template-areas:\"image-ctl\" \"image-view\"}}\n"] }]
3404
3547
  }], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: GveApiService }, { type: i3$2.MatDialog }, { type: i4$1.DialogService }, { type: i2$3.MatSnackBar }], propDecorators: { renditionRef: [{ type: i0.ViewChild, args: ['rendition', { isSignal: true }] }], operationEditorPanelRef: [{ type: i0.ViewChild, args: ['operationEditorPanel', { isSignal: true }] }], snapshot: [{ type: i0.Input, args: [{ isSignal: true, alias: "snapshot", required: false }] }, { type: i0.Output, args: ["snapshotChange"] }], batchOps: [{ type: i0.Input, args: [{ isSignal: true, alias: "batchOps", required: false }] }], noSave: [{ type: i0.Input, args: [{ isSignal: true, alias: "noSave", required: false }] }], debug: [{ type: i0.Input, args: [{ isSignal: true, alias: "debug", required: false }] }], noDecoration: [{ type: i0.Input, args: [{ isSignal: true, alias: "noDecoration", required: false }] }], featureDefs: [{ type: i0.Input, args: [{ isSignal: true, alias: "featureDefs", required: false }] }], multiValuedFeatureIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiValuedFeatureIds", required: false }] }], sourceIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceIds", required: false }] }], sourceTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceTypes", required: false }] }], renditionSettings: [{ type: i0.Input, args: [{ isSignal: true, alias: "renditionSettings", required: false }] }], opListMaxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "opListMaxHeight", required: false }] }], snapshotCancel: [{ type: i0.Output, args: ["snapshotCancel"] }] } });
3405
3548
 
3406
3549
  class GveGraphvizService {
@@ -3508,10 +3651,10 @@ class GveGraphvizService {
3508
3651
  sb.push('}');
3509
3652
  return sb.join('\n');
3510
3653
  }
3511
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GveGraphvizService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3512
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GveGraphvizService, providedIn: 'root' }); }
3654
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: GveGraphvizService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3655
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: GveGraphvizService, providedIn: 'root' }); }
3513
3656
  }
3514
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GveGraphvizService, decorators: [{
3657
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: GveGraphvizService, decorators: [{
3515
3658
  type: Injectable,
3516
3659
  args: [{
3517
3660
  providedIn: 'root',