@myrmidon/gve-core 5.0.3 → 6.0.0
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.
- package/README.md +0 -1
- package/fesm2022/myrmidon-gve-core.mjs +820 -1442
- package/fesm2022/myrmidon-gve-core.mjs.map +1 -1
- package/package.json +13 -13
- package/{index.d.ts → types/myrmidon-gve-core.d.ts} +340 -377
|
@@ -1,470 +1,74 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
3
|
-
import * as i1 from '@angular/
|
|
4
|
-
import {
|
|
2
|
+
import { input, output, ChangeDetectionStrategy, Component, inject, DestroyRef, signal, computed, effect, model, ViewChild, Injectable, Optional, Inject, viewChild, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
3
|
+
import * as i1 from '@angular/material/core';
|
|
4
|
+
import { MatRippleModule } from '@angular/material/core';
|
|
5
|
+
import * as i2$1 from '@myrmidon/ngx-tools';
|
|
6
|
+
import { ColorToContrastPipe, FlatLookupPipe } from '@myrmidon/ngx-tools';
|
|
7
|
+
import * as i1$1 from '@angular/forms';
|
|
8
|
+
import { FormsModule, Validators, ReactiveFormsModule, FormControl, FormGroup } from '@angular/forms';
|
|
5
9
|
import * as i2 from '@angular/material/button';
|
|
6
10
|
import { MatButtonModule } from '@angular/material/button';
|
|
7
|
-
import * as i3$2 from '@angular/material/checkbox';
|
|
8
|
-
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
9
|
-
import * as i3$1 from '@angular/material/expansion';
|
|
10
|
-
import { MatExpansionModule } from '@angular/material/expansion';
|
|
11
11
|
import * as i3 from '@angular/material/form-field';
|
|
12
12
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
13
13
|
import * as i4 from '@angular/material/icon';
|
|
14
14
|
import { MatIconModule } from '@angular/material/icon';
|
|
15
15
|
import * as i5 from '@angular/material/input';
|
|
16
16
|
import { MatInputModule } from '@angular/material/input';
|
|
17
|
-
import * as i6 from '@angular/material/
|
|
18
|
-
import { MatSelectModule } from '@angular/material/select';
|
|
19
|
-
import * as i14 from '@angular/material/tabs';
|
|
20
|
-
import { MatTabsModule } from '@angular/material/tabs';
|
|
21
|
-
import * as i7 from '@angular/material/tooltip';
|
|
17
|
+
import * as i6 from '@angular/material/tooltip';
|
|
22
18
|
import { MatTooltipModule, MatTooltip } from '@angular/material/tooltip';
|
|
23
|
-
import
|
|
24
|
-
import {
|
|
25
|
-
import
|
|
19
|
+
import { Subject, debounceTime, distinctUntilChanged, catchError, BehaviorSubject } from 'rxjs';
|
|
20
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
21
|
+
import * as i2$3 from '@angular/material/snack-bar';
|
|
22
|
+
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
|
23
|
+
import * as i2$2 from '@angular/cdk/clipboard';
|
|
24
|
+
import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard';
|
|
25
|
+
import * as i7$1 from '@angular/material/expansion';
|
|
26
|
+
import { MatExpansionModule } from '@angular/material/expansion';
|
|
27
|
+
import * as i7 from '@angular/material/select';
|
|
28
|
+
import { MatSelectModule } from '@angular/material/select';
|
|
29
|
+
import * as i3$1 from '@angular/material/checkbox';
|
|
30
|
+
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
26
31
|
import * as i4$1 from '@myrmidon/ngx-mat-tools';
|
|
27
|
-
import * as
|
|
28
|
-
import { MatRippleModule } from '@angular/material/core';
|
|
29
|
-
import { FeatureSetPolicy, SnapshotViewService, OperationType, DEFAULT_SVG_BASE_TEXT_OPTIONS } from '@myrmidon/gve-snapshot-view';
|
|
30
|
-
import * as i3$3 from '@angular/material/dialog';
|
|
32
|
+
import * as i3$2 from '@angular/material/dialog';
|
|
31
33
|
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
|
32
34
|
import * as i1$2 from '@angular/common/http';
|
|
33
35
|
import { filter, debounceTime as debounceTime$1 } from 'rxjs/operators';
|
|
34
|
-
import * as i2$2 from '@angular/cdk/clipboard';
|
|
35
|
-
import { ClipboardModule } from '@angular/cdk/clipboard';
|
|
36
|
-
import * as i5$1 from '@angular/material/badge';
|
|
37
36
|
import { MatBadgeModule } from '@angular/material/badge';
|
|
38
|
-
import * as
|
|
37
|
+
import * as i10 from '@angular/material/tabs';
|
|
38
|
+
import { MatTabsModule } from '@angular/material/tabs';
|
|
39
39
|
import { NgeMonacoModule } from '@cisstech/nge/monaco';
|
|
40
40
|
import { customAlphabet } from 'nanoid';
|
|
41
|
-
import * as i16 from '@angular/common';
|
|
42
41
|
import { CommonModule } from '@angular/common';
|
|
43
|
-
import { VizComponent } from '@myrmidon/ngx-viz';
|
|
44
|
-
import * as i4$2 from '@angular/material/snack-bar';
|
|
45
|
-
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
|
46
|
-
import * as i7$1 from '@angular/material/button-toggle';
|
|
47
42
|
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
|
48
|
-
import * as
|
|
43
|
+
import * as i9 from '@angular/material/progress-bar';
|
|
49
44
|
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
|
50
|
-
import * as i13 from '@angular/material/slider';
|
|
51
45
|
import { MatSliderModule } from '@angular/material/slider';
|
|
52
46
|
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
|
47
|
+
import { DEFAULT_SETTINGS, GveSnapshotRendition } from '@myrmidon/gve-snapshot-rendition';
|
|
53
48
|
|
|
54
49
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* A component to edit an animation tween.
|
|
58
|
-
* Used by the `gve-animation-timeline` component.
|
|
59
|
-
*
|
|
60
|
-
* - ▶️ `tween` (`GveAnimationTween`): the tween to edit.
|
|
61
|
-
* - ▶️ `elementIds` (`string[]`): the IDs of the elements that can be selected by the tween.
|
|
62
|
-
* - 🔥 `tweenChange` (`GveAnimationTween`): emitted when the tween is changed.
|
|
63
|
-
* - 🔥 `tweenCancel` (`void`): emitted when the user cancels the edit.
|
|
50
|
+
* Operation feature set policy.
|
|
64
51
|
*/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* The IDs of the elements that can be selected by the tween.
|
|
73
|
-
* This list is used to allow the user to select an element from a dropdown.
|
|
74
|
-
*/
|
|
75
|
-
this.elementIds = input(...(ngDevMode ? [undefined, { debugName: "elementIds" }] : []));
|
|
76
|
-
/**
|
|
77
|
-
* Emitted when the user cancels the edit.
|
|
78
|
-
*/
|
|
79
|
-
this.tweenCancel = output();
|
|
80
|
-
this.types = ['to', 'from', 'fromTo', 'set'];
|
|
81
|
-
this.label = formBuilder.control('', {
|
|
82
|
-
nonNullable: true,
|
|
83
|
-
validators: [Validators.required, Validators.maxLength(100)],
|
|
84
|
-
});
|
|
85
|
-
this.note = formBuilder.control(null);
|
|
86
|
-
this.type = formBuilder.control('to', { nonNullable: true });
|
|
87
|
-
this.selector = formBuilder.control('', {
|
|
88
|
-
nonNullable: true,
|
|
89
|
-
validators: [Validators.required, Validators.maxLength(200)],
|
|
90
|
-
});
|
|
91
|
-
this.vars = formBuilder.control('{}', {
|
|
92
|
-
nonNullable: true,
|
|
93
|
-
validators: [Validators.required, this.jsonValidator],
|
|
94
|
-
});
|
|
95
|
-
this.vars2 = formBuilder.control(null, this.jsonValidator);
|
|
96
|
-
this.position = formBuilder.control(null, Validators.maxLength(100));
|
|
97
|
-
this.form = formBuilder.group({
|
|
98
|
-
label: this.label,
|
|
99
|
-
note: this.note,
|
|
100
|
-
type: this.type,
|
|
101
|
-
selector: this.selector,
|
|
102
|
-
vars: this.vars,
|
|
103
|
-
vars2: this.vars2,
|
|
104
|
-
position: this.position,
|
|
105
|
-
});
|
|
106
|
-
this.elementId = formBuilder.control(null);
|
|
107
|
-
effect(() => {
|
|
108
|
-
this.updateForm(this.tween());
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
ngOnInit() {
|
|
112
|
-
// set selector when elementId changes
|
|
113
|
-
this._sub = this.elementId.valueChanges
|
|
114
|
-
.pipe(debounceTime(200), distinctUntilChanged())
|
|
115
|
-
.subscribe((elementId) => {
|
|
116
|
-
if (elementId) {
|
|
117
|
-
this.selector.setValue('#' + elementId);
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
ngOnDestroy() {
|
|
122
|
-
this._sub?.unsubscribe();
|
|
123
|
-
}
|
|
124
|
-
onVarsChange(vars) {
|
|
125
|
-
this.vars.setValue(vars);
|
|
126
|
-
this.vars.markAsDirty();
|
|
127
|
-
this.vars.updateValueAndValidity();
|
|
128
|
-
}
|
|
129
|
-
onVars2Change(vars) {
|
|
130
|
-
this.vars2.setValue(vars);
|
|
131
|
-
this.vars2.markAsDirty();
|
|
132
|
-
this.vars2.updateValueAndValidity();
|
|
133
|
-
}
|
|
134
|
-
close() {
|
|
135
|
-
this.tweenCancel.emit();
|
|
136
|
-
}
|
|
137
|
-
jsonValidator(control) {
|
|
138
|
-
if (!control.value) {
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
try {
|
|
142
|
-
JSON.parse(control.value);
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
catch (e) {
|
|
146
|
-
return { json: true };
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
updateForm(tween) {
|
|
150
|
-
if (!tween) {
|
|
151
|
-
this.form.reset();
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
this.label.setValue(tween.label);
|
|
155
|
-
this.note.setValue(tween.note || null);
|
|
156
|
-
this.type.setValue(tween.type);
|
|
157
|
-
this.selector.setValue(tween.selector);
|
|
158
|
-
this.vars.setValue(tween.vars || '{}');
|
|
159
|
-
this.vars2.setValue(tween.vars2 || null);
|
|
160
|
-
this.position.setValue(tween.position || null);
|
|
161
|
-
this.form.markAsPristine();
|
|
162
|
-
}
|
|
163
|
-
getTween() {
|
|
164
|
-
return {
|
|
165
|
-
label: this.label.value,
|
|
166
|
-
note: this.note.value?.trim() || undefined,
|
|
167
|
-
type: this.type.value,
|
|
168
|
-
selector: this.selector.value.trim(),
|
|
169
|
-
vars: this.vars.value,
|
|
170
|
-
vars2: this.vars2.value || undefined,
|
|
171
|
-
position: this.position.value?.trim() || undefined,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
openUrl(url) {
|
|
175
|
-
window.open(url, '_blank');
|
|
176
|
-
}
|
|
177
|
-
save() {
|
|
178
|
-
if (!this.form.valid) {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
this.tween.set(this.getTween());
|
|
182
|
-
}
|
|
183
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: AnimationTweenComponent, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
184
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.10", type: AnimationTweenComponent, isStandalone: true, selector: "gve-animation-tween", inputs: { tween: { classPropertyName: "tween", publicName: "tween", isSignal: true, isRequired: false, transformFunction: null }, elementIds: { classPropertyName: "elementIds", publicName: "elementIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { tween: "tweenChange", tweenCancel: "tweenCancel" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <div class=\"form-row\">\r\n <!-- label -->\r\n <mat-form-field>\r\n <mat-label>label</mat-label>\r\n <input matInput [formControl]=\"label\" />\r\n @if ($any(label).errors?.required && (label.dirty || label.touched)) {\r\n <mat-error>label required</mat-error>\r\n } @if ($any(label).errors?.maxLength && (label.dirty || label.touched)) {\r\n <mat-error>label too long</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- type -->\r\n <mat-form-field>\r\n <mat-label>type</mat-label>\r\n <mat-select [formControl]=\"type\">\r\n @for (t of types; 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 <!-- position -->\r\n <mat-form-field>\r\n <mat-label>position</mat-label>\r\n <input matInput [formControl]=\"position\" />\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matSuffix\r\n [attr.aria-label]=\"'Information about position'\"\r\n (click)=\"\r\n openUrl(\r\n 'https://gsap.com/docs/v3/GSAP/Timeline/#positioning-animations-in-a-timeline'\r\n )\r\n \"\r\n >\r\n <mat-icon>info</mat-icon>\r\n </button>\r\n <mat-hint>time label < > += -=</mat-hint>\r\n @if ( $any(position).errors?.maxLength && (position.dirty ||\r\n position.touched) ) {\r\n <mat-error>position too long</mat-error>\r\n }\r\n </mat-form-field>\r\n </div>\r\n\r\n <div class=\"form-row\">\r\n <!-- selector -->\r\n <mat-form-field>\r\n <mat-label>selector</mat-label>\r\n <input matInput [formControl]=\"selector\" />\r\n <mat-hint>CSS selector</mat-hint>\r\n @if ( $any(selector).errors?.required && (selector.dirty ||\r\n selector.touched) ) {\r\n <mat-error>selector required</mat-error>\r\n } @if ( $any(selector).errors?.maxLength && (selector.dirty ||\r\n selector.touched) ) {\r\n <mat-error>selector too long</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- element id -->\r\n @if ((elementIds())?.length) {\r\n <mat-form-field>\r\n <mat-label>element ID</mat-label>\r\n <mat-select [formControl]=\"elementId\">\r\n @for (id of elementIds(); track id) {\r\n <mat-option [value]=\"id\">{{ id }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n }\r\n </div>\r\n\r\n <!-- note -->\r\n <div>\r\n <mat-form-field class=\"long-text\">\r\n <mat-label>note</mat-label>\r\n <textarea rows=\"2\" matInput [formControl]=\"note\"></textarea>\r\n @if ($any(note).errors?.maxLength && (note.dirty || note.touched)) {\r\n <mat-error>note too long</mat-error>\r\n }\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- vars -->\r\n <div>\r\n <mat-form-field class=\"long-text\">\r\n <mat-label>vars</mat-label>\r\n <textarea matInput [formControl]=\"vars\"></textarea>\r\n <mat-hint>JSON object</mat-hint>\r\n @if ($any(vars).errors?.required && (vars.dirty || vars.touched)) {\r\n <mat-error>vars required</mat-error>\r\n } @if ($any(vars).errors?.json && (vars.dirty || vars.touched)) {\r\n <mat-error>invalid JSON</mat-error>\r\n }\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- vars2 only when type is fromTo -->\r\n @if (type.value === 'fromTo') {\r\n <div>\r\n <mat-form-field class=\"long-text\">\r\n <mat-label>2nd vars</mat-label>\r\n <textarea matInput [formControl]=\"vars2\"></textarea>\r\n <mat-hint>JSON object</mat-hint>\r\n @if ($any(vars2).errors?.required && (vars2.dirty || vars2.touched)) {\r\n <mat-error>vars required</mat-error>\r\n } @if ($any(vars2).errors?.json && (vars2.dirty || vars2.touched)) {\r\n <mat-error>invalid JSON</mat-error>\r\n }\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n <div class=\"form-row\">\r\n <button\r\n type=\"button\"\r\n class=\"mat-warn\"\r\n mat-icon-button\r\n matTooltip=\"Close tween\"\r\n (click)=\"close()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"submit\"\r\n class=\"mat-primary\"\r\n mat-icon-button\r\n matTooltip=\"Save tween\"\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}.long-text{width:100%;max-width:800px}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "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: "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: "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: "component", type: i6.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: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] }); }
|
|
185
|
-
}
|
|
186
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: AnimationTweenComponent, decorators: [{
|
|
187
|
-
type: Component,
|
|
188
|
-
args: [{ selector: 'gve-animation-tween', imports: [
|
|
189
|
-
ReactiveFormsModule,
|
|
190
|
-
MatButtonModule,
|
|
191
|
-
MatCheckboxModule,
|
|
192
|
-
MatFormFieldModule,
|
|
193
|
-
MatIconModule,
|
|
194
|
-
MatInputModule,
|
|
195
|
-
MatSelectModule,
|
|
196
|
-
MatTabsModule,
|
|
197
|
-
MatTooltipModule,
|
|
198
|
-
], template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <div class=\"form-row\">\r\n <!-- label -->\r\n <mat-form-field>\r\n <mat-label>label</mat-label>\r\n <input matInput [formControl]=\"label\" />\r\n @if ($any(label).errors?.required && (label.dirty || label.touched)) {\r\n <mat-error>label required</mat-error>\r\n } @if ($any(label).errors?.maxLength && (label.dirty || label.touched)) {\r\n <mat-error>label too long</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- type -->\r\n <mat-form-field>\r\n <mat-label>type</mat-label>\r\n <mat-select [formControl]=\"type\">\r\n @for (t of types; 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 <!-- position -->\r\n <mat-form-field>\r\n <mat-label>position</mat-label>\r\n <input matInput [formControl]=\"position\" />\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matSuffix\r\n [attr.aria-label]=\"'Information about position'\"\r\n (click)=\"\r\n openUrl(\r\n 'https://gsap.com/docs/v3/GSAP/Timeline/#positioning-animations-in-a-timeline'\r\n )\r\n \"\r\n >\r\n <mat-icon>info</mat-icon>\r\n </button>\r\n <mat-hint>time label < > += -=</mat-hint>\r\n @if ( $any(position).errors?.maxLength && (position.dirty ||\r\n position.touched) ) {\r\n <mat-error>position too long</mat-error>\r\n }\r\n </mat-form-field>\r\n </div>\r\n\r\n <div class=\"form-row\">\r\n <!-- selector -->\r\n <mat-form-field>\r\n <mat-label>selector</mat-label>\r\n <input matInput [formControl]=\"selector\" />\r\n <mat-hint>CSS selector</mat-hint>\r\n @if ( $any(selector).errors?.required && (selector.dirty ||\r\n selector.touched) ) {\r\n <mat-error>selector required</mat-error>\r\n } @if ( $any(selector).errors?.maxLength && (selector.dirty ||\r\n selector.touched) ) {\r\n <mat-error>selector too long</mat-error>\r\n }\r\n </mat-form-field>\r\n\r\n <!-- element id -->\r\n @if ((elementIds())?.length) {\r\n <mat-form-field>\r\n <mat-label>element ID</mat-label>\r\n <mat-select [formControl]=\"elementId\">\r\n @for (id of elementIds(); track id) {\r\n <mat-option [value]=\"id\">{{ id }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n }\r\n </div>\r\n\r\n <!-- note -->\r\n <div>\r\n <mat-form-field class=\"long-text\">\r\n <mat-label>note</mat-label>\r\n <textarea rows=\"2\" matInput [formControl]=\"note\"></textarea>\r\n @if ($any(note).errors?.maxLength && (note.dirty || note.touched)) {\r\n <mat-error>note too long</mat-error>\r\n }\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- vars -->\r\n <div>\r\n <mat-form-field class=\"long-text\">\r\n <mat-label>vars</mat-label>\r\n <textarea matInput [formControl]=\"vars\"></textarea>\r\n <mat-hint>JSON object</mat-hint>\r\n @if ($any(vars).errors?.required && (vars.dirty || vars.touched)) {\r\n <mat-error>vars required</mat-error>\r\n } @if ($any(vars).errors?.json && (vars.dirty || vars.touched)) {\r\n <mat-error>invalid JSON</mat-error>\r\n }\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- vars2 only when type is fromTo -->\r\n @if (type.value === 'fromTo') {\r\n <div>\r\n <mat-form-field class=\"long-text\">\r\n <mat-label>2nd vars</mat-label>\r\n <textarea matInput [formControl]=\"vars2\"></textarea>\r\n <mat-hint>JSON object</mat-hint>\r\n @if ($any(vars2).errors?.required && (vars2.dirty || vars2.touched)) {\r\n <mat-error>vars required</mat-error>\r\n } @if ($any(vars2).errors?.json && (vars2.dirty || vars2.touched)) {\r\n <mat-error>invalid JSON</mat-error>\r\n }\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n <div class=\"form-row\">\r\n <button\r\n type=\"button\"\r\n class=\"mat-warn\"\r\n mat-icon-button\r\n matTooltip=\"Close tween\"\r\n (click)=\"close()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"submit\"\r\n class=\"mat-primary\"\r\n mat-icon-button\r\n matTooltip=\"Save tween\"\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}.long-text{width:100%;max-width:800px}\n"] }]
|
|
199
|
-
}], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { tween: [{ type: i0.Input, args: [{ isSignal: true, alias: "tween", required: false }] }, { type: i0.Output, args: ["tweenChange"] }], elementIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "elementIds", required: false }] }], tweenCancel: [{ type: i0.Output, args: ["tweenCancel"] }] } });
|
|
200
|
-
|
|
52
|
+
var FeatureSetPolicy;
|
|
53
|
+
(function (FeatureSetPolicy) {
|
|
54
|
+
FeatureSetPolicy[FeatureSetPolicy["multiple"] = 0] = "multiple";
|
|
55
|
+
FeatureSetPolicy[FeatureSetPolicy["single"] = 1] = "single";
|
|
56
|
+
FeatureSetPolicy[FeatureSetPolicy["singleFirst"] = 2] = "singleFirst";
|
|
57
|
+
})(FeatureSetPolicy || (FeatureSetPolicy = {}));
|
|
201
58
|
/**
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
* A component to edit an animation timeline.
|
|
205
|
-
* Used by the `gve-animation-timeline-set` component.
|
|
206
|
-
*
|
|
207
|
-
* - ▶️ `timeline` (`GveAnimationTimeline`): the animation timeline to edit.
|
|
208
|
-
* - ▶️ `elementIds` (`string[]`): the IDs of the elements that can be selected
|
|
209
|
-
* by the tween.
|
|
210
|
-
* - ▶️ `tags` (`string[]`): the tags that can be used by the timeline.
|
|
211
|
-
* - 🔥 `timelineChange` (`GveAnimationTimeline`): emitted when the timeline
|
|
212
|
-
* is changed.
|
|
213
|
-
* - 🔥 `timelineCancel` (`void`): emitted when the timeline editing is canceled.
|
|
59
|
+
* Type of a text operation.
|
|
214
60
|
*/
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* The tags that can be used by the timeline.
|
|
228
|
-
*/
|
|
229
|
-
this.tags = input([], ...(ngDevMode ? [{ debugName: "tags" }] : []));
|
|
230
|
-
/**
|
|
231
|
-
* Emitted when the timeline is changed.
|
|
232
|
-
*/
|
|
233
|
-
this.timelineChange = output();
|
|
234
|
-
/**
|
|
235
|
-
* Emitted when the timeline editing is canceled.
|
|
236
|
-
*/
|
|
237
|
-
this.timelineCancel = output();
|
|
238
|
-
this.editedTweenIndex = signal(-1, ...(ngDevMode ? [{ debugName: "editedTweenIndex" }] : []));
|
|
239
|
-
this.editedTween = signal(undefined, ...(ngDevMode ? [{ debugName: "editedTween" }] : []));
|
|
240
|
-
this.tag = formBuilder.control('', {
|
|
241
|
-
nonNullable: true,
|
|
242
|
-
validators: [Validators.required, Validators.maxLength(100)],
|
|
243
|
-
});
|
|
244
|
-
this.tweens = formBuilder.control([], {
|
|
245
|
-
nonNullable: true,
|
|
246
|
-
validators: NgxToolsValidators.strictMinLengthValidator(1),
|
|
247
|
-
});
|
|
248
|
-
this.vars = formBuilder.control(null, this.jsonValidator);
|
|
249
|
-
this.form = formBuilder.group({
|
|
250
|
-
tag: this.tag,
|
|
251
|
-
tweens: this.tweens,
|
|
252
|
-
vars: this.vars,
|
|
253
|
-
});
|
|
254
|
-
// when the timeline changes, update the form
|
|
255
|
-
effect(() => {
|
|
256
|
-
this.updateForm(this.timeline());
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
jsonValidator(control) {
|
|
260
|
-
if (!control.value) {
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
try {
|
|
264
|
-
JSON.parse(control.value);
|
|
265
|
-
return null;
|
|
266
|
-
}
|
|
267
|
-
catch (e) {
|
|
268
|
-
return { json: true };
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
updateForm(timeline) {
|
|
272
|
-
if (!timeline) {
|
|
273
|
-
this.form.reset();
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
this.tag.setValue(timeline.tag);
|
|
277
|
-
this.tweens.setValue(timeline.tweens);
|
|
278
|
-
this.vars.setValue(timeline.vars || null);
|
|
279
|
-
this.form.markAsPristine();
|
|
280
|
-
}
|
|
281
|
-
addTween() {
|
|
282
|
-
this.editedTweenIndex.set(-1);
|
|
283
|
-
this.editedTween.set({
|
|
284
|
-
label: 'tween #' + (this.tweens.value.length + 1),
|
|
285
|
-
type: 'to',
|
|
286
|
-
selector: '',
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
editTween(index) {
|
|
290
|
-
this.editedTweenIndex.set(index);
|
|
291
|
-
this.editedTween.set(deepCopy(this.tweens.value[index]));
|
|
292
|
-
}
|
|
293
|
-
deleteTween(index) {
|
|
294
|
-
this.tweens.setValue(this.tweens.value.filter((_, i) => i !== index));
|
|
295
|
-
this.tweens.markAsDirty();
|
|
296
|
-
this.tweens.updateValueAndValidity();
|
|
297
|
-
}
|
|
298
|
-
closeTween() {
|
|
299
|
-
this.editedTween.set(undefined);
|
|
300
|
-
this.editedTweenIndex.set(-1);
|
|
301
|
-
}
|
|
302
|
-
saveTween(tween) {
|
|
303
|
-
if (this.editedTweenIndex() === -1) {
|
|
304
|
-
this.tweens.setValue([...this.tweens.value, tween]);
|
|
305
|
-
}
|
|
306
|
-
else {
|
|
307
|
-
this.tweens.setValue(this.tweens.value.map((t, index) => index === this.editedTweenIndex() ? tween : t));
|
|
308
|
-
}
|
|
309
|
-
this.tweens.markAsDirty();
|
|
310
|
-
this.tweens.updateValueAndValidity();
|
|
311
|
-
this.closeTween();
|
|
312
|
-
}
|
|
313
|
-
moveTweenUp(index) {
|
|
314
|
-
if (index < 1) {
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
const tweens = [...this.tweens.value];
|
|
318
|
-
const tmp = tweens[index];
|
|
319
|
-
tweens[index] = tweens[index - 1];
|
|
320
|
-
tweens[index - 1] = tmp;
|
|
321
|
-
this.tweens.setValue(tweens);
|
|
322
|
-
this.tweens.markAsDirty();
|
|
323
|
-
this.tweens.updateValueAndValidity();
|
|
324
|
-
}
|
|
325
|
-
moveTweenDown(index) {
|
|
326
|
-
if (index >= this.tweens.value.length - 1) {
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
const tweens = [...this.tweens.value];
|
|
330
|
-
const tmp = tweens[index];
|
|
331
|
-
tweens[index] = tweens[index + 1];
|
|
332
|
-
tweens[index + 1] = tmp;
|
|
333
|
-
this.tweens.setValue(tweens);
|
|
334
|
-
this.tweens.markAsDirty();
|
|
335
|
-
this.tweens.updateValueAndValidity();
|
|
336
|
-
}
|
|
337
|
-
getTimeline() {
|
|
338
|
-
return {
|
|
339
|
-
tag: this.tag.value || '',
|
|
340
|
-
tweens: this.tweens.value,
|
|
341
|
-
vars: this.vars.value || undefined,
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
close() {
|
|
345
|
-
this.timelineCancel.emit();
|
|
346
|
-
}
|
|
347
|
-
save() {
|
|
348
|
-
if (this.form.invalid) {
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
this.timeline.set(this.getTimeline());
|
|
352
|
-
this.timelineChange.emit(this.timeline());
|
|
353
|
-
this.form.markAsPristine();
|
|
354
|
-
}
|
|
355
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: AnimationTimelineComponent, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
356
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.10", type: AnimationTimelineComponent, isStandalone: true, selector: "gve-animation-timeline", inputs: { timeline: { classPropertyName: "timeline", publicName: "timeline", isSignal: true, isRequired: false, transformFunction: null }, elementIds: { classPropertyName: "elementIds", publicName: "elementIds", isSignal: true, isRequired: false, transformFunction: null }, tags: { classPropertyName: "tags", publicName: "tags", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { timeline: "timelineChange", timelineChange: "timelineChange", timelineCancel: "timelineCancel" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <div class=\"form-row\">\r\n <!-- tag (bound) -->\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 @if ($any(tag).errors?.required && (tag.dirty || tag.touched)) {\r\n <mat-error>tag required</mat-error>\r\n }\r\n </mat-form-field>\r\n } @else {\r\n <!-- tag (free) -->\r\n <mat-form-field>\r\n <mat-label>tag</mat-label>\r\n <input matInput [formControl]=\"tag\" />\r\n @if ($any(tag).errors?.required && (tag.dirty || tag.touched)) {\r\n <mat-error>tag required</mat-error>\r\n } @if ($any(tag).errors?.maxLength && (tag.dirty || tag.touched)) {\r\n <mat-error>tag too long</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n\r\n <button\r\n mat-flat-button\r\n type=\"button\"\r\n color=\"primary\"\r\n class=\"mat-primary\"\r\n (click)=\"addTween()\"\r\n >\r\n <mat-icon>add_circle</mat-icon> tween\r\n </button>\r\n </div>\r\n\r\n <!-- vars -->\r\n <div>\r\n <mat-form-field class=\"long-text\">\r\n <mat-label>vars</mat-label>\r\n <textarea matInput [formControl]=\"vars\"></textarea>\r\n <mat-hint>JSON object</mat-hint>\r\n @if ($any(vars).errors?.json && (vars.dirty || vars.touched)) {\r\n <mat-error>invalid JSON</mat-error>\r\n }\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- tweens -->\r\n <table>\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>label</th>\r\n <th>type</th>\r\n <th>selector</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (t of tweens.value; track t; let index = $index) {\r\n <tr [class.selected]=\"index === editedTweenIndex()\">\r\n <td class=\"fit-width\">\r\n <!-- edit -->\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n color=\"primary\"\r\n (click)=\"editTween(index)\"\r\n matTooltip=\"Edit tween\"\r\n >\r\n <mat-icon class=\"mat-primary\">edit</mat-icon>\r\n </button>\r\n <!-- delete -->\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n color=\"warn\"\r\n (click)=\"deleteTween(index)\"\r\n matTooltip=\"Delete tween\"\r\n >\r\n <mat-icon class=\"mat-warn\">remove_circle</mat-icon>\r\n </button>\r\n <!-- up -->\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n (click)=\"moveTweenUp(index)\"\r\n matTooltip=\"Move tween up\"\r\n [disabled]=\"index === 0\"\r\n >\r\n <mat-icon>arrow_circle_up</mat-icon>\r\n </button>\r\n <!-- down -->\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n (click)=\"moveTweenDown(index)\"\r\n matTooltip=\"Move tween down\"\r\n [disabled]=\"index === tweens.value.length - 1\"\r\n >\r\n <mat-icon>arrow_circle_down</mat-icon>\r\n </button>\r\n </td>\r\n <td>\r\n {{ t.label }}\r\n </td>\r\n <td>\r\n {{ t.type }}\r\n </td>\r\n <td>\r\n {{ t.selector }}\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n\r\n <!-- tween editor -->\r\n @if (editedTween()) {\r\n <mat-expansion-panel [expanded]=\"editedTween()\" [disabled]=\"!editedTween()\">\r\n @if (editedTween()) {\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>tween #{{ editedTweenIndex() + 1 }}</mat-panel-title>\r\n </mat-expansion-panel-header>\r\n }\r\n <fieldset>\r\n <gve-animation-tween\r\n [elementIds]=\"elementIds()\"\r\n [tween]=\"editedTween()\"\r\n (tweenChange)=\"saveTween($event)\"\r\n (tweenCancel)=\"closeTween()\"\r\n />\r\n </fieldset>\r\n </mat-expansion-panel>\r\n }\r\n\r\n <!-- buttons -->\r\n <div class=\"button-row\">\r\n <button\r\n type=\"button\"\r\n class=\"mat-warn\"\r\n mat-icon-button\r\n matTooltip=\"Close timeline\"\r\n (click)=\"close()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"submit\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Save timeline\"\r\n [disabled]=\"form.invalid\"\r\n >\r\n <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n timeline\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}.button-row{display:flex;align-items:center;flex-wrap:wrap}.button-row *{flex:0 0 auto}.long-text{width:100%;max-width:800px}table{width:100%;border-collapse:collapse}tbody tr:nth-child(odd){background-color:#e2e2e2}th{text-align:left;font-weight:400;color:silver}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#d0d0d0!important}fieldset{border:1px solid silver;border-radius:6px;padding:8px;margin:8px 0}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "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: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i3$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i3$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i3$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: i6.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: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: AnimationTweenComponent, selector: "gve-animation-tween", inputs: ["tween", "elementIds"], outputs: ["tweenChange", "tweenCancel"] }] }); }
|
|
357
|
-
}
|
|
358
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: AnimationTimelineComponent, decorators: [{
|
|
359
|
-
type: Component,
|
|
360
|
-
args: [{ selector: 'gve-animation-timeline', imports: [
|
|
361
|
-
ReactiveFormsModule,
|
|
362
|
-
MatButtonModule,
|
|
363
|
-
MatCheckboxModule,
|
|
364
|
-
MatExpansionModule,
|
|
365
|
-
MatFormFieldModule,
|
|
366
|
-
MatIconModule,
|
|
367
|
-
MatInputModule,
|
|
368
|
-
MatSelectModule,
|
|
369
|
-
MatTabsModule,
|
|
370
|
-
MatTooltipModule,
|
|
371
|
-
AnimationTweenComponent
|
|
372
|
-
], template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <div class=\"form-row\">\r\n <!-- tag (bound) -->\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 @if ($any(tag).errors?.required && (tag.dirty || tag.touched)) {\r\n <mat-error>tag required</mat-error>\r\n }\r\n </mat-form-field>\r\n } @else {\r\n <!-- tag (free) -->\r\n <mat-form-field>\r\n <mat-label>tag</mat-label>\r\n <input matInput [formControl]=\"tag\" />\r\n @if ($any(tag).errors?.required && (tag.dirty || tag.touched)) {\r\n <mat-error>tag required</mat-error>\r\n } @if ($any(tag).errors?.maxLength && (tag.dirty || tag.touched)) {\r\n <mat-error>tag too long</mat-error>\r\n }\r\n </mat-form-field>\r\n }\r\n\r\n <button\r\n mat-flat-button\r\n type=\"button\"\r\n color=\"primary\"\r\n class=\"mat-primary\"\r\n (click)=\"addTween()\"\r\n >\r\n <mat-icon>add_circle</mat-icon> tween\r\n </button>\r\n </div>\r\n\r\n <!-- vars -->\r\n <div>\r\n <mat-form-field class=\"long-text\">\r\n <mat-label>vars</mat-label>\r\n <textarea matInput [formControl]=\"vars\"></textarea>\r\n <mat-hint>JSON object</mat-hint>\r\n @if ($any(vars).errors?.json && (vars.dirty || vars.touched)) {\r\n <mat-error>invalid JSON</mat-error>\r\n }\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- tweens -->\r\n <table>\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>label</th>\r\n <th>type</th>\r\n <th>selector</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (t of tweens.value; track t; let index = $index) {\r\n <tr [class.selected]=\"index === editedTweenIndex()\">\r\n <td class=\"fit-width\">\r\n <!-- edit -->\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n color=\"primary\"\r\n (click)=\"editTween(index)\"\r\n matTooltip=\"Edit tween\"\r\n >\r\n <mat-icon class=\"mat-primary\">edit</mat-icon>\r\n </button>\r\n <!-- delete -->\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n color=\"warn\"\r\n (click)=\"deleteTween(index)\"\r\n matTooltip=\"Delete tween\"\r\n >\r\n <mat-icon class=\"mat-warn\">remove_circle</mat-icon>\r\n </button>\r\n <!-- up -->\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n (click)=\"moveTweenUp(index)\"\r\n matTooltip=\"Move tween up\"\r\n [disabled]=\"index === 0\"\r\n >\r\n <mat-icon>arrow_circle_up</mat-icon>\r\n </button>\r\n <!-- down -->\r\n <button\r\n mat-icon-button\r\n type=\"button\"\r\n (click)=\"moveTweenDown(index)\"\r\n matTooltip=\"Move tween down\"\r\n [disabled]=\"index === tweens.value.length - 1\"\r\n >\r\n <mat-icon>arrow_circle_down</mat-icon>\r\n </button>\r\n </td>\r\n <td>\r\n {{ t.label }}\r\n </td>\r\n <td>\r\n {{ t.type }}\r\n </td>\r\n <td>\r\n {{ t.selector }}\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n\r\n <!-- tween editor -->\r\n @if (editedTween()) {\r\n <mat-expansion-panel [expanded]=\"editedTween()\" [disabled]=\"!editedTween()\">\r\n @if (editedTween()) {\r\n <mat-expansion-panel-header>\r\n <mat-panel-title>tween #{{ editedTweenIndex() + 1 }}</mat-panel-title>\r\n </mat-expansion-panel-header>\r\n }\r\n <fieldset>\r\n <gve-animation-tween\r\n [elementIds]=\"elementIds()\"\r\n [tween]=\"editedTween()\"\r\n (tweenChange)=\"saveTween($event)\"\r\n (tweenCancel)=\"closeTween()\"\r\n />\r\n </fieldset>\r\n </mat-expansion-panel>\r\n }\r\n\r\n <!-- buttons -->\r\n <div class=\"button-row\">\r\n <button\r\n type=\"button\"\r\n class=\"mat-warn\"\r\n mat-icon-button\r\n matTooltip=\"Close timeline\"\r\n (click)=\"close()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"submit\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n matTooltip=\"Save timeline\"\r\n [disabled]=\"form.invalid\"\r\n >\r\n <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n timeline\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}.button-row{display:flex;align-items:center;flex-wrap:wrap}.button-row *{flex:0 0 auto}.long-text{width:100%;max-width:800px}table{width:100%;border-collapse:collapse}tbody tr:nth-child(odd){background-color:#e2e2e2}th{text-align:left;font-weight:400;color:silver}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#d0d0d0!important}fieldset{border:1px solid silver;border-radius:6px;padding:8px;margin:8px 0}\n"] }]
|
|
373
|
-
}], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { timeline: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeline", required: false }] }, { type: i0.Output, args: ["timelineChange"] }], elementIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "elementIds", required: false }] }], tags: [{ type: i0.Input, args: [{ isSignal: true, alias: "tags", required: false }] }], timelineChange: [{ type: i0.Output, args: ["timelineChange"] }], timelineCancel: [{ type: i0.Output, args: ["timelineCancel"] }] } });
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* 🔑 `gve-animation-timeline-set`
|
|
377
|
-
*
|
|
378
|
-
* A component to edit a set of animation timelines.
|
|
379
|
-
* Used by the `gve-snapshot-editor` component.
|
|
380
|
-
*
|
|
381
|
-
* - ▶️ `timelines` (`GveAnimationTimeline[]`): the animation timelines to edit.
|
|
382
|
-
* - ▶️ `elementIds` (`string[]`): the IDs of the elements that can be selected by the tween.
|
|
383
|
-
* - ▶️ `tags` (`string[]`): the tags that can be used by the timeline.
|
|
384
|
-
* - 🔥 `timelinesChange` (`GveAnimationTimeline[]`): emitted when the timelines are changed.
|
|
385
|
-
* - 🔥 `timelinesCancel` (`void`): emitted when the timeline editing is canceled.
|
|
386
|
-
*/
|
|
387
|
-
class AnimationTimelineSetComponent {
|
|
388
|
-
constructor(_dialogService) {
|
|
389
|
-
this._dialogService = _dialogService;
|
|
390
|
-
/**
|
|
391
|
-
* The animation timelines to edit.
|
|
392
|
-
*/
|
|
393
|
-
this.timelines = model([], ...(ngDevMode ? [{ debugName: "timelines" }] : []));
|
|
394
|
-
/**
|
|
395
|
-
* The IDs of the elements that can be selected by the tween.
|
|
396
|
-
* This list is used to allow the user to select an element from a dropdown.
|
|
397
|
-
*/
|
|
398
|
-
this.elementIds = input(...(ngDevMode ? [undefined, { debugName: "elementIds" }] : []));
|
|
399
|
-
/**
|
|
400
|
-
* The tags that can be used by the timeline.
|
|
401
|
-
*/
|
|
402
|
-
this.tags = input([], ...(ngDevMode ? [{ debugName: "tags" }] : []));
|
|
403
|
-
/**
|
|
404
|
-
* Emitted when the timeline editing is canceled.
|
|
405
|
-
*/
|
|
406
|
-
this.timelinesCancel = output();
|
|
407
|
-
this.editedTimeline = signal(undefined, ...(ngDevMode ? [{ debugName: "editedTimeline" }] : []));
|
|
408
|
-
this.editedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "editedIndex" }] : []));
|
|
409
|
-
}
|
|
410
|
-
closeTimeline() {
|
|
411
|
-
this.editedTimeline.set(undefined);
|
|
412
|
-
this.editedIndex.set(-1);
|
|
413
|
-
}
|
|
414
|
-
newTimeline() {
|
|
415
|
-
this.editedTimeline.set({
|
|
416
|
-
tag: '',
|
|
417
|
-
tweens: [],
|
|
418
|
-
});
|
|
419
|
-
this.editedIndex.set(-1);
|
|
420
|
-
}
|
|
421
|
-
editTimeline(index) {
|
|
422
|
-
this.editedTimeline.set(deepCopy(this.timelines()[index]));
|
|
423
|
-
this.editedIndex.set(index);
|
|
424
|
-
}
|
|
425
|
-
onTimelineChange(timeline) {
|
|
426
|
-
const timelines = [...this.timelines()];
|
|
427
|
-
if (this.editedIndex() === -1) {
|
|
428
|
-
timelines.push(timeline);
|
|
429
|
-
}
|
|
430
|
-
else {
|
|
431
|
-
timelines.splice(this.editedIndex(), 1, timeline);
|
|
432
|
-
}
|
|
433
|
-
// sort timelines by tag
|
|
434
|
-
timelines.sort((a, b) => a.tag.localeCompare(b.tag));
|
|
435
|
-
this.timelines.set(timelines);
|
|
436
|
-
this.closeTimeline();
|
|
437
|
-
}
|
|
438
|
-
deleteTimeline(index) {
|
|
439
|
-
this._dialogService
|
|
440
|
-
.confirm('Confirm Deletion', `Delete ${this.timelines()[index].tag}?`)
|
|
441
|
-
.subscribe((yes) => {
|
|
442
|
-
if (yes) {
|
|
443
|
-
const timelines = [...this.timelines()];
|
|
444
|
-
timelines.splice(index, 1);
|
|
445
|
-
this.timelines.set(timelines);
|
|
446
|
-
}
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: AnimationTimelineSetComponent, deps: [{ token: i4$1.DialogService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
450
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.10", type: AnimationTimelineSetComponent, isStandalone: true, selector: "gve-animation-timeline-set", inputs: { timelines: { classPropertyName: "timelines", publicName: "timelines", isSignal: true, isRequired: false, transformFunction: null }, elementIds: { classPropertyName: "elementIds", publicName: "elementIds", isSignal: true, isRequired: false, transformFunction: null }, tags: { classPropertyName: "tags", publicName: "tags", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { timelines: "timelinesChange", timelinesCancel: "timelinesCancel" }, ngImport: i0, template: "<div>\r\n <!-- add -->\r\n <div>\r\n <button\r\n type=\"button\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n (click)=\"newTimeline()\"\r\n >\r\n <mat-icon>add_circle</mat-icon>\r\n timeline\r\n </button>\r\n </div>\r\n\r\n <!-- table -->\r\n <table>\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>tag</th>\r\n <th>tweens</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (t of timelines(); track t.tag; let index = $index) {\r\n <tr [class.selected]=\"editedIndex() === index\">\r\n <td class=\"fit-width\">\r\n <!-- edit -->\r\n <button type=\"button\" mat-icon-button (click)=\"editTimeline(index)\">\r\n <mat-icon class=\"mat-primary\">edit</mat-icon>\r\n </button>\r\n <!-- delete -->\r\n <button type=\"button\" mat-icon-button (click)=\"deleteTimeline(index)\">\r\n <mat-icon class=\"mat-warn\">remove_circle</mat-icon>\r\n </button>\r\n </td>\r\n <td>{{ t.tag }}</td>\r\n <td>{{ t.tweens.length }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n\r\n <!-- editor -->\r\n @if (editedTimeline()) {\r\n <mat-expansion-panel [disabled]=\"!editedTimeline\" [expanded]=\"editedTimeline\">\r\n <mat-expansion-panel-header>\r\n timeline {{ editedTimeline()?.tag }}\r\n </mat-expansion-panel-header>\r\n <gve-animation-timeline\r\n [elementIds]=\"elementIds()\"\r\n [tags]=\"tags()\"\r\n [timeline]=\"editedTimeline()\"\r\n (timelineChange)=\"onTimelineChange($event!)\"\r\n (timelineCancel)=\"closeTimeline()\"\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}table{width:100%;border-collapse:collapse}tbody tr:nth-child(odd){background-color:#e2e2e2}th{text-align:left;font-weight:400;color:silver}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#d0d0d0!important}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { 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: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i3$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i3$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { 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: MatSelectModule }, { kind: "ngmodule", type: MatTabsModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "component", type: AnimationTimelineComponent, selector: "gve-animation-timeline", inputs: ["timeline", "elementIds", "tags"], outputs: ["timelineChange", "timelineCancel"] }] }); }
|
|
451
|
-
}
|
|
452
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: AnimationTimelineSetComponent, decorators: [{
|
|
453
|
-
type: Component,
|
|
454
|
-
args: [{ selector: 'gve-animation-timeline-set', imports: [
|
|
455
|
-
ReactiveFormsModule,
|
|
456
|
-
MatButtonModule,
|
|
457
|
-
MatCheckboxModule,
|
|
458
|
-
MatExpansionModule,
|
|
459
|
-
MatFormFieldModule,
|
|
460
|
-
MatIconModule,
|
|
461
|
-
MatInputModule,
|
|
462
|
-
MatSelectModule,
|
|
463
|
-
MatTabsModule,
|
|
464
|
-
MatTooltipModule,
|
|
465
|
-
AnimationTimelineComponent,
|
|
466
|
-
], template: "<div>\r\n <!-- add -->\r\n <div>\r\n <button\r\n type=\"button\"\r\n class=\"mat-primary\"\r\n mat-flat-button\r\n (click)=\"newTimeline()\"\r\n >\r\n <mat-icon>add_circle</mat-icon>\r\n timeline\r\n </button>\r\n </div>\r\n\r\n <!-- table -->\r\n <table>\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>tag</th>\r\n <th>tweens</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (t of timelines(); track t.tag; let index = $index) {\r\n <tr [class.selected]=\"editedIndex() === index\">\r\n <td class=\"fit-width\">\r\n <!-- edit -->\r\n <button type=\"button\" mat-icon-button (click)=\"editTimeline(index)\">\r\n <mat-icon class=\"mat-primary\">edit</mat-icon>\r\n </button>\r\n <!-- delete -->\r\n <button type=\"button\" mat-icon-button (click)=\"deleteTimeline(index)\">\r\n <mat-icon class=\"mat-warn\">remove_circle</mat-icon>\r\n </button>\r\n </td>\r\n <td>{{ t.tag }}</td>\r\n <td>{{ t.tweens.length }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n\r\n <!-- editor -->\r\n @if (editedTimeline()) {\r\n <mat-expansion-panel [disabled]=\"!editedTimeline\" [expanded]=\"editedTimeline\">\r\n <mat-expansion-panel-header>\r\n timeline {{ editedTimeline()?.tag }}\r\n </mat-expansion-panel-header>\r\n <gve-animation-timeline\r\n [elementIds]=\"elementIds()\"\r\n [tags]=\"tags()\"\r\n [timeline]=\"editedTimeline()\"\r\n (timelineChange)=\"onTimelineChange($event!)\"\r\n (timelineCancel)=\"closeTimeline()\"\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}table{width:100%;border-collapse:collapse}tbody tr:nth-child(odd){background-color:#e2e2e2}th{text-align:left;font-weight:400;color:silver}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#d0d0d0!important}\n"] }]
|
|
467
|
-
}], ctorParameters: () => [{ type: i4$1.DialogService }], propDecorators: { timelines: [{ type: i0.Input, args: [{ isSignal: true, alias: "timelines", required: false }] }, { type: i0.Output, args: ["timelinesChange"] }], elementIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "elementIds", required: false }] }], tags: [{ type: i0.Input, args: [{ isSignal: true, alias: "tags", required: false }] }], timelinesCancel: [{ type: i0.Output, args: ["timelinesCancel"] }] } });
|
|
61
|
+
var OperationType;
|
|
62
|
+
(function (OperationType) {
|
|
63
|
+
OperationType[OperationType["replace"] = 0] = "replace";
|
|
64
|
+
OperationType[OperationType["delete"] = 1] = "delete";
|
|
65
|
+
OperationType[OperationType["addBefore"] = 2] = "addBefore";
|
|
66
|
+
OperationType[OperationType["addAfter"] = 3] = "addAfter";
|
|
67
|
+
OperationType[OperationType["moveBefore"] = 4] = "moveBefore";
|
|
68
|
+
OperationType[OperationType["moveAfter"] = 5] = "moveAfter";
|
|
69
|
+
OperationType[OperationType["swap"] = 6] = "swap";
|
|
70
|
+
OperationType[OperationType["annotate"] = 7] = "annotate";
|
|
71
|
+
})(OperationType || (OperationType = {}));
|
|
468
72
|
|
|
469
73
|
/**
|
|
470
74
|
* 🔑 `gve-base-text-char`
|
|
@@ -489,19 +93,21 @@ class BaseTextCharComponent {
|
|
|
489
93
|
onCharClick(event) {
|
|
490
94
|
this.charPick.emit({ char: this.char(), event });
|
|
491
95
|
}
|
|
492
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
493
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
96
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: BaseTextCharComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
97
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", type: BaseTextCharComponent, isStandalone: true, selector: "gve-base-text-char", inputs: { char: { classPropertyName: "char", publicName: "char", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { charPick: "charPick" }, ngImport: i0, template: "@if (char()) {\r\n<div matRipple id=\"container\" (click)=\"onCharClick($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 silver;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 silver;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 }); }
|
|
494
98
|
}
|
|
495
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
99
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: BaseTextCharComponent, decorators: [{
|
|
496
100
|
type: Component,
|
|
497
|
-
args: [{ selector: 'gve-base-text-char', imports: [MatRippleModule, ColorToContrastPipe], template: "@if (char()) {\r\n<div matRipple id=\"container\" (click)=\"onCharClick($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 silver;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 silver;border-radius:6px;padding:6px;align-items:center;justify-content:center;text-align:center}\n"] }]
|
|
101
|
+
args: [{ selector: 'gve-base-text-char', imports: [MatRippleModule, ColorToContrastPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (char()) {\r\n<div matRipple id=\"container\" (click)=\"onCharClick($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 silver;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 silver;border-radius:6px;padding:6px;align-items:center;justify-content:center;text-align:center}\n"] }]
|
|
498
102
|
}], propDecorators: { char: [{ type: i0.Input, args: [{ isSignal: true, alias: "char", required: false }] }], charPick: [{ type: i0.Output, args: ["charPick"] }] } });
|
|
499
103
|
|
|
500
104
|
/**
|
|
501
105
|
* 🔑 `gve-base-text-view`
|
|
502
106
|
*
|
|
503
107
|
* A component to display a selectable base text. Its input is either a string or an
|
|
504
|
-
* array of `CharNode`'s.
|
|
108
|
+
* array of `CharNode`'s. The base text is the starting text in a set of transformations,
|
|
109
|
+
* and is represented as a sequence of characters with optional metadata.
|
|
110
|
+
* This component allows users to view and select characters or ranges of characters.
|
|
505
111
|
* Used by the chain result view component and the base text editor component.
|
|
506
112
|
*
|
|
507
113
|
* - ▶️ `defaultColor` (`string`): the default color for the text.
|
|
@@ -515,10 +121,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
515
121
|
* to each line.
|
|
516
122
|
* - ▶️ `text` (`string` | `CharNode[]`): the text to display.
|
|
517
123
|
* - 🔥 `charPick` (`BaseTextCharEvent`): emitted when a character is picked.
|
|
518
|
-
* - 🔥 `rangePick` (`
|
|
124
|
+
* - 🔥 `rangePick` (`BaseTextRange`): emitted when a range is picked.
|
|
519
125
|
*/
|
|
520
126
|
class BaseTextViewComponent {
|
|
521
127
|
constructor() {
|
|
128
|
+
this._destroyRef = inject(DestroyRef);
|
|
129
|
+
this._clipboard = inject(Clipboard);
|
|
130
|
+
this._snackBar = inject(MatSnackBar);
|
|
131
|
+
this._searchSubject = new Subject();
|
|
522
132
|
/**
|
|
523
133
|
* The default color for the text.
|
|
524
134
|
*/
|
|
@@ -531,10 +141,22 @@ class BaseTextViewComponent {
|
|
|
531
141
|
* The color for the selected text.
|
|
532
142
|
*/
|
|
533
143
|
this.selectionColor = input('#3E92CC', ...(ngDevMode ? [{ debugName: "selectionColor" }] : []));
|
|
144
|
+
/**
|
|
145
|
+
* The color for search match highlights.
|
|
146
|
+
*/
|
|
147
|
+
this.searchHighlightColor = input('#FFD54F', ...(ngDevMode ? [{ debugName: "searchHighlightColor" }] : []));
|
|
534
148
|
/**
|
|
535
149
|
* True if line numbers should be displayed next to each line.
|
|
536
150
|
*/
|
|
537
151
|
this.hasLineNumber = input(false, ...(ngDevMode ? [{ debugName: "hasLineNumber" }] : []));
|
|
152
|
+
/**
|
|
153
|
+
* The search query entered in the toolbar.
|
|
154
|
+
*/
|
|
155
|
+
this.searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : []));
|
|
156
|
+
/**
|
|
157
|
+
* The positions of search matches (start and length pairs).
|
|
158
|
+
*/
|
|
159
|
+
this.searchMatches = signal([], ...(ngDevMode ? [{ debugName: "searchMatches" }] : []));
|
|
538
160
|
/**
|
|
539
161
|
* The text to display.
|
|
540
162
|
*/
|
|
@@ -570,11 +192,32 @@ class BaseTextViewComponent {
|
|
|
570
192
|
*/
|
|
571
193
|
this.selectedRange = signal(null, ...(ngDevMode ? [{ debugName: "selectedRange" }] : []));
|
|
572
194
|
/**
|
|
573
|
-
* The lines with selection
|
|
195
|
+
* The lines with selection or search highlight state applied.
|
|
196
|
+
* Search highlights take priority over selection when present.
|
|
574
197
|
*/
|
|
575
198
|
this.lines = computed(() => {
|
|
576
199
|
const base = this.baseLines();
|
|
577
200
|
const range = this.selectedRange();
|
|
201
|
+
const matches = this.searchMatches();
|
|
202
|
+
// If there are search matches, highlight them instead of selection
|
|
203
|
+
if (matches.length > 0) {
|
|
204
|
+
let position = 0;
|
|
205
|
+
return base.map((line) => line.map((char) => {
|
|
206
|
+
const currentPos = position++;
|
|
207
|
+
const isMatch = matches.some((m) => currentPos >= m.start && currentPos < m.start + m.length);
|
|
208
|
+
if (isMatch) {
|
|
209
|
+
return {
|
|
210
|
+
...char,
|
|
211
|
+
oldColor: char.oldColor || char.color,
|
|
212
|
+
oldBorderColor: char.oldBorderColor || char.borderColor,
|
|
213
|
+
color: this.searchHighlightColor(),
|
|
214
|
+
borderColor: this.searchHighlightColor(),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return char;
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
// Otherwise, apply selection highlight if present
|
|
578
221
|
if (!range) {
|
|
579
222
|
return base;
|
|
580
223
|
}
|
|
@@ -594,11 +237,40 @@ class BaseTextViewComponent {
|
|
|
594
237
|
return char;
|
|
595
238
|
}));
|
|
596
239
|
}, ...(ngDevMode ? [{ debugName: "lines" }] : []));
|
|
240
|
+
/**
|
|
241
|
+
* Check if there is an active selection.
|
|
242
|
+
*/
|
|
243
|
+
this.hasSelection = computed(() => {
|
|
244
|
+
return this.selectedRange() !== null;
|
|
245
|
+
}, ...(ngDevMode ? [{ debugName: "hasSelection" }] : []));
|
|
246
|
+
/**
|
|
247
|
+
* Get the total count of matched characters.
|
|
248
|
+
*/
|
|
249
|
+
this.matchCount = computed(() => {
|
|
250
|
+
const matches = this.searchMatches();
|
|
251
|
+
return matches.reduce((sum, m) => sum + m.length, 0);
|
|
252
|
+
}, ...(ngDevMode ? [{ debugName: "matchCount" }] : []));
|
|
253
|
+
/**
|
|
254
|
+
* Check if a search is active (query is not empty).
|
|
255
|
+
*/
|
|
256
|
+
this.isSearchActive = computed(() => {
|
|
257
|
+
return this.searchQuery().length > 0;
|
|
258
|
+
}, ...(ngDevMode ? [{ debugName: "isSearchActive" }] : []));
|
|
597
259
|
// reset selection when text changes
|
|
598
260
|
effect(() => {
|
|
599
261
|
this.text();
|
|
600
262
|
this._lastSelectedPosition = undefined;
|
|
263
|
+
this._selectionAnchor = undefined;
|
|
601
264
|
this.selectedRange.set(null);
|
|
265
|
+
this.clearSearch();
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
ngOnInit() {
|
|
269
|
+
// Setup debounced search
|
|
270
|
+
this._searchSubject
|
|
271
|
+
.pipe(debounceTime(300), distinctUntilChanged(), takeUntilDestroyed(this._destroyRef))
|
|
272
|
+
.subscribe((query) => {
|
|
273
|
+
this.performSearch(query);
|
|
602
274
|
});
|
|
603
275
|
}
|
|
604
276
|
buildLines(text, colorCallback, borderColorCallback) {
|
|
@@ -658,8 +330,9 @@ class BaseTextViewComponent {
|
|
|
658
330
|
else {
|
|
659
331
|
// single character selection
|
|
660
332
|
this.selectedRange.set({ start: clickedPosition, end: clickedPosition });
|
|
661
|
-
// update last selected character and position
|
|
333
|
+
// update last selected character and position, and set anchor for keyboard navigation
|
|
662
334
|
this._lastSelectedPosition = clickedPosition;
|
|
335
|
+
this._selectionAnchor = clickedPosition;
|
|
663
336
|
rangeToEmit = { at: event.char.id, run: 1 };
|
|
664
337
|
}
|
|
665
338
|
// emit events last, after all internal state is updated
|
|
@@ -700,13 +373,243 @@ class BaseTextViewComponent {
|
|
|
700
373
|
}
|
|
701
374
|
return -1;
|
|
702
375
|
}
|
|
703
|
-
|
|
704
|
-
|
|
376
|
+
/**
|
|
377
|
+
* Handle search input changes and trigger debounced search.
|
|
378
|
+
*/
|
|
379
|
+
onSearchInput(query) {
|
|
380
|
+
this.searchQuery.set(query);
|
|
381
|
+
this._searchSubject.next(query);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Perform the actual search and update match positions.
|
|
385
|
+
*/
|
|
386
|
+
performSearch(query) {
|
|
387
|
+
if (!query || query.length === 0) {
|
|
388
|
+
this.searchMatches.set([]);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const text = this.text();
|
|
392
|
+
if (!text || text.length === 0) {
|
|
393
|
+
this.searchMatches.set([]);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
// Build the full text string from CharNodes
|
|
397
|
+
const fullText = text.map((node) => node.data).join('');
|
|
398
|
+
// Find all matches (case-insensitive)
|
|
399
|
+
const matches = [];
|
|
400
|
+
const lowerQuery = query.toLowerCase();
|
|
401
|
+
const lowerText = fullText.toLowerCase();
|
|
402
|
+
let startIndex = 0;
|
|
403
|
+
while (startIndex < lowerText.length) {
|
|
404
|
+
const index = lowerText.indexOf(lowerQuery, startIndex);
|
|
405
|
+
if (index === -1) {
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
matches.push({ start: index, length: query.length });
|
|
409
|
+
startIndex = index + 1;
|
|
410
|
+
}
|
|
411
|
+
this.searchMatches.set(matches);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Clear the search query and remove search highlights.
|
|
415
|
+
*/
|
|
416
|
+
clearSearch() {
|
|
417
|
+
this.searchQuery.set('');
|
|
418
|
+
this.searchMatches.set([]);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Get the total number of characters in the text.
|
|
422
|
+
*/
|
|
423
|
+
getTotalCharCount() {
|
|
424
|
+
return this.text().length;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Get the character at a specific position.
|
|
428
|
+
*/
|
|
429
|
+
getCharAtPosition(position) {
|
|
430
|
+
const baseLines = this.baseLines();
|
|
431
|
+
let currentPos = 0;
|
|
432
|
+
for (const line of baseLines) {
|
|
433
|
+
for (const char of line) {
|
|
434
|
+
if (currentPos === position) {
|
|
435
|
+
return char;
|
|
436
|
+
}
|
|
437
|
+
currentPos++;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Handle keyboard events for arrow key navigation.
|
|
444
|
+
* - Left/Right: Move single selection to previous/next character
|
|
445
|
+
* - Shift+Left/Right: Extend selection range
|
|
446
|
+
*/
|
|
447
|
+
onKeyDown(event) {
|
|
448
|
+
const range = this.selectedRange();
|
|
449
|
+
if (!range) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const totalChars = this.getTotalCharCount();
|
|
453
|
+
if (totalChars === 0) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
let newStart = range.start;
|
|
457
|
+
let newEnd = range.end;
|
|
458
|
+
let handled = false;
|
|
459
|
+
if (event.key === 'ArrowLeft') {
|
|
460
|
+
if (event.shiftKey) {
|
|
461
|
+
// Shift+Left: extend selection to the left from anchor
|
|
462
|
+
if (this._selectionAnchor === undefined) {
|
|
463
|
+
this._selectionAnchor = range.start;
|
|
464
|
+
}
|
|
465
|
+
// Determine which end to move based on anchor position
|
|
466
|
+
if (range.end === this._selectionAnchor) {
|
|
467
|
+
// Moving start to the left
|
|
468
|
+
newStart = Math.max(0, range.start - 1);
|
|
469
|
+
newEnd = range.end;
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
// Shrinking from right toward anchor, or extending left past anchor
|
|
473
|
+
if (range.end > this._selectionAnchor) {
|
|
474
|
+
// Shrink from right
|
|
475
|
+
newStart = range.start;
|
|
476
|
+
newEnd = range.end - 1;
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
// Extend left
|
|
480
|
+
newStart = Math.max(0, range.start - 1);
|
|
481
|
+
newEnd = range.end;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
handled = true;
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
// Left without shift: move to single selection one position left
|
|
488
|
+
const currentPos = range.start;
|
|
489
|
+
const newPos = Math.max(0, currentPos - 1);
|
|
490
|
+
newStart = newPos;
|
|
491
|
+
newEnd = newPos;
|
|
492
|
+
this._selectionAnchor = newPos;
|
|
493
|
+
this._lastSelectedPosition = newPos;
|
|
494
|
+
handled = true;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
else if (event.key === 'ArrowRight') {
|
|
498
|
+
if (event.shiftKey) {
|
|
499
|
+
// Shift+Right: extend selection to the right from anchor
|
|
500
|
+
if (this._selectionAnchor === undefined) {
|
|
501
|
+
this._selectionAnchor = range.start;
|
|
502
|
+
}
|
|
503
|
+
// Determine which end to move based on anchor position
|
|
504
|
+
if (range.start === this._selectionAnchor) {
|
|
505
|
+
// Moving end to the right
|
|
506
|
+
newStart = range.start;
|
|
507
|
+
newEnd = Math.min(totalChars - 1, range.end + 1);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
// Shrinking from left toward anchor, or extending right past anchor
|
|
511
|
+
if (range.start < this._selectionAnchor) {
|
|
512
|
+
// Shrink from left
|
|
513
|
+
newStart = range.start + 1;
|
|
514
|
+
newEnd = range.end;
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
// Extend right
|
|
518
|
+
newStart = range.start;
|
|
519
|
+
newEnd = Math.min(totalChars - 1, range.end + 1);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
handled = true;
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
// Right without shift: move to single selection one position right
|
|
526
|
+
const currentPos = range.end;
|
|
527
|
+
const newPos = Math.min(totalChars - 1, currentPos + 1);
|
|
528
|
+
newStart = newPos;
|
|
529
|
+
newEnd = newPos;
|
|
530
|
+
this._selectionAnchor = newPos;
|
|
531
|
+
this._lastSelectedPosition = newPos;
|
|
532
|
+
handled = true;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (handled) {
|
|
536
|
+
event.preventDefault();
|
|
537
|
+
this.selectedRange.set({ start: newStart, end: newEnd });
|
|
538
|
+
// Emit rangePick event
|
|
539
|
+
const firstCharId = this.getCharIdAtPosition(newStart);
|
|
540
|
+
const rangeToEmit = {
|
|
541
|
+
at: firstCharId,
|
|
542
|
+
run: newEnd - newStart + 1,
|
|
543
|
+
};
|
|
544
|
+
this.rangePick.emit(rangeToEmit);
|
|
545
|
+
// Emit charPick for single selection
|
|
546
|
+
if (newStart === newEnd) {
|
|
547
|
+
const char = this.getCharAtPosition(newStart);
|
|
548
|
+
if (char) {
|
|
549
|
+
this.charPick.emit({
|
|
550
|
+
char,
|
|
551
|
+
event: null,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Copy the currently selected text to the clipboard.
|
|
559
|
+
*/
|
|
560
|
+
copySelection() {
|
|
561
|
+
const range = this.selectedRange();
|
|
562
|
+
if (!range) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const text = this.text();
|
|
566
|
+
if (!text || text.length === 0) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
// Extract the selected characters
|
|
570
|
+
const selectedChars = text
|
|
571
|
+
.slice(range.start, range.end + 1)
|
|
572
|
+
.map((node) => node.data)
|
|
573
|
+
.join('');
|
|
574
|
+
this._clipboard.copy(selectedChars);
|
|
575
|
+
this._snackBar.open('Text copied', 'OK', { duration: 2000 });
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Copy the currently selected range coordinates to the clipboard.
|
|
579
|
+
* Format: "ID" for single character, "IDxCOUNT" for range.
|
|
580
|
+
*/
|
|
581
|
+
copyCoordinates() {
|
|
582
|
+
const range = this.selectedRange();
|
|
583
|
+
if (!range) {
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
const text = this.text();
|
|
587
|
+
if (!text || text.length === 0) {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const startId = text[range.start].id;
|
|
591
|
+
const count = range.end - range.start + 1;
|
|
592
|
+
const coords = count === 1 ? `${startId}` : `${startId}x${count}`;
|
|
593
|
+
this._clipboard.copy(coords);
|
|
594
|
+
this._snackBar.open(`Coordinates copied: ${coords}`, 'OK', {
|
|
595
|
+
duration: 2000,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: BaseTextViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
599
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", 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</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)\" />\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;background-color:#e8f5e9;color:#2e7d32}.match-count.no-matches{background-color:#ffebee;color:#c62828}.line{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.line *{flex:0 0 auto}.nr{font-size:.8em;font-weight:700;color:silver;margin:0 4px}\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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
705
600
|
}
|
|
706
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
601
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: BaseTextViewComponent, decorators: [{
|
|
707
602
|
type: Component,
|
|
708
|
-
args: [{ selector: 'gve-base-text-view', imports: [
|
|
709
|
-
|
|
603
|
+
args: [{ selector: 'gve-base-text-view', imports: [
|
|
604
|
+
FormsModule,
|
|
605
|
+
MatButtonModule,
|
|
606
|
+
MatFormFieldModule,
|
|
607
|
+
MatIconModule,
|
|
608
|
+
MatInputModule,
|
|
609
|
+
MatTooltipModule,
|
|
610
|
+
BaseTextCharComponent,
|
|
611
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, 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</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)\" />\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;background-color:#e8f5e9;color:#2e7d32}.match-count.no-matches{background-color:#ffebee;color:#c62828}.line{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.line *{flex:0 0 auto}.nr{font-size:.8em;font-weight:700;color:silver;margin:0 4px}\n"] }]
|
|
612
|
+
}], ctorParameters: () => [], propDecorators: { defaultColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultColor", required: false }] }], defaultBorderColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultBorderColor", required: false }] }], selectionColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionColor", required: false }] }], searchHighlightColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchHighlightColor", required: false }] }], hasLineNumber: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasLineNumber", required: false }] }], text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }], colorCallback: [{ type: i0.Input, args: [{ isSignal: true, alias: "colorCallback", required: false }] }], borderColorCallback: [{ type: i0.Input, args: [{ isSignal: true, alias: "borderColorCallback", required: false }] }], charPick: [{ type: i0.Output, args: ["charPick"] }], rangePick: [{ type: i0.Output, args: ["rangePick"] }] } });
|
|
710
613
|
|
|
711
614
|
/**
|
|
712
615
|
* 🔑 `gve-feature-editor`
|
|
@@ -724,6 +627,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
724
627
|
* - ▶️ `featValues` (`FeatureMap`): the feature values map. When
|
|
725
628
|
* specified and the user selects a feature name present in the map keys,
|
|
726
629
|
* the corresponding values will be used to populate the value selection.
|
|
630
|
+
* - ▶️ `multiValuedFeatureIds` (`string[]`): the IDs of the features that are
|
|
631
|
+
* multi-valued. Used to determine if the current feature being edited should
|
|
632
|
+
* display multi-value controls.
|
|
633
|
+
* - ▶️ `isVar`: true if the feature is a variant operation feature, which
|
|
634
|
+
* has additional properties like negation, global, and short-lived.
|
|
727
635
|
* - 🔥 `featureChange` (`Feature`): emitted when feature has changed.
|
|
728
636
|
* - 🔥 `featureCancel`: emitted when the user cancels the feature editing.
|
|
729
637
|
*/
|
|
@@ -749,11 +657,17 @@ class FeatureEditorComponent {
|
|
|
749
657
|
* additional properties like negation, global, and short-lived.
|
|
750
658
|
*/
|
|
751
659
|
this.isVar = input(false, ...(ngDevMode ? [{ debugName: "isVar" }] : []));
|
|
660
|
+
/**
|
|
661
|
+
* The IDs of the features that are multi-valued. Used to determine
|
|
662
|
+
* if the current feature being edited should display multi-value controls.
|
|
663
|
+
*/
|
|
664
|
+
this.multiValuedFeatureIds = input(...(ngDevMode ? [undefined, { debugName: "multiValuedFeatureIds" }] : []));
|
|
752
665
|
/**
|
|
753
666
|
* Event emitted when the user cancels the feature editing.
|
|
754
667
|
*/
|
|
755
668
|
this.featureCancel = output();
|
|
756
669
|
this.nameIds = signal(undefined, ...(ngDevMode ? [{ debugName: "nameIds" }] : []));
|
|
670
|
+
this.isMultiValued = signal(false, ...(ngDevMode ? [{ debugName: "isMultiValued" }] : []));
|
|
757
671
|
this.name = formBuilder.control('', {
|
|
758
672
|
validators: [Validators.required, Validators.maxLength(50)],
|
|
759
673
|
nonNullable: true,
|
|
@@ -762,6 +676,9 @@ class FeatureEditorComponent {
|
|
|
762
676
|
validators: [Validators.maxLength(5000)],
|
|
763
677
|
nonNullable: true,
|
|
764
678
|
});
|
|
679
|
+
this.selectedValue = formBuilder.control('', {
|
|
680
|
+
nonNullable: true,
|
|
681
|
+
});
|
|
765
682
|
this.setPolicy = formBuilder.control(FeatureSetPolicy.multiple, {
|
|
766
683
|
nonNullable: true,
|
|
767
684
|
});
|
|
@@ -771,6 +688,7 @@ class FeatureEditorComponent {
|
|
|
771
688
|
this.form = formBuilder.group({
|
|
772
689
|
name: this.name,
|
|
773
690
|
value: this.value,
|
|
691
|
+
selectedValue: this.selectedValue,
|
|
774
692
|
setPolicy: this.setPolicy,
|
|
775
693
|
isNegated: this.isNegated,
|
|
776
694
|
isGlobal: this.isGlobal,
|
|
@@ -786,7 +704,7 @@ class FeatureEditorComponent {
|
|
|
786
704
|
});
|
|
787
705
|
}
|
|
788
706
|
ngOnInit() {
|
|
789
|
-
// whenever the name changes, update the value selection
|
|
707
|
+
// whenever the name changes, update the value selection and multi-valued status
|
|
790
708
|
this._sub = this.name.valueChanges
|
|
791
709
|
.pipe(debounceTime(300), distinctUntilChanged())
|
|
792
710
|
.subscribe((name) => {
|
|
@@ -794,6 +712,9 @@ class FeatureEditorComponent {
|
|
|
794
712
|
this.value.reset();
|
|
795
713
|
this.nameIds.set(this.getLabeledIdsFor(name, this.featValues()));
|
|
796
714
|
}
|
|
715
|
+
// Update multi-valued status
|
|
716
|
+
const ids = this.multiValuedFeatureIds();
|
|
717
|
+
this.isMultiValued.set(!!ids && ids.includes(name));
|
|
797
718
|
});
|
|
798
719
|
}
|
|
799
720
|
ngOnDestroy() {
|
|
@@ -809,6 +730,7 @@ class FeatureEditorComponent {
|
|
|
809
730
|
updateForm(feature) {
|
|
810
731
|
if (!feature) {
|
|
811
732
|
this.form.reset();
|
|
733
|
+
this.isMultiValued.set(false);
|
|
812
734
|
}
|
|
813
735
|
else {
|
|
814
736
|
this._frozen = true;
|
|
@@ -818,9 +740,27 @@ class FeatureEditorComponent {
|
|
|
818
740
|
this.isNegated.setValue(feature.isNegated || false);
|
|
819
741
|
this.isGlobal.setValue(feature.isGlobal || false);
|
|
820
742
|
this.isShortLived.setValue(feature.isShortLived || false);
|
|
743
|
+
// Set multi-valued status based on feature name
|
|
744
|
+
const ids = this.multiValuedFeatureIds();
|
|
745
|
+
this.isMultiValued.set(!!ids && ids.includes(feature.name));
|
|
821
746
|
this._frozen = false;
|
|
822
747
|
}
|
|
823
748
|
}
|
|
749
|
+
addValueToComposite() {
|
|
750
|
+
if (!this.selectedValue.value) {
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
const currentValue = this.value.value.trim();
|
|
754
|
+
const newValue = this.selectedValue.value.trim();
|
|
755
|
+
if (currentValue) {
|
|
756
|
+
this.value.setValue(currentValue + ' ' + newValue);
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
this.value.setValue(newValue);
|
|
760
|
+
}
|
|
761
|
+
this.selectedValue.reset();
|
|
762
|
+
this.value.markAsDirty();
|
|
763
|
+
}
|
|
824
764
|
cancel() {
|
|
825
765
|
this.featureCancel.emit();
|
|
826
766
|
}
|
|
@@ -843,12 +783,12 @@ class FeatureEditorComponent {
|
|
|
843
783
|
setPolicy: this.setPolicy.value,
|
|
844
784
|
});
|
|
845
785
|
}
|
|
846
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
847
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
786
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: FeatureEditorComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
787
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", 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 }], 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 </div>\r\n\r\n @if (isVar()) {\r\n <div class=\"form-row\">\r\n <!-- isNegated -->\r\n <mat-checkbox [formControl]=\"isNegated\">negated</mat-checkbox>\r\n\r\n <!-- isShortLived -->\r\n <mat-checkbox [formControl]=\"isShortLived\">short-lived</mat-checkbox>\r\n\r\n <!-- isGlobal -->\r\n <mat-checkbox [formControl]=\"isGlobal\">global</mat-checkbox>\r\n </div>\r\n }\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 }); }
|
|
848
788
|
}
|
|
849
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
789
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: FeatureEditorComponent, decorators: [{
|
|
850
790
|
type: Component,
|
|
851
|
-
args: [{ selector: 'gve-feature-editor', imports: [
|
|
791
|
+
args: [{ selector: 'gve-feature-editor', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
852
792
|
ReactiveFormsModule,
|
|
853
793
|
MatButtonModule,
|
|
854
794
|
MatCheckboxModule,
|
|
@@ -857,11 +797,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
857
797
|
MatInputModule,
|
|
858
798
|
MatSelectModule,
|
|
859
799
|
MatTooltipModule,
|
|
860
|
-
], template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <div class=\"form-row\">\r\n @if (featNames()?.length) {\r\n
|
|
861
|
-
}], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { nameControl: [{
|
|
800
|
+
], 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 </div>\r\n\r\n @if (isVar()) {\r\n <div class=\"form-row\">\r\n <!-- isNegated -->\r\n <mat-checkbox [formControl]=\"isNegated\">negated</mat-checkbox>\r\n\r\n <!-- isShortLived -->\r\n <mat-checkbox [formControl]=\"isShortLived\">short-lived</mat-checkbox>\r\n\r\n <!-- isGlobal -->\r\n <mat-checkbox [formControl]=\"isGlobal\">global</mat-checkbox>\r\n </div>\r\n }\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"] }]
|
|
801
|
+
}], ctorParameters: () => [{ type: i1$1.FormBuilder }], propDecorators: { nameControl: [{
|
|
862
802
|
type: ViewChild,
|
|
863
803
|
args: ['nameCtl']
|
|
864
|
-
}], featNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "featNames", required: false }] }], featValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "featValues", required: false }] }], feature: [{ type: i0.Input, args: [{ isSignal: true, alias: "feature", required: false }] }, { type: i0.Output, args: ["featureChange"] }], isVar: [{ type: i0.Input, args: [{ isSignal: true, alias: "isVar", required: false }] }], featureCancel: [{ type: i0.Output, args: ["featureCancel"] }] } });
|
|
804
|
+
}], featNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "featNames", required: false }] }], featValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "featValues", required: false }] }], feature: [{ type: i0.Input, args: [{ isSignal: true, alias: "feature", required: false }] }, { type: i0.Output, args: ["featureChange"] }], isVar: [{ type: i0.Input, args: [{ isSignal: true, alias: "isVar", required: false }] }], multiValuedFeatureIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiValuedFeatureIds", required: false }] }], featureCancel: [{ type: i0.Output, args: ["featureCancel"] }] } });
|
|
865
805
|
|
|
866
806
|
/**
|
|
867
807
|
* 🔑 `gve-feature-set-editor`
|
|
@@ -881,6 +821,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
881
821
|
* it is always invisible; otherwise, it gets visible when the number of features
|
|
882
822
|
* is equal to or greater than the threshold. Default is 5.
|
|
883
823
|
* - ▶️ `features` (`Feature[]`): the features to edit.
|
|
824
|
+
* - ▶️ `multiValuedFeatureIds` (`string[]`): the IDs of the features that are
|
|
825
|
+
* multi-valued. If a feature being edited is in this list, the feature editor
|
|
826
|
+
* will allow adding multiple values to it.
|
|
884
827
|
* - ▶️ `isVar`: true if the feature is a variant operation feature, which
|
|
885
828
|
* has additional properties like negation, global, and short-lived.
|
|
886
829
|
* - 🔥 `featuresChange` (`Feature[]`): emitted when features have changed.
|
|
@@ -911,6 +854,12 @@ class FeatureSetEditorComponent {
|
|
|
911
854
|
* is greater than or equal to the threshold. Default is 5.
|
|
912
855
|
*/
|
|
913
856
|
this.filterThreshold = input(5, ...(ngDevMode ? [{ debugName: "filterThreshold" }] : []));
|
|
857
|
+
/**
|
|
858
|
+
* The IDs of the features that are multi-valued. If a feature being
|
|
859
|
+
* edited is in this list, the feature editor will allow adding multiple
|
|
860
|
+
* values to it.
|
|
861
|
+
*/
|
|
862
|
+
this.multiValuedFeatureIds = input(...(ngDevMode ? [undefined, { debugName: "multiValuedFeatureIds" }] : []));
|
|
914
863
|
/**
|
|
915
864
|
* The features to edit.
|
|
916
865
|
*/
|
|
@@ -949,7 +898,7 @@ class FeatureSetEditorComponent {
|
|
|
949
898
|
this.editedFeatureIndex.set(-1);
|
|
950
899
|
}
|
|
951
900
|
editFeature(feature) {
|
|
952
|
-
this.editedFeature.set(
|
|
901
|
+
this.editedFeature.set(structuredClone(feature));
|
|
953
902
|
this.editedFeatureIndex.set(this.features().indexOf(feature));
|
|
954
903
|
}
|
|
955
904
|
deleteFeature(feature) {
|
|
@@ -961,6 +910,12 @@ class FeatureSetEditorComponent {
|
|
|
961
910
|
features.splice(index, 1);
|
|
962
911
|
this.features.set(features);
|
|
963
912
|
}
|
|
913
|
+
isFeatureMultiValued(feature) {
|
|
914
|
+
if (!feature || !this.multiValuedFeatureIds()) {
|
|
915
|
+
return false;
|
|
916
|
+
}
|
|
917
|
+
return this.multiValuedFeatureIds().includes(feature.name);
|
|
918
|
+
}
|
|
964
919
|
onFeatureChange(feature) {
|
|
965
920
|
if (!feature) {
|
|
966
921
|
return;
|
|
@@ -987,12 +942,12 @@ class FeatureSetEditorComponent {
|
|
|
987
942
|
this.editedFeature.set(undefined);
|
|
988
943
|
this.editedFeatureIndex.set(-1);
|
|
989
944
|
}
|
|
990
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
991
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
945
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: FeatureSetEditorComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
946
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", 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 <input matInput placeholder=\"filter\" [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{width:100%;border-collapse:collapse}tbody tr:nth-child(odd){background-color:#e2e2e2}th{text-align:left;font-weight:400;color:silver}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#d0d0d0!important}\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 }); }
|
|
992
947
|
}
|
|
993
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
948
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: FeatureSetEditorComponent, decorators: [{
|
|
994
949
|
type: Component,
|
|
995
|
-
args: [{ selector: 'gve-feature-set-editor', imports: [
|
|
950
|
+
args: [{ selector: 'gve-feature-set-editor', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
996
951
|
ReactiveFormsModule,
|
|
997
952
|
MatButtonModule,
|
|
998
953
|
MatExpansionModule,
|
|
@@ -1003,8 +958,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
1003
958
|
MatTooltipModule,
|
|
1004
959
|
FlatLookupPipe,
|
|
1005
960
|
FeatureEditorComponent,
|
|
1006
|
-
], template: "<div>\r\n <div class=\"form-row\">\r\n <!-- filter -->\r\n @if (filterThreshold() === 0 || features().length >= filterThreshold()) {\r\n
|
|
1007
|
-
}], ctorParameters: () => [{ type: i1.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 }] }], features: [{ type: i0.Input, args: [{ isSignal: true, alias: "features", required: false }] }, { type: i0.Output, args: ["featuresChange"] }] } });
|
|
961
|
+
], 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 <input matInput placeholder=\"filter\" [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{width:100%;border-collapse:collapse}tbody tr:nth-child(odd){background-color:#e2e2e2}th{text-align:left;font-weight:400;color:silver}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#d0d0d0!important}\n"] }]
|
|
962
|
+
}], 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"] }] } });
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Service for base text operations.
|
|
966
|
+
*/
|
|
967
|
+
class GveBaseTextService {
|
|
968
|
+
static { this._asciiTable = {
|
|
969
|
+
'\u0007': '\u21B5', // BEL (bell)
|
|
970
|
+
'\u0008': '\u21B6', // BS (backspace)
|
|
971
|
+
'\u0009': '\u2192', // HT (horizontal tab)
|
|
972
|
+
'\u000A': '\u2193', // LF (line feed)
|
|
973
|
+
'\u000B': '\u2194', // VT (vertical tab)
|
|
974
|
+
'\u000C': '\u2195', // FF (form feed)
|
|
975
|
+
'\u000D': '\u21A6', // CR (carriage return)
|
|
976
|
+
'\u0020': ' ', // Space
|
|
977
|
+
'\u007F': '~', // DEL (delete)
|
|
978
|
+
}; }
|
|
979
|
+
/**
|
|
980
|
+
* Translate an ASCII special character to a printable character.
|
|
981
|
+
*
|
|
982
|
+
* @param c The character to translate.
|
|
983
|
+
* @returns The translated character.
|
|
984
|
+
*/
|
|
985
|
+
static translateSpecialChar(c) {
|
|
986
|
+
return this._asciiTable[c] || c;
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Convert a string to the character nodes of a base text.
|
|
990
|
+
*
|
|
991
|
+
* @param text The text to convert to base characters.
|
|
992
|
+
* @returns Character nodes for the given text.
|
|
993
|
+
*/
|
|
994
|
+
static stringToBaseChars(text) {
|
|
995
|
+
if (!text) {
|
|
996
|
+
return [];
|
|
997
|
+
}
|
|
998
|
+
return text.split('').map((c, i) => {
|
|
999
|
+
return {
|
|
1000
|
+
id: i + 1,
|
|
1001
|
+
label: this.translateSpecialChar(c),
|
|
1002
|
+
index: i,
|
|
1003
|
+
data: c,
|
|
1004
|
+
};
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
1008
|
|
|
1009
1009
|
/**
|
|
1010
1010
|
* 🔑 `gve-base-text-editor`
|
|
@@ -1031,7 +1031,7 @@ class BaseTextEditorComponent {
|
|
|
1031
1031
|
return value;
|
|
1032
1032
|
}
|
|
1033
1033
|
if (typeof value === 'string') {
|
|
1034
|
-
return
|
|
1034
|
+
return GveBaseTextService.stringToBaseChars(value);
|
|
1035
1035
|
}
|
|
1036
1036
|
return undefined;
|
|
1037
1037
|
} }] : [{
|
|
@@ -1043,11 +1043,17 @@ class BaseTextEditorComponent {
|
|
|
1043
1043
|
return value;
|
|
1044
1044
|
}
|
|
1045
1045
|
if (typeof value === 'string') {
|
|
1046
|
-
return
|
|
1046
|
+
return GveBaseTextService.stringToBaseChars(value);
|
|
1047
1047
|
}
|
|
1048
1048
|
return undefined;
|
|
1049
1049
|
},
|
|
1050
1050
|
}]));
|
|
1051
|
+
/**
|
|
1052
|
+
* The IDs of the features that are multi-valued. If a feature being
|
|
1053
|
+
* edited is in this list, the feature editor will allow adding multiple
|
|
1054
|
+
* values to it. Passed down to feature editors.
|
|
1055
|
+
*/
|
|
1056
|
+
this.multiValuedFeatureIds = input(...(ngDevMode ? [undefined, { debugName: "multiValuedFeatureIds" }] : []));
|
|
1051
1057
|
/**
|
|
1052
1058
|
* Emitted for the edited text as an array of `CharNode`'s whenever it changes.
|
|
1053
1059
|
*/
|
|
@@ -1096,22 +1102,19 @@ class BaseTextEditorComponent {
|
|
|
1096
1102
|
onRangePick(range) {
|
|
1097
1103
|
this.textRange.set(range);
|
|
1098
1104
|
}
|
|
1099
|
-
patchTextFromUser() {
|
|
1100
|
-
// TODO
|
|
1101
|
-
}
|
|
1102
1105
|
setTextFromUser() {
|
|
1103
1106
|
this._dialogService
|
|
1104
1107
|
.confirm('Confirm', 'Reset text?')
|
|
1105
1108
|
.subscribe((yes) => {
|
|
1106
1109
|
if (yes) {
|
|
1107
|
-
this.textChange.emit(
|
|
1110
|
+
this.textChange.emit(GveBaseTextService.stringToBaseChars(this.userText.value));
|
|
1108
1111
|
}
|
|
1109
1112
|
});
|
|
1110
1113
|
}
|
|
1111
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1112
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
1114
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: BaseTextEditorComponent, deps: [{ token: i1$1.FormBuilder }, { token: i4$1.DialogService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1115
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", 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{border:1px solid #ccc;padding:10px;margin:10px 0;border-radius:5px}legend{color:silver}.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"] }] }); }
|
|
1113
1116
|
}
|
|
1114
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1117
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: BaseTextEditorComponent, decorators: [{
|
|
1115
1118
|
type: Component,
|
|
1116
1119
|
args: [{ selector: 'gve-base-text-editor', imports: [
|
|
1117
1120
|
ReactiveFormsModule,
|
|
@@ -1122,8 +1125,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
1122
1125
|
MatTooltipModule,
|
|
1123
1126
|
BaseTextViewComponent,
|
|
1124
1127
|
FeatureSetEditorComponent,
|
|
1125
|
-
], 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
|
|
1126
|
-
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i4$1.DialogService }], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: true }] }], textChange: [{ type: i0.Output, args: ["textChange"] }] } });
|
|
1128
|
+
], 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{border:1px solid #ccc;padding:10px;margin:10px 0;border-radius:5px}legend{color:silver}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"] }]
|
|
1129
|
+
}], 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"] }] } });
|
|
1127
1130
|
|
|
1128
1131
|
/**
|
|
1129
1132
|
* Service to interact with the GVE API.
|
|
@@ -1192,10 +1195,10 @@ class GveApiService {
|
|
|
1192
1195
|
})
|
|
1193
1196
|
.pipe(catchError(this._error.handleError));
|
|
1194
1197
|
}
|
|
1195
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1196
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
1198
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: GveApiService, deps: [{ token: i1$2.HttpClient }, { token: i2$1.ErrorService }, { token: i2$1.EnvService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1199
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: GveApiService, providedIn: 'root' }); }
|
|
1197
1200
|
}
|
|
1198
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1201
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: GveApiService, decorators: [{
|
|
1199
1202
|
type: Injectable,
|
|
1200
1203
|
args: [{
|
|
1201
1204
|
providedIn: 'root',
|
|
@@ -1273,10 +1276,10 @@ class BatchOperationEditorComponent {
|
|
|
1273
1276
|
close() {
|
|
1274
1277
|
this.dialogRef?.close();
|
|
1275
1278
|
}
|
|
1276
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1277
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
1279
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", 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 }); }
|
|
1280
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", 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\">\r\n <h2>Add Operations</h2>\r\n </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>>[</strong>TO</code>\r\n </li>\r\n <li>\r\n <strong>move-after</strong>:\r\n <code>ATxRUN<strong>>]</strong>TO</code>\r\n </li>\r\n <li>\r\n <strong>swap</strong>:\r\n <code>ATxRUN<strong><></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>@</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:red}.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}div#batch-help strong{color:#ff8c00}\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"] }] }); }
|
|
1278
1281
|
}
|
|
1279
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1282
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: BatchOperationEditorComponent, decorators: [{
|
|
1280
1283
|
type: Component,
|
|
1281
1284
|
args: [{ selector: 'gve-batch-operation-editor', imports: [
|
|
1282
1285
|
ReactiveFormsModule,
|
|
@@ -1285,7 +1288,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
1285
1288
|
MatIconModule,
|
|
1286
1289
|
MatInputModule,
|
|
1287
1290
|
], template: "<div [style.padding]=\"dialogRef ? '8px' : '0'\">\r\n @if (dialogRef) {\r\n <div id=\"heading\">\r\n <h2>Add Operations</h2>\r\n </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>>[</strong>TO</code>\r\n </li>\r\n <li>\r\n <strong>move-after</strong>:\r\n <code>ATxRUN<strong>>]</strong>TO</code>\r\n </li>\r\n <li>\r\n <strong>swap</strong>:\r\n <code>ATxRUN<strong><></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>@</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:red}.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}div#batch-help strong{color:#ff8c00}\n"] }]
|
|
1288
|
-
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: GveApiService }, { type: i3$
|
|
1291
|
+
}], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: GveApiService }, { type: i3$2.MatDialogRef, decorators: [{
|
|
1289
1292
|
type: Optional
|
|
1290
1293
|
}] }, { type: undefined, decorators: [{
|
|
1291
1294
|
type: Optional
|
|
@@ -1378,12 +1381,12 @@ class OperationSourceEditorComponent {
|
|
|
1378
1381
|
note: this.note.value || undefined,
|
|
1379
1382
|
});
|
|
1380
1383
|
}
|
|
1381
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1382
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
1384
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: OperationSourceEditorComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1385
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", 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 || 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: [".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}th{color:#909090;font-weight:400;text-align:left;background-color:#e1e0e0}th,td{padding:4px;border-bottom:1px solid silver}tbody tr:nth-child(2n){background-color:#e8e8e8}td.fit-width{width:1px;white-space:nowrap}\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 }); }
|
|
1383
1386
|
}
|
|
1384
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1387
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: OperationSourceEditorComponent, decorators: [{
|
|
1385
1388
|
type: Component,
|
|
1386
|
-
args: [{ selector: 'gve-operation-source-editor', imports: [
|
|
1389
|
+
args: [{ selector: 'gve-operation-source-editor', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
1387
1390
|
ReactiveFormsModule,
|
|
1388
1391
|
MatButtonModule,
|
|
1389
1392
|
MatFormFieldModule,
|
|
@@ -1392,7 +1395,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
1392
1395
|
MatSelectModule,
|
|
1393
1396
|
MatTooltipModule
|
|
1394
1397
|
], 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 || 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: [".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}th{color:#909090;font-weight:400;text-align:left;background-color:#e1e0e0}th,td{padding:4px;border-bottom:1px solid silver}tbody tr:nth-child(2n){background-color:#e8e8e8}td.fit-width{width:1px;white-space:nowrap}\n"] }]
|
|
1395
|
-
}], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }, { type: i0.Output, args: ["sourceChange"] }], ids: [{ type: i0.Input, args: [{ isSignal: true, alias: "ids", required: false }] }], types: [{ type: i0.Input, args: [{ isSignal: true, alias: "types", required: false }] }], sourceCancel: [{ type: i0.Output, args: ["sourceCancel"] }] } });
|
|
1398
|
+
}], ctorParameters: () => [{ type: i1$1.FormBuilder }], propDecorators: { source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }, { type: i0.Output, args: ["sourceChange"] }], ids: [{ type: i0.Input, args: [{ isSignal: true, alias: "ids", required: false }] }], types: [{ type: i0.Input, args: [{ isSignal: true, alias: "types", required: false }] }], sourceCancel: [{ type: i0.Output, args: ["sourceCancel"] }] } });
|
|
1396
1399
|
|
|
1397
1400
|
/**
|
|
1398
1401
|
* Validators for SVG.
|
|
@@ -1610,10 +1613,10 @@ class SettingsService {
|
|
|
1610
1613
|
// for when the cache was not in sync with the local storage
|
|
1611
1614
|
removedKeys.forEach((key) => this._subject.next(key));
|
|
1612
1615
|
}
|
|
1613
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1614
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
1616
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: SettingsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1617
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: SettingsService, providedIn: 'root' }); }
|
|
1615
1618
|
}
|
|
1616
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1619
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: SettingsService, decorators: [{
|
|
1617
1620
|
type: Injectable,
|
|
1618
1621
|
args: [{
|
|
1619
1622
|
providedIn: 'root',
|
|
@@ -1626,7 +1629,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
1626
1629
|
* A component for editing a variant generation operation.
|
|
1627
1630
|
* Used by the `gve-snapshot-editor` component.
|
|
1628
1631
|
* - ▶️ `operation` (`CharChainOperation`): the operation to edit.
|
|
1629
|
-
* - ▶️ `snapshot` (`
|
|
1632
|
+
* - ▶️ `snapshot` (`Snapshot`): the snapshot the operation refers to.
|
|
1630
1633
|
* - ▶️ `hidePreview` (`boolean`): whether to hide the preview request button.
|
|
1631
1634
|
* - 🔥 `operationChange` (`CharChainOperation`): emitted when the operation
|
|
1632
1635
|
* is changed.
|
|
@@ -1675,8 +1678,6 @@ class ChainOperationEditorComponent {
|
|
|
1675
1678
|
this._clipboard = _clipboard;
|
|
1676
1679
|
this._settings = _settings;
|
|
1677
1680
|
this._dialogService = _dialogService;
|
|
1678
|
-
// monaco
|
|
1679
|
-
this._disposables = [];
|
|
1680
1681
|
this._nanoid = customAlphabet('1234567890abcdef', 10);
|
|
1681
1682
|
/**
|
|
1682
1683
|
* The operation to edit.
|
|
@@ -1694,18 +1695,16 @@ class ChainOperationEditorComponent {
|
|
|
1694
1695
|
* Definitions for features, including names and values.
|
|
1695
1696
|
*/
|
|
1696
1697
|
this.featureDefs = input(...(ngDevMode ? [undefined, { debugName: "featureDefs" }] : []));
|
|
1697
|
-
/**
|
|
1698
|
-
* Definitions for element features, including names and values.
|
|
1699
|
-
*/
|
|
1700
|
-
this.elementFeatureDefs = input(...(ngDevMode ? [undefined, { debugName: "elementFeatureDefs" }] : []));
|
|
1701
|
-
/**
|
|
1702
|
-
* Definitions for diplomatic features, including names and values.
|
|
1703
|
-
*/
|
|
1704
|
-
this.diplomaticFeatureDefs = input(...(ngDevMode ? [undefined, { debugName: "diplomaticFeatureDefs" }] : []));
|
|
1705
1698
|
/**
|
|
1706
1699
|
* Set when the edited operation's text range is to be patched.
|
|
1707
1700
|
*/
|
|
1708
1701
|
this.rangePatch = input(...(ngDevMode ? [undefined, { debugName: "rangePatch" }] : []));
|
|
1702
|
+
/**
|
|
1703
|
+
* The IDs of the features that are multi-valued. If a feature being
|
|
1704
|
+
* edited is in this list, the feature editor will allow adding multiple
|
|
1705
|
+
* values to it. Passed down to feature editors.
|
|
1706
|
+
*/
|
|
1707
|
+
this.multiValuedFeatureIds = input(...(ngDevMode ? [undefined, { debugName: "multiValuedFeatureIds" }] : []));
|
|
1709
1708
|
/**
|
|
1710
1709
|
* Emitted when the operation is changed.
|
|
1711
1710
|
*/
|
|
@@ -1791,15 +1790,6 @@ class ChainOperationEditorComponent {
|
|
|
1791
1790
|
effect(() => {
|
|
1792
1791
|
this.updateForm(this.operation());
|
|
1793
1792
|
});
|
|
1794
|
-
// when snapshot changes, update SVG
|
|
1795
|
-
effect(() => {
|
|
1796
|
-
const snapshot = this.snapshot();
|
|
1797
|
-
const dirty = this.hasTextChanges(snapshot || undefined);
|
|
1798
|
-
if (dirty) {
|
|
1799
|
-
this.requestPreview();
|
|
1800
|
-
this._editorModel?.setValue(this.svg.value || '');
|
|
1801
|
-
}
|
|
1802
|
-
});
|
|
1803
1793
|
// when rangePatch changes, patch operation range (at and run,
|
|
1804
1794
|
// resetting atAsIndex to false)
|
|
1805
1795
|
effect(() => {
|
|
@@ -1829,7 +1819,6 @@ class ChainOperationEditorComponent {
|
|
|
1829
1819
|
this._subs.push(this.type.valueChanges.subscribe(() => this.updateArgsUI()));
|
|
1830
1820
|
}
|
|
1831
1821
|
ngOnDestroy() {
|
|
1832
|
-
this._disposables.forEach((d) => d.dispose());
|
|
1833
1822
|
for (const sub of this._subs) {
|
|
1834
1823
|
sub.unsubscribe();
|
|
1835
1824
|
}
|
|
@@ -1843,51 +1832,11 @@ class ChainOperationEditorComponent {
|
|
|
1843
1832
|
if ((!snapshot && this.snapshot()) || (snapshot && !this.snapshot())) {
|
|
1844
1833
|
return true;
|
|
1845
1834
|
}
|
|
1846
|
-
if (snapshot?.
|
|
1847
|
-
snapshot?.size?.height !== this.snapshot()?.size?.height) {
|
|
1848
|
-
return true;
|
|
1849
|
-
}
|
|
1850
|
-
if (snapshot?.style !== this.snapshot()?.style ||
|
|
1851
|
-
snapshot?.text !== this.snapshot()?.text ||
|
|
1852
|
-
snapshot?.textStyle !== this.snapshot()?.textStyle) {
|
|
1853
|
-
return true;
|
|
1854
|
-
}
|
|
1855
|
-
// compare textOptions returning true if any different
|
|
1856
|
-
const options = snapshot?.textOptions;
|
|
1857
|
-
if (options?.lineHeightOffset !==
|
|
1858
|
-
this.snapshot()?.textOptions?.lineHeightOffset ||
|
|
1859
|
-
options?.charSpacingOffset !==
|
|
1860
|
-
this.snapshot()?.textOptions?.charSpacingOffset ||
|
|
1861
|
-
options?.spcWidthOffset !== this.snapshot()?.textOptions?.spcWidthOffset) {
|
|
1835
|
+
if (snapshot?.text !== this.snapshot()?.text) {
|
|
1862
1836
|
return true;
|
|
1863
1837
|
}
|
|
1864
1838
|
return false;
|
|
1865
1839
|
}
|
|
1866
|
-
onCreateEditor(editor) {
|
|
1867
|
-
console.log('creating editor');
|
|
1868
|
-
editor.updateOptions({
|
|
1869
|
-
minimap: {
|
|
1870
|
-
side: 'right',
|
|
1871
|
-
},
|
|
1872
|
-
wordWrap: 'on',
|
|
1873
|
-
automaticLayout: true,
|
|
1874
|
-
});
|
|
1875
|
-
this._editorModel =
|
|
1876
|
-
this._editorModel || monaco.editor.createModel(this.svg.value, 'xml');
|
|
1877
|
-
editor.setModel(this._editorModel);
|
|
1878
|
-
this._editor = editor;
|
|
1879
|
-
this._disposables.push(
|
|
1880
|
-
// when the editor content changes, update the SVG control value
|
|
1881
|
-
this._editorModel.onDidChangeContent((e) => {
|
|
1882
|
-
console.log('change content');
|
|
1883
|
-
const code = this._editorModel.getValue();
|
|
1884
|
-
if (code !== this.svg.value) {
|
|
1885
|
-
this.svg.setValue(code);
|
|
1886
|
-
this.svg.markAsDirty();
|
|
1887
|
-
this.svg.updateValueAndValidity();
|
|
1888
|
-
}
|
|
1889
|
-
}));
|
|
1890
|
-
}
|
|
1891
1840
|
onFeaturesChange(features) {
|
|
1892
1841
|
this.features.setValue(features);
|
|
1893
1842
|
this.features.markAsDirty();
|
|
@@ -1903,7 +1852,7 @@ class ChainOperationEditorComponent {
|
|
|
1903
1852
|
}
|
|
1904
1853
|
editSource(index) {
|
|
1905
1854
|
this.editedSourceIndex.set(index);
|
|
1906
|
-
this.editedSource.set(
|
|
1855
|
+
this.editedSource.set(structuredClone(this.sources.value[index]));
|
|
1907
1856
|
}
|
|
1908
1857
|
closeSource() {
|
|
1909
1858
|
this.editedSourceIndex.set(-1);
|
|
@@ -1921,137 +1870,6 @@ class ChainOperationEditorComponent {
|
|
|
1921
1870
|
this.closeSource();
|
|
1922
1871
|
}
|
|
1923
1872
|
// #endregion
|
|
1924
|
-
// #region svg
|
|
1925
|
-
saveSvg() {
|
|
1926
|
-
if (!this.svg.value) {
|
|
1927
|
-
return;
|
|
1928
|
-
}
|
|
1929
|
-
const blob = new Blob([this.svg.value], { type: 'application/xml' });
|
|
1930
|
-
const url = window.URL.createObjectURL(blob);
|
|
1931
|
-
const a = document.createElement('a');
|
|
1932
|
-
a.href = url;
|
|
1933
|
-
// create filename from date and time
|
|
1934
|
-
const now = new Date();
|
|
1935
|
-
const date = now.toISOString().split('T')[0];
|
|
1936
|
-
const time = now.toTimeString().split(' ')[0].replace(':', '-');
|
|
1937
|
-
a.download = `svg-${date}_${time}.svg`;
|
|
1938
|
-
a.click();
|
|
1939
|
-
window.URL.revokeObjectURL(url);
|
|
1940
|
-
}
|
|
1941
|
-
openSvgEditor() {
|
|
1942
|
-
const url = this._settings.get('svg-editor', 'https://editor.method.ac/'
|
|
1943
|
-
// 'https://boxy-svg.com/app'
|
|
1944
|
-
);
|
|
1945
|
-
if (url) {
|
|
1946
|
-
if (this.svg.value) {
|
|
1947
|
-
this._clipboard.copy(this.svg.value);
|
|
1948
|
-
}
|
|
1949
|
-
window.open(url, '_blank');
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
1952
|
-
loadSvg() {
|
|
1953
|
-
const input = document.createElement('input');
|
|
1954
|
-
input.type = 'file';
|
|
1955
|
-
input.accept = '.svg';
|
|
1956
|
-
input.onchange = () => {
|
|
1957
|
-
const file = input.files?.[0];
|
|
1958
|
-
if (file) {
|
|
1959
|
-
const reader = new FileReader();
|
|
1960
|
-
reader.onload = (e) => {
|
|
1961
|
-
this._editorModel?.setValue(e.target?.result);
|
|
1962
|
-
};
|
|
1963
|
-
reader.readAsText(file);
|
|
1964
|
-
}
|
|
1965
|
-
};
|
|
1966
|
-
input.click();
|
|
1967
|
-
}
|
|
1968
|
-
setSvgFromClipboard() {
|
|
1969
|
-
navigator.clipboard.readText().then((text) => {
|
|
1970
|
-
this._editorModel?.setValue(text);
|
|
1971
|
-
});
|
|
1972
|
-
}
|
|
1973
|
-
parseSvg(svg) {
|
|
1974
|
-
if (!svg) {
|
|
1975
|
-
return [];
|
|
1976
|
-
}
|
|
1977
|
-
try {
|
|
1978
|
-
// parse SVG code extracting all the SVG elements with an id attribute
|
|
1979
|
-
const parser = new DOMParser();
|
|
1980
|
-
const doc = parser.parseFromString(svg, 'image/svg+xml');
|
|
1981
|
-
const elements = Array.from(doc.querySelectorAll('[id]'));
|
|
1982
|
-
// for each element, read x and y and add a transform with a translate
|
|
1983
|
-
// equal to -x and -y so that the element is at the origin. This is useful
|
|
1984
|
-
// so that the element can be previewed in its box without wasting space
|
|
1985
|
-
// just because it happens not to be placed at the SVG origin
|
|
1986
|
-
for (const element of elements) {
|
|
1987
|
-
const x = parseFloat(element.getAttribute('x') || '0');
|
|
1988
|
-
const y = parseFloat(element.getAttribute('y') || '0');
|
|
1989
|
-
element.setAttribute('transform', `translate(${-x},${-y})`);
|
|
1990
|
-
}
|
|
1991
|
-
return elements;
|
|
1992
|
-
}
|
|
1993
|
-
catch (e) {
|
|
1994
|
-
this.svg.setErrors({ invalidSvg: true });
|
|
1995
|
-
return [];
|
|
1996
|
-
}
|
|
1997
|
-
}
|
|
1998
|
-
removeDecimals() {
|
|
1999
|
-
const svg = this.svg.value;
|
|
2000
|
-
const newSvg = svg.replace(/(\d+)\.\d+/g, '$1');
|
|
2001
|
-
this._editorModel?.setValue(newSvg);
|
|
2002
|
-
}
|
|
2003
|
-
wrapInGroup() {
|
|
2004
|
-
// wrap SVG code in <g>...</g> if it does not already start with <g>
|
|
2005
|
-
const svg = this.svg.value;
|
|
2006
|
-
if (!svg.startsWith('<g')) {
|
|
2007
|
-
this._editorModel?.setValue(`<g>${svg}</g>`);
|
|
2008
|
-
}
|
|
2009
|
-
}
|
|
2010
|
-
// #endregion
|
|
2011
|
-
// #region element features
|
|
2012
|
-
onTabIndexChange(index) {
|
|
2013
|
-
this.tabIndex.set(index);
|
|
2014
|
-
if (index === 3) {
|
|
2015
|
-
this.elements.set(this.parseSvg(this.svg.value));
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
2018
|
-
editElementFeatures(element) {
|
|
2019
|
-
const id = element.id;
|
|
2020
|
-
const features = this.elementFeatures.value[id] || [];
|
|
2021
|
-
this.elementFeatures.setValue({
|
|
2022
|
-
...this.elementFeatures.value,
|
|
2023
|
-
[id]: features,
|
|
2024
|
-
});
|
|
2025
|
-
this.editedElementId.set(id);
|
|
2026
|
-
}
|
|
2027
|
-
deleteElementFeatures(element) {
|
|
2028
|
-
const id = element.id;
|
|
2029
|
-
const elementFeatures = { ...this.elementFeatures.value };
|
|
2030
|
-
if (!elementFeatures[id]) {
|
|
2031
|
-
return;
|
|
2032
|
-
}
|
|
2033
|
-
this._dialogService
|
|
2034
|
-
.confirm('Confirm', 'Delete element features?')
|
|
2035
|
-
.subscribe((yes) => {
|
|
2036
|
-
if (yes) {
|
|
2037
|
-
delete elementFeatures[id];
|
|
2038
|
-
this.elementFeatures.setValue(elementFeatures);
|
|
2039
|
-
if (this.editedElementId() === id) {
|
|
2040
|
-
this.editedElementId.set(undefined);
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
2043
|
-
});
|
|
2044
|
-
}
|
|
2045
|
-
onElementFeaturesChange(features) {
|
|
2046
|
-
if (this.editedElementId()) {
|
|
2047
|
-
this.elementFeatures.setValue({
|
|
2048
|
-
...this.elementFeatures.value,
|
|
2049
|
-
[this.editedElementId()]: features,
|
|
2050
|
-
});
|
|
2051
|
-
this.editedElementId.set(undefined);
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
// #endregion
|
|
2055
1873
|
updateArgsUI() {
|
|
2056
1874
|
let to = false, toRun = false, value = false;
|
|
2057
1875
|
switch (this.type.value) {
|
|
@@ -2099,8 +1917,6 @@ class ChainOperationEditorComponent {
|
|
|
2099
1917
|
this.groupId.setValue(operation.groupId || null);
|
|
2100
1918
|
this.features.setValue(operation.features || []);
|
|
2101
1919
|
this.sources.setValue(operation.sources || []);
|
|
2102
|
-
this.newTextHidden.setValue(operation.diplomatics?.isNewTextHidden || false);
|
|
2103
|
-
this.dpFeatures.setValue(operation.diplomatics?.features || []);
|
|
2104
1920
|
this.type.setValue(operation.type);
|
|
2105
1921
|
this.at.setValue(operation.at);
|
|
2106
1922
|
this.atAsIndex.setValue(operation.atAsIndex || false);
|
|
@@ -2112,11 +1928,7 @@ class ChainOperationEditorComponent {
|
|
|
2112
1928
|
this.toRun.setValue(operation.toRun || 0);
|
|
2113
1929
|
// escape line feeds in value for editing
|
|
2114
1930
|
this.value.setValue(this.toggleLfEscape(operation.value, true) || null);
|
|
2115
|
-
this.svg.setValue(operation.diplomatics?.g || '');
|
|
2116
|
-
this._editorModel?.setValue(this.svg.value || '');
|
|
2117
|
-
this.elementFeatures.setValue(operation.diplomatics?.elementFeatures || {});
|
|
2118
1931
|
this.form.markAsPristine();
|
|
2119
|
-
this.elements.set(this.parseSvg(operation.diplomatics?.g));
|
|
2120
1932
|
this.updateArgsUI();
|
|
2121
1933
|
}
|
|
2122
1934
|
cancel() {
|
|
@@ -2128,12 +1940,6 @@ class ChainOperationEditorComponent {
|
|
|
2128
1940
|
groupId: this.groupId.value || undefined,
|
|
2129
1941
|
features: this.features.value?.length ? this.features.value : undefined,
|
|
2130
1942
|
sources: this.sources.value?.length ? this.sources.value : undefined,
|
|
2131
|
-
diplomatics: {
|
|
2132
|
-
g: this.svg.value,
|
|
2133
|
-
isNewTextHidden: this.newTextHidden.value,
|
|
2134
|
-
features: this.dpFeatures.value,
|
|
2135
|
-
elementFeatures: this.elementFeatures.value,
|
|
2136
|
-
},
|
|
2137
1943
|
id: this.id(),
|
|
2138
1944
|
type: this.type.value,
|
|
2139
1945
|
at: this.at.value,
|
|
@@ -2158,12 +1964,12 @@ class ChainOperationEditorComponent {
|
|
|
2158
1964
|
this.operation.set(this.getOperation());
|
|
2159
1965
|
this.operationChange.emit(this.operation());
|
|
2160
1966
|
}
|
|
2161
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2162
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.10", 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 }, elementFeatureDefs: { classPropertyName: "elementFeatureDefs", publicName: "elementFeatureDefs", isSignal: true, isRequired: false, transformFunction: null }, diplomaticFeatureDefs: { classPropertyName: "diplomaticFeatureDefs", publicName: "diplomaticFeatureDefs", isSignal: true, isRequired: false, transformFunction: null }, rangePatch: { classPropertyName: "rangePatch", publicName: "rangePatch", 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\r\n [selectedIndex]=\"tabIndex()\"\r\n (selectedIndexChange)=\"onTabIndexChange($event)\"\r\n >\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]=\"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-option [value]=\"7\"\r\n ><mat-icon>edit_note</mat-icon>annotate</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 </div>\r\n <div class=\"form-row\">\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 ( $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 <!-- 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 ( $any(groupId).errors?.required && (groupId.dirty ||\r\n groupId.touched) ) {\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 (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 }}</td>\r\n <td>{{ s.id }}</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 (sourceChange)=\"onSourceChange($event!)\"\r\n (sourceCancel)=\"closeSource()\"\r\n />\r\n </mat-expansion-panel>\r\n }\r\n </mat-tab>\r\n\r\n <!-- DIPLOMATIC -->\r\n <mat-tab label=\"diplomatic\">\r\n <div class=\"toolbar-row\">\r\n <button\r\n id=\"btn-save\"\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Save to file\"\r\n [disabled]=\"!svg.value\"\r\n (click)=\"saveSvg()\"\r\n >\r\n <mat-icon>save</mat-icon>\r\n </button>\r\n <button\r\n id=\"btn-load\"\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Load from file\"\r\n (click)=\"loadSvg()\"\r\n >\r\n <mat-icon>folder</mat-icon>\r\n </button>\r\n <button\r\n id=\"btn-copy\"\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Copy\"\r\n [cdkCopyToClipboard]=\"svg.value\"\r\n >\r\n <mat-icon>content_copy</mat-icon>\r\n </button>\r\n <button\r\n id=\"btn-paste\"\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Set SVG from clipboard\"\r\n (click)=\"setSvgFromClipboard()\"\r\n >\r\n <mat-icon>content_paste_go</mat-icon>\r\n </button>\r\n <button\r\n id=\"btn-editor\"\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Open SVG external editor\"\r\n (click)=\"openSvgEditor()\"\r\n >\r\n <mat-icon>launch</mat-icon>\r\n </button>\r\n <button\r\n id=\"btn-decimals\"\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Remove decimals\"\r\n [disabled]=\"!svg.value\"\r\n (click)=\"removeDecimals()\"\r\n >\r\n <mat-icon>pin</mat-icon>\r\n </button>\r\n <button\r\n id=\"btn-group\"\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Wrap code in group\"\r\n (click)=\"wrapInGroup()\"\r\n >\r\n <mat-icon>code</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <div id=\"code\">\r\n <nge-monaco-editor\r\n style=\"--editor-height: 400px\"\r\n (ready)=\"onCreateEditor($event)\"\r\n />\r\n @if (svg.invalid) {\r\n <mat-error>invalid SVG</mat-error>\r\n }\r\n </div>\r\n </mat-tab>\r\n\r\n <!-- ELEMENTS -->\r\n <mat-tab label=\"elements\">\r\n <table>\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>id</th>\r\n <th>visual</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (e of elements(); track e.id) {\r\n <tr [class.selected]=\"e.id === editedElementId()\">\r\n <td class=\"fit-width\">\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n color=\"primary\"\r\n matTooltip=\"Edit element features\"\r\n (click)=\"editElementFeatures(e)\"\r\n >\r\n <mat-icon class=\"mat-primary\">edit</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n color=\"warn\"\r\n matTooltip=\"Delete all element features\"\r\n (click)=\"deleteElementFeatures(e)\"\r\n [disabled]=\"!$any(elementFeatures.value)[e.id]?.length\"\r\n >\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n </td>\r\n <td>\r\n <span\r\n [matBadge]=\"$any(elementFeatures.value)[e.id]?.length || 0\"\r\n [matBadgeHidden]=\"!$any(elementFeatures.value)[e.id]?.length\"\r\n matBadgeOverlap=\"false\"\r\n >{{ e.id }}</span\r\n >\r\n </td>\r\n <td class=\"svg-cell\">\r\n <svg [innerHTML]=\"e.outerHTML | safeHtml : 'html'\"></svg>\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n @if (editedElementId()) {\r\n <mat-expansion-panel\r\n [disabled]=\"!editedElementId()\"\r\n [expanded]=\"editedElementId()\"\r\n >\r\n <mat-expansion-panel-header>{{\r\n editedElementId()\r\n }}</mat-expansion-panel-header>\r\n <gve-feature-set-editor\r\n [featNames]=\"elementFeatureDefs()?.names\"\r\n [featValues]=\"elementFeatureDefs()?.values\"\r\n [features]=\"elementFeatures.value[editedElementId()!] || []\"\r\n (featuresChange)=\"onElementFeaturesChange($event)\"\r\n />\r\n </mat-expansion-panel>\r\n }\r\n </mat-tab>\r\n\r\n <!-- DP FEATS -->\r\n <mat-tab label=\"d-features\">\r\n <div>\r\n <mat-checkbox [formControl]=\"newTextHidden\">hide new text</mat-checkbox>\r\n </div>\r\n <gve-feature-set-editor\r\n [featNames]=\"diplomaticFeatureDefs()?.names\"\r\n [featValues]=\"diplomaticFeatureDefs()?.values\"\r\n [features]=\"dpFeatures.value\"\r\n (featuresChange)=\"onDpFeaturesChange($event)\"\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 silver}.toolbar-row{display:flex;align-items:center;flex-wrap:wrap}.nr{width:5em}.long-text{width:100%;max-width:800px}#id{border-radius:6px;padding:4px;background-color:#beb9b9;color:#fff;margin-top:-16px}fieldset{border:1px solid silver;border-radius:6px;padding:8px;margin:8px 0}legend{color:silver}table{width:100%;border-collapse:collapse}tbody tr:nth-child(odd){background-color:#e2e2e2}th{text-align:left;font-weight:400;color:silver}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#d0d0d0!important}#btn-save{color:#072d3e}#btn-load{color:#dbd112}#btn-copy{color:#22549f}#btn-preview{color:#095409}#code{height:500px;border:1px solid silver}#monaco{height:100%}.svg-cell{padding:0 8px}.svg-cell svg{border:1px solid silver;width:100%;height:auto}#preview{box-sizing:border-box;width:100%;border:1px solid silver;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:#e5e5e5}.child-title{font-weight:700;margin:0;background-color:#ccc;color:#fff;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 silver;border-radius:6px;margin:8px 0;padding-right:8px}#tree-ed{grid-area:ed;border:1px solid silver;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.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: ClipboardModule }, { kind: "directive", type: i2$2.CdkCopyToClipboard, selector: "[cdkCopyToClipboard]", inputs: ["cdkCopyToClipboard", "cdkCopyToClipboardAttempts"], outputs: ["cdkCopyToClipboardCopied"] }, { kind: "ngmodule", type: MatBadgeModule }, { kind: "directive", type: i5$1.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: MatCheckboxModule }, { kind: "component", type: i3$2.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: i3$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i3$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i3$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: i6.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: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i14.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i14.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: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: NgeMonacoModule }, { kind: "component", type: i15.NgeMonacoEditorComponent, selector: "nge-monaco-editor", inputs: ["autoLayout", "options"], outputs: ["ready"] }, { kind: "component", type: FeatureSetEditorComponent, selector: "gve-feature-set-editor", inputs: ["isVar", "featNames", "featValues", "filterThreshold", "features"], outputs: ["featuresChange"] }, { kind: "component", type: OperationSourceEditorComponent, selector: "gve-operation-source-editor", inputs: ["source", "ids", "types"], outputs: ["sourceChange", "sourceCancel"] }, { kind: "pipe", type: SafeHtmlPipe, name: "safeHtml" }] }); }
|
|
1967
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: ChainOperationEditorComponent, deps: [{ token: i1$1.FormBuilder }, { token: i2$2.Clipboard }, { token: SettingsService }, { token: i4$1.DialogService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1968
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", 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 } }, 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]=\"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-option [value]=\"7\"\r\n ><mat-icon>edit_note</mat-icon>annotate</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 }}</td>\r\n <td>{{ s.id }}</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 (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 silver}.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:#beb9b9;color:#fff;margin-top:-16px}fieldset{border:1px solid silver;border-radius:6px;padding:8px;margin:8px 0}legend{color:silver}table{width:100%;border-collapse:collapse}tbody tr:nth-child(odd){background-color:#e2e2e2}th{text-align:left;font-weight:400;color:silver}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#d0d0d0!important}#btn-save{color:#072d3e}#btn-load{color:#dbd112}#btn-copy{color:#22549f}#btn-preview{color:#095409}#code{height:500px;border:1px solid silver}#monaco{height:100%}#preview{box-sizing:border-box;width:100%;border:1px solid silver;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:#e5e5e5}.child-title{font-weight:700;margin:0;background-color:#ccc;color:#fff;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 silver;border-radius:6px;margin:8px 0;padding-right:8px}#tree-ed{grid-area:ed;border:1px solid silver;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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2163
1969
|
}
|
|
2164
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1970
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: ChainOperationEditorComponent, decorators: [{
|
|
2165
1971
|
type: Component,
|
|
2166
|
-
args: [{ selector: 'gve-chain-operation-editor', imports: [
|
|
1972
|
+
args: [{ selector: 'gve-chain-operation-editor', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
2167
1973
|
ReactiveFormsModule,
|
|
2168
1974
|
ClipboardModule,
|
|
2169
1975
|
MatBadgeModule,
|
|
@@ -2177,11 +1983,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
2177
1983
|
MatTabsModule,
|
|
2178
1984
|
MatTooltipModule,
|
|
2179
1985
|
NgeMonacoModule,
|
|
2180
|
-
SafeHtmlPipe,
|
|
2181
1986
|
FeatureSetEditorComponent,
|
|
2182
1987
|
OperationSourceEditorComponent,
|
|
2183
|
-
], template: "<form [formGroup]=\"form\" (submit)=\"save()\">\r\n <!-- tabs -->\r\n <mat-tab-group\r\n [selectedIndex]=\"tabIndex()\"\r\n (selectedIndexChange)=\"onTabIndexChange($event)\"\r\n >\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]=\"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-option [value]=\"7\"\r\n ><mat-icon>edit_note</mat-icon>annotate</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 </div>\r\n <div class=\"form-row\">\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 ( $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 <!-- 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 ( $any(groupId).errors?.required && (groupId.dirty ||\r\n groupId.touched) ) {\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 (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 }}</td>\r\n <td>{{ s.id }}</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 (sourceChange)=\"onSourceChange($event!)\"\r\n (sourceCancel)=\"closeSource()\"\r\n />\r\n </mat-expansion-panel>\r\n }\r\n </mat-tab>\r\n\r\n <!-- DIPLOMATIC -->\r\n <mat-tab label=\"diplomatic\">\r\n <div class=\"toolbar-row\">\r\n <button\r\n id=\"btn-save\"\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Save to file\"\r\n [disabled]=\"!svg.value\"\r\n (click)=\"saveSvg()\"\r\n >\r\n <mat-icon>save</mat-icon>\r\n </button>\r\n <button\r\n id=\"btn-load\"\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Load from file\"\r\n (click)=\"loadSvg()\"\r\n >\r\n <mat-icon>folder</mat-icon>\r\n </button>\r\n <button\r\n id=\"btn-copy\"\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Copy\"\r\n [cdkCopyToClipboard]=\"svg.value\"\r\n >\r\n <mat-icon>content_copy</mat-icon>\r\n </button>\r\n <button\r\n id=\"btn-paste\"\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Set SVG from clipboard\"\r\n (click)=\"setSvgFromClipboard()\"\r\n >\r\n <mat-icon>content_paste_go</mat-icon>\r\n </button>\r\n <button\r\n id=\"btn-editor\"\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Open SVG external editor\"\r\n (click)=\"openSvgEditor()\"\r\n >\r\n <mat-icon>launch</mat-icon>\r\n </button>\r\n <button\r\n id=\"btn-decimals\"\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Remove decimals\"\r\n [disabled]=\"!svg.value\"\r\n (click)=\"removeDecimals()\"\r\n >\r\n <mat-icon>pin</mat-icon>\r\n </button>\r\n <button\r\n id=\"btn-group\"\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Wrap code in group\"\r\n (click)=\"wrapInGroup()\"\r\n >\r\n <mat-icon>code</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <div id=\"code\">\r\n <nge-monaco-editor\r\n style=\"--editor-height: 400px\"\r\n (ready)=\"onCreateEditor($event)\"\r\n />\r\n @if (svg.invalid) {\r\n <mat-error>invalid SVG</mat-error>\r\n }\r\n </div>\r\n </mat-tab>\r\n\r\n <!-- ELEMENTS -->\r\n <mat-tab label=\"elements\">\r\n <table>\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>id</th>\r\n <th>visual</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (e of elements(); track e.id) {\r\n <tr [class.selected]=\"e.id === editedElementId()\">\r\n <td class=\"fit-width\">\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n color=\"primary\"\r\n matTooltip=\"Edit element features\"\r\n (click)=\"editElementFeatures(e)\"\r\n >\r\n <mat-icon class=\"mat-primary\">edit</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n color=\"warn\"\r\n matTooltip=\"Delete all element features\"\r\n (click)=\"deleteElementFeatures(e)\"\r\n [disabled]=\"!$any(elementFeatures.value)[e.id]?.length\"\r\n >\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n </td>\r\n <td>\r\n <span\r\n [matBadge]=\"$any(elementFeatures.value)[e.id]?.length || 0\"\r\n [matBadgeHidden]=\"!$any(elementFeatures.value)[e.id]?.length\"\r\n matBadgeOverlap=\"false\"\r\n >{{ e.id }}</span\r\n >\r\n </td>\r\n <td class=\"svg-cell\">\r\n <svg [innerHTML]=\"e.outerHTML | safeHtml : 'html'\"></svg>\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n @if (editedElementId()) {\r\n <mat-expansion-panel\r\n [disabled]=\"!editedElementId()\"\r\n [expanded]=\"editedElementId()\"\r\n >\r\n <mat-expansion-panel-header>{{\r\n editedElementId()\r\n }}</mat-expansion-panel-header>\r\n <gve-feature-set-editor\r\n [featNames]=\"elementFeatureDefs()?.names\"\r\n [featValues]=\"elementFeatureDefs()?.values\"\r\n [features]=\"elementFeatures.value[editedElementId()!] || []\"\r\n (featuresChange)=\"onElementFeaturesChange($event)\"\r\n />\r\n </mat-expansion-panel>\r\n }\r\n </mat-tab>\r\n\r\n <!-- DP FEATS -->\r\n <mat-tab label=\"d-features\">\r\n <div>\r\n <mat-checkbox [formControl]=\"newTextHidden\">hide new text</mat-checkbox>\r\n </div>\r\n <gve-feature-set-editor\r\n [featNames]=\"diplomaticFeatureDefs()?.names\"\r\n [featValues]=\"diplomaticFeatureDefs()?.values\"\r\n [features]=\"dpFeatures.value\"\r\n (featuresChange)=\"onDpFeaturesChange($event)\"\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 silver}.toolbar-row{display:flex;align-items:center;flex-wrap:wrap}.nr{width:5em}.long-text{width:100%;max-width:800px}#id{border-radius:6px;padding:4px;background-color:#beb9b9;color:#fff;margin-top:-16px}fieldset{border:1px solid silver;border-radius:6px;padding:8px;margin:8px 0}legend{color:silver}table{width:100%;border-collapse:collapse}tbody tr:nth-child(odd){background-color:#e2e2e2}th{text-align:left;font-weight:400;color:silver}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#d0d0d0!important}#btn-save{color:#072d3e}#btn-load{color:#dbd112}#btn-copy{color:#22549f}#btn-preview{color:#095409}#code{height:500px;border:1px solid silver}#monaco{height:100%}.svg-cell{padding:0 8px}.svg-cell svg{border:1px solid silver;width:100%;height:auto}#preview{box-sizing:border-box;width:100%;border:1px solid silver;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:#e5e5e5}.child-title{font-weight:700;margin:0;background-color:#ccc;color:#fff;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 silver;border-radius:6px;margin:8px 0;padding-right:8px}#tree-ed{grid-area:ed;border:1px solid silver;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"] }]
|
|
2184
|
-
}], ctorParameters: () => [{ type: i1.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 }] }],
|
|
1988
|
+
], 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]=\"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-option [value]=\"7\"\r\n ><mat-icon>edit_note</mat-icon>annotate</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 }}</td>\r\n <td>{{ s.id }}</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 (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 silver}.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:#beb9b9;color:#fff;margin-top:-16px}fieldset{border:1px solid silver;border-radius:6px;padding:8px;margin:8px 0}legend{color:silver}table{width:100%;border-collapse:collapse}tbody tr:nth-child(odd){background-color:#e2e2e2}th{text-align:left;font-weight:400;color:silver}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#d0d0d0!important}#btn-save{color:#072d3e}#btn-load{color:#dbd112}#btn-copy{color:#22549f}#btn-preview{color:#095409}#code{height:500px;border:1px solid silver}#monaco{height:100%}#preview{box-sizing:border-box;width:100%;border:1px solid silver;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:#e5e5e5}.child-title{font-weight:700;margin:0;background-color:#ccc;color:#fff;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 silver;border-radius:6px;margin:8px 0;padding-right:8px}#tree-ed{grid-area:ed;border:1px solid silver;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"] }]
|
|
1989
|
+
}], 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 }] }], operationChange: [{ type: i0.Output, args: ["operationChange"] }], operationPreview: [{ type: i0.Output, args: ["operationPreview"] }], operationCancel: [{ type: i0.Output, args: ["operationCancel"] }] } });
|
|
2185
1990
|
|
|
2186
1991
|
/**
|
|
2187
1992
|
* 🔑 `gve-feature-set-view`
|
|
@@ -2249,10 +2054,10 @@ class FeatureSetViewComponent {
|
|
|
2249
2054
|
ngOnDestroy() {
|
|
2250
2055
|
this._sub?.unsubscribe();
|
|
2251
2056
|
}
|
|
2252
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2253
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
2057
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: FeatureSetViewComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2058
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", 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;min-width:0}th{text-align:left;background-color:#c8d9eb;color:#333;font-weight:400}th,td{border:1px solid silver;padding:4px}tr:nth-child(2n){background-color:#dfdfdf}#filter{width:7em}#badge{border:1px solid silver;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" }] }); }
|
|
2254
2059
|
}
|
|
2255
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2060
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: FeatureSetViewComponent, decorators: [{
|
|
2256
2061
|
type: Component,
|
|
2257
2062
|
args: [{ selector: 'gve-feature-set-view', imports: [
|
|
2258
2063
|
ReactiveFormsModule,
|
|
@@ -2261,8 +2066,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
2261
2066
|
MatIconModule,
|
|
2262
2067
|
MatInputModule,
|
|
2263
2068
|
FlatLookupPipe,
|
|
2264
|
-
], template: "<div>\r\n <!-- filter -->\r\n <div>\r\n @if (filterThreshold() === 0 || features()!.length > filterThreshold()) {\r\n
|
|
2265
|
-
}], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { features: [{ type: i0.Input, args: [{ isSignal: true, alias: "features", 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 }] }] } });
|
|
2069
|
+
], 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;min-width:0}th{text-align:left;background-color:#c8d9eb;color:#333;font-weight:400}th,td{border:1px solid silver;padding:4px}tr:nth-child(2n){background-color:#dfdfdf}#filter{width:7em}#badge{border:1px solid silver;border-radius:6px;padding:4px;margin-top:-18px}\n"] }]
|
|
2070
|
+
}], ctorParameters: () => [{ type: i1$1.FormBuilder }], propDecorators: { features: [{ type: i0.Input, args: [{ isSignal: true, alias: "features", 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 }] }] } });
|
|
2266
2071
|
|
|
2267
2072
|
/**
|
|
2268
2073
|
* 🔑 `gve-steps-map`
|
|
@@ -2372,12 +2177,12 @@ class StepsMapComponent {
|
|
|
2372
2177
|
onStepClick(step) {
|
|
2373
2178
|
this.selectedStep.set(step);
|
|
2374
2179
|
}
|
|
2375
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2376
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
2180
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: StepsMapComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2181
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", 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) { @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 }} ▶ </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 <div class=\"line\">{{ line }}</div>\r\n }\r\n <div></div>\r\n </div>\r\n </div>\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}.selected{background-color:#ffc}.step{border:1px solid rgb(68,114,253);border-radius:6px;margin:8px 0}.tag{background-color:#4472fd;color:#fff;border:1px solid rgb(68,114,253);border-radius:6px;padding:4px;cursor:pointer}.muted-tag{color:silver}.text{font-size:.5em}\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 }); }
|
|
2377
2182
|
}
|
|
2378
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2183
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: StepsMapComponent, decorators: [{
|
|
2379
2184
|
type: Component,
|
|
2380
|
-
args: [{ selector: 'gve-steps-map', imports: [
|
|
2185
|
+
args: [{ selector: 'gve-steps-map', imports: [MatButtonModule, MatRippleModule, MatTooltipModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div>\r\n @if (steps().length) { @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 }} ▶ </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 <div class=\"line\">{{ line }}</div>\r\n }\r\n <div></div>\r\n </div>\r\n </div>\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}.selected{background-color:#ffc}.step{border:1px solid rgb(68,114,253);border-radius:6px;margin:8px 0}.tag{background-color:#4472fd;color:#fff;border:1px solid rgb(68,114,253);border-radius:6px;padding:4px;cursor:pointer}.muted-tag{color:silver}.text{font-size:.5em}\n"] }]
|
|
2381
2186
|
}], 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 }] }] } });
|
|
2382
2187
|
|
|
2383
2188
|
/**
|
|
@@ -2610,346 +2415,35 @@ class ChainResultViewComponent {
|
|
|
2610
2415
|
}
|
|
2611
2416
|
this.selectionFeatures.set(features);
|
|
2612
2417
|
}
|
|
2613
|
-
onStepChange(step) {
|
|
2614
|
-
this.selectionFeatures.set([]);
|
|
2615
|
-
if (step) {
|
|
2616
|
-
// setting the tag will trigger the step change
|
|
2617
|
-
this.tag.setValue(step.outputTag);
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
pickRange() {
|
|
2621
|
-
if (this.selectionRange()) {
|
|
2622
|
-
this.rangePick.emit(this.selectionRange());
|
|
2623
|
-
}
|
|
2624
|
-
}
|
|
2625
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: ChainResultViewComponent, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2626
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.10", 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>version</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 (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}.range{color:silver;border:1px solid silver;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}div#bar{grid-area:bar}div#map{grid-area:map}div#step{grid-area:step}.feat-header{background-color:#3e92cc;color:#fff;text-align:center;border:1px solid #3e92cc;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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.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: i6.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: i6.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", "hasLineNumber", "text", "colorCallback", "borderColorCallback"], outputs: ["charPick", "rangePick"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] }); }
|
|
2627
|
-
}
|
|
2628
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: ChainResultViewComponent, decorators: [{
|
|
2629
|
-
type: Component,
|
|
2630
|
-
args: [{ selector: 'gve-chain-result-view', imports: [
|
|
2631
|
-
ReactiveFormsModule,
|
|
2632
|
-
MatButtonModule,
|
|
2633
|
-
MatFormFieldModule,
|
|
2634
|
-
MatIconModule,
|
|
2635
|
-
MatSelectModule,
|
|
2636
|
-
FeatureSetViewComponent,
|
|
2637
|
-
StepsMapComponent,
|
|
2638
|
-
BaseTextViewComponent,
|
|
2639
|
-
MatTooltip
|
|
2640
|
-
], 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>version</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 (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}.range{color:silver;border:1px solid silver;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}div#bar{grid-area:bar}div#map{grid-area:map}div#step{grid-area:step}.feat-header{background-color:#3e92cc;color:#fff;text-align:center;border:1px solid #3e92cc;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"] }]
|
|
2641
|
-
}], ctorParameters: () => [{ type: i1.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"] }] } });
|
|
2642
|
-
|
|
2643
|
-
class GveGraphvizService {
|
|
2644
|
-
hashString(str) {
|
|
2645
|
-
let hash = 0;
|
|
2646
|
-
for (let i = 0; i < str.length; i++) {
|
|
2647
|
-
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
2648
|
-
hash = hash & hash; // convert to 32bit integer
|
|
2649
|
-
}
|
|
2650
|
-
return Math.abs(hash);
|
|
2651
|
-
}
|
|
2652
|
-
hslToRgb(hue, saturation, lightness) {
|
|
2653
|
-
const chroma = ((1 - Math.abs((2 * lightness) / 100 - 1)) * saturation) / 100;
|
|
2654
|
-
const x = chroma * (1 - Math.abs(((hue / 60) % 2) - 1));
|
|
2655
|
-
const m = lightness / 100 - chroma / 2;
|
|
2656
|
-
let r = 0, g = 0, b = 0;
|
|
2657
|
-
if (hue >= 0 && hue < 60) {
|
|
2658
|
-
r = chroma;
|
|
2659
|
-
g = x;
|
|
2660
|
-
}
|
|
2661
|
-
else if (hue >= 60 && hue < 120) {
|
|
2662
|
-
r = x;
|
|
2663
|
-
g = chroma;
|
|
2664
|
-
}
|
|
2665
|
-
else if (hue >= 120 && hue < 180) {
|
|
2666
|
-
g = chroma;
|
|
2667
|
-
b = x;
|
|
2668
|
-
}
|
|
2669
|
-
else if (hue >= 180 && hue < 240) {
|
|
2670
|
-
g = x;
|
|
2671
|
-
b = chroma;
|
|
2672
|
-
}
|
|
2673
|
-
else if (hue >= 240 && hue < 300) {
|
|
2674
|
-
r = x;
|
|
2675
|
-
b = chroma;
|
|
2676
|
-
}
|
|
2677
|
-
else if (hue >= 300 && hue < 360) {
|
|
2678
|
-
r = chroma;
|
|
2679
|
-
b = x;
|
|
2680
|
-
}
|
|
2681
|
-
r = Math.round((r + m) * 255);
|
|
2682
|
-
g = Math.round((g + m) * 255);
|
|
2683
|
-
b = Math.round((b + m) * 255);
|
|
2684
|
-
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
|
2685
|
-
}
|
|
2686
|
-
getColorForTag(tag) {
|
|
2687
|
-
const hash = this.hashString(tag);
|
|
2688
|
-
const hue = (hash * 137) % 360; // Use a prime number to spread out the hues
|
|
2689
|
-
const saturation = 60 + (hash % 40); // Saturation between 60% and 100%
|
|
2690
|
-
const lightness = 50 + (hash % 30); // Lightness between 50% and 80%
|
|
2691
|
-
return this.hslToRgb(hue, saturation, lightness);
|
|
2692
|
-
}
|
|
2693
|
-
getExcludedNodeIds(chain, tags) {
|
|
2694
|
-
// get only the desired links
|
|
2695
|
-
const links = chain.links.filter((link) => tags ? tags.includes(link.tag) : true);
|
|
2696
|
-
// return all the nodes not referenced by the links
|
|
2697
|
-
const excluded = new Set();
|
|
2698
|
-
chain.nodes.forEach((node) => {
|
|
2699
|
-
excluded.add(node.id);
|
|
2700
|
-
});
|
|
2701
|
-
links.forEach((link) => {
|
|
2702
|
-
excluded.delete(link.sourceId);
|
|
2703
|
-
excluded.delete(link.targetId);
|
|
2704
|
-
});
|
|
2705
|
-
return excluded;
|
|
2706
|
-
}
|
|
2707
|
-
/**
|
|
2708
|
-
* Represent the received chain as a Graphviz digraph.
|
|
2709
|
-
*
|
|
2710
|
-
* @param chain The source chain if any.
|
|
2711
|
-
* @param tags The tags to show. When set, only the links with these tags are shown.
|
|
2712
|
-
* @param rankdir The rank direction.
|
|
2713
|
-
* @returns Graphviz representation of the chain.
|
|
2714
|
-
*/
|
|
2715
|
-
generateGraph(chain, tags, rankdir = 'LR') {
|
|
2716
|
-
if (!chain) {
|
|
2717
|
-
return 'digraph G {}';
|
|
2718
|
-
}
|
|
2719
|
-
const sb = [];
|
|
2720
|
-
const excludedNodeIds = this.getExcludedNodeIds(chain, tags);
|
|
2721
|
-
sb.push('digraph G {');
|
|
2722
|
-
sb.push(' bgcolor=transparent;');
|
|
2723
|
-
sb.push(' node [style=filled];');
|
|
2724
|
-
sb.push(` rankdir=${rankdir};`);
|
|
2725
|
-
chain.nodes.forEach((node) => {
|
|
2726
|
-
// note that in label we must escape the double quotes
|
|
2727
|
-
sb.push(` ${node.id} [label="${node.label === '"' ? '"' : node.label}"` +
|
|
2728
|
-
(node.sourceTag
|
|
2729
|
-
? ` fillcolor="${this.getColorForTag(node.sourceTag)}"`
|
|
2730
|
-
: '') +
|
|
2731
|
-
(node.sourceTag && excludedNodeIds.has(node.id)
|
|
2732
|
-
? ` xlabel="${node.sourceTag}"`
|
|
2733
|
-
: '') +
|
|
2734
|
-
'];');
|
|
2735
|
-
});
|
|
2736
|
-
chain.links.forEach((link) => {
|
|
2737
|
-
if (!link.sourceId ||
|
|
2738
|
-
!link.targetId ||
|
|
2739
|
-
(tags && !tags.includes(link.tag))) {
|
|
2740
|
-
return;
|
|
2741
|
-
}
|
|
2742
|
-
const color = this.getColorForTag(link.tag);
|
|
2743
|
-
sb.push(` ${link.sourceId} -> ${link.targetId} [label="${link.tag}", color="${color}"];`);
|
|
2744
|
-
});
|
|
2745
|
-
sb.push('}');
|
|
2746
|
-
return sb.join('\n');
|
|
2747
|
-
}
|
|
2748
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: GveGraphvizService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2749
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: GveGraphvizService, providedIn: 'root' }); }
|
|
2750
|
-
}
|
|
2751
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: GveGraphvizService, decorators: [{
|
|
2752
|
-
type: Injectable,
|
|
2753
|
-
args: [{
|
|
2754
|
-
providedIn: 'root',
|
|
2755
|
-
}]
|
|
2756
|
-
}] });
|
|
2757
|
-
|
|
2758
|
-
class ChainViewComponent {
|
|
2759
|
-
constructor(_graphviz, _settings, _clipboard, _snackbar) {
|
|
2760
|
-
this._graphviz = _graphviz;
|
|
2761
|
-
this._settings = _settings;
|
|
2762
|
-
this._clipboard = _clipboard;
|
|
2763
|
-
this._snackbar = _snackbar;
|
|
2764
|
-
/**
|
|
2765
|
-
* The chain to display.
|
|
2766
|
-
*/
|
|
2767
|
-
this.chain = input(...(ngDevMode ? [undefined, { debugName: "chain" }] : []));
|
|
2768
|
-
/**
|
|
2769
|
-
* The direction of the graph.
|
|
2770
|
-
*/
|
|
2771
|
-
this.direction = input('LR', ...(ngDevMode ? [{ debugName: "direction" }] : []));
|
|
2772
|
-
/**
|
|
2773
|
-
* All the distinct tags in the chain.
|
|
2774
|
-
*/
|
|
2775
|
-
this.tags = computed(() => {
|
|
2776
|
-
const chain = this.chain();
|
|
2777
|
-
if (!chain) {
|
|
2778
|
-
return [];
|
|
2779
|
-
}
|
|
2780
|
-
const set = new Set();
|
|
2781
|
-
for (const link of chain.links) {
|
|
2782
|
-
set.add(link.tag);
|
|
2783
|
-
}
|
|
2784
|
-
return Array.from(set).sort();
|
|
2785
|
-
}, ...(ngDevMode ? [{ debugName: "tags" }] : []));
|
|
2786
|
-
/**
|
|
2787
|
-
* The tags to show, or empty to show all of them.
|
|
2788
|
-
*/
|
|
2789
|
-
this.selectedTags = model([], ...(ngDevMode ? [{ debugName: "selectedTags" }] : []));
|
|
2790
|
-
/**
|
|
2791
|
-
* The Graphviz representation of the chain.
|
|
2792
|
-
*/
|
|
2793
|
-
this.graph = computed(() => {
|
|
2794
|
-
let tags = this.selectedTags();
|
|
2795
|
-
if (tags && tags.length === 0) {
|
|
2796
|
-
tags = undefined;
|
|
2797
|
-
}
|
|
2798
|
-
return this._graphviz.generateGraph(this.chain(), tags, this.direction());
|
|
2799
|
-
}, ...(ngDevMode ? [{ debugName: "graph" }] : []));
|
|
2800
|
-
this.userTags = new FormControl([], {
|
|
2801
|
-
nonNullable: true,
|
|
2802
|
-
});
|
|
2803
|
-
// when the user changes the tags, update the selected tags
|
|
2804
|
-
this._sub = this.userTags.valueChanges.subscribe((value) => {
|
|
2805
|
-
this.selectedTags.set([...value]);
|
|
2806
|
-
});
|
|
2807
|
-
effect(() => {
|
|
2808
|
-
// update the user tags when tags change
|
|
2809
|
-
const tags = this.tags();
|
|
2810
|
-
let userTags = [];
|
|
2811
|
-
if (tags.length > 1) {
|
|
2812
|
-
// pick first and last tag from tags
|
|
2813
|
-
userTags = [tags[0], tags[tags.length - 1]];
|
|
2814
|
-
}
|
|
2815
|
-
else {
|
|
2816
|
-
// if there is only one tag, pick it
|
|
2817
|
-
userTags = [...tags];
|
|
2818
|
-
}
|
|
2819
|
-
this.userTags.setValue(userTags);
|
|
2820
|
-
});
|
|
2821
|
-
}
|
|
2822
|
-
ngOnDestroy() {
|
|
2823
|
-
this._sub?.unsubscribe();
|
|
2824
|
-
}
|
|
2825
|
-
copyGraph() {
|
|
2826
|
-
const graph = this.graph();
|
|
2827
|
-
if (!graph) {
|
|
2828
|
-
return;
|
|
2829
|
-
}
|
|
2830
|
-
this._clipboard.copy(graph);
|
|
2831
|
-
this._snackbar.open('Graph copied to clipboard', 'OK', {
|
|
2832
|
-
duration: 2000,
|
|
2833
|
-
});
|
|
2834
|
-
}
|
|
2835
|
-
openExternalEditor() {
|
|
2836
|
-
let url = this._settings.get('graphviz-editor', 'https://dreampuf.github.io/GraphvizOnline?engine=dot#CODE');
|
|
2837
|
-
if (!url) {
|
|
2838
|
-
return;
|
|
2839
|
-
}
|
|
2840
|
-
const graph = this.graph();
|
|
2841
|
-
if (!graph) {
|
|
2842
|
-
return;
|
|
2843
|
-
}
|
|
2844
|
-
if (url.indexOf('CODE') > -1) {
|
|
2845
|
-
url = url.replace('CODE', encodeURIComponent(graph));
|
|
2846
|
-
}
|
|
2847
|
-
window.open(url, '_blank');
|
|
2848
|
-
}
|
|
2849
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: ChainViewComponent, deps: [{ token: GveGraphvizService }, { token: SettingsService }, { token: i2$2.Clipboard }, { token: i4$2.MatSnackBar }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2850
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.10", type: ChainViewComponent, isStandalone: true, selector: "gve-chain-view", inputs: { chain: { classPropertyName: "chain", publicName: "chain", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, selectedTags: { classPropertyName: "selectedTags", publicName: "selectedTags", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedTags: "selectedTagsChange" }, ngImport: i0, template: "<div id=\"container\">\r\n <div>\r\n <ngx-viz [code]=\"graph()\" />\r\n </div>\r\n <div class=\"button-row\">\r\n <!-- select -->\r\n <mat-form-field>\r\n <mat-label>tags</mat-label>\r\n <mat-select [formControl]=\"userTags\" multiple>\r\n @for (tag of tags(); track tag) {\r\n <mat-option [value]=\"tag\">{{ tag }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n <!-- copy -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Copy graphviz code\"\r\n [disabled]=\"!graph()\"\r\n (click)=\"copyGraph()\"\r\n >\r\n <mat-icon>content_copy</mat-icon>\r\n </button>\r\n <!--editor -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Open in editor\"\r\n [disabled]=\"!graph()\"\r\n (click)=\"openExternalEditor()\"\r\n >\r\n <mat-icon>launch</mat-icon>\r\n </button>\r\n </div>\r\n</div>\r\n", styles: ["div#container{border:1px solid silver;border-radius:6px;padding:4px}.button-row{display:flex;align-items:center;flex-wrap:wrap}.button-row *{flex:0 0 auto}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.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: i6.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: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: VizComponent, selector: "ngx-viz", inputs: ["code"] }] }); }
|
|
2851
|
-
}
|
|
2852
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: ChainViewComponent, decorators: [{
|
|
2853
|
-
type: Component,
|
|
2854
|
-
args: [{ selector: 'gve-chain-view', imports: [
|
|
2855
|
-
ReactiveFormsModule,
|
|
2856
|
-
MatButtonModule,
|
|
2857
|
-
MatFormFieldModule,
|
|
2858
|
-
MatIconModule,
|
|
2859
|
-
MatSelectModule,
|
|
2860
|
-
MatTooltipModule,
|
|
2861
|
-
VizComponent
|
|
2862
|
-
], schemas: [CUSTOM_ELEMENTS_SCHEMA], template: "<div id=\"container\">\r\n <div>\r\n <ngx-viz [code]=\"graph()\" />\r\n </div>\r\n <div class=\"button-row\">\r\n <!-- select -->\r\n <mat-form-field>\r\n <mat-label>tags</mat-label>\r\n <mat-select [formControl]=\"userTags\" multiple>\r\n @for (tag of tags(); track tag) {\r\n <mat-option [value]=\"tag\">{{ tag }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n <!-- copy -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Copy graphviz code\"\r\n [disabled]=\"!graph()\"\r\n (click)=\"copyGraph()\"\r\n >\r\n <mat-icon>content_copy</mat-icon>\r\n </button>\r\n <!--editor -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Open in editor\"\r\n [disabled]=\"!graph()\"\r\n (click)=\"openExternalEditor()\"\r\n >\r\n <mat-icon>launch</mat-icon>\r\n </button>\r\n </div>\r\n</div>\r\n", styles: ["div#container{border:1px solid silver;border-radius:6px;padding:4px}.button-row{display:flex;align-items:center;flex-wrap:wrap}.button-row *{flex:0 0 auto}\n"] }]
|
|
2863
|
-
}], ctorParameters: () => [{ type: GveGraphvizService }, { type: SettingsService }, { type: i2$2.Clipboard }, { type: i4$2.MatSnackBar }], propDecorators: { chain: [{ type: i0.Input, args: [{ isSignal: true, alias: "chain", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], selectedTags: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedTags", required: false }] }, { type: i0.Output, args: ["selectedTagsChange"] }] } });
|
|
2864
|
-
|
|
2865
|
-
/**
|
|
2866
|
-
* 🔑 `gve-ln-heights-editor`
|
|
2867
|
-
*
|
|
2868
|
-
* A component to edit line heights.
|
|
2869
|
-
* Used by the `gve-snapshot-editor` component.
|
|
2870
|
-
*
|
|
2871
|
-
* - ▶️ `lineCount` (`number`): the number of lines.
|
|
2872
|
-
* - ▶️ `heights` (`Record<number, number>`): the line heights.
|
|
2873
|
-
* - 🔥 `heightsChange` (`EventEmitter<Record<number, number>>`): the event
|
|
2874
|
-
* emitted when the heights change.
|
|
2875
|
-
*/
|
|
2876
|
-
class LnHeightsEditorComponent {
|
|
2877
|
-
constructor(formBuilder) {
|
|
2878
|
-
/**
|
|
2879
|
-
* The total number of lines in the text.
|
|
2880
|
-
*/
|
|
2881
|
-
this.lineCount = input(0, ...(ngDevMode ? [{ debugName: "lineCount" }] : []));
|
|
2882
|
-
/**
|
|
2883
|
-
* The heights map of the lines. Each key is a line number and the value is
|
|
2884
|
-
* the height of the line.
|
|
2885
|
-
*/
|
|
2886
|
-
this.heights = input(...(ngDevMode ? [undefined, { debugName: "heights" }] : []));
|
|
2887
|
-
/**
|
|
2888
|
-
* The event emitted when the heights change.
|
|
2889
|
-
*/
|
|
2890
|
-
this.heightsChange = output();
|
|
2891
|
-
// when lineCount changes, update lineNumbers
|
|
2892
|
-
this.lineNumbers = computed(() => Array.from({ length: this.lineCount() }, (_, i) => i + 1), ...(ngDevMode ? [{ debugName: "lineNumbers" }] : []));
|
|
2893
|
-
this.lineNumber = formBuilder.control(0, { nonNullable: true });
|
|
2894
|
-
this.height = formBuilder.control(0, { nonNullable: true });
|
|
2895
|
-
}
|
|
2896
|
-
pruneHeights() {
|
|
2897
|
-
// remove all the heigths with value=0
|
|
2898
|
-
this._heights = Object.fromEntries(Object.entries(this._heights || {}).filter(([_, v]) => v !== 0));
|
|
2899
|
-
// if heights are now empty, set to undefined
|
|
2900
|
-
if (Object.keys(this._heights).length === 0) {
|
|
2901
|
-
this._heights = undefined;
|
|
2902
|
-
}
|
|
2903
|
-
}
|
|
2904
|
-
ngOnInit() {
|
|
2905
|
-
this._subs = [];
|
|
2906
|
-
// update heights[ln] when height changes and emit heightsChange
|
|
2907
|
-
this._subs.push(this.height.valueChanges
|
|
2908
|
-
.pipe(distinctUntilChanged(), debounceTime(200))
|
|
2909
|
-
.subscribe((value) => {
|
|
2910
|
-
if (!this._heights) {
|
|
2911
|
-
this._heights = {};
|
|
2912
|
-
}
|
|
2913
|
-
const ln = this.lineNumber.value;
|
|
2914
|
-
const newHeights = { ...(this._heights || {}) };
|
|
2915
|
-
newHeights[ln] = value;
|
|
2916
|
-
this._heights = Object.fromEntries(Object.entries(newHeights).filter(([_, v]) => v !== 0));
|
|
2917
|
-
if (Object.keys(this._heights).length === 0) {
|
|
2918
|
-
this._heights = undefined;
|
|
2919
|
-
}
|
|
2920
|
-
this.heightsChange.emit(this._heights);
|
|
2921
|
-
}));
|
|
2922
|
-
// update height when line number changes
|
|
2923
|
-
this._subs.push(this.lineNumber.valueChanges
|
|
2924
|
-
.pipe(distinctUntilChanged(), debounceTime(200))
|
|
2925
|
-
.subscribe((value) => {
|
|
2926
|
-
if (!this._heights)
|
|
2927
|
-
return;
|
|
2928
|
-
this.height.setValue(this._heights[value] || 0);
|
|
2929
|
-
}));
|
|
2930
|
-
}
|
|
2931
|
-
ngOnDestroy() {
|
|
2932
|
-
this._subs?.forEach((sub) => sub.unsubscribe());
|
|
2933
|
-
}
|
|
2934
|
-
reset() {
|
|
2935
|
-
this._heights = undefined;
|
|
2936
|
-
this.heightsChange.emit(undefined);
|
|
2418
|
+
onStepChange(step) {
|
|
2419
|
+
this.selectionFeatures.set([]);
|
|
2420
|
+
if (step) {
|
|
2421
|
+
// setting the tag will trigger the step change
|
|
2422
|
+
this.tag.setValue(step.outputTag);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
pickRange() {
|
|
2426
|
+
if (this.selectionRange()) {
|
|
2427
|
+
this.rangePick.emit(this.selectionRange());
|
|
2428
|
+
}
|
|
2937
2429
|
}
|
|
2938
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2939
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
2430
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: ChainResultViewComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2431
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", 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:silver;border:1px solid silver;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:#3e92cc;color:#fff;text-align:center;border:1px solid #3e92cc;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 }); }
|
|
2940
2432
|
}
|
|
2941
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2433
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: ChainResultViewComponent, decorators: [{
|
|
2942
2434
|
type: Component,
|
|
2943
|
-
args: [{ selector: 'gve-
|
|
2435
|
+
args: [{ selector: 'gve-chain-result-view', imports: [
|
|
2944
2436
|
ReactiveFormsModule,
|
|
2945
2437
|
MatButtonModule,
|
|
2946
2438
|
MatFormFieldModule,
|
|
2947
2439
|
MatIconModule,
|
|
2948
|
-
MatInputModule,
|
|
2949
2440
|
MatSelectModule,
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2441
|
+
FeatureSetViewComponent,
|
|
2442
|
+
StepsMapComponent,
|
|
2443
|
+
BaseTextViewComponent,
|
|
2444
|
+
MatTooltip
|
|
2445
|
+
], 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:silver;border:1px solid silver;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:#3e92cc;color:#fff;text-align:center;border:1px solid #3e92cc;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"] }]
|
|
2446
|
+
}], 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"] }] } });
|
|
2953
2447
|
|
|
2954
2448
|
/**
|
|
2955
2449
|
* 🔑 `gve-snapshot-text-editor`
|
|
@@ -2961,9 +2455,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
2961
2455
|
* - 🔥 `textChange` (`CharNode[]`): event emitted when text changes.
|
|
2962
2456
|
*/
|
|
2963
2457
|
class SnapshotTextEditorComponent {
|
|
2964
|
-
constructor(formBuilder,
|
|
2458
|
+
constructor(formBuilder, _snackbar,
|
|
2965
2459
|
// this component can be used as a dialog
|
|
2966
2460
|
dialogRef, data) {
|
|
2461
|
+
this._snackbar = _snackbar;
|
|
2967
2462
|
this.dialogRef = dialogRef;
|
|
2968
2463
|
this.data = data;
|
|
2969
2464
|
this.text = model([], ...(ngDevMode ? [{ debugName: "text" }] : []));
|
|
@@ -2992,6 +2487,11 @@ class SnapshotTextEditorComponent {
|
|
|
2992
2487
|
close() {
|
|
2993
2488
|
this.dialogRef?.close();
|
|
2994
2489
|
}
|
|
2490
|
+
copyToClipboard() {
|
|
2491
|
+
const json = JSON.stringify(GveBaseTextService.stringToBaseChars(this.userText.value));
|
|
2492
|
+
navigator.clipboard.writeText(json);
|
|
2493
|
+
this._snackbar.open('Copied JSON code to clipboard', undefined, { duration: 2000 });
|
|
2494
|
+
}
|
|
2995
2495
|
save() {
|
|
2996
2496
|
if (this.form.invalid) {
|
|
2997
2497
|
return;
|
|
@@ -3000,13 +2500,13 @@ class SnapshotTextEditorComponent {
|
|
|
3000
2500
|
if (!this.userText.value) {
|
|
3001
2501
|
return;
|
|
3002
2502
|
}
|
|
3003
|
-
this.text.set(
|
|
2503
|
+
this.text.set(GveBaseTextService.stringToBaseChars(this.userText.value));
|
|
3004
2504
|
this.dialogRef?.close(this.text());
|
|
3005
2505
|
}
|
|
3006
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
3007
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
2506
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", 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 }); }
|
|
2507
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", 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\">\r\n <h2>Set Base Text</h2>\r\n </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 <mat-label>base text</mat-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}\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"] }] }); }
|
|
3008
2508
|
}
|
|
3009
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2509
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: SnapshotTextEditorComponent, decorators: [{
|
|
3010
2510
|
type: Component,
|
|
3011
2511
|
args: [{ selector: 'gve-snapshot-text-editor', imports: [
|
|
3012
2512
|
ReactiveFormsModule,
|
|
@@ -3014,8 +2514,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
3014
2514
|
MatFormFieldModule,
|
|
3015
2515
|
MatIconModule,
|
|
3016
2516
|
MatInputModule,
|
|
3017
|
-
], template: "<div [style.padding]=\"dialogRef ? '8px' : '0'\">\r\n @if (dialogRef) {\r\n
|
|
3018
|
-
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type:
|
|
2517
|
+
], template: "<div [style.padding]=\"dialogRef ? '8px' : '0'\">\r\n @if (dialogRef) {\r\n <div id=\"heading\">\r\n <h2>Set Base Text</h2>\r\n </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 <mat-label>base text</mat-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}\n"] }]
|
|
2518
|
+
}], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: i2$3.MatSnackBar }, { type: i3$2.MatDialogRef, decorators: [{
|
|
3019
2519
|
type: Optional
|
|
3020
2520
|
}] }, { type: undefined, decorators: [{
|
|
3021
2521
|
type: Optional
|
|
@@ -3047,6 +2547,12 @@ class SnapshotEditorComponent {
|
|
|
3047
2547
|
this._dialogService = _dialogService;
|
|
3048
2548
|
this._snackbar = _snackbar;
|
|
3049
2549
|
this._nanoid = customAlphabet('1234567890abcdef', 10);
|
|
2550
|
+
/**
|
|
2551
|
+
* Reference to the snapshot rendition web component.
|
|
2552
|
+
* Using viewChild signal to handle conditionally rendered element -
|
|
2553
|
+
* the element only exists when result() is truthy.
|
|
2554
|
+
*/
|
|
2555
|
+
this.renditionRef = viewChild('rendition', ...(ngDevMode ? [{ debugName: "renditionRef" }] : []));
|
|
3050
2556
|
/**
|
|
3051
2557
|
* The snapshot to edit.
|
|
3052
2558
|
*/
|
|
@@ -3073,24 +2579,24 @@ class SnapshotEditorComponent {
|
|
|
3073
2579
|
*/
|
|
3074
2580
|
this.featureDefs = input(...(ngDevMode ? [undefined, { debugName: "featureDefs" }] : []));
|
|
3075
2581
|
/**
|
|
3076
|
-
*
|
|
2582
|
+
* The IDs of the features that are multi-valued. If a feature being
|
|
2583
|
+
* edited is in this list, the feature editor will allow adding multiple
|
|
2584
|
+
* values to it. Passed down to feature editors.
|
|
3077
2585
|
*/
|
|
3078
|
-
this.
|
|
2586
|
+
this.multiValuedFeatureIds = input(...(ngDevMode ? [undefined, { debugName: "multiValuedFeatureIds" }] : []));
|
|
3079
2587
|
/**
|
|
3080
|
-
*
|
|
2588
|
+
* Settings for rendering the snapshot. These are passed to the snapshot rendition
|
|
2589
|
+
* component.
|
|
3081
2590
|
*/
|
|
3082
|
-
this.
|
|
2591
|
+
this.renditionSettings = input(DEFAULT_SETTINGS, ...(ngDevMode ? [{ debugName: "renditionSettings" }] : []));
|
|
3083
2592
|
/**
|
|
3084
2593
|
* Emitted when the user cancels the snapshot editing.
|
|
3085
2594
|
*/
|
|
3086
2595
|
this.snapshotCancel = output();
|
|
3087
2596
|
this.showChain = new FormControl(false, { nonNullable: true });
|
|
3088
2597
|
this.featDetails = new FormControl(false, { nonNullable: true });
|
|
2598
|
+
this.autoRun = new FormControl(true, { nonNullable: true });
|
|
3089
2599
|
this.chain = signal(undefined, ...(ngDevMode ? [{ debugName: "chain" }] : []));
|
|
3090
|
-
// list of operations output tags
|
|
3091
|
-
this.opTags = signal([], ...(ngDevMode ? [{ debugName: "opTags" }] : []));
|
|
3092
|
-
// list of operation diplomatic.g element IDs
|
|
3093
|
-
this.opElementIds = signal([], ...(ngDevMode ? [{ debugName: "opElementIds" }] : []));
|
|
3094
2600
|
// the currently picked base text range
|
|
3095
2601
|
this.textRange = signal(undefined, ...(ngDevMode ? [{ debugName: "textRange" }] : []));
|
|
3096
2602
|
// the lines count for the current base text
|
|
@@ -3111,68 +2617,23 @@ class SnapshotEditorComponent {
|
|
|
3111
2617
|
6: 'swap',
|
|
3112
2618
|
7: 'annotate',
|
|
3113
2619
|
};
|
|
3114
|
-
//
|
|
3115
|
-
this.
|
|
3116
|
-
this.
|
|
3117
|
-
this.initialStepIndex = -1;
|
|
3118
|
-
// general
|
|
3119
|
-
this.width = new FormControl(800, { nonNullable: true });
|
|
3120
|
-
this.height = new FormControl(600, { nonNullable: true });
|
|
3121
|
-
this.style = new FormControl(null);
|
|
2620
|
+
// operations execution result
|
|
2621
|
+
this.result = signal(undefined, ...(ngDevMode ? [{ debugName: "result" }] : []));
|
|
2622
|
+
this.resultOperationId = signal(undefined, ...(ngDevMode ? [{ debugName: "resultOperationId" }] : []));
|
|
2623
|
+
this.initialStepIndex = signal(-1, ...(ngDevMode ? [{ debugName: "initialStepIndex" }] : []));
|
|
3122
2624
|
// base text
|
|
3123
2625
|
this.baseText = new FormControl([], {
|
|
3124
2626
|
nonNullable: true,
|
|
3125
2627
|
validators: [Validators.required],
|
|
3126
2628
|
});
|
|
3127
|
-
this.offsetX = new FormControl(0, { nonNullable: true });
|
|
3128
|
-
this.offsetY = new FormControl(0, { nonNullable: true });
|
|
3129
|
-
this.lineHeightOffset = new FormControl(DEFAULT_SVG_BASE_TEXT_OPTIONS.lineHeightOffset, { nonNullable: true });
|
|
3130
|
-
this.lnHeights = new FormControl(null);
|
|
3131
|
-
this.charSpacingOffset = new FormControl(0, { nonNullable: true });
|
|
3132
|
-
this.spcWidthOffset = new FormControl(0, { nonNullable: true });
|
|
3133
|
-
this.textStyle = new FormControl(null);
|
|
3134
2629
|
// operations
|
|
3135
2630
|
this.operations = new FormControl([], {
|
|
3136
2631
|
nonNullable: true,
|
|
3137
2632
|
});
|
|
3138
|
-
this.opStyle = new FormControl(null);
|
|
3139
|
-
// image
|
|
3140
|
-
this.imageUrl = new FormControl(null);
|
|
3141
|
-
this.imageOpacity = new FormControl(1, { nonNullable: true });
|
|
3142
|
-
this.imageX = new FormControl(0, { nonNullable: true });
|
|
3143
|
-
this.imageY = new FormControl(0, { nonNullable: true });
|
|
3144
|
-
this.imageWidth = new FormControl(0, { nonNullable: true });
|
|
3145
|
-
this.imageHeight = new FormControl(0, { nonNullable: true });
|
|
3146
|
-
this.defs = new FormControl(null);
|
|
3147
|
-
// timelines
|
|
3148
|
-
this.timelines = new FormControl([], {
|
|
3149
|
-
nonNullable: true,
|
|
3150
|
-
});
|
|
3151
2633
|
// form
|
|
3152
2634
|
this.form = formBuilder.group({
|
|
3153
|
-
width: this.width,
|
|
3154
|
-
height: this.height,
|
|
3155
|
-
style: this.style,
|
|
3156
2635
|
baseText: this.baseText,
|
|
3157
|
-
offsetX: this.offsetX,
|
|
3158
|
-
offsetY: this.offsetY,
|
|
3159
|
-
lineHeightOffset: this.lineHeightOffset,
|
|
3160
|
-
lnHeights: this.lnHeights,
|
|
3161
|
-
charSpacingOffset: this.charSpacingOffset,
|
|
3162
|
-
spcWidthOffset: this.spcWidthOffset,
|
|
3163
|
-
textStyle: this.textStyle,
|
|
3164
2636
|
operations: this.operations,
|
|
3165
|
-
opStyle: this.opStyle,
|
|
3166
|
-
// image
|
|
3167
|
-
imageUrl: this.imageUrl,
|
|
3168
|
-
imageOpacity: this.imageOpacity,
|
|
3169
|
-
imageX: this.imageX,
|
|
3170
|
-
imageY: this.imageY,
|
|
3171
|
-
imageWidth: this.imageWidth,
|
|
3172
|
-
imageHeight: this.imageHeight,
|
|
3173
|
-
defs: this.defs,
|
|
3174
|
-
// timelines
|
|
3175
|
-
timelines: this.timelines,
|
|
3176
2637
|
});
|
|
3177
2638
|
// when snapshot changes, update the form
|
|
3178
2639
|
effect(() => {
|
|
@@ -3181,6 +2642,55 @@ class SnapshotEditorComponent {
|
|
|
3181
2642
|
this.runToLast();
|
|
3182
2643
|
}, 0);
|
|
3183
2644
|
});
|
|
2645
|
+
// when rendition element becomes available (conditionally rendered),
|
|
2646
|
+
// initialize its settings and event listeners
|
|
2647
|
+
effect(() => {
|
|
2648
|
+
const rendition = this.renditionRef()?.nativeElement;
|
|
2649
|
+
if (rendition) {
|
|
2650
|
+
this.initRendition(rendition);
|
|
2651
|
+
}
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
/**
|
|
2655
|
+
* Initialize rendition settings and event listeners when the element becomes available.
|
|
2656
|
+
* Called via effect since the element is conditionally rendered.
|
|
2657
|
+
*/
|
|
2658
|
+
initRendition(rendition) {
|
|
2659
|
+
console.log('GveSnapshotRendition.version:', GveSnapshotRendition.version);
|
|
2660
|
+
// set initial settings
|
|
2661
|
+
this.applyRenditionSettings();
|
|
2662
|
+
// listen to version change events
|
|
2663
|
+
rendition.addEventListener('versionTagChange', (event) => {
|
|
2664
|
+
const customEvent = event;
|
|
2665
|
+
console.log('Version changed:', customEvent.detail);
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
/**
|
|
2669
|
+
* Apply settings to the rendition component.
|
|
2670
|
+
*/
|
|
2671
|
+
applyRenditionSettings() {
|
|
2672
|
+
const rendition = this.renditionRef()?.nativeElement;
|
|
2673
|
+
if (!rendition) {
|
|
2674
|
+
return;
|
|
2675
|
+
}
|
|
2676
|
+
const settings = this.renditionSettings();
|
|
2677
|
+
settings.debug = this.debug() || false;
|
|
2678
|
+
rendition.settings = settings;
|
|
2679
|
+
}
|
|
2680
|
+
/**
|
|
2681
|
+
* Update the snapshot rendition component with the current result data.
|
|
2682
|
+
*/
|
|
2683
|
+
updateRendition() {
|
|
2684
|
+
const rendition = this.renditionRef()?.nativeElement;
|
|
2685
|
+
const result = this.result();
|
|
2686
|
+
if (rendition && result) {
|
|
2687
|
+
// update settings with latest rendition settings
|
|
2688
|
+
this.applyRenditionSettings();
|
|
2689
|
+
// supply the base text (not returned by API, must be provided by caller)
|
|
2690
|
+
result.text = this.baseText.value;
|
|
2691
|
+
// set data
|
|
2692
|
+
rendition.data = result;
|
|
2693
|
+
}
|
|
3184
2694
|
}
|
|
3185
2695
|
/**
|
|
3186
2696
|
* Set the view data for the snapshot view.
|
|
@@ -3195,65 +2705,22 @@ class SnapshotEditorComponent {
|
|
|
3195
2705
|
if (!snapshot) {
|
|
3196
2706
|
snapshot = this.getSnapshot();
|
|
3197
2707
|
}
|
|
3198
|
-
// update view data
|
|
3199
|
-
this.viewTitle = title;
|
|
3200
|
-
this.visualInfo = undefined;
|
|
3201
|
-
this.viewData = {
|
|
3202
|
-
snapshot: snapshot,
|
|
3203
|
-
options: {
|
|
3204
|
-
debug: this.debug(),
|
|
3205
|
-
delayedRender: true,
|
|
3206
|
-
showRulers: true,
|
|
3207
|
-
showGrid: true,
|
|
3208
|
-
panZoom: true,
|
|
3209
|
-
transparentIds: this._transparentIds,
|
|
3210
|
-
},
|
|
3211
|
-
};
|
|
3212
|
-
console.log('view data: ', this.viewData);
|
|
3213
2708
|
}
|
|
3214
2709
|
updateForm(snapshot) {
|
|
3215
|
-
this.
|
|
3216
|
-
this.initialStepIndex = -1;
|
|
2710
|
+
this.initialStepIndex.set(-1);
|
|
3217
2711
|
if (!snapshot) {
|
|
3218
2712
|
this.form.reset();
|
|
3219
2713
|
}
|
|
3220
2714
|
else {
|
|
3221
|
-
this.width.setValue(snapshot.size.width);
|
|
3222
|
-
this.height.setValue(snapshot.size.height);
|
|
3223
|
-
this.style.setValue(snapshot.style || null);
|
|
3224
|
-
this.imageUrl.setValue(snapshot.image?.url || null);
|
|
3225
|
-
this.imageOpacity.setValue(snapshot.image?.opacity || 0);
|
|
3226
|
-
this.imageX.setValue(snapshot.image?.canvas?.x || 0);
|
|
3227
|
-
this.imageY.setValue(snapshot.image?.canvas?.y || 0);
|
|
3228
|
-
this.imageWidth.setValue(snapshot.image?.canvas?.width || 0);
|
|
3229
|
-
this.imageHeight.setValue(snapshot.image?.canvas?.height || 0);
|
|
3230
|
-
this.defs.setValue(snapshot.defs || null);
|
|
3231
2715
|
if (Array.isArray(snapshot.text)) {
|
|
3232
2716
|
this.baseText.setValue(snapshot.text);
|
|
3233
2717
|
}
|
|
3234
2718
|
else {
|
|
3235
|
-
this.baseText.setValue(
|
|
2719
|
+
this.baseText.setValue(GveBaseTextService.stringToBaseChars(snapshot.text));
|
|
3236
2720
|
}
|
|
3237
|
-
this.offsetX.setValue(snapshot.textOptions?.offset?.x || 0);
|
|
3238
|
-
this.offsetY.setValue(snapshot.textOptions?.offset?.y || 0);
|
|
3239
|
-
this.lineHeightOffset.setValue(snapshot.textOptions?.lineHeightOffset);
|
|
3240
|
-
this.lnHeights.setValue(snapshot.textOptions?.minLineHeights || null);
|
|
3241
|
-
this.charSpacingOffset.setValue(snapshot.textOptions?.charSpacingOffset);
|
|
3242
|
-
this.spcWidthOffset.setValue(snapshot.textOptions?.spcWidthOffset);
|
|
3243
|
-
this.textStyle.setValue(snapshot.textStyle || null);
|
|
3244
2721
|
this.operations.setValue(snapshot.operations);
|
|
3245
|
-
this.opStyle.setValue(snapshot.opStyle || null);
|
|
3246
|
-
// timelines
|
|
3247
|
-
const timelines = [];
|
|
3248
|
-
if (snapshot.timelines) {
|
|
3249
|
-
for (let tag in snapshot.timelines) {
|
|
3250
|
-
timelines.push(snapshot.timelines[tag]);
|
|
3251
|
-
}
|
|
3252
|
-
}
|
|
3253
|
-
this.timelines.setValue(timelines);
|
|
3254
2722
|
}
|
|
3255
2723
|
this.updateLineCount(this.baseText.value);
|
|
3256
|
-
this.updateOpLists();
|
|
3257
2724
|
this.form.markAsPristine();
|
|
3258
2725
|
}
|
|
3259
2726
|
// #region base text
|
|
@@ -3276,8 +2743,6 @@ class SnapshotEditorComponent {
|
|
|
3276
2743
|
this.updateLineCount(text);
|
|
3277
2744
|
// remove all operations and update the view data
|
|
3278
2745
|
this.removeAllOperations();
|
|
3279
|
-
// remove all timelines
|
|
3280
|
-
this.timelines.reset();
|
|
3281
2746
|
}
|
|
3282
2747
|
});
|
|
3283
2748
|
}
|
|
@@ -3306,27 +2771,6 @@ class SnapshotEditorComponent {
|
|
|
3306
2771
|
}
|
|
3307
2772
|
// #endregion
|
|
3308
2773
|
// #region operations
|
|
3309
|
-
/**
|
|
3310
|
-
* Update the lists of operation output tags and element IDs by collecting
|
|
3311
|
-
* all the operation tags and the IDs of the elements in their diplomatic.g
|
|
3312
|
-
* SVG code if any.
|
|
3313
|
-
*/
|
|
3314
|
-
updateOpLists() {
|
|
3315
|
-
const tags = new Set();
|
|
3316
|
-
const ids = new Set();
|
|
3317
|
-
let n = 0;
|
|
3318
|
-
for (const op of this.operations.value) {
|
|
3319
|
-
// output tag
|
|
3320
|
-
n++;
|
|
3321
|
-
tags.add(op.outputTag || `v${n}`);
|
|
3322
|
-
// element IDs
|
|
3323
|
-
if (op.diplomatics?.g) {
|
|
3324
|
-
this.parseSvgIds(op.diplomatics.g)?.forEach((id) => ids.add(id));
|
|
3325
|
-
}
|
|
3326
|
-
}
|
|
3327
|
-
this.opTags.set([...tags]);
|
|
3328
|
-
this.opElementIds.set([...ids]);
|
|
3329
|
-
}
|
|
3330
2774
|
/**
|
|
3331
2775
|
* Edit a new operation.
|
|
3332
2776
|
*/
|
|
@@ -3348,7 +2792,7 @@ class SnapshotEditorComponent {
|
|
|
3348
2792
|
*/
|
|
3349
2793
|
editOperation(index) {
|
|
3350
2794
|
this.editedOpIndex.set(index);
|
|
3351
|
-
this.editedOp.set(
|
|
2795
|
+
this.editedOp.set(structuredClone(this.operations.value[index]));
|
|
3352
2796
|
}
|
|
3353
2797
|
/**
|
|
3354
2798
|
* Close the currently edited operation.
|
|
@@ -3389,10 +2833,10 @@ class SnapshotEditorComponent {
|
|
|
3389
2833
|
this.operations.setValue(operations);
|
|
3390
2834
|
this.operations.markAsDirty();
|
|
3391
2835
|
this.operations.updateValueAndValidity();
|
|
3392
|
-
//
|
|
3393
|
-
this.
|
|
3394
|
-
|
|
3395
|
-
|
|
2836
|
+
// run to the edited operation if auto-run is enabled
|
|
2837
|
+
if (this.autoRun.value) {
|
|
2838
|
+
this.runTo(i);
|
|
2839
|
+
}
|
|
3396
2840
|
// close the edited operation
|
|
3397
2841
|
this.closeEditedOperation();
|
|
3398
2842
|
}
|
|
@@ -3410,8 +2854,8 @@ class SnapshotEditorComponent {
|
|
|
3410
2854
|
this.closeEditedOperation();
|
|
3411
2855
|
}
|
|
3412
2856
|
// reset the result operation ID if it is the one being deleted
|
|
3413
|
-
if (this.resultOperationId === this.operations.value[index].id) {
|
|
3414
|
-
this.resultOperationId
|
|
2857
|
+
if (this.resultOperationId() === this.operations.value[index].id) {
|
|
2858
|
+
this.resultOperationId.set(undefined);
|
|
3415
2859
|
}
|
|
3416
2860
|
// delete the operation and update the form control
|
|
3417
2861
|
const operations = [...this.operations.value];
|
|
@@ -3419,8 +2863,6 @@ class SnapshotEditorComponent {
|
|
|
3419
2863
|
this.operations.setValue(operations);
|
|
3420
2864
|
this.operations.markAsDirty();
|
|
3421
2865
|
this.operations.updateValueAndValidity();
|
|
3422
|
-
// update the operation lists
|
|
3423
|
-
this.updateOpLists();
|
|
3424
2866
|
// update the view data
|
|
3425
2867
|
setTimeout(() => {
|
|
3426
2868
|
this.runToLast();
|
|
@@ -3428,6 +2870,58 @@ class SnapshotEditorComponent {
|
|
|
3428
2870
|
}
|
|
3429
2871
|
});
|
|
3430
2872
|
}
|
|
2873
|
+
/**
|
|
2874
|
+
* Duplicate the operation at the specified index, inserting the copy
|
|
2875
|
+
* immediately after the original.
|
|
2876
|
+
* @param index The index of the operation to duplicate.
|
|
2877
|
+
*/
|
|
2878
|
+
duplicateOperation(index) {
|
|
2879
|
+
const operations = [...this.operations.value];
|
|
2880
|
+
const clone = structuredClone(operations[index]);
|
|
2881
|
+
clone.id = this._nanoid();
|
|
2882
|
+
operations.splice(index + 1, 0, clone);
|
|
2883
|
+
this.operations.setValue(operations);
|
|
2884
|
+
this.operations.markAsDirty();
|
|
2885
|
+
this.operations.updateValueAndValidity();
|
|
2886
|
+
}
|
|
2887
|
+
/**
|
|
2888
|
+
* Move the operation at the specified index up by one position.
|
|
2889
|
+
* @param index The index of the operation to move up.
|
|
2890
|
+
*/
|
|
2891
|
+
moveOperationUp(index) {
|
|
2892
|
+
if (index < 1) {
|
|
2893
|
+
return;
|
|
2894
|
+
}
|
|
2895
|
+
const operations = [...this.operations.value];
|
|
2896
|
+
const op = operations.splice(index, 1)[0];
|
|
2897
|
+
operations.splice(index - 1, 0, op);
|
|
2898
|
+
this.operations.setValue(operations);
|
|
2899
|
+
this.operations.markAsDirty();
|
|
2900
|
+
this.operations.updateValueAndValidity();
|
|
2901
|
+
}
|
|
2902
|
+
/**
|
|
2903
|
+
* Move the operation at the specified index down by one position.
|
|
2904
|
+
* @param index The index of the operation to move down.
|
|
2905
|
+
*/
|
|
2906
|
+
moveOperationDown(index) {
|
|
2907
|
+
const operations = [...this.operations.value];
|
|
2908
|
+
if (index >= operations.length - 1) {
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
const op = operations.splice(index, 1)[0];
|
|
2912
|
+
operations.splice(index + 1, 0, op);
|
|
2913
|
+
this.operations.setValue(operations);
|
|
2914
|
+
this.operations.markAsDirty();
|
|
2915
|
+
this.operations.updateValueAndValidity();
|
|
2916
|
+
}
|
|
2917
|
+
copyOperationsJson() {
|
|
2918
|
+
const json = JSON.stringify(this.operations.value, null, 2);
|
|
2919
|
+
navigator.clipboard.writeText(json).then(() => {
|
|
2920
|
+
this._snackbar.open('Operations JSON copied to clipboard', 'OK', {
|
|
2921
|
+
duration: 3000,
|
|
2922
|
+
});
|
|
2923
|
+
});
|
|
2924
|
+
}
|
|
3431
2925
|
/**
|
|
3432
2926
|
* Parse the operations from their text and append them to the current
|
|
3433
2927
|
* snapshot operations.
|
|
@@ -3446,8 +2940,6 @@ class SnapshotEditorComponent {
|
|
|
3446
2940
|
this.operations.setValue(operations);
|
|
3447
2941
|
this.operations.markAsDirty();
|
|
3448
2942
|
this.operations.updateValueAndValidity();
|
|
3449
|
-
// update the operation lists
|
|
3450
|
-
this.updateOpLists();
|
|
3451
2943
|
// update the view data
|
|
3452
2944
|
setTimeout(() => {
|
|
3453
2945
|
this.runToLast();
|
|
@@ -3459,15 +2951,13 @@ class SnapshotEditorComponent {
|
|
|
3459
2951
|
* Remove all the operations, close the edited operation and update the view data.
|
|
3460
2952
|
*/
|
|
3461
2953
|
removeAllOperations() {
|
|
3462
|
-
this.resultOperationId
|
|
2954
|
+
this.resultOperationId.set(undefined);
|
|
3463
2955
|
this.closeEditedOperation();
|
|
3464
2956
|
this.operations.reset();
|
|
3465
2957
|
this.operations.markAsDirty();
|
|
3466
2958
|
this.operations.updateValueAndValidity();
|
|
3467
|
-
this.opTags.set([]);
|
|
3468
|
-
this.opElementIds.set([]);
|
|
3469
2959
|
this.setViewData();
|
|
3470
|
-
this.result
|
|
2960
|
+
this.result.set(undefined);
|
|
3471
2961
|
}
|
|
3472
2962
|
/**
|
|
3473
2963
|
* Remove all the operations.
|
|
@@ -3591,16 +3081,10 @@ class SnapshotEditorComponent {
|
|
|
3591
3081
|
snapshot.operations = snapshot.operations.slice(0, index + 1);
|
|
3592
3082
|
}
|
|
3593
3083
|
// update result operation ID
|
|
3594
|
-
this.resultOperationId
|
|
3595
|
-
// extract the IDs from the last operation's diplomatics and filter
|
|
3596
|
-
// them so that only the ones ending with _t are kept. By convention,
|
|
3597
|
-
// all the IDs ending with this suffix are to be made invisible at
|
|
3598
|
-
// their first rendition (opacity=0). An animation will then make
|
|
3599
|
-
// them visible.
|
|
3600
|
-
this._transparentIds = this.getTransparentIds(snapshot.operations[index].diplomatics?.g);
|
|
3084
|
+
this.resultOperationId.set(snapshot.operations[index].id);
|
|
3601
3085
|
// run the operations
|
|
3602
3086
|
this.busy.set(true);
|
|
3603
|
-
this.initialStepIndex
|
|
3087
|
+
this.initialStepIndex.set(index);
|
|
3604
3088
|
this._api
|
|
3605
3089
|
.runOperations(snapshot.text, snapshot.operations)
|
|
3606
3090
|
.subscribe({
|
|
@@ -3612,7 +3096,9 @@ class SnapshotEditorComponent {
|
|
|
3612
3096
|
}
|
|
3613
3097
|
// set the result
|
|
3614
3098
|
console.log('result:', wrapper.result);
|
|
3615
|
-
this.result
|
|
3099
|
+
this.result.set(wrapper.result);
|
|
3100
|
+
// update the rendition component
|
|
3101
|
+
this.updateRendition();
|
|
3616
3102
|
// supply the last operation output tag from its result step if not set
|
|
3617
3103
|
if (!lastOperation.outputTag) {
|
|
3618
3104
|
const step = wrapper.result.steps.find((s) => s.operation.id === lastOperation.id);
|
|
@@ -3622,14 +3108,6 @@ class SnapshotEditorComponent {
|
|
|
3622
3108
|
this.supplyOpNodes(snapshot, wrapper.result, lastOperation.outputTag);
|
|
3623
3109
|
// update the view data
|
|
3624
3110
|
this.setViewData(snapshot, lastOperation.outputTag);
|
|
3625
|
-
// play animation if any
|
|
3626
|
-
const tl = this.timelines.value.find((t) => t.tag === lastOperation.outputTag);
|
|
3627
|
-
if (tl) {
|
|
3628
|
-
// play the timeline
|
|
3629
|
-
setTimeout(() => {
|
|
3630
|
-
this.playTimeline(tl);
|
|
3631
|
-
}, 0);
|
|
3632
|
-
}
|
|
3633
3111
|
// get the chain model if requested
|
|
3634
3112
|
if (this.showChain.value) {
|
|
3635
3113
|
this.getChainAt(index);
|
|
@@ -3637,7 +3115,6 @@ class SnapshotEditorComponent {
|
|
|
3637
3115
|
},
|
|
3638
3116
|
error: (error) => {
|
|
3639
3117
|
console.error(error);
|
|
3640
|
-
this._transparentIds = undefined;
|
|
3641
3118
|
this._snackbar.open('Error running operations', 'OK');
|
|
3642
3119
|
},
|
|
3643
3120
|
complete: () => {
|
|
@@ -3656,7 +3133,7 @@ class SnapshotEditorComponent {
|
|
|
3656
3133
|
}
|
|
3657
3134
|
else {
|
|
3658
3135
|
this.setViewData();
|
|
3659
|
-
this.result
|
|
3136
|
+
this.result.set(undefined);
|
|
3660
3137
|
}
|
|
3661
3138
|
}
|
|
3662
3139
|
getChainAt(index, lastOperation) {
|
|
@@ -3691,7 +3168,6 @@ class SnapshotEditorComponent {
|
|
|
3691
3168
|
},
|
|
3692
3169
|
error: (error) => {
|
|
3693
3170
|
console.error(error);
|
|
3694
|
-
this._transparentIds = undefined;
|
|
3695
3171
|
this._snackbar.open('Error running operations', 'OK');
|
|
3696
3172
|
},
|
|
3697
3173
|
complete: () => {
|
|
@@ -3706,8 +3182,8 @@ class SnapshotEditorComponent {
|
|
|
3706
3182
|
* @param operation The operation being previewed.
|
|
3707
3183
|
*/
|
|
3708
3184
|
onOperationPreview(operation) {
|
|
3709
|
-
// no multiple previews or previewing a new operation
|
|
3710
|
-
if (this._previewing || this.editedOpIndex() < 0) {
|
|
3185
|
+
// no multiple previews or previewing a new operation or auto-run disabled
|
|
3186
|
+
if (this._previewing || this.editedOpIndex() < 0 || !this.autoRun.value) {
|
|
3711
3187
|
return;
|
|
3712
3188
|
}
|
|
3713
3189
|
this._previewing = true;
|
|
@@ -3717,28 +3193,14 @@ class SnapshotEditorComponent {
|
|
|
3717
3193
|
}, 0);
|
|
3718
3194
|
}
|
|
3719
3195
|
// #endregion
|
|
3720
|
-
playTimeline(tl) {
|
|
3721
|
-
const shadowRoot = this.snapshotView?.nativeElement?.shadowRoot;
|
|
3722
|
-
if (tl && shadowRoot) {
|
|
3723
|
-
console.log('play timeline', tl);
|
|
3724
|
-
this._renderer?.playTimeline(tl, undefined, shadowRoot);
|
|
3725
|
-
}
|
|
3726
|
-
else {
|
|
3727
|
-
if (!this.snapshotView) {
|
|
3728
|
-
console.warn('no snapshotView for timeline');
|
|
3729
|
-
}
|
|
3730
|
-
else {
|
|
3731
|
-
console.warn('no shadowRoot for timeline');
|
|
3732
|
-
}
|
|
3733
|
-
}
|
|
3734
|
-
}
|
|
3735
3196
|
/**
|
|
3736
3197
|
* Handle the event fired by the chain result view to pick a step.
|
|
3737
3198
|
*
|
|
3738
3199
|
* @param step The step to pick.
|
|
3739
3200
|
*/
|
|
3740
3201
|
onStepPick(step) {
|
|
3741
|
-
|
|
3202
|
+
const result = this.result();
|
|
3203
|
+
if (!result || this._stepPickFrozen) {
|
|
3742
3204
|
return;
|
|
3743
3205
|
}
|
|
3744
3206
|
// get base text snapshot
|
|
@@ -3749,87 +3211,11 @@ class SnapshotEditorComponent {
|
|
|
3749
3211
|
// their visuals creep into the snapshot view
|
|
3750
3212
|
snapshot.operations = snapshot.operations.slice(0, index + 1);
|
|
3751
3213
|
// update result operation ID
|
|
3752
|
-
this.resultOperationId
|
|
3753
|
-
// extract the IDs from the last operation's diplomatics and filter
|
|
3754
|
-
// them so that only the ones ending with _t are kept. By convention,
|
|
3755
|
-
// all the IDs ending with this suffix are to be made invisible at
|
|
3756
|
-
// their first rendition (opacity=0). An animation will then make
|
|
3757
|
-
// them visible.
|
|
3758
|
-
this._transparentIds = this.getTransparentIds(snapshot.operations[index].diplomatics?.g);
|
|
3214
|
+
this.resultOperationId.set(snapshot.operations[index].id);
|
|
3759
3215
|
// update the snapshot text nodes with those introduced by operations
|
|
3760
|
-
this.supplyOpNodes(snapshot,
|
|
3216
|
+
this.supplyOpNodes(snapshot, result, step.outputTag);
|
|
3761
3217
|
// update the view data
|
|
3762
3218
|
this.setViewData(snapshot, step.outputTag);
|
|
3763
|
-
// play animation if any
|
|
3764
|
-
const tl = this.timelines.value.find((t) => t.tag === step.outputTag);
|
|
3765
|
-
if (tl) {
|
|
3766
|
-
// play the timeline
|
|
3767
|
-
setTimeout(() => {
|
|
3768
|
-
this.playTimeline(tl);
|
|
3769
|
-
}, 0);
|
|
3770
|
-
}
|
|
3771
|
-
}
|
|
3772
|
-
/**
|
|
3773
|
-
* Handle the event fired by a visual in the snapshot view.
|
|
3774
|
-
*
|
|
3775
|
-
* @param event The event.
|
|
3776
|
-
*/
|
|
3777
|
-
onVisualEvent(event) {
|
|
3778
|
-
if (this._handlingOver) {
|
|
3779
|
-
return;
|
|
3780
|
-
}
|
|
3781
|
-
const d = event.detail;
|
|
3782
|
-
if (d.event.type === 'mouseover') {
|
|
3783
|
-
const visual = d.source;
|
|
3784
|
-
if (visual.id === this._lastOverId) {
|
|
3785
|
-
return;
|
|
3786
|
-
}
|
|
3787
|
-
this._handlingOver = true;
|
|
3788
|
-
const sb = [];
|
|
3789
|
-
// id (type)
|
|
3790
|
-
sb.push('#' + visual.id);
|
|
3791
|
-
sb.push(` (${visual.type})`);
|
|
3792
|
-
// : label and features
|
|
3793
|
-
if (visual.data?.label) {
|
|
3794
|
-
sb.push(': ');
|
|
3795
|
-
sb.push(visual.data.label);
|
|
3796
|
-
}
|
|
3797
|
-
if (visual.data?.features?.length) {
|
|
3798
|
-
sb.push(' ');
|
|
3799
|
-
sb.push(visual.data.features
|
|
3800
|
-
.map((f) => `${f.name}=${f.value}`)
|
|
3801
|
-
.join('\n'));
|
|
3802
|
-
}
|
|
3803
|
-
// (@x,y width×height)
|
|
3804
|
-
sb.push(` (@${visual.x.toFixed(1)},${visual.y.toFixed(1)} ` +
|
|
3805
|
-
`◻${visual.width.toFixed(1)}×${visual.height.toFixed(1)})`);
|
|
3806
|
-
this.visualInfo = sb.join('');
|
|
3807
|
-
this._lastOverId = visual.id;
|
|
3808
|
-
}
|
|
3809
|
-
this._handlingOver = false;
|
|
3810
|
-
}
|
|
3811
|
-
/**
|
|
3812
|
-
* Handle the change of line heights by updating the form control.
|
|
3813
|
-
*
|
|
3814
|
-
* @param heights The line heights.
|
|
3815
|
-
*/
|
|
3816
|
-
onHeightsChange(heights) {
|
|
3817
|
-
this.lnHeights.setValue(heights || null);
|
|
3818
|
-
this.lnHeights.markAsDirty();
|
|
3819
|
-
this.lnHeights.updateValueAndValidity();
|
|
3820
|
-
}
|
|
3821
|
-
/**
|
|
3822
|
-
* Handle the change of timelines by updating the form control.
|
|
3823
|
-
*
|
|
3824
|
-
* @param timelines The timelines.
|
|
3825
|
-
*/
|
|
3826
|
-
onTimelinesChange(timelines) {
|
|
3827
|
-
if (!timelines) {
|
|
3828
|
-
return;
|
|
3829
|
-
}
|
|
3830
|
-
this.timelines.setValue(timelines);
|
|
3831
|
-
this.timelines.markAsDirty();
|
|
3832
|
-
this.timelines.updateValueAndValidity();
|
|
3833
3219
|
}
|
|
3834
3220
|
/**
|
|
3835
3221
|
* Emit the cancel event for this snapshot edit.
|
|
@@ -3837,73 +3223,6 @@ class SnapshotEditorComponent {
|
|
|
3837
3223
|
close() {
|
|
3838
3224
|
this.snapshotCancel.emit();
|
|
3839
3225
|
}
|
|
3840
|
-
/**
|
|
3841
|
-
* Handle the render event from the snapshot view to get a reference
|
|
3842
|
-
* to the renderer.
|
|
3843
|
-
*
|
|
3844
|
-
* @param event The rendition event.
|
|
3845
|
-
*/
|
|
3846
|
-
onSnapshotRender(event) {
|
|
3847
|
-
this._renderer = event.detail.renderer;
|
|
3848
|
-
}
|
|
3849
|
-
onImageLoad(imageElement) {
|
|
3850
|
-
const size = {
|
|
3851
|
-
width: imageElement.naturalWidth,
|
|
3852
|
-
height: imageElement.naturalHeight,
|
|
3853
|
-
};
|
|
3854
|
-
if (!this.imageWidth.value) {
|
|
3855
|
-
this.imageWidth.setValue(size.width);
|
|
3856
|
-
this.imageWidth.updateValueAndValidity();
|
|
3857
|
-
this.imageWidth.markAsDirty();
|
|
3858
|
-
}
|
|
3859
|
-
if (!this.imageHeight.value) {
|
|
3860
|
-
this.imageHeight.setValue(size.height);
|
|
3861
|
-
this.imageHeight.updateValueAndValidity();
|
|
3862
|
-
this.imageHeight.markAsDirty();
|
|
3863
|
-
}
|
|
3864
|
-
}
|
|
3865
|
-
onImageOpacityChange(value) {
|
|
3866
|
-
this.imageOpacity.setValue(value);
|
|
3867
|
-
this.imageOpacity.updateValueAndValidity();
|
|
3868
|
-
this.imageOpacity.markAsDirty();
|
|
3869
|
-
if (this._renderer) {
|
|
3870
|
-
this._renderer.setImageOpacity(value);
|
|
3871
|
-
}
|
|
3872
|
-
}
|
|
3873
|
-
resetImgMetadata() {
|
|
3874
|
-
this.imageX.reset();
|
|
3875
|
-
this.imageY.reset();
|
|
3876
|
-
this.imageWidth.reset();
|
|
3877
|
-
this.imageHeight.reset();
|
|
3878
|
-
}
|
|
3879
|
-
/**
|
|
3880
|
-
* Toggle rulers in the snapshot view.
|
|
3881
|
-
*/
|
|
3882
|
-
toggleRulers() {
|
|
3883
|
-
if (!this._renderer) {
|
|
3884
|
-
return;
|
|
3885
|
-
}
|
|
3886
|
-
this.rulers = this._renderer.toggleRulers();
|
|
3887
|
-
}
|
|
3888
|
-
defaultCharDecorator(char, lineNumber, x, y) {
|
|
3889
|
-
const features = char.features;
|
|
3890
|
-
const segOut = features?.find((f) => f.name === '$seg-out');
|
|
3891
|
-
const seg2Out = features?.find((f) => f.name === '$seg2-out');
|
|
3892
|
-
const segIn = features?.find((f) => f.name === '$seg-in');
|
|
3893
|
-
const seg2In = features?.find((f) => f.name === '$seg2-in');
|
|
3894
|
-
if (!segOut && !seg2Out && !segIn && !seg2In) {
|
|
3895
|
-
return null;
|
|
3896
|
-
}
|
|
3897
|
-
const decoration = {};
|
|
3898
|
-
if (segOut || seg2Out) {
|
|
3899
|
-
decoration.fill = segOut ? '#9B2915' : '#9B6F91';
|
|
3900
|
-
}
|
|
3901
|
-
// if (segIn || seg2In) {
|
|
3902
|
-
// decoration.stroke = segIn ? '#E9B44C' : '#DF928E';
|
|
3903
|
-
// decoration.strokeWidth = 3;
|
|
3904
|
-
// }
|
|
3905
|
-
return decoration;
|
|
3906
|
-
}
|
|
3907
3226
|
/**
|
|
3908
3227
|
* Get a snapshot from the form data.
|
|
3909
3228
|
*
|
|
@@ -3911,61 +3230,11 @@ class SnapshotEditorComponent {
|
|
|
3911
3230
|
*/
|
|
3912
3231
|
getSnapshot() {
|
|
3913
3232
|
const snapshot = {
|
|
3914
|
-
size: {
|
|
3915
|
-
width: this.width.value,
|
|
3916
|
-
height: this.height.value,
|
|
3917
|
-
},
|
|
3918
|
-
style: this.style.value || undefined,
|
|
3919
|
-
defs: this.defs.value || undefined,
|
|
3920
3233
|
// snapshot nodes might be changed, so we copy them
|
|
3921
3234
|
// to avoid changing the original snapshot
|
|
3922
|
-
text:
|
|
3923
|
-
|
|
3924
|
-
textOptions: {
|
|
3925
|
-
lineHeightOffset: this.lineHeightOffset.value,
|
|
3926
|
-
minLineHeights: this.lnHeights.value || undefined,
|
|
3927
|
-
charSpacingOffset: this.charSpacingOffset.value,
|
|
3928
|
-
spcWidthOffset: this.spcWidthOffset.value,
|
|
3929
|
-
offset: {
|
|
3930
|
-
x: this.offsetX.value,
|
|
3931
|
-
y: this.offsetY.value,
|
|
3932
|
-
},
|
|
3933
|
-
},
|
|
3934
|
-
operations: [...this.operations.value],
|
|
3935
|
-
opStyle: this.opStyle.value || undefined,
|
|
3235
|
+
text: structuredClone(this.baseText.value),
|
|
3236
|
+
operations: structuredClone(this.operations.value),
|
|
3936
3237
|
};
|
|
3937
|
-
// add char decoration unless opted out
|
|
3938
|
-
if (!this.noDecoration()) {
|
|
3939
|
-
snapshot.textOptions.charDecorator = this.defaultCharDecorator;
|
|
3940
|
-
}
|
|
3941
|
-
// image
|
|
3942
|
-
if (this.imageUrl.value) {
|
|
3943
|
-
snapshot.image = {
|
|
3944
|
-
url: this.imageUrl.value,
|
|
3945
|
-
opacity: this.imageOpacity.value || undefined,
|
|
3946
|
-
};
|
|
3947
|
-
if (this.imageX.value ||
|
|
3948
|
-
this.imageY.value ||
|
|
3949
|
-
this.imageWidth.value ||
|
|
3950
|
-
this.imageHeight.value) {
|
|
3951
|
-
snapshot.image.canvas = {
|
|
3952
|
-
x: this.imageX.value,
|
|
3953
|
-
y: this.imageY.value,
|
|
3954
|
-
width: this.imageWidth.value,
|
|
3955
|
-
height: this.imageHeight.value,
|
|
3956
|
-
};
|
|
3957
|
-
}
|
|
3958
|
-
}
|
|
3959
|
-
// timelines
|
|
3960
|
-
if (this.timelines.value.length) {
|
|
3961
|
-
snapshot.timelines = {};
|
|
3962
|
-
this.timelines.value.forEach((t) => {
|
|
3963
|
-
snapshot.timelines[t.tag] = t;
|
|
3964
|
-
});
|
|
3965
|
-
}
|
|
3966
|
-
else {
|
|
3967
|
-
snapshot.timelines = undefined;
|
|
3968
|
-
}
|
|
3969
3238
|
return snapshot;
|
|
3970
3239
|
}
|
|
3971
3240
|
/**
|
|
@@ -3978,10 +3247,10 @@ class SnapshotEditorComponent {
|
|
|
3978
3247
|
}
|
|
3979
3248
|
this.snapshot.set(this.getSnapshot());
|
|
3980
3249
|
}
|
|
3981
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
3982
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.10", 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 }, elementFeatureDefs: { classPropertyName: "elementFeatureDefs", publicName: "elementFeatureDefs", isSignal: true, isRequired: false, transformFunction: null }, diplomaticFeatureDefs: { classPropertyName: "diplomaticFeatureDefs", publicName: "diplomaticFeatureDefs", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { snapshot: "snapshotChange", snapshotCancel: "snapshotCancel" }, viewQueries: [{ propertyName: "snapshotView", first: true, predicate: ["snapshotView"], descendants: 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 <!-- base text metadata -->\r\n <fieldset>\r\n <div class=\"form-row\">\r\n <!-- offsetX -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>X offset</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"offsetX\"\r\n placeholder=\"X offset\"\r\n />\r\n </mat-form-field>\r\n\r\n <!-- offsetY -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>Y offset</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"offsetY\"\r\n placeholder=\"Y offset\"\r\n />\r\n </mat-form-field>\r\n\r\n <!-- lineHeightOffset -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>ln h-offset</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"lineHeightOffset\"\r\n placeholder=\"ln h-offset\"\r\n />\r\n </mat-form-field>\r\n\r\n <!-- charSpacingOffset -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>char spacing</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"charSpacingOffset\"\r\n placeholder=\"char spacing\"\r\n />\r\n </mat-form-field>\r\n\r\n <!-- spcWidthOffset -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>spc w-offset</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"spcWidthOffset\"\r\n placeholder=\"spc w-offset\"\r\n />\r\n </mat-form-field>\r\n <!-- minLineHeights -->\r\n <div class=\"boxed\">\r\n <gve-ln-heights-editor\r\n [lineCount]=\"lineCount()\"\r\n [heights]=\"lnHeights.value || undefined\"\r\n (heightsChange)=\"onHeightsChange($event)\"\r\n />\r\n </div>\r\n </div>\r\n <!-- textStyle -->\r\n <div>\r\n <mat-form-field class=\"long-text\" appearance=\"outline\">\r\n <mat-label>text style</mat-label>\r\n <textarea\r\n matInput\r\n [formControl]=\"textStyle\"\r\n placeholder=\"text style\"\r\n ></textarea>\r\n </mat-form-field>\r\n </div>\r\n </fieldset>\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 <!-- width -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>width</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n min=\"0\"\r\n [formControl]=\"width\"\r\n placeholder=\"width\"\r\n />\r\n </mat-form-field>\r\n\r\n <!-- height -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>height</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n min=\"0\"\r\n [formControl]=\"height\"\r\n placeholder=\"height\"\r\n />\r\n </mat-form-field>\r\n\r\n <!-- chain -->\r\n <mat-slide-toggle [formControl]=\"showChain\"\r\n >show chain</mat-slide-toggle\r\n >\r\n\r\n <!-- feat details -->\r\n <mat-slide-toggle [formControl]=\"featDetails\"\r\n >feat. details</mat-slide-toggle\r\n >\r\n\r\n <div class=\"form-row right\">\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 >\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 <!-- ops -->\r\n <div id=\"ops\">\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 (operation of operations.value; track operation.id; let\r\n index=$index) {\r\n <tr\r\n [class.selected]=\"operation.id === resultOperationId\"\r\n [class.edited]=\"operation.id === editedOp()?.id\"\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 </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) { @if (featDetails.value) {\r\n <div class=\"form-row\">\r\n @for (feat of operation.features; track $index; let\r\n i=$index) {\r\n <div class=\"feature\">\r\n <span class=\"fname\">{{\r\n feat.name\r\n | flatLookup : featureDefs()?.names : \"id\" : \"label\"\r\n }}</span\r\n >=<span\r\n class=\"fvalue\"\r\n >{{ feat.value | flatLookup:(featureDefs()?.values?.[feat.name]): \"id\" : \"label\" }}</span\r\n >\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n {{ operation.features?.length }}\r\n } }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n }\r\n\r\n <!-- operation editor -->\r\n @if (editedOp()) {\r\n <mat-expansion-panel [expanded]=\"editedOp()\" [disabled]=\"!editedOp()\">\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 [elementFeatureDefs]=\"elementFeatureDefs()\"\r\n [diplomaticFeatureDefs]=\"diplomaticFeatureDefs()\"\r\n [hidePreview]=\"editedOpIndex() === -1\"\r\n [operation]=\"editedOp()\"\r\n [rangePatch]=\"editedOpRangePatch()\"\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 <!-- opStyle -->\r\n <div id=\"opStyle\">\r\n <mat-form-field class=\"long-text\" appearance=\"outline\">\r\n <mat-label>operations style</mat-label>\r\n <textarea\r\n matInput\r\n [formControl]=\"opStyle\"\r\n placeholder=\"operations style\"\r\n ></textarea>\r\n </mat-form-field>\r\n </div>\r\n </div>\r\n </div>\r\n </mat-tab>\r\n\r\n <!-- image -->\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n <mat-icon>image</mat-icon> <span class=\"label\">image</span>\r\n </ng-template>\r\n\r\n <div id=\"image\">\r\n <div id=\"image-ctl\">\r\n <!-- url -->\r\n <mat-form-field class=\"long-text\">\r\n <mat-label>URL</mat-label>\r\n <input matInput [formControl]=\"imageUrl\" />\r\n </mat-form-field>\r\n <div class=\"form-row\">\r\n <!-- x -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>X</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"imageX\"\r\n placeholder=\"X\"\r\n />\r\n </mat-form-field>\r\n <!-- y -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>Y</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"imageY\"\r\n placeholder=\"Y\"\r\n />\r\n </mat-form-field>\r\n <!-- width -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>width</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n min=\"0\"\r\n [formControl]=\"imageWidth\"\r\n />\r\n </mat-form-field>\r\n <!-- height -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>height</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n min=\"0\"\r\n [formControl]=\"imageHeight\"\r\n />\r\n </mat-form-field>\r\n <!-- reset -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Reset image metadata\"\r\n (click)=\"resetImgMetadata()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n </div>\r\n <div>\r\n <!-- image opacity -->\r\n <mat-slider\r\n min=\"0\"\r\n max=\"1\"\r\n step=\"0.01\"\r\n thumbLabel\r\n color=\"primary\"\r\n class=\"mat-primary\"\r\n matTooltip=\"Image opacity\"\r\n >\r\n <input\r\n matSliderThumb\r\n [value]=\"imageOpacity.value\"\r\n (valueChange)=\"onImageOpacityChange($event)\"\r\n />\r\n </mat-slider>\r\n <span id=\"opacity-value\">{{\r\n imageOpacity.value | number : \"1.2-2\"\r\n }}</span>\r\n </div>\r\n <!-- defs -->\r\n <div>\r\n <mat-form-field class=\"long-text\">\r\n <mat-label>defs</mat-label>\r\n <textarea matInput [formControl]=\"defs\" rows=\"3\"></textarea>\r\n </mat-form-field>\r\n </div>\r\n </div>\r\n <div id=\"image-view\">\r\n @if (imageUrl.value) {\r\n <img\r\n #imgElement\r\n alt=\"background\"\r\n [src]=\"imageUrl.value\"\r\n width=\"600\"\r\n (load)=\"onImageLoad(imgElement)\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n </mat-tab>\r\n\r\n <!-- timelines -->\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n <mat-icon>animation</mat-icon> <span class=\"label\">animation</span>\r\n </ng-template>\r\n\r\n <gve-animation-timeline-set\r\n [tags]=\"opTags()\"\r\n [elementIds]=\"opElementIds()\"\r\n [timelines]=\"timelines.value\"\r\n (timelinesChange)=\"onTimelinesChange($event)\"\r\n />\r\n </mat-tab>\r\n </mat-tab-group>\r\n\r\n <!-- progress -->\r\n <div>\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 <fieldset id=\"preview\">\r\n <legend class=\"button-row\">\r\n <span>{{ viewTitle }}</span>\r\n </legend>\r\n <!-- snapshot view -->\r\n <gve-snapshot-view\r\n #snapshotView\r\n [debug]=\"debug()\"\r\n [data]=\"viewData\"\r\n (snapshotRender)=\"onSnapshotRender($any($event))\"\r\n (visualEvent)=\"onVisualEvent($any($event))\"\r\n />\r\n <div class=\"button-row\">\r\n <!-- toggle ruler -->\r\n <mat-button-toggle\r\n type=\"button\"\r\n mat-icon-button\r\n color=\"primary\"\r\n matTooltip=\"Toggle rulers\"\r\n [checked]=\"rulers\"\r\n (change)=\"toggleRulers()\"\r\n >\r\n <mat-icon class=\"mat-primary\">straighten</mat-icon>\r\n </mat-button-toggle>\r\n <!-- run to last -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n class=\"primary\"\r\n matTooltip=\"Refresh preview\"\r\n (click)=\"runToLast()\"\r\n >\r\n <mat-icon>refresh</mat-icon>\r\n </button>\r\n </div>\r\n @if (visualInfo) {\r\n <div id=\"visual-info\">{{ visualInfo }}</div>\r\n }\r\n </fieldset>\r\n\r\n <!-- chain view -->\r\n @if (chain()) {\r\n <div id=\"chain-view\">\r\n <gve-chain-view [chain]=\"chain()\" />\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}#text-range{margin:8px;border:1px solid silver;border-radius:6px;padding:6px}mat-expansion-panel{margin:8px 0}div#visual-info{font-size:95%;color:#909090;margin:8px}#list{margin:8px 0}#opStyle{margin-top:8px}table{width:100%;border-collapse:collapse}th{color:#909090;font-weight:400;text-align:left;background-color:#e1e0e0}th,td{padding:4px;border-bottom:1px solid silver}tbody tr:nth-child(2n){background-color:#e8e8e8}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#c8d9eb}tr.edited{background-color:#f6f6e4}fieldset{border:1px solid silver;border-radius:6px;padding:8px;margin:8px 0}legend{color:#909090}.error{color:red}.input-nr{width:6em}.full-width{width:100%}.code{font-family:Courier New,Courier,monospace}.boxed{border:1px solid silver;border-radius:6px;padding:8px;margin:8px 0}span.label{margin-left:8px}.feature,.fname{color:silver}.fvalue{color:#fff;background-color:#b5bdc9;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}@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.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "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: "component", type: i7$1.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i3$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i3$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i3$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: "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: i12.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatSliderModule }, { kind: "component", type: i13.MatSlider, selector: "mat-slider", inputs: ["disabled", "discrete", "showTickMarks", "min", "color", "disableRipple", "max", "step", "displayWith"], exportAs: ["matSlider"] }, { kind: "directive", type: i13.MatSliderThumb, selector: "input[matSliderThumb]", inputs: ["value"], outputs: ["valueChange", "dragStart", "dragEnd"], exportAs: ["matSliderThumb"] }, { 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: i14.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i14.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i14.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: i7.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", "elementFeatureDefs", "diplomaticFeatureDefs", "rangePatch"], 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", "hasLineNumber", "text", "colorCallback", "borderColorCallback"], outputs: ["charPick", "rangePick"] }, { kind: "component", type: LnHeightsEditorComponent, selector: "gve-ln-heights-editor", inputs: ["lineCount", "heights"], outputs: ["heightsChange"] }, { kind: "component", type: AnimationTimelineSetComponent, selector: "gve-animation-timeline-set", inputs: ["timelines", "elementIds", "tags"], outputs: ["timelinesChange", "timelinesCancel"] }, { kind: "component", type: ChainViewComponent, selector: "gve-chain-view", inputs: ["chain", "direction", "selectedTags"], outputs: ["selectedTagsChange"] }, { kind: "pipe", type: i16.DecimalPipe, name: "number" }, { kind: "pipe", type: FlatLookupPipe, name: "flatLookup" }] }); }
|
|
3250
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", 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 }); }
|
|
3251
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", 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 }, renditionSettings: { classPropertyName: "renditionSettings", publicName: "renditionSettings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { snapshot: "snapshotChange", snapshotCancel: "snapshotCancel" }, viewQueries: [{ propertyName: "renditionRef", first: true, predicate: ["rendition"], 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 [formControl]=\"featDetails\"\r\n >feat. details</mat-slide-toggle\r\n >\r\n <!-- auto run on edit -->\r\n <mat-slide-toggle [formControl]=\"autoRun\"\r\n >auto-run</mat-slide-toggle\r\n >\r\n\r\n <div class=\"form-row right\">\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>content_copy</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 >\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 <!-- ops -->\r\n <div id=\"ops\">\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\r\n [class.selected]=\"operation.id === resultOperationId()\"\r\n [class.edited]=\"operation.id === editedOp()?.id\"\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 {{ 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\r\n <!-- operation editor -->\r\n @if (editedOp()) {\r\n <mat-expansion-panel\r\n [expanded]=\"editedOp()\"\r\n [disabled]=\"!editedOp()\"\r\n >\r\n <mat-expansion-panel-header>\r\n <mat-panel-title\r\n >operation {{ editedOp()?.id }}</mat-panel-title\r\n >\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 (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 </div>\r\n </div>\r\n </mat-tab>\r\n </mat-tab-group>\r\n\r\n <!-- progress -->\r\n <div>\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}#text-range{margin:8px;border:1px solid silver;border-radius:6px;padding:6px}mat-expansion-panel{margin:8px 0}div#visual-info{font-size:95%;color:#909090;margin:8px}#result{max-height:800px;overflow:auto}#list{margin:8px 0}#opStyle{margin-top:8px}table{width:100%;border-collapse:collapse}th{color:#909090;font-weight:400;text-align:left;background-color:#e1e0e0}th,td{padding:4px;border-bottom:1px solid silver}tbody tr:nth-child(2n){background-color:#e8e8e8}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#c8d9eb}tr.edited{background-color:#f6f6e4}fieldset{border:1px solid silver;border-radius:6px;padding:8px;margin:8px 0}legend{color:#909090}.error{color:red}.input-nr{width:6em}.full-width{width:100%}.code{font-family:Courier New,Courier,monospace}.boxed{border:1px solid silver;border-radius:6px;padding:8px;margin:8px 0}span.label{margin-left:8px}.feature,.fname{color:silver}.fvalue{color:#fff;background-color:#b5bdc9;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 #ccc;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"], 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" }] }); }
|
|
3983
3252
|
}
|
|
3984
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3253
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: SnapshotEditorComponent, decorators: [{
|
|
3985
3254
|
type: Component,
|
|
3986
3255
|
args: [{ selector: 'gve-snapshot-editor', imports: [
|
|
3987
3256
|
CommonModule,
|
|
@@ -4003,15 +3272,124 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
4003
3272
|
ChainOperationEditorComponent,
|
|
4004
3273
|
ChainResultViewComponent,
|
|
4005
3274
|
BaseTextViewComponent,
|
|
4006
|
-
LnHeightsEditorComponent,
|
|
4007
|
-
AnimationTimelineSetComponent,
|
|
4008
3275
|
FlatLookupPipe,
|
|
4009
|
-
ChainViewComponent,
|
|
4010
|
-
], schemas: [CUSTOM_ELEMENTS_SCHEMA], 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 <!-- base text metadata -->\r\n <fieldset>\r\n <div class=\"form-row\">\r\n <!-- offsetX -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>X offset</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"offsetX\"\r\n placeholder=\"X offset\"\r\n />\r\n </mat-form-field>\r\n\r\n <!-- offsetY -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>Y offset</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"offsetY\"\r\n placeholder=\"Y offset\"\r\n />\r\n </mat-form-field>\r\n\r\n <!-- lineHeightOffset -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>ln h-offset</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"lineHeightOffset\"\r\n placeholder=\"ln h-offset\"\r\n />\r\n </mat-form-field>\r\n\r\n <!-- charSpacingOffset -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>char spacing</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"charSpacingOffset\"\r\n placeholder=\"char spacing\"\r\n />\r\n </mat-form-field>\r\n\r\n <!-- spcWidthOffset -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>spc w-offset</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"spcWidthOffset\"\r\n placeholder=\"spc w-offset\"\r\n />\r\n </mat-form-field>\r\n <!-- minLineHeights -->\r\n <div class=\"boxed\">\r\n <gve-ln-heights-editor\r\n [lineCount]=\"lineCount()\"\r\n [heights]=\"lnHeights.value || undefined\"\r\n (heightsChange)=\"onHeightsChange($event)\"\r\n />\r\n </div>\r\n </div>\r\n <!-- textStyle -->\r\n <div>\r\n <mat-form-field class=\"long-text\" appearance=\"outline\">\r\n <mat-label>text style</mat-label>\r\n <textarea\r\n matInput\r\n [formControl]=\"textStyle\"\r\n placeholder=\"text style\"\r\n ></textarea>\r\n </mat-form-field>\r\n </div>\r\n </fieldset>\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 <!-- width -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>width</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n min=\"0\"\r\n [formControl]=\"width\"\r\n placeholder=\"width\"\r\n />\r\n </mat-form-field>\r\n\r\n <!-- height -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>height</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n min=\"0\"\r\n [formControl]=\"height\"\r\n placeholder=\"height\"\r\n />\r\n </mat-form-field>\r\n\r\n <!-- chain -->\r\n <mat-slide-toggle [formControl]=\"showChain\"\r\n >show chain</mat-slide-toggle\r\n >\r\n\r\n <!-- feat details -->\r\n <mat-slide-toggle [formControl]=\"featDetails\"\r\n >feat. details</mat-slide-toggle\r\n >\r\n\r\n <div class=\"form-row right\">\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 >\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 <!-- ops -->\r\n <div id=\"ops\">\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 (operation of operations.value; track operation.id; let\r\n index=$index) {\r\n <tr\r\n [class.selected]=\"operation.id === resultOperationId\"\r\n [class.edited]=\"operation.id === editedOp()?.id\"\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 </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) { @if (featDetails.value) {\r\n <div class=\"form-row\">\r\n @for (feat of operation.features; track $index; let\r\n i=$index) {\r\n <div class=\"feature\">\r\n <span class=\"fname\">{{\r\n feat.name\r\n | flatLookup : featureDefs()?.names : \"id\" : \"label\"\r\n }}</span\r\n >=<span\r\n class=\"fvalue\"\r\n >{{ feat.value | flatLookup:(featureDefs()?.values?.[feat.name]): \"id\" : \"label\" }}</span\r\n >\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n {{ operation.features?.length }}\r\n } }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n }\r\n\r\n <!-- operation editor -->\r\n @if (editedOp()) {\r\n <mat-expansion-panel [expanded]=\"editedOp()\" [disabled]=\"!editedOp()\">\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 [elementFeatureDefs]=\"elementFeatureDefs()\"\r\n [diplomaticFeatureDefs]=\"diplomaticFeatureDefs()\"\r\n [hidePreview]=\"editedOpIndex() === -1\"\r\n [operation]=\"editedOp()\"\r\n [rangePatch]=\"editedOpRangePatch()\"\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 <!-- opStyle -->\r\n <div id=\"opStyle\">\r\n <mat-form-field class=\"long-text\" appearance=\"outline\">\r\n <mat-label>operations style</mat-label>\r\n <textarea\r\n matInput\r\n [formControl]=\"opStyle\"\r\n placeholder=\"operations style\"\r\n ></textarea>\r\n </mat-form-field>\r\n </div>\r\n </div>\r\n </div>\r\n </mat-tab>\r\n\r\n <!-- image -->\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n <mat-icon>image</mat-icon> <span class=\"label\">image</span>\r\n </ng-template>\r\n\r\n <div id=\"image\">\r\n <div id=\"image-ctl\">\r\n <!-- url -->\r\n <mat-form-field class=\"long-text\">\r\n <mat-label>URL</mat-label>\r\n <input matInput [formControl]=\"imageUrl\" />\r\n </mat-form-field>\r\n <div class=\"form-row\">\r\n <!-- x -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>X</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"imageX\"\r\n placeholder=\"X\"\r\n />\r\n </mat-form-field>\r\n <!-- y -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>Y</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [formControl]=\"imageY\"\r\n placeholder=\"Y\"\r\n />\r\n </mat-form-field>\r\n <!-- width -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>width</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n min=\"0\"\r\n [formControl]=\"imageWidth\"\r\n />\r\n </mat-form-field>\r\n <!-- height -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>height</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n min=\"0\"\r\n [formControl]=\"imageHeight\"\r\n />\r\n </mat-form-field>\r\n <!-- reset -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Reset image metadata\"\r\n (click)=\"resetImgMetadata()\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n </div>\r\n <div>\r\n <!-- image opacity -->\r\n <mat-slider\r\n min=\"0\"\r\n max=\"1\"\r\n step=\"0.01\"\r\n thumbLabel\r\n color=\"primary\"\r\n class=\"mat-primary\"\r\n matTooltip=\"Image opacity\"\r\n >\r\n <input\r\n matSliderThumb\r\n [value]=\"imageOpacity.value\"\r\n (valueChange)=\"onImageOpacityChange($event)\"\r\n />\r\n </mat-slider>\r\n <span id=\"opacity-value\">{{\r\n imageOpacity.value | number : \"1.2-2\"\r\n }}</span>\r\n </div>\r\n <!-- defs -->\r\n <div>\r\n <mat-form-field class=\"long-text\">\r\n <mat-label>defs</mat-label>\r\n <textarea matInput [formControl]=\"defs\" rows=\"3\"></textarea>\r\n </mat-form-field>\r\n </div>\r\n </div>\r\n <div id=\"image-view\">\r\n @if (imageUrl.value) {\r\n <img\r\n #imgElement\r\n alt=\"background\"\r\n [src]=\"imageUrl.value\"\r\n width=\"600\"\r\n (load)=\"onImageLoad(imgElement)\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n </mat-tab>\r\n\r\n <!-- timelines -->\r\n <mat-tab>\r\n <ng-template mat-tab-label>\r\n <mat-icon>animation</mat-icon> <span class=\"label\">animation</span>\r\n </ng-template>\r\n\r\n <gve-animation-timeline-set\r\n [tags]=\"opTags()\"\r\n [elementIds]=\"opElementIds()\"\r\n [timelines]=\"timelines.value\"\r\n (timelinesChange)=\"onTimelinesChange($event)\"\r\n />\r\n </mat-tab>\r\n </mat-tab-group>\r\n\r\n <!-- progress -->\r\n <div>\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 <fieldset id=\"preview\">\r\n <legend class=\"button-row\">\r\n <span>{{ viewTitle }}</span>\r\n </legend>\r\n <!-- snapshot view -->\r\n <gve-snapshot-view\r\n #snapshotView\r\n [debug]=\"debug()\"\r\n [data]=\"viewData\"\r\n (snapshotRender)=\"onSnapshotRender($any($event))\"\r\n (visualEvent)=\"onVisualEvent($any($event))\"\r\n />\r\n <div class=\"button-row\">\r\n <!-- toggle ruler -->\r\n <mat-button-toggle\r\n type=\"button\"\r\n mat-icon-button\r\n color=\"primary\"\r\n matTooltip=\"Toggle rulers\"\r\n [checked]=\"rulers\"\r\n (change)=\"toggleRulers()\"\r\n >\r\n <mat-icon class=\"mat-primary\">straighten</mat-icon>\r\n </mat-button-toggle>\r\n <!-- run to last -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n class=\"primary\"\r\n matTooltip=\"Refresh preview\"\r\n (click)=\"runToLast()\"\r\n >\r\n <mat-icon>refresh</mat-icon>\r\n </button>\r\n </div>\r\n @if (visualInfo) {\r\n <div id=\"visual-info\">{{ visualInfo }}</div>\r\n }\r\n </fieldset>\r\n\r\n <!-- chain view -->\r\n @if (chain()) {\r\n <div id=\"chain-view\">\r\n <gve-chain-view [chain]=\"chain()\" />\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}#text-range{margin:8px;border:1px solid silver;border-radius:6px;padding:6px}mat-expansion-panel{margin:8px 0}div#visual-info{font-size:95%;color:#909090;margin:8px}#list{margin:8px 0}#opStyle{margin-top:8px}table{width:100%;border-collapse:collapse}th{color:#909090;font-weight:400;text-align:left;background-color:#e1e0e0}th,td{padding:4px;border-bottom:1px solid silver}tbody tr:nth-child(2n){background-color:#e8e8e8}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#c8d9eb}tr.edited{background-color:#f6f6e4}fieldset{border:1px solid silver;border-radius:6px;padding:8px;margin:8px 0}legend{color:#909090}.error{color:red}.input-nr{width:6em}.full-width{width:100%}.code{font-family:Courier New,Courier,monospace}.boxed{border:1px solid silver;border-radius:6px;padding:8px;margin:8px 0}span.label{margin-left:8px}.feature,.fname{color:silver}.fvalue{color:#fff;background-color:#b5bdc9;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}@media only screen and (max-width: 959px){div#image{grid-template-columns:1fr;grid-template-areas:\"image-ctl\" \"image-view\"}}\n"] }]
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
3276
|
+
], schemas: [CUSTOM_ELEMENTS_SCHEMA], 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 [formControl]=\"featDetails\"\r\n >feat. details</mat-slide-toggle\r\n >\r\n <!-- auto run on edit -->\r\n <mat-slide-toggle [formControl]=\"autoRun\"\r\n >auto-run</mat-slide-toggle\r\n >\r\n\r\n <div class=\"form-row right\">\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>content_copy</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 >\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 <!-- ops -->\r\n <div id=\"ops\">\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\r\n [class.selected]=\"operation.id === resultOperationId()\"\r\n [class.edited]=\"operation.id === editedOp()?.id\"\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 {{ 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\r\n <!-- operation editor -->\r\n @if (editedOp()) {\r\n <mat-expansion-panel\r\n [expanded]=\"editedOp()\"\r\n [disabled]=\"!editedOp()\"\r\n >\r\n <mat-expansion-panel-header>\r\n <mat-panel-title\r\n >operation {{ editedOp()?.id }}</mat-panel-title\r\n >\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 (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 </div>\r\n </div>\r\n </mat-tab>\r\n </mat-tab-group>\r\n\r\n <!-- progress -->\r\n <div>\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}#text-range{margin:8px;border:1px solid silver;border-radius:6px;padding:6px}mat-expansion-panel{margin:8px 0}div#visual-info{font-size:95%;color:#909090;margin:8px}#result{max-height:800px;overflow:auto}#list{margin:8px 0}#opStyle{margin-top:8px}table{width:100%;border-collapse:collapse}th{color:#909090;font-weight:400;text-align:left;background-color:#e1e0e0}th,td{padding:4px;border-bottom:1px solid silver}tbody tr:nth-child(2n){background-color:#e8e8e8}td.fit-width{width:1px;white-space:nowrap}tr.selected{background-color:#c8d9eb}tr.edited{background-color:#f6f6e4}fieldset{border:1px solid silver;border-radius:6px;padding:8px;margin:8px 0}legend{color:#909090}.error{color:red}.input-nr{width:6em}.full-width{width:100%}.code{font-family:Courier New,Courier,monospace}.boxed{border:1px solid silver;border-radius:6px;padding:8px;margin:8px 0}span.label{margin-left:8px}.feature,.fname{color:silver}.fvalue{color:#fff;background-color:#b5bdc9;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 #ccc;overflow:auto}@media only screen and (max-width:959px){div#image{grid-template-columns:1fr;grid-template-areas:\"image-ctl\" \"image-view\"}}\n"] }]
|
|
3277
|
+
}], 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 }] }], 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 }] }], renditionSettings: [{ type: i0.Input, args: [{ isSignal: true, alias: "renditionSettings", required: false }] }], snapshotCancel: [{ type: i0.Output, args: ["snapshotCancel"] }] } });
|
|
3278
|
+
|
|
3279
|
+
class GveGraphvizService {
|
|
3280
|
+
hashString(str) {
|
|
3281
|
+
let hash = 0;
|
|
3282
|
+
for (let i = 0; i < str.length; i++) {
|
|
3283
|
+
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
3284
|
+
hash = hash & hash; // convert to 32bit integer
|
|
3285
|
+
}
|
|
3286
|
+
return Math.abs(hash);
|
|
3287
|
+
}
|
|
3288
|
+
hslToRgb(hue, saturation, lightness) {
|
|
3289
|
+
const chroma = ((1 - Math.abs((2 * lightness) / 100 - 1)) * saturation) / 100;
|
|
3290
|
+
const x = chroma * (1 - Math.abs(((hue / 60) % 2) - 1));
|
|
3291
|
+
const m = lightness / 100 - chroma / 2;
|
|
3292
|
+
let r = 0, g = 0, b = 0;
|
|
3293
|
+
if (hue >= 0 && hue < 60) {
|
|
3294
|
+
r = chroma;
|
|
3295
|
+
g = x;
|
|
3296
|
+
}
|
|
3297
|
+
else if (hue >= 60 && hue < 120) {
|
|
3298
|
+
r = x;
|
|
3299
|
+
g = chroma;
|
|
3300
|
+
}
|
|
3301
|
+
else if (hue >= 120 && hue < 180) {
|
|
3302
|
+
g = chroma;
|
|
3303
|
+
b = x;
|
|
3304
|
+
}
|
|
3305
|
+
else if (hue >= 180 && hue < 240) {
|
|
3306
|
+
g = x;
|
|
3307
|
+
b = chroma;
|
|
3308
|
+
}
|
|
3309
|
+
else if (hue >= 240 && hue < 300) {
|
|
3310
|
+
r = x;
|
|
3311
|
+
b = chroma;
|
|
3312
|
+
}
|
|
3313
|
+
else if (hue >= 300 && hue < 360) {
|
|
3314
|
+
r = chroma;
|
|
3315
|
+
b = x;
|
|
3316
|
+
}
|
|
3317
|
+
r = Math.round((r + m) * 255);
|
|
3318
|
+
g = Math.round((g + m) * 255);
|
|
3319
|
+
b = Math.round((b + m) * 255);
|
|
3320
|
+
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
|
3321
|
+
}
|
|
3322
|
+
getColorForTag(tag) {
|
|
3323
|
+
const hash = this.hashString(tag);
|
|
3324
|
+
const hue = (hash * 137) % 360; // Use a prime number to spread out the hues
|
|
3325
|
+
const saturation = 60 + (hash % 40); // Saturation between 60% and 100%
|
|
3326
|
+
const lightness = 50 + (hash % 30); // Lightness between 50% and 80%
|
|
3327
|
+
return this.hslToRgb(hue, saturation, lightness);
|
|
3328
|
+
}
|
|
3329
|
+
getExcludedNodeIds(chain, tags) {
|
|
3330
|
+
// get only the desired links
|
|
3331
|
+
const links = chain.links.filter((link) => tags ? tags.includes(link.tag) : true);
|
|
3332
|
+
// return all the nodes not referenced by the links
|
|
3333
|
+
const excluded = new Set();
|
|
3334
|
+
chain.nodes.forEach((node) => {
|
|
3335
|
+
excluded.add(node.id);
|
|
3336
|
+
});
|
|
3337
|
+
links.forEach((link) => {
|
|
3338
|
+
excluded.delete(link.sourceId);
|
|
3339
|
+
excluded.delete(link.targetId);
|
|
3340
|
+
});
|
|
3341
|
+
return excluded;
|
|
3342
|
+
}
|
|
3343
|
+
/**
|
|
3344
|
+
* Represent the received chain as a Graphviz digraph.
|
|
3345
|
+
*
|
|
3346
|
+
* @param chain The source chain if any.
|
|
3347
|
+
* @param tags The tags to show. When set, only the links with these tags are shown.
|
|
3348
|
+
* @param rankdir The rank direction.
|
|
3349
|
+
* @returns Graphviz representation of the chain.
|
|
3350
|
+
*/
|
|
3351
|
+
generateGraph(chain, tags, rankdir = 'LR') {
|
|
3352
|
+
if (!chain) {
|
|
3353
|
+
return 'digraph G {}';
|
|
3354
|
+
}
|
|
3355
|
+
const sb = [];
|
|
3356
|
+
const excludedNodeIds = this.getExcludedNodeIds(chain, tags);
|
|
3357
|
+
sb.push('digraph G {');
|
|
3358
|
+
sb.push(' bgcolor=transparent;');
|
|
3359
|
+
sb.push(' node [style=filled];');
|
|
3360
|
+
sb.push(` rankdir=${rankdir};`);
|
|
3361
|
+
chain.nodes.forEach((node) => {
|
|
3362
|
+
// note that in label we must escape the double quotes
|
|
3363
|
+
sb.push(` ${node.id} [label="${node.label === '"' ? '"' : node.label}"` +
|
|
3364
|
+
(node.sourceTag
|
|
3365
|
+
? ` fillcolor="${this.getColorForTag(node.sourceTag)}"`
|
|
3366
|
+
: '') +
|
|
3367
|
+
(node.sourceTag && excludedNodeIds.has(node.id)
|
|
3368
|
+
? ` xlabel="${node.sourceTag}"`
|
|
3369
|
+
: '') +
|
|
3370
|
+
'];');
|
|
3371
|
+
});
|
|
3372
|
+
chain.links.forEach((link) => {
|
|
3373
|
+
if (!link.sourceId ||
|
|
3374
|
+
!link.targetId ||
|
|
3375
|
+
(tags && !tags.includes(link.tag))) {
|
|
3376
|
+
return;
|
|
3377
|
+
}
|
|
3378
|
+
const color = this.getColorForTag(link.tag);
|
|
3379
|
+
sb.push(` ${link.sourceId} -> ${link.targetId} [label="${link.tag}", color="${color}"];`);
|
|
3380
|
+
});
|
|
3381
|
+
sb.push('}');
|
|
3382
|
+
return sb.join('\n');
|
|
3383
|
+
}
|
|
3384
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: GveGraphvizService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3385
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: GveGraphvizService, providedIn: 'root' }); }
|
|
3386
|
+
}
|
|
3387
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: GveGraphvizService, decorators: [{
|
|
3388
|
+
type: Injectable,
|
|
3389
|
+
args: [{
|
|
3390
|
+
providedIn: 'root',
|
|
3391
|
+
}]
|
|
3392
|
+
}] });
|
|
4015
3393
|
|
|
4016
3394
|
/*
|
|
4017
3395
|
* Public API Surface of gve-core
|
|
@@ -4021,5 +3399,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
|
|
|
4021
3399
|
* Generated bundle index. Do not edit.
|
|
4022
3400
|
*/
|
|
4023
3401
|
|
|
4024
|
-
export {
|
|
3402
|
+
export { BaseTextCharComponent, BaseTextEditorComponent, BaseTextViewComponent, BatchOperationEditorComponent, ChainOperationEditorComponent, ChainResultViewComponent, FeatureEditorComponent, FeatureSetEditorComponent, FeatureSetPolicy, FeatureSetViewComponent, GveApiService, GveBaseTextService, GveGraphvizService, OperationSourceEditorComponent, OperationType, SettingsService, SnapshotEditorComponent, SnapshotTextEditorComponent, StepsMapComponent };
|
|
4025
3403
|
//# sourceMappingURL=myrmidon-gve-core.mjs.map
|