@myrmidon/gve-core 5.0.4 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/fesm2022/myrmidon-gve-core.mjs +816 -1494
- package/fesm2022/myrmidon-gve-core.mjs.map +1 -1
- package/package.json +4 -4
- package/types/myrmidon-gve-core.d.ts +340 -379
|
@@ -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 {
|
|
29
|
-
import { FeatureSetPolicy, SnapshotViewService, OperationType, DEFAULT_SVG_BASE_TEXT_OPTIONS } from '@myrmidon/gve-snapshot-view';
|
|
30
|
-
import * as i3$3 from '@angular/material/dialog';
|
|
31
|
-
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
|
32
|
+
import * as i3$2 from '@angular/material/dialog';
|
|
33
|
+
import { MAT_DIALOG_DATA, MatDialogClose } 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 { VizComponent } from '@myrmidon/ngx-viz';
|
|
42
|
-
import * as i4$2 from '@angular/material/snack-bar';
|
|
43
|
-
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
|
44
|
-
import * as i16 from '@angular/common';
|
|
45
41
|
import { CommonModule } from '@angular/common';
|
|
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: "21.0.0", ngImport: i0, type: AnimationTweenComponent, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
184
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", 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],[formArray],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: "21.0.0", 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: "21.0.0", ngImport: i0, type: AnimationTimelineComponent, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
356
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", 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],[formArray],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: "21.0.0", 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: "21.0.0", ngImport: i0, type: AnimationTimelineSetComponent, deps: [{ token: i4$1.DialogService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
450
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", 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: "21.0.0", 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: "21.
|
|
493
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
96
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: BaseTextCharComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
97
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", 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: "21.
|
|
99
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", 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: "21.0.0", ngImpor
|
|
|
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.1.2", ngImport: i0, type: BaseTextViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
599
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", 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: "21.
|
|
601
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", 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: "21.0.0", ngImpor
|
|
|
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: "21.
|
|
847
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
786
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", 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.1.2", 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: "21.
|
|
789
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", 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: "21.0.0", ngImpor
|
|
|
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: "21.0.0", ngImpor
|
|
|
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: "21.
|
|
991
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
945
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", 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.1.2", 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: "21.
|
|
948
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", 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: "21.0.0", ngImpor
|
|
|
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`
|
|
@@ -1023,31 +1023,24 @@ class BaseTextEditorComponent {
|
|
|
1023
1023
|
* objects, or undefined. In output this will be an array of `CharNode`
|
|
1024
1024
|
* objects.
|
|
1025
1025
|
*/
|
|
1026
|
-
this.text = input.required(...(ngDevMode ?
|
|
1027
|
-
|
|
1028
|
-
return undefined;
|
|
1029
|
-
}
|
|
1030
|
-
if (Array.isArray(value)) {
|
|
1031
|
-
return value;
|
|
1032
|
-
}
|
|
1033
|
-
if (typeof value === 'string') {
|
|
1034
|
-
return SnapshotViewService.stringToBaseChars(value);
|
|
1035
|
-
}
|
|
1026
|
+
this.text = input.required({ ...(ngDevMode ? { debugName: "text" } : {}), transform: (value) => {
|
|
1027
|
+
if (value === undefined) {
|
|
1036
1028
|
return undefined;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1029
|
+
}
|
|
1030
|
+
if (Array.isArray(value)) {
|
|
1031
|
+
return value;
|
|
1032
|
+
}
|
|
1033
|
+
if (typeof value === 'string') {
|
|
1034
|
+
return GveBaseTextService.stringToBaseChars(value);
|
|
1035
|
+
}
|
|
1036
|
+
return undefined;
|
|
1037
|
+
} });
|
|
1038
|
+
/**
|
|
1039
|
+
* The IDs of the features that are multi-valued. If a feature being
|
|
1040
|
+
* edited is in this list, the feature editor will allow adding multiple
|
|
1041
|
+
* values to it. Passed down to feature editors.
|
|
1042
|
+
*/
|
|
1043
|
+
this.multiValuedFeatureIds = input(...(ngDevMode ? [undefined, { debugName: "multiValuedFeatureIds" }] : []));
|
|
1051
1044
|
/**
|
|
1052
1045
|
* Emitted for the edited text as an array of `CharNode`'s whenever it changes.
|
|
1053
1046
|
*/
|
|
@@ -1096,22 +1089,19 @@ class BaseTextEditorComponent {
|
|
|
1096
1089
|
onRangePick(range) {
|
|
1097
1090
|
this.textRange.set(range);
|
|
1098
1091
|
}
|
|
1099
|
-
patchTextFromUser() {
|
|
1100
|
-
// TODO
|
|
1101
|
-
}
|
|
1102
1092
|
setTextFromUser() {
|
|
1103
1093
|
this._dialogService
|
|
1104
1094
|
.confirm('Confirm', 'Reset text?')
|
|
1105
1095
|
.subscribe((yes) => {
|
|
1106
1096
|
if (yes) {
|
|
1107
|
-
this.textChange.emit(
|
|
1097
|
+
this.textChange.emit(GveBaseTextService.stringToBaseChars(this.userText.value));
|
|
1108
1098
|
}
|
|
1109
1099
|
});
|
|
1110
1100
|
}
|
|
1111
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
1112
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
1101
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: BaseTextEditorComponent, deps: [{ token: i1$1.FormBuilder }, { token: i4$1.DialogService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1102
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", 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
1103
|
}
|
|
1114
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
1104
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: BaseTextEditorComponent, decorators: [{
|
|
1115
1105
|
type: Component,
|
|
1116
1106
|
args: [{ selector: 'gve-base-text-editor', imports: [
|
|
1117
1107
|
ReactiveFormsModule,
|
|
@@ -1122,8 +1112,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImpor
|
|
|
1122
1112
|
MatTooltipModule,
|
|
1123
1113
|
BaseTextViewComponent,
|
|
1124
1114
|
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"] }] } });
|
|
1115
|
+
], 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"] }]
|
|
1116
|
+
}], 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
1117
|
|
|
1128
1118
|
/**
|
|
1129
1119
|
* Service to interact with the GVE API.
|
|
@@ -1192,10 +1182,10 @@ class GveApiService {
|
|
|
1192
1182
|
})
|
|
1193
1183
|
.pipe(catchError(this._error.handleError));
|
|
1194
1184
|
}
|
|
1195
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
1196
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.
|
|
1185
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GveApiService, deps: [{ token: i1$2.HttpClient }, { token: i2$1.ErrorService }, { token: i2$1.EnvService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1186
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GveApiService, providedIn: 'root' }); }
|
|
1197
1187
|
}
|
|
1198
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
1188
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GveApiService, decorators: [{
|
|
1199
1189
|
type: Injectable,
|
|
1200
1190
|
args: [{
|
|
1201
1191
|
providedIn: 'root',
|
|
@@ -1273,10 +1263,10 @@ class BatchOperationEditorComponent {
|
|
|
1273
1263
|
close() {
|
|
1274
1264
|
this.dialogRef?.close();
|
|
1275
1265
|
}
|
|
1276
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
1277
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
1266
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", 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 }); }
|
|
1267
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", 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
1268
|
}
|
|
1279
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
1269
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: BatchOperationEditorComponent, decorators: [{
|
|
1280
1270
|
type: Component,
|
|
1281
1271
|
args: [{ selector: 'gve-batch-operation-editor', imports: [
|
|
1282
1272
|
ReactiveFormsModule,
|
|
@@ -1285,7 +1275,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImpor
|
|
|
1285
1275
|
MatIconModule,
|
|
1286
1276
|
MatInputModule,
|
|
1287
1277
|
], 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$
|
|
1278
|
+
}], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: GveApiService }, { type: i3$2.MatDialogRef, decorators: [{
|
|
1289
1279
|
type: Optional
|
|
1290
1280
|
}] }, { type: undefined, decorators: [{
|
|
1291
1281
|
type: Optional
|
|
@@ -1378,12 +1368,12 @@ class OperationSourceEditorComponent {
|
|
|
1378
1368
|
note: this.note.value || undefined,
|
|
1379
1369
|
});
|
|
1380
1370
|
}
|
|
1381
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
1382
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
1371
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: OperationSourceEditorComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1372
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", 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
1373
|
}
|
|
1384
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
1374
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: OperationSourceEditorComponent, decorators: [{
|
|
1385
1375
|
type: Component,
|
|
1386
|
-
args: [{ selector: 'gve-operation-source-editor', imports: [
|
|
1376
|
+
args: [{ selector: 'gve-operation-source-editor', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
1387
1377
|
ReactiveFormsModule,
|
|
1388
1378
|
MatButtonModule,
|
|
1389
1379
|
MatFormFieldModule,
|
|
@@ -1392,7 +1382,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImpor
|
|
|
1392
1382
|
MatSelectModule,
|
|
1393
1383
|
MatTooltipModule
|
|
1394
1384
|
], 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"] }] } });
|
|
1385
|
+
}], 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
1386
|
|
|
1397
1387
|
/**
|
|
1398
1388
|
* Validators for SVG.
|
|
@@ -1610,10 +1600,10 @@ class SettingsService {
|
|
|
1610
1600
|
// for when the cache was not in sync with the local storage
|
|
1611
1601
|
removedKeys.forEach((key) => this._subject.next(key));
|
|
1612
1602
|
}
|
|
1613
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
1614
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.
|
|
1603
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: SettingsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1604
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: SettingsService, providedIn: 'root' }); }
|
|
1615
1605
|
}
|
|
1616
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
1606
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: SettingsService, decorators: [{
|
|
1617
1607
|
type: Injectable,
|
|
1618
1608
|
args: [{
|
|
1619
1609
|
providedIn: 'root',
|
|
@@ -1626,7 +1616,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImpor
|
|
|
1626
1616
|
* A component for editing a variant generation operation.
|
|
1627
1617
|
* Used by the `gve-snapshot-editor` component.
|
|
1628
1618
|
* - ▶️ `operation` (`CharChainOperation`): the operation to edit.
|
|
1629
|
-
* - ▶️ `snapshot` (`
|
|
1619
|
+
* - ▶️ `snapshot` (`Snapshot`): the snapshot the operation refers to.
|
|
1630
1620
|
* - ▶️ `hidePreview` (`boolean`): whether to hide the preview request button.
|
|
1631
1621
|
* - 🔥 `operationChange` (`CharChainOperation`): emitted when the operation
|
|
1632
1622
|
* is changed.
|
|
@@ -1675,8 +1665,6 @@ class ChainOperationEditorComponent {
|
|
|
1675
1665
|
this._clipboard = _clipboard;
|
|
1676
1666
|
this._settings = _settings;
|
|
1677
1667
|
this._dialogService = _dialogService;
|
|
1678
|
-
// monaco
|
|
1679
|
-
this._disposables = [];
|
|
1680
1668
|
this._nanoid = customAlphabet('1234567890abcdef', 10);
|
|
1681
1669
|
/**
|
|
1682
1670
|
* The operation to edit.
|
|
@@ -1694,18 +1682,16 @@ class ChainOperationEditorComponent {
|
|
|
1694
1682
|
* Definitions for features, including names and values.
|
|
1695
1683
|
*/
|
|
1696
1684
|
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
1685
|
/**
|
|
1706
1686
|
* Set when the edited operation's text range is to be patched.
|
|
1707
1687
|
*/
|
|
1708
1688
|
this.rangePatch = input(...(ngDevMode ? [undefined, { debugName: "rangePatch" }] : []));
|
|
1689
|
+
/**
|
|
1690
|
+
* The IDs of the features that are multi-valued. If a feature being
|
|
1691
|
+
* edited is in this list, the feature editor will allow adding multiple
|
|
1692
|
+
* values to it. Passed down to feature editors.
|
|
1693
|
+
*/
|
|
1694
|
+
this.multiValuedFeatureIds = input(...(ngDevMode ? [undefined, { debugName: "multiValuedFeatureIds" }] : []));
|
|
1709
1695
|
/**
|
|
1710
1696
|
* Emitted when the operation is changed.
|
|
1711
1697
|
*/
|
|
@@ -1791,15 +1777,6 @@ class ChainOperationEditorComponent {
|
|
|
1791
1777
|
effect(() => {
|
|
1792
1778
|
this.updateForm(this.operation());
|
|
1793
1779
|
});
|
|
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
1780
|
// when rangePatch changes, patch operation range (at and run,
|
|
1804
1781
|
// resetting atAsIndex to false)
|
|
1805
1782
|
effect(() => {
|
|
@@ -1829,7 +1806,6 @@ class ChainOperationEditorComponent {
|
|
|
1829
1806
|
this._subs.push(this.type.valueChanges.subscribe(() => this.updateArgsUI()));
|
|
1830
1807
|
}
|
|
1831
1808
|
ngOnDestroy() {
|
|
1832
|
-
this._disposables.forEach((d) => d.dispose());
|
|
1833
1809
|
for (const sub of this._subs) {
|
|
1834
1810
|
sub.unsubscribe();
|
|
1835
1811
|
}
|
|
@@ -1840,54 +1816,14 @@ class ChainOperationEditorComponent {
|
|
|
1840
1816
|
return on ? text.replace(/\\/g, '\n') : text.replace(/\n/g, '\\');
|
|
1841
1817
|
}
|
|
1842
1818
|
hasTextChanges(snapshot) {
|
|
1843
|
-
if ((!snapshot && this.snapshot()) || (snapshot && !this.snapshot())) {
|
|
1844
|
-
return true;
|
|
1845
|
-
}
|
|
1846
|
-
if (snapshot?.size?.width !== this.snapshot()?.size?.width ||
|
|
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) {
|
|
1819
|
+
if ((!snapshot && this.snapshot()) || (snapshot && !this.snapshot())) {
|
|
1853
1820
|
return true;
|
|
1854
1821
|
}
|
|
1855
|
-
|
|
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) {
|
|
1822
|
+
if (snapshot?.text !== this.snapshot()?.text) {
|
|
1862
1823
|
return true;
|
|
1863
1824
|
}
|
|
1864
1825
|
return false;
|
|
1865
1826
|
}
|
|
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
1827
|
onFeaturesChange(features) {
|
|
1892
1828
|
this.features.setValue(features);
|
|
1893
1829
|
this.features.markAsDirty();
|
|
@@ -1903,7 +1839,7 @@ class ChainOperationEditorComponent {
|
|
|
1903
1839
|
}
|
|
1904
1840
|
editSource(index) {
|
|
1905
1841
|
this.editedSourceIndex.set(index);
|
|
1906
|
-
this.editedSource.set(
|
|
1842
|
+
this.editedSource.set(structuredClone(this.sources.value[index]));
|
|
1907
1843
|
}
|
|
1908
1844
|
closeSource() {
|
|
1909
1845
|
this.editedSourceIndex.set(-1);
|
|
@@ -1921,137 +1857,6 @@ class ChainOperationEditorComponent {
|
|
|
1921
1857
|
this.closeSource();
|
|
1922
1858
|
}
|
|
1923
1859
|
// #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
1860
|
updateArgsUI() {
|
|
2056
1861
|
let to = false, toRun = false, value = false;
|
|
2057
1862
|
switch (this.type.value) {
|
|
@@ -2099,8 +1904,6 @@ class ChainOperationEditorComponent {
|
|
|
2099
1904
|
this.groupId.setValue(operation.groupId || null);
|
|
2100
1905
|
this.features.setValue(operation.features || []);
|
|
2101
1906
|
this.sources.setValue(operation.sources || []);
|
|
2102
|
-
this.newTextHidden.setValue(operation.diplomatics?.isNewTextHidden || false);
|
|
2103
|
-
this.dpFeatures.setValue(operation.diplomatics?.features || []);
|
|
2104
1907
|
this.type.setValue(operation.type);
|
|
2105
1908
|
this.at.setValue(operation.at);
|
|
2106
1909
|
this.atAsIndex.setValue(operation.atAsIndex || false);
|
|
@@ -2112,11 +1915,7 @@ class ChainOperationEditorComponent {
|
|
|
2112
1915
|
this.toRun.setValue(operation.toRun || 0);
|
|
2113
1916
|
// escape line feeds in value for editing
|
|
2114
1917
|
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
1918
|
this.form.markAsPristine();
|
|
2119
|
-
this.elements.set(this.parseSvg(operation.diplomatics?.g));
|
|
2120
1919
|
this.updateArgsUI();
|
|
2121
1920
|
}
|
|
2122
1921
|
cancel() {
|
|
@@ -2128,12 +1927,6 @@ class ChainOperationEditorComponent {
|
|
|
2128
1927
|
groupId: this.groupId.value || undefined,
|
|
2129
1928
|
features: this.features.value?.length ? this.features.value : undefined,
|
|
2130
1929
|
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
1930
|
id: this.id(),
|
|
2138
1931
|
type: this.type.value,
|
|
2139
1932
|
at: this.at.value,
|
|
@@ -2158,12 +1951,12 @@ class ChainOperationEditorComponent {
|
|
|
2158
1951
|
this.operation.set(this.getOperation());
|
|
2159
1952
|
this.operationChange.emit(this.operation());
|
|
2160
1953
|
}
|
|
2161
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
2162
|
-
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 }, 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],[formArray],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" }] }); }
|
|
1954
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ChainOperationEditorComponent, deps: [{ token: i1$1.FormBuilder }, { token: i2$2.Clipboard }, { token: SettingsService }, { token: i4$1.DialogService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1955
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", 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
1956
|
}
|
|
2164
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
1957
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ChainOperationEditorComponent, decorators: [{
|
|
2165
1958
|
type: Component,
|
|
2166
|
-
args: [{ selector: 'gve-chain-operation-editor', imports: [
|
|
1959
|
+
args: [{ selector: 'gve-chain-operation-editor', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
2167
1960
|
ReactiveFormsModule,
|
|
2168
1961
|
ClipboardModule,
|
|
2169
1962
|
MatBadgeModule,
|
|
@@ -2177,11 +1970,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImpor
|
|
|
2177
1970
|
MatTabsModule,
|
|
2178
1971
|
MatTooltipModule,
|
|
2179
1972
|
NgeMonacoModule,
|
|
2180
|
-
SafeHtmlPipe,
|
|
2181
1973
|
FeatureSetEditorComponent,
|
|
2182
1974
|
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 }] }],
|
|
1975
|
+
], 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"] }]
|
|
1976
|
+
}], 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
1977
|
|
|
2186
1978
|
/**
|
|
2187
1979
|
* 🔑 `gve-feature-set-view`
|
|
@@ -2249,10 +2041,10 @@ class FeatureSetViewComponent {
|
|
|
2249
2041
|
ngOnDestroy() {
|
|
2250
2042
|
this._sub?.unsubscribe();
|
|
2251
2043
|
}
|
|
2252
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
2253
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
2044
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: FeatureSetViewComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2045
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", 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
2046
|
}
|
|
2255
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
2047
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: FeatureSetViewComponent, decorators: [{
|
|
2256
2048
|
type: Component,
|
|
2257
2049
|
args: [{ selector: 'gve-feature-set-view', imports: [
|
|
2258
2050
|
ReactiveFormsModule,
|
|
@@ -2261,8 +2053,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImpor
|
|
|
2261
2053
|
MatIconModule,
|
|
2262
2054
|
MatInputModule,
|
|
2263
2055
|
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 }] }] } });
|
|
2056
|
+
], 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"] }]
|
|
2057
|
+
}], 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
2058
|
|
|
2267
2059
|
/**
|
|
2268
2060
|
* 🔑 `gve-steps-map`
|
|
@@ -2372,12 +2164,12 @@ class StepsMapComponent {
|
|
|
2372
2164
|
onStepClick(step) {
|
|
2373
2165
|
this.selectedStep.set(step);
|
|
2374
2166
|
}
|
|
2375
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
2376
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
2167
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: StepsMapComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2168
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", 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
2169
|
}
|
|
2378
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
2170
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: StepsMapComponent, decorators: [{
|
|
2379
2171
|
type: Component,
|
|
2380
|
-
args: [{ selector: 'gve-steps-map', imports: [MatButtonModule, MatRippleModule, MatTooltipModule], 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"] }]
|
|
2172
|
+
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
2173
|
}], 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
2174
|
|
|
2383
2175
|
/**
|
|
@@ -2622,10 +2414,10 @@ class ChainResultViewComponent {
|
|
|
2622
2414
|
this.rangePick.emit(this.selectionRange());
|
|
2623
2415
|
}
|
|
2624
2416
|
}
|
|
2625
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
2626
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
2417
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ChainResultViewComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2418
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", 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 }); }
|
|
2627
2419
|
}
|
|
2628
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
2420
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ChainResultViewComponent, decorators: [{
|
|
2629
2421
|
type: Component,
|
|
2630
2422
|
args: [{ selector: 'gve-chain-result-view', imports: [
|
|
2631
2423
|
ReactiveFormsModule,
|
|
@@ -2637,319 +2429,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImpor
|
|
|
2637
2429
|
StepsMapComponent,
|
|
2638
2430
|
BaseTextViewComponent,
|
|
2639
2431
|
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>
|
|
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: "21.0.0", ngImport: i0, type: GveGraphvizService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2749
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: GveGraphvizService, providedIn: 'root' }); }
|
|
2750
|
-
}
|
|
2751
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", 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: "21.0.0", 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: "21.0.0", 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: "21.0.0", 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);
|
|
2937
|
-
}
|
|
2938
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: LnHeightsEditorComponent, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2939
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", type: LnHeightsEditorComponent, isStandalone: true, selector: "gve-ln-heights-editor", inputs: { lineCount: { classPropertyName: "lineCount", publicName: "lineCount", isSignal: true, isRequired: false, transformFunction: null }, heights: { classPropertyName: "heights", publicName: "heights", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { heightsChange: "heightsChange" }, ngImport: i0, template: "@if (lineNumbers().length) {\r\n <div class=\"form-row\">\r\n <!-- line number -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>line</mat-label>\r\n <mat-select [formControl]=\"lineNumber\">\r\n @for (n of lineNumbers(); track n) {\r\n <mat-option [value]=\"n\">\r\n {{ n }}\r\n </mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n <!-- height -->\r\n @if (lineNumber.value) {\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>height</mat-label>\r\n <input matInput type=\"number\" [formControl]=\"height\" min=\"0\" />\r\n </mat-form-field>\r\n }\r\n\r\n <!-- reset button -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n color=\"warn\"\r\n (click)=\"reset()\"\r\n matTooltip=\"Remove all the heights\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n </div>\r\n}\r\n", styles: [".input-nr{width:5em}.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.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.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: "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: 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: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] }); }
|
|
2940
|
-
}
|
|
2941
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: LnHeightsEditorComponent, decorators: [{
|
|
2942
|
-
type: Component,
|
|
2943
|
-
args: [{ selector: 'gve-ln-heights-editor', imports: [
|
|
2944
|
-
ReactiveFormsModule,
|
|
2945
|
-
MatButtonModule,
|
|
2946
|
-
MatFormFieldModule,
|
|
2947
|
-
MatIconModule,
|
|
2948
|
-
MatInputModule,
|
|
2949
|
-
MatSelectModule,
|
|
2950
|
-
MatTooltipModule,
|
|
2951
|
-
], template: "@if (lineNumbers().length) {\r\n <div class=\"form-row\">\r\n <!-- line number -->\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>line</mat-label>\r\n <mat-select [formControl]=\"lineNumber\">\r\n @for (n of lineNumbers(); track n) {\r\n <mat-option [value]=\"n\">\r\n {{ n }}\r\n </mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n <!-- height -->\r\n @if (lineNumber.value) {\r\n <mat-form-field class=\"input-nr\">\r\n <mat-label>height</mat-label>\r\n <input matInput type=\"number\" [formControl]=\"height\" min=\"0\" />\r\n </mat-form-field>\r\n }\r\n\r\n <!-- reset button -->\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n color=\"warn\"\r\n (click)=\"reset()\"\r\n matTooltip=\"Remove all the heights\"\r\n >\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n </div>\r\n}\r\n", styles: [".input-nr{width:5em}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}\n"] }]
|
|
2952
|
-
}], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { lineCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "lineCount", required: false }] }], heights: [{ type: i0.Input, args: [{ isSignal: true, alias: "heights", required: false }] }], heightsChange: [{ type: i0.Output, args: ["heightsChange"] }] } });
|
|
2432
|
+
], 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"] }]
|
|
2433
|
+
}], 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
2434
|
|
|
2954
2435
|
/**
|
|
2955
2436
|
* 🔑 `gve-snapshot-text-editor`
|
|
@@ -2961,9 +2442,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImpor
|
|
|
2961
2442
|
* - 🔥 `textChange` (`CharNode[]`): event emitted when text changes.
|
|
2962
2443
|
*/
|
|
2963
2444
|
class SnapshotTextEditorComponent {
|
|
2964
|
-
constructor(formBuilder,
|
|
2445
|
+
constructor(formBuilder, _snackbar,
|
|
2965
2446
|
// this component can be used as a dialog
|
|
2966
2447
|
dialogRef, data) {
|
|
2448
|
+
this._snackbar = _snackbar;
|
|
2967
2449
|
this.dialogRef = dialogRef;
|
|
2968
2450
|
this.data = data;
|
|
2969
2451
|
this.text = model([], ...(ngDevMode ? [{ debugName: "text" }] : []));
|
|
@@ -2992,6 +2474,11 @@ class SnapshotTextEditorComponent {
|
|
|
2992
2474
|
close() {
|
|
2993
2475
|
this.dialogRef?.close();
|
|
2994
2476
|
}
|
|
2477
|
+
copyToClipboard() {
|
|
2478
|
+
const json = JSON.stringify(GveBaseTextService.stringToBaseChars(this.userText.value));
|
|
2479
|
+
navigator.clipboard.writeText(json);
|
|
2480
|
+
this._snackbar.open('Copied JSON code to clipboard', undefined, { duration: 2000 });
|
|
2481
|
+
}
|
|
2995
2482
|
save() {
|
|
2996
2483
|
if (this.form.invalid) {
|
|
2997
2484
|
return;
|
|
@@ -3000,13 +2487,13 @@ class SnapshotTextEditorComponent {
|
|
|
3000
2487
|
if (!this.userText.value) {
|
|
3001
2488
|
return;
|
|
3002
2489
|
}
|
|
3003
|
-
this.text.set(
|
|
2490
|
+
this.text.set(GveBaseTextService.stringToBaseChars(this.userText.value));
|
|
3004
2491
|
this.dialogRef?.close(this.text());
|
|
3005
2492
|
}
|
|
3006
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
3007
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
2493
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", 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 }); }
|
|
2494
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", 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
2495
|
}
|
|
3009
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
2496
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: SnapshotTextEditorComponent, decorators: [{
|
|
3010
2497
|
type: Component,
|
|
3011
2498
|
args: [{ selector: 'gve-snapshot-text-editor', imports: [
|
|
3012
2499
|
ReactiveFormsModule,
|
|
@@ -3014,8 +2501,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImpor
|
|
|
3014
2501
|
MatFormFieldModule,
|
|
3015
2502
|
MatIconModule,
|
|
3016
2503
|
MatInputModule,
|
|
3017
|
-
], template: "<div [style.padding]=\"dialogRef ? '8px' : '0'\">\r\n @if (dialogRef) {\r\n
|
|
3018
|
-
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type:
|
|
2504
|
+
], 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"] }]
|
|
2505
|
+
}], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: i2$3.MatSnackBar }, { type: i3$2.MatDialogRef, decorators: [{
|
|
3019
2506
|
type: Optional
|
|
3020
2507
|
}] }, { type: undefined, decorators: [{
|
|
3021
2508
|
type: Optional
|
|
@@ -3047,6 +2534,12 @@ class SnapshotEditorComponent {
|
|
|
3047
2534
|
this._dialogService = _dialogService;
|
|
3048
2535
|
this._snackbar = _snackbar;
|
|
3049
2536
|
this._nanoid = customAlphabet('1234567890abcdef', 10);
|
|
2537
|
+
/**
|
|
2538
|
+
* Reference to the snapshot rendition web component.
|
|
2539
|
+
* Using viewChild signal to handle conditionally rendered element -
|
|
2540
|
+
* the element only exists when result() is truthy.
|
|
2541
|
+
*/
|
|
2542
|
+
this.renditionRef = viewChild('rendition', ...(ngDevMode ? [{ debugName: "renditionRef" }] : []));
|
|
3050
2543
|
/**
|
|
3051
2544
|
* The snapshot to edit.
|
|
3052
2545
|
*/
|
|
@@ -3073,24 +2566,23 @@ class SnapshotEditorComponent {
|
|
|
3073
2566
|
*/
|
|
3074
2567
|
this.featureDefs = input(...(ngDevMode ? [undefined, { debugName: "featureDefs" }] : []));
|
|
3075
2568
|
/**
|
|
3076
|
-
*
|
|
2569
|
+
* The IDs of the features that are multi-valued. If a feature being
|
|
2570
|
+
* edited is in this list, the feature editor will allow adding multiple
|
|
2571
|
+
* values to it. Passed down to feature editors.
|
|
3077
2572
|
*/
|
|
3078
|
-
this.
|
|
2573
|
+
this.multiValuedFeatureIds = input(...(ngDevMode ? [undefined, { debugName: "multiValuedFeatureIds" }] : []));
|
|
3079
2574
|
/**
|
|
3080
|
-
*
|
|
2575
|
+
* Settings for rendering the snapshot. These are passed to the snapshot rendition
|
|
2576
|
+
* component.
|
|
3081
2577
|
*/
|
|
3082
|
-
this.
|
|
2578
|
+
this.renditionSettings = input(DEFAULT_SETTINGS, ...(ngDevMode ? [{ debugName: "renditionSettings" }] : []));
|
|
3083
2579
|
/**
|
|
3084
2580
|
* Emitted when the user cancels the snapshot editing.
|
|
3085
2581
|
*/
|
|
3086
2582
|
this.snapshotCancel = output();
|
|
3087
|
-
this.showChain = new FormControl(false, { nonNullable: true });
|
|
3088
2583
|
this.featDetails = new FormControl(false, { nonNullable: true });
|
|
2584
|
+
this.autoRun = new FormControl(false, { nonNullable: true });
|
|
3089
2585
|
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
2586
|
// the currently picked base text range
|
|
3095
2587
|
this.textRange = signal(undefined, ...(ngDevMode ? [{ debugName: "textRange" }] : []));
|
|
3096
2588
|
// the lines count for the current base text
|
|
@@ -3111,68 +2603,23 @@ class SnapshotEditorComponent {
|
|
|
3111
2603
|
6: 'swap',
|
|
3112
2604
|
7: 'annotate',
|
|
3113
2605
|
};
|
|
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);
|
|
2606
|
+
// operations execution result
|
|
2607
|
+
this.result = signal(undefined, ...(ngDevMode ? [{ debugName: "result" }] : []));
|
|
2608
|
+
this.resultOperationId = signal(undefined, ...(ngDevMode ? [{ debugName: "resultOperationId" }] : []));
|
|
2609
|
+
this.initialStepIndex = signal(-1, ...(ngDevMode ? [{ debugName: "initialStepIndex" }] : []));
|
|
3122
2610
|
// base text
|
|
3123
2611
|
this.baseText = new FormControl([], {
|
|
3124
2612
|
nonNullable: true,
|
|
3125
2613
|
validators: [Validators.required],
|
|
3126
2614
|
});
|
|
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
2615
|
// operations
|
|
3135
2616
|
this.operations = new FormControl([], {
|
|
3136
2617
|
nonNullable: true,
|
|
3137
2618
|
});
|
|
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
2619
|
// form
|
|
3152
2620
|
this.form = formBuilder.group({
|
|
3153
|
-
width: this.width,
|
|
3154
|
-
height: this.height,
|
|
3155
|
-
style: this.style,
|
|
3156
2621
|
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
2622
|
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
2623
|
});
|
|
3177
2624
|
// when snapshot changes, update the form
|
|
3178
2625
|
effect(() => {
|
|
@@ -3181,6 +2628,55 @@ class SnapshotEditorComponent {
|
|
|
3181
2628
|
this.runToLast();
|
|
3182
2629
|
}, 0);
|
|
3183
2630
|
});
|
|
2631
|
+
// when rendition element becomes available (conditionally rendered),
|
|
2632
|
+
// initialize its settings and event listeners
|
|
2633
|
+
effect(() => {
|
|
2634
|
+
const rendition = this.renditionRef()?.nativeElement;
|
|
2635
|
+
if (rendition) {
|
|
2636
|
+
this.initRendition(rendition);
|
|
2637
|
+
}
|
|
2638
|
+
});
|
|
2639
|
+
}
|
|
2640
|
+
/**
|
|
2641
|
+
* Initialize rendition settings and event listeners when the element becomes available.
|
|
2642
|
+
* Called via effect since the element is conditionally rendered.
|
|
2643
|
+
*/
|
|
2644
|
+
initRendition(rendition) {
|
|
2645
|
+
console.log('GveSnapshotRendition.version:', GveSnapshotRendition.version);
|
|
2646
|
+
// set initial settings
|
|
2647
|
+
this.applyRenditionSettings();
|
|
2648
|
+
// listen to version change events
|
|
2649
|
+
rendition.addEventListener('versionTagChange', (event) => {
|
|
2650
|
+
const customEvent = event;
|
|
2651
|
+
console.log('Version changed:', customEvent.detail);
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
/**
|
|
2655
|
+
* Apply settings to the rendition component.
|
|
2656
|
+
*/
|
|
2657
|
+
applyRenditionSettings() {
|
|
2658
|
+
const rendition = this.renditionRef()?.nativeElement;
|
|
2659
|
+
if (!rendition) {
|
|
2660
|
+
return;
|
|
2661
|
+
}
|
|
2662
|
+
const settings = this.renditionSettings();
|
|
2663
|
+
settings.debug = this.debug() || false;
|
|
2664
|
+
rendition.settings = settings;
|
|
2665
|
+
}
|
|
2666
|
+
/**
|
|
2667
|
+
* Update the snapshot rendition component with the current result data.
|
|
2668
|
+
*/
|
|
2669
|
+
updateRendition() {
|
|
2670
|
+
const rendition = this.renditionRef()?.nativeElement;
|
|
2671
|
+
const result = this.result();
|
|
2672
|
+
if (rendition && result) {
|
|
2673
|
+
// update settings with latest rendition settings
|
|
2674
|
+
this.applyRenditionSettings();
|
|
2675
|
+
// supply the base text (not returned by API, must be provided by caller)
|
|
2676
|
+
result.text = this.baseText.value;
|
|
2677
|
+
// set data
|
|
2678
|
+
rendition.data = result;
|
|
2679
|
+
}
|
|
3184
2680
|
}
|
|
3185
2681
|
/**
|
|
3186
2682
|
* Set the view data for the snapshot view.
|
|
@@ -3195,65 +2691,22 @@ class SnapshotEditorComponent {
|
|
|
3195
2691
|
if (!snapshot) {
|
|
3196
2692
|
snapshot = this.getSnapshot();
|
|
3197
2693
|
}
|
|
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
2694
|
}
|
|
3214
2695
|
updateForm(snapshot) {
|
|
3215
|
-
this.
|
|
3216
|
-
this.initialStepIndex = -1;
|
|
2696
|
+
this.initialStepIndex.set(-1);
|
|
3217
2697
|
if (!snapshot) {
|
|
3218
2698
|
this.form.reset();
|
|
3219
2699
|
}
|
|
3220
2700
|
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
2701
|
if (Array.isArray(snapshot.text)) {
|
|
3232
2702
|
this.baseText.setValue(snapshot.text);
|
|
3233
2703
|
}
|
|
3234
2704
|
else {
|
|
3235
|
-
this.baseText.setValue(
|
|
2705
|
+
this.baseText.setValue(GveBaseTextService.stringToBaseChars(snapshot.text));
|
|
3236
2706
|
}
|
|
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
2707
|
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
2708
|
}
|
|
3255
2709
|
this.updateLineCount(this.baseText.value);
|
|
3256
|
-
this.updateOpLists();
|
|
3257
2710
|
this.form.markAsPristine();
|
|
3258
2711
|
}
|
|
3259
2712
|
// #region base text
|
|
@@ -3276,8 +2729,6 @@ class SnapshotEditorComponent {
|
|
|
3276
2729
|
this.updateLineCount(text);
|
|
3277
2730
|
// remove all operations and update the view data
|
|
3278
2731
|
this.removeAllOperations();
|
|
3279
|
-
// remove all timelines
|
|
3280
|
-
this.timelines.reset();
|
|
3281
2732
|
}
|
|
3282
2733
|
});
|
|
3283
2734
|
}
|
|
@@ -3306,27 +2757,6 @@ class SnapshotEditorComponent {
|
|
|
3306
2757
|
}
|
|
3307
2758
|
// #endregion
|
|
3308
2759
|
// #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
2760
|
/**
|
|
3331
2761
|
* Edit a new operation.
|
|
3332
2762
|
*/
|
|
@@ -3348,7 +2778,7 @@ class SnapshotEditorComponent {
|
|
|
3348
2778
|
*/
|
|
3349
2779
|
editOperation(index) {
|
|
3350
2780
|
this.editedOpIndex.set(index);
|
|
3351
|
-
this.editedOp.set(
|
|
2781
|
+
this.editedOp.set(structuredClone(this.operations.value[index]));
|
|
3352
2782
|
}
|
|
3353
2783
|
/**
|
|
3354
2784
|
* Close the currently edited operation.
|
|
@@ -3389,10 +2819,10 @@ class SnapshotEditorComponent {
|
|
|
3389
2819
|
this.operations.setValue(operations);
|
|
3390
2820
|
this.operations.markAsDirty();
|
|
3391
2821
|
this.operations.updateValueAndValidity();
|
|
3392
|
-
//
|
|
3393
|
-
this.
|
|
3394
|
-
|
|
3395
|
-
|
|
2822
|
+
// run to the edited operation if auto-run is enabled
|
|
2823
|
+
if (this.autoRun.value) {
|
|
2824
|
+
this.runTo(i);
|
|
2825
|
+
}
|
|
3396
2826
|
// close the edited operation
|
|
3397
2827
|
this.closeEditedOperation();
|
|
3398
2828
|
}
|
|
@@ -3410,8 +2840,8 @@ class SnapshotEditorComponent {
|
|
|
3410
2840
|
this.closeEditedOperation();
|
|
3411
2841
|
}
|
|
3412
2842
|
// reset the result operation ID if it is the one being deleted
|
|
3413
|
-
if (this.resultOperationId === this.operations.value[index].id) {
|
|
3414
|
-
this.resultOperationId
|
|
2843
|
+
if (this.resultOperationId() === this.operations.value[index].id) {
|
|
2844
|
+
this.resultOperationId.set(undefined);
|
|
3415
2845
|
}
|
|
3416
2846
|
// delete the operation and update the form control
|
|
3417
2847
|
const operations = [...this.operations.value];
|
|
@@ -3419,8 +2849,6 @@ class SnapshotEditorComponent {
|
|
|
3419
2849
|
this.operations.setValue(operations);
|
|
3420
2850
|
this.operations.markAsDirty();
|
|
3421
2851
|
this.operations.updateValueAndValidity();
|
|
3422
|
-
// update the operation lists
|
|
3423
|
-
this.updateOpLists();
|
|
3424
2852
|
// update the view data
|
|
3425
2853
|
setTimeout(() => {
|
|
3426
2854
|
this.runToLast();
|
|
@@ -3428,6 +2856,58 @@ class SnapshotEditorComponent {
|
|
|
3428
2856
|
}
|
|
3429
2857
|
});
|
|
3430
2858
|
}
|
|
2859
|
+
/**
|
|
2860
|
+
* Duplicate the operation at the specified index, inserting the copy
|
|
2861
|
+
* immediately after the original.
|
|
2862
|
+
* @param index The index of the operation to duplicate.
|
|
2863
|
+
*/
|
|
2864
|
+
duplicateOperation(index) {
|
|
2865
|
+
const operations = [...this.operations.value];
|
|
2866
|
+
const clone = structuredClone(operations[index]);
|
|
2867
|
+
clone.id = this._nanoid();
|
|
2868
|
+
operations.splice(index + 1, 0, clone);
|
|
2869
|
+
this.operations.setValue(operations);
|
|
2870
|
+
this.operations.markAsDirty();
|
|
2871
|
+
this.operations.updateValueAndValidity();
|
|
2872
|
+
}
|
|
2873
|
+
/**
|
|
2874
|
+
* Move the operation at the specified index up by one position.
|
|
2875
|
+
* @param index The index of the operation to move up.
|
|
2876
|
+
*/
|
|
2877
|
+
moveOperationUp(index) {
|
|
2878
|
+
if (index < 1) {
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
const operations = [...this.operations.value];
|
|
2882
|
+
const op = operations.splice(index, 1)[0];
|
|
2883
|
+
operations.splice(index - 1, 0, op);
|
|
2884
|
+
this.operations.setValue(operations);
|
|
2885
|
+
this.operations.markAsDirty();
|
|
2886
|
+
this.operations.updateValueAndValidity();
|
|
2887
|
+
}
|
|
2888
|
+
/**
|
|
2889
|
+
* Move the operation at the specified index down by one position.
|
|
2890
|
+
* @param index The index of the operation to move down.
|
|
2891
|
+
*/
|
|
2892
|
+
moveOperationDown(index) {
|
|
2893
|
+
const operations = [...this.operations.value];
|
|
2894
|
+
if (index >= operations.length - 1) {
|
|
2895
|
+
return;
|
|
2896
|
+
}
|
|
2897
|
+
const op = operations.splice(index, 1)[0];
|
|
2898
|
+
operations.splice(index + 1, 0, op);
|
|
2899
|
+
this.operations.setValue(operations);
|
|
2900
|
+
this.operations.markAsDirty();
|
|
2901
|
+
this.operations.updateValueAndValidity();
|
|
2902
|
+
}
|
|
2903
|
+
copyOperationsJson() {
|
|
2904
|
+
const json = JSON.stringify(this.operations.value, null, 2);
|
|
2905
|
+
navigator.clipboard.writeText(json).then(() => {
|
|
2906
|
+
this._snackbar.open('Operations JSON copied to clipboard', 'OK', {
|
|
2907
|
+
duration: 3000,
|
|
2908
|
+
});
|
|
2909
|
+
});
|
|
2910
|
+
}
|
|
3431
2911
|
/**
|
|
3432
2912
|
* Parse the operations from their text and append them to the current
|
|
3433
2913
|
* snapshot operations.
|
|
@@ -3446,8 +2926,6 @@ class SnapshotEditorComponent {
|
|
|
3446
2926
|
this.operations.setValue(operations);
|
|
3447
2927
|
this.operations.markAsDirty();
|
|
3448
2928
|
this.operations.updateValueAndValidity();
|
|
3449
|
-
// update the operation lists
|
|
3450
|
-
this.updateOpLists();
|
|
3451
2929
|
// update the view data
|
|
3452
2930
|
setTimeout(() => {
|
|
3453
2931
|
this.runToLast();
|
|
@@ -3459,15 +2937,13 @@ class SnapshotEditorComponent {
|
|
|
3459
2937
|
* Remove all the operations, close the edited operation and update the view data.
|
|
3460
2938
|
*/
|
|
3461
2939
|
removeAllOperations() {
|
|
3462
|
-
this.resultOperationId
|
|
2940
|
+
this.resultOperationId.set(undefined);
|
|
3463
2941
|
this.closeEditedOperation();
|
|
3464
2942
|
this.operations.reset();
|
|
3465
2943
|
this.operations.markAsDirty();
|
|
3466
2944
|
this.operations.updateValueAndValidity();
|
|
3467
|
-
this.opTags.set([]);
|
|
3468
|
-
this.opElementIds.set([]);
|
|
3469
2945
|
this.setViewData();
|
|
3470
|
-
this.result
|
|
2946
|
+
this.result.set(undefined);
|
|
3471
2947
|
}
|
|
3472
2948
|
/**
|
|
3473
2949
|
* Remove all the operations.
|
|
@@ -3591,16 +3067,10 @@ class SnapshotEditorComponent {
|
|
|
3591
3067
|
snapshot.operations = snapshot.operations.slice(0, index + 1);
|
|
3592
3068
|
}
|
|
3593
3069
|
// 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);
|
|
3070
|
+
this.resultOperationId.set(snapshot.operations[index].id);
|
|
3601
3071
|
// run the operations
|
|
3602
3072
|
this.busy.set(true);
|
|
3603
|
-
this.initialStepIndex
|
|
3073
|
+
this.initialStepIndex.set(index);
|
|
3604
3074
|
this._api
|
|
3605
3075
|
.runOperations(snapshot.text, snapshot.operations)
|
|
3606
3076
|
.subscribe({
|
|
@@ -3612,7 +3082,9 @@ class SnapshotEditorComponent {
|
|
|
3612
3082
|
}
|
|
3613
3083
|
// set the result
|
|
3614
3084
|
console.log('result:', wrapper.result);
|
|
3615
|
-
this.result
|
|
3085
|
+
this.result.set(wrapper.result);
|
|
3086
|
+
// update the rendition component
|
|
3087
|
+
this.updateRendition();
|
|
3616
3088
|
// supply the last operation output tag from its result step if not set
|
|
3617
3089
|
if (!lastOperation.outputTag) {
|
|
3618
3090
|
const step = wrapper.result.steps.find((s) => s.operation.id === lastOperation.id);
|
|
@@ -3622,22 +3094,9 @@ class SnapshotEditorComponent {
|
|
|
3622
3094
|
this.supplyOpNodes(snapshot, wrapper.result, lastOperation.outputTag);
|
|
3623
3095
|
// update the view data
|
|
3624
3096
|
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
|
-
// get the chain model if requested
|
|
3634
|
-
if (this.showChain.value) {
|
|
3635
|
-
this.getChainAt(index);
|
|
3636
|
-
}
|
|
3637
3097
|
},
|
|
3638
3098
|
error: (error) => {
|
|
3639
3099
|
console.error(error);
|
|
3640
|
-
this._transparentIds = undefined;
|
|
3641
3100
|
this._snackbar.open('Error running operations', 'OK');
|
|
3642
3101
|
},
|
|
3643
3102
|
complete: () => {
|
|
@@ -3656,48 +3115,8 @@ class SnapshotEditorComponent {
|
|
|
3656
3115
|
}
|
|
3657
3116
|
else {
|
|
3658
3117
|
this.setViewData();
|
|
3659
|
-
this.result
|
|
3660
|
-
}
|
|
3661
|
-
}
|
|
3662
|
-
getChainAt(index, lastOperation) {
|
|
3663
|
-
// get the snapshot to run operations on
|
|
3664
|
-
const snapshot = this.getSnapshot();
|
|
3665
|
-
if (!snapshot.text) {
|
|
3666
|
-
return;
|
|
3667
|
-
}
|
|
3668
|
-
// remove from the snapshot the operations past the specified index,
|
|
3669
|
-
// also replacing the last operation when this was received as a parameter
|
|
3670
|
-
if (lastOperation) {
|
|
3671
|
-
snapshot.operations = snapshot.operations.slice(0, index);
|
|
3672
|
-
snapshot.operations.push(lastOperation);
|
|
3673
|
-
}
|
|
3674
|
-
else {
|
|
3675
|
-
lastOperation = snapshot.operations[index];
|
|
3676
|
-
snapshot.operations = snapshot.operations.slice(0, index + 1);
|
|
3118
|
+
this.result.set(undefined);
|
|
3677
3119
|
}
|
|
3678
|
-
this.busy.set(true);
|
|
3679
|
-
this._api
|
|
3680
|
-
.getChain(snapshot.text, snapshot.operations)
|
|
3681
|
-
.subscribe({
|
|
3682
|
-
next: (wrapper) => {
|
|
3683
|
-
// handle operation (non-fatal) error or result
|
|
3684
|
-
if (wrapper.error) {
|
|
3685
|
-
this._snackbar.open(wrapper.error, 'OK');
|
|
3686
|
-
this.chain.set(undefined);
|
|
3687
|
-
}
|
|
3688
|
-
else {
|
|
3689
|
-
this.chain.set(wrapper.result);
|
|
3690
|
-
}
|
|
3691
|
-
},
|
|
3692
|
-
error: (error) => {
|
|
3693
|
-
console.error(error);
|
|
3694
|
-
this._transparentIds = undefined;
|
|
3695
|
-
this._snackbar.open('Error running operations', 'OK');
|
|
3696
|
-
},
|
|
3697
|
-
complete: () => {
|
|
3698
|
-
this.busy.set(false);
|
|
3699
|
-
},
|
|
3700
|
-
});
|
|
3701
3120
|
}
|
|
3702
3121
|
/**
|
|
3703
3122
|
* Update the snapshot view by running the operations up to the
|
|
@@ -3706,8 +3125,8 @@ class SnapshotEditorComponent {
|
|
|
3706
3125
|
* @param operation The operation being previewed.
|
|
3707
3126
|
*/
|
|
3708
3127
|
onOperationPreview(operation) {
|
|
3709
|
-
// no multiple previews or previewing a new operation
|
|
3710
|
-
if (this._previewing || this.editedOpIndex() < 0) {
|
|
3128
|
+
// no multiple previews or previewing a new operation or auto-run disabled
|
|
3129
|
+
if (this._previewing || this.editedOpIndex() < 0 || !this.autoRun.value) {
|
|
3711
3130
|
return;
|
|
3712
3131
|
}
|
|
3713
3132
|
this._previewing = true;
|
|
@@ -3717,28 +3136,14 @@ class SnapshotEditorComponent {
|
|
|
3717
3136
|
}, 0);
|
|
3718
3137
|
}
|
|
3719
3138
|
// #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
3139
|
/**
|
|
3736
3140
|
* Handle the event fired by the chain result view to pick a step.
|
|
3737
3141
|
*
|
|
3738
3142
|
* @param step The step to pick.
|
|
3739
3143
|
*/
|
|
3740
3144
|
onStepPick(step) {
|
|
3741
|
-
|
|
3145
|
+
const result = this.result();
|
|
3146
|
+
if (!result || this._stepPickFrozen) {
|
|
3742
3147
|
return;
|
|
3743
3148
|
}
|
|
3744
3149
|
// get base text snapshot
|
|
@@ -3749,87 +3154,11 @@ class SnapshotEditorComponent {
|
|
|
3749
3154
|
// their visuals creep into the snapshot view
|
|
3750
3155
|
snapshot.operations = snapshot.operations.slice(0, index + 1);
|
|
3751
3156
|
// 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);
|
|
3157
|
+
this.resultOperationId.set(snapshot.operations[index].id);
|
|
3759
3158
|
// update the snapshot text nodes with those introduced by operations
|
|
3760
|
-
this.supplyOpNodes(snapshot,
|
|
3159
|
+
this.supplyOpNodes(snapshot, result, step.outputTag);
|
|
3761
3160
|
// update the view data
|
|
3762
3161
|
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
3162
|
}
|
|
3834
3163
|
/**
|
|
3835
3164
|
* Emit the cancel event for this snapshot edit.
|
|
@@ -3837,73 +3166,6 @@ class SnapshotEditorComponent {
|
|
|
3837
3166
|
close() {
|
|
3838
3167
|
this.snapshotCancel.emit();
|
|
3839
3168
|
}
|
|
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
3169
|
/**
|
|
3908
3170
|
* Get a snapshot from the form data.
|
|
3909
3171
|
*
|
|
@@ -3911,61 +3173,11 @@ class SnapshotEditorComponent {
|
|
|
3911
3173
|
*/
|
|
3912
3174
|
getSnapshot() {
|
|
3913
3175
|
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
3176
|
// snapshot nodes might be changed, so we copy them
|
|
3921
3177
|
// 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,
|
|
3178
|
+
text: structuredClone(this.baseText.value),
|
|
3179
|
+
operations: structuredClone(this.operations.value),
|
|
3936
3180
|
};
|
|
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
3181
|
return snapshot;
|
|
3970
3182
|
}
|
|
3971
3183
|
/**
|
|
@@ -3978,10 +3190,10 @@ class SnapshotEditorComponent {
|
|
|
3978
3190
|
}
|
|
3979
3191
|
this.snapshot.set(this.getSnapshot());
|
|
3980
3192
|
}
|
|
3981
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
3982
|
-
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 }, 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],[formArray],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" }] }); }
|
|
3193
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", 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 }); }
|
|
3194
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", 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\r\n [formControl]=\"featDetails\"\r\n matTooltip=\"Toggle feature details\"\r\n >feat. details</mat-slide-toggle\r\n >\r\n <!-- auto run on edit -->\r\n <mat-slide-toggle\r\n [formControl]=\"autoRun\"\r\n matTooltip=\"Auto-run operations on edit\"\r\n >auto-run</mat-slide-toggle\r\n >\r\n\r\n <div class=\"form-row right\">\r\n <!-- copy ops 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 matTooltip=\"Add a new operation\"\r\n >\r\n <mat-icon>add_circle</mat-icon> operation\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 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
3195
|
}
|
|
3984
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
3196
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: SnapshotEditorComponent, decorators: [{
|
|
3985
3197
|
type: Component,
|
|
3986
3198
|
args: [{ selector: 'gve-snapshot-editor', imports: [
|
|
3987
3199
|
CommonModule,
|
|
@@ -4003,15 +3215,125 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImpor
|
|
|
4003
3215
|
ChainOperationEditorComponent,
|
|
4004
3216
|
ChainResultViewComponent,
|
|
4005
3217
|
BaseTextViewComponent,
|
|
4006
|
-
LnHeightsEditorComponent,
|
|
4007
|
-
AnimationTimelineSetComponent,
|
|
4008
3218
|
FlatLookupPipe,
|
|
4009
|
-
|
|
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
|
-
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: GveApiService }, { type: i3$
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
3219
|
+
MatDialogClose
|
|
3220
|
+
], 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\r\n [formControl]=\"featDetails\"\r\n matTooltip=\"Toggle feature details\"\r\n >feat. details</mat-slide-toggle\r\n >\r\n <!-- auto run on edit -->\r\n <mat-slide-toggle\r\n [formControl]=\"autoRun\"\r\n matTooltip=\"Auto-run operations on edit\"\r\n >auto-run</mat-slide-toggle\r\n >\r\n\r\n <div class=\"form-row right\">\r\n <!-- copy ops 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 matTooltip=\"Add a new operation\"\r\n >\r\n <mat-icon>add_circle</mat-icon> operation\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 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"] }]
|
|
3221
|
+
}], 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"] }] } });
|
|
3222
|
+
|
|
3223
|
+
class GveGraphvizService {
|
|
3224
|
+
hashString(str) {
|
|
3225
|
+
let hash = 0;
|
|
3226
|
+
for (let i = 0; i < str.length; i++) {
|
|
3227
|
+
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
3228
|
+
hash = hash & hash; // convert to 32bit integer
|
|
3229
|
+
}
|
|
3230
|
+
return Math.abs(hash);
|
|
3231
|
+
}
|
|
3232
|
+
hslToRgb(hue, saturation, lightness) {
|
|
3233
|
+
const chroma = ((1 - Math.abs((2 * lightness) / 100 - 1)) * saturation) / 100;
|
|
3234
|
+
const x = chroma * (1 - Math.abs(((hue / 60) % 2) - 1));
|
|
3235
|
+
const m = lightness / 100 - chroma / 2;
|
|
3236
|
+
let r = 0, g = 0, b = 0;
|
|
3237
|
+
if (hue >= 0 && hue < 60) {
|
|
3238
|
+
r = chroma;
|
|
3239
|
+
g = x;
|
|
3240
|
+
}
|
|
3241
|
+
else if (hue >= 60 && hue < 120) {
|
|
3242
|
+
r = x;
|
|
3243
|
+
g = chroma;
|
|
3244
|
+
}
|
|
3245
|
+
else if (hue >= 120 && hue < 180) {
|
|
3246
|
+
g = chroma;
|
|
3247
|
+
b = x;
|
|
3248
|
+
}
|
|
3249
|
+
else if (hue >= 180 && hue < 240) {
|
|
3250
|
+
g = x;
|
|
3251
|
+
b = chroma;
|
|
3252
|
+
}
|
|
3253
|
+
else if (hue >= 240 && hue < 300) {
|
|
3254
|
+
r = x;
|
|
3255
|
+
b = chroma;
|
|
3256
|
+
}
|
|
3257
|
+
else if (hue >= 300 && hue < 360) {
|
|
3258
|
+
r = chroma;
|
|
3259
|
+
b = x;
|
|
3260
|
+
}
|
|
3261
|
+
r = Math.round((r + m) * 255);
|
|
3262
|
+
g = Math.round((g + m) * 255);
|
|
3263
|
+
b = Math.round((b + m) * 255);
|
|
3264
|
+
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
|
3265
|
+
}
|
|
3266
|
+
getColorForTag(tag) {
|
|
3267
|
+
const hash = this.hashString(tag);
|
|
3268
|
+
const hue = (hash * 137) % 360; // Use a prime number to spread out the hues
|
|
3269
|
+
const saturation = 60 + (hash % 40); // Saturation between 60% and 100%
|
|
3270
|
+
const lightness = 50 + (hash % 30); // Lightness between 50% and 80%
|
|
3271
|
+
return this.hslToRgb(hue, saturation, lightness);
|
|
3272
|
+
}
|
|
3273
|
+
getExcludedNodeIds(chain, tags) {
|
|
3274
|
+
// get only the desired links
|
|
3275
|
+
const links = chain.links.filter((link) => tags ? tags.includes(link.tag) : true);
|
|
3276
|
+
// return all the nodes not referenced by the links
|
|
3277
|
+
const excluded = new Set();
|
|
3278
|
+
chain.nodes.forEach((node) => {
|
|
3279
|
+
excluded.add(node.id);
|
|
3280
|
+
});
|
|
3281
|
+
links.forEach((link) => {
|
|
3282
|
+
excluded.delete(link.sourceId);
|
|
3283
|
+
excluded.delete(link.targetId);
|
|
3284
|
+
});
|
|
3285
|
+
return excluded;
|
|
3286
|
+
}
|
|
3287
|
+
/**
|
|
3288
|
+
* Represent the received chain as a Graphviz digraph.
|
|
3289
|
+
*
|
|
3290
|
+
* @param chain The source chain if any.
|
|
3291
|
+
* @param tags The tags to show. When set, only the links with these tags are shown.
|
|
3292
|
+
* @param rankdir The rank direction.
|
|
3293
|
+
* @returns Graphviz representation of the chain.
|
|
3294
|
+
*/
|
|
3295
|
+
generateGraph(chain, tags, rankdir = 'LR') {
|
|
3296
|
+
if (!chain) {
|
|
3297
|
+
return 'digraph G {}';
|
|
3298
|
+
}
|
|
3299
|
+
const sb = [];
|
|
3300
|
+
const excludedNodeIds = this.getExcludedNodeIds(chain, tags);
|
|
3301
|
+
sb.push('digraph G {');
|
|
3302
|
+
sb.push(' bgcolor=transparent;');
|
|
3303
|
+
sb.push(' node [style=filled];');
|
|
3304
|
+
sb.push(` rankdir=${rankdir};`);
|
|
3305
|
+
chain.nodes.forEach((node) => {
|
|
3306
|
+
// note that in label we must escape the double quotes
|
|
3307
|
+
sb.push(` ${node.id} [label="${node.label === '"' ? '"' : node.label}"` +
|
|
3308
|
+
(node.sourceTag
|
|
3309
|
+
? ` fillcolor="${this.getColorForTag(node.sourceTag)}"`
|
|
3310
|
+
: '') +
|
|
3311
|
+
(node.sourceTag && excludedNodeIds.has(node.id)
|
|
3312
|
+
? ` xlabel="${node.sourceTag}"`
|
|
3313
|
+
: '') +
|
|
3314
|
+
'];');
|
|
3315
|
+
});
|
|
3316
|
+
chain.links.forEach((link) => {
|
|
3317
|
+
if (!link.sourceId ||
|
|
3318
|
+
!link.targetId ||
|
|
3319
|
+
(tags && !tags.includes(link.tag))) {
|
|
3320
|
+
return;
|
|
3321
|
+
}
|
|
3322
|
+
const color = this.getColorForTag(link.tag);
|
|
3323
|
+
sb.push(` ${link.sourceId} -> ${link.targetId} [label="${link.tag}", color="${color}"];`);
|
|
3324
|
+
});
|
|
3325
|
+
sb.push('}');
|
|
3326
|
+
return sb.join('\n');
|
|
3327
|
+
}
|
|
3328
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GveGraphvizService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3329
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GveGraphvizService, providedIn: 'root' }); }
|
|
3330
|
+
}
|
|
3331
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GveGraphvizService, decorators: [{
|
|
3332
|
+
type: Injectable,
|
|
3333
|
+
args: [{
|
|
3334
|
+
providedIn: 'root',
|
|
3335
|
+
}]
|
|
3336
|
+
}] });
|
|
4015
3337
|
|
|
4016
3338
|
/*
|
|
4017
3339
|
* Public API Surface of gve-core
|
|
@@ -4021,5 +3343,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImpor
|
|
|
4021
3343
|
* Generated bundle index. Do not edit.
|
|
4022
3344
|
*/
|
|
4023
3345
|
|
|
4024
|
-
export {
|
|
3346
|
+
export { BaseTextCharComponent, BaseTextEditorComponent, BaseTextViewComponent, BatchOperationEditorComponent, ChainOperationEditorComponent, ChainResultViewComponent, FeatureEditorComponent, FeatureSetEditorComponent, FeatureSetPolicy, FeatureSetViewComponent, GveApiService, GveBaseTextService, GveGraphvizService, OperationSourceEditorComponent, OperationType, SettingsService, SnapshotEditorComponent, SnapshotTextEditorComponent, StepsMapComponent };
|
|
4025
3347
|
//# sourceMappingURL=myrmidon-gve-core.mjs.map
|