phoenix-ui-components 4.0.1 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Component, EventEmitter, Output, Input, ViewEncapsulation, ViewChild, Injectable, ContentChild, Pipe, ViewChildren, Inject, ErrorHandler, NgModule } from '@angular/core';
2
+ import { Component, EventEmitter, Output, Input, ViewEncapsulation, ViewChild, Injectable, Inject, ContentChild, Pipe, ViewChildren, ErrorHandler, NgModule } from '@angular/core';
3
3
  import * as i1 from '@angular/router';
4
4
  import { RouterModule } from '@angular/router';
5
5
  import * as i2 from '@angular/common';
@@ -14,27 +14,33 @@ import * as i4$1 from '@angular/material/slide-toggle';
14
14
  import { MatSlideToggleModule } from '@angular/material/slide-toggle';
15
15
  import * as i5 from '@angular/material/checkbox';
16
16
  import { MatCheckboxModule } from '@angular/material/checkbox';
17
- import { EventDisplay, SceneManager, PrettySymbols, Edm4hepJsonLoader, JiveXMLLoader, PHYSLITELoader, CMSLoader, readZipFile, VRManager, XRSessionType, ARManager, phoenixURLOptions, ActiveVariable, getAvailableColumns, DEFAULT_ETA_PHI_CONFIG, ATLAS_KINEMATICS, ATLAS_MASTERCLASS_CONFIG, invariantMass, fourMomentumFromCluster, fourMomentumFromTrack } from 'phoenix-event-display';
18
- import * as i3$2 from '@angular/material/menu';
19
- import { MatMenuTrigger, MatMenuModule } from '@angular/material/menu';
17
+ import * as i1$1 from '@angular/material/dialog';
18
+ import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
19
+ import * as i2$2 from '@angular/material/icon';
20
+ import { MatIconModule } from '@angular/material/icon';
21
+ import { ActiveVariable, EventDisplay, SceneManager, PrettySymbols, Edm4hepJsonLoader, JiveXMLLoader, PHYSLITELoader, ATLASESDLoader, CMSLoader, readZipFile, VRManager, XRSessionType, ARManager, phoenixURLOptions, getAvailableColumns, DEFAULT_ETA_PHI_CONFIG, ATLAS_KINEMATICS, ATLAS_MASTERCLASS_CONFIG, invariantMass, fourMomentumFromCluster, fourMomentumFromTrack } from 'phoenix-event-display';
22
+ import * as i2$3 from '@angular/material/snack-bar';
23
+ import { MatSnackBarModule } from '@angular/material/snack-bar';
24
+ import RecordRTC from 'recordrtc';
25
+ import * as i1$2 from '@angular/material/progress-bar';
26
+ import { MatProgressBarModule } from '@angular/material/progress-bar';
27
+ import { BehaviorSubject } from 'rxjs';
20
28
  import * as i3$1 from '@angular/material/tooltip';
21
29
  import { MatTooltipModule } from '@angular/material/tooltip';
22
- import { ComponentPortal } from '@angular/cdk/portal';
30
+ import * as i4$2 from '@angular/material/menu';
31
+ import { MatMenuTrigger, MatMenuModule } from '@angular/material/menu';
23
32
  import { ResizeSensor } from 'css-element-queries';
24
- import * as i3$3 from '@angular/cdk/drag-drop';
33
+ import * as i3$2 from '@angular/cdk/drag-drop';
25
34
  import { DragDropModule } from '@angular/cdk/drag-drop';
26
- import * as i1$1 from '@angular/cdk/overlay';
35
+ import { ComponentPortal } from '@angular/cdk/portal';
36
+ import * as i1$3 from '@angular/cdk/overlay';
27
37
  import { OverlayModule } from '@angular/cdk/overlay';
28
38
  import packageJson from 'phoenix-event-display/package.json';
29
- import * as i1$2 from '@angular/material/dialog';
30
- import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
31
- import * as i4$2 from '@angular/material/icon';
32
- import { MatIconModule } from '@angular/material/icon';
33
39
  import { Vector3 } from 'three';
34
40
  import * as i5$1 from '@angular/material/radio';
35
41
  import { MatRadioModule } from '@angular/material/radio';
36
42
  import QRCode from 'qrcode';
37
- import * as i2$2 from '@angular/cdk/tree';
43
+ import * as i2$4 from '@angular/cdk/tree';
38
44
  import { NestedTreeControl, CdkTreeModule } from '@angular/cdk/tree';
39
45
  import { MatTreeNestedDataSource } from '@angular/material/tree';
40
46
  import JSZip from 'jszip';
@@ -45,7 +51,7 @@ class NavComponent {
45
51
  constructor() { }
46
52
  ngOnInit() { }
47
53
  static ɵfac = function NavComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || NavComponent)(); };
48
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: NavComponent, selectors: [["app-nav"]], standalone: false, decls: 2, vars: 0, consts: [["id", "mainLogo", "routerLink", "/", 1, "main-logo", "fixed-top", "p-2", "mx-auto", "mt-2"], ["src", "assets/images/logo-text.svg"]], template: function NavComponent_Template(rf, ctx) { if (rf & 1) {
54
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: NavComponent, selectors: [["app-nav"]], standalone: false, decls: 2, vars: 0, consts: [["id", "mainLogo", "routerLink", "/", "aria-label", "Phoenix home", 1, "main-logo", "fixed-top", "p-2", "mx-auto", "mt-2"], ["src", "assets/images/logo-text.svg", "alt", "Phoenix logo"]], template: function NavComponent_Template(rf, ctx) { if (rf & 1) {
49
55
  i0.ɵɵelementStart(0, "a", 0);
50
56
  i0.ɵɵelement(1, "img", 1);
51
57
  i0.ɵɵelementEnd();
@@ -53,7 +59,7 @@ class NavComponent {
53
59
  }
54
60
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(NavComponent, [{
55
61
  type: Component,
56
- args: [{ standalone: false, selector: 'app-nav', template: "<a id=\"mainLogo\" class=\"main-logo fixed-top p-2 mx-auto mt-2\" routerLink=\"/\">\n <img src=\"assets/images/logo-text.svg\" />\n</a>\n", styles: [".main-logo{width:140px;z-index:9999}.main-logo img{transition:all .5s}.main-logo:hover img{filter:drop-shadow(0px 0px 10px var(--phoenix-text-color));transition:all .5s}\n"] }]
62
+ args: [{ standalone: false, selector: 'app-nav', template: "<a\n id=\"mainLogo\"\n class=\"main-logo fixed-top p-2 mx-auto mt-2\"\n routerLink=\"/\"\n aria-label=\"Phoenix home\"\n>\n <img src=\"assets/images/logo-text.svg\" alt=\"Phoenix logo\" />\n</a>\n", styles: [".main-logo{width:140px;z-index:9999}.main-logo img{transition:all .5s}.main-logo:hover img{filter:drop-shadow(0px 0px 10px var(--phoenix-text-color));transition:all .5s}\n"] }]
57
63
  }], () => [], null); })();
58
64
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(NavComponent, { className: "NavComponent", filePath: "components/nav/nav.component.ts", lineNumber: 9 }); })();
59
65
 
@@ -136,48 +142,80 @@ class ConfigSliderComponent {
136
142
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ConfigSliderComponent, { className: "ConfigSliderComponent", filePath: "components/phoenix-menu/config/config-slider/config-slider.component.ts", lineNumber: 11 }); })();
137
143
 
138
144
  const _c0$e = ["phoenixMenuItem"];
139
- const _c1$7 = a0 => ({ "top.px": a0 });
140
- const _c2$3 = a0 => ({ "font-weight-bold w-100": a0 });
141
- const _c3$2 = a0 => ({ "p-0": a0 });
142
- const _c4$2 = a0 => ({ expanded: a0 });
143
- function PhoenixMenuItemComponent_ng_container_3_ng_container_2_Template(rf, ctx) { if (rf & 1) {
145
+ const _c1$7 = a0 => ({ expanded: a0 });
146
+ const _c2$3 = a0 => ({ "top.px": a0 });
147
+ const _c3$2 = a0 => ({ "range-slider-row": a0 });
148
+ const _c4$2 = a0 => ({ "font-weight-bold w-100": a0 });
149
+ const _c5$2 = a0 => ({ "p-0": a0 });
150
+ function PhoenixMenuItemComponent_div_0_ng_container_8_Template(rf, ctx) { if (rf & 1) {
144
151
  i0.ɵɵelementContainer(0);
145
152
  } }
146
- function PhoenixMenuItemComponent_ng_container_3_Template(rf, ctx) { if (rf & 1) {
153
+ function PhoenixMenuItemComponent_div_0_Template(rf, ctx) { if (rf & 1) {
147
154
  const _r1 = i0.ɵɵgetCurrentView();
155
+ i0.ɵɵelementStart(0, "div", 6, 2)(2, "button", 7);
156
+ i0.ɵɵlistener("click", function PhoenixMenuItemComponent_div_0_Template_button_click_2_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); ctx_r1.calculateConfigTop(); return i0.ɵɵresetView(ctx_r1.currentNode.configActive = !ctx_r1.currentNode.configActive); });
157
+ i0.ɵɵelementStart(3, "span", 8);
158
+ i0.ɵɵnamespaceSVG();
159
+ i0.ɵɵelementStart(4, "svg");
160
+ i0.ɵɵelement(5, "use");
161
+ i0.ɵɵelementEnd()();
162
+ i0.ɵɵnamespaceHTML();
163
+ i0.ɵɵelementStart(6, "span", 9);
164
+ i0.ɵɵtext(7);
165
+ i0.ɵɵelementEnd()();
166
+ i0.ɵɵtemplate(8, PhoenixMenuItemComponent_div_0_ng_container_8_Template, 1, 0, "ng-container", 10);
167
+ i0.ɵɵelementEnd();
168
+ } if (rf & 2) {
169
+ const ctx_r1 = i0.ɵɵnextContext();
170
+ const configPopup_r3 = i0.ɵɵreference(5);
171
+ i0.ɵɵadvance(2);
172
+ i0.ɵɵproperty("title", ctx_r1.currentNode.name);
173
+ i0.ɵɵadvance(3);
174
+ i0.ɵɵattribute("href", i0.ɵɵinterpolate2("assets/icons/", ctx_r1.currentNode.icon, ".svg#", ctx_r1.currentNode.icon));
175
+ i0.ɵɵadvance(2);
176
+ i0.ɵɵtextInterpolate(ctx_r1.compactLabel);
177
+ i0.ɵɵadvance();
178
+ i0.ɵɵproperty("ngTemplateOutlet", configPopup_r3);
179
+ } }
180
+ function PhoenixMenuItemComponent_ng_template_1_ng_container_3_ng_container_2_Template(rf, ctx) { if (rf & 1) {
181
+ i0.ɵɵelementContainer(0);
182
+ } }
183
+ function PhoenixMenuItemComponent_ng_template_1_ng_container_3_Template(rf, ctx) { if (rf & 1) {
184
+ const _r4 = i0.ɵɵgetCurrentView();
148
185
  i0.ɵɵelementContainerStart(0);
149
- i0.ɵɵelementStart(1, "mat-slide-toggle", 9);
150
- i0.ɵɵlistener("change", function PhoenixMenuItemComponent_ng_container_3_Template_mat_slide_toggle_change_1_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.currentNode.toggleSelfAndDescendants($event.checked)); });
151
- i0.ɵɵtemplate(2, PhoenixMenuItemComponent_ng_container_3_ng_container_2_Template, 1, 0, "ng-container", 10);
186
+ i0.ɵɵelementStart(1, "mat-slide-toggle", 17);
187
+ i0.ɵɵlistener("change", function PhoenixMenuItemComponent_ng_template_1_ng_container_3_Template_mat_slide_toggle_change_1_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.currentNode.toggleSelfAndDescendants($event.checked)); });
188
+ i0.ɵɵtemplate(2, PhoenixMenuItemComponent_ng_template_1_ng_container_3_ng_container_2_Template, 1, 0, "ng-container", 10);
152
189
  i0.ɵɵelementEnd();
153
190
  i0.ɵɵelementContainerEnd();
154
191
  } if (rf & 2) {
192
+ i0.ɵɵnextContext();
193
+ const nodeName_r5 = i0.ɵɵreference(5);
155
194
  const ctx_r1 = i0.ɵɵnextContext();
156
- const nodeName_r3 = i0.ɵɵreference(5);
157
195
  i0.ɵɵadvance();
158
196
  i0.ɵɵproperty("checked", ctx_r1.currentNode.toggleState);
159
197
  i0.ɵɵadvance();
160
- i0.ɵɵproperty("ngTemplateOutlet", nodeName_r3);
198
+ i0.ɵɵproperty("ngTemplateOutlet", nodeName_r5);
161
199
  } }
162
- function PhoenixMenuItemComponent_ng_template_4_span_1_Template(rf, ctx) { if (rf & 1) {
163
- i0.ɵɵelementStart(0, "span", 14);
200
+ function PhoenixMenuItemComponent_ng_template_1_ng_template_4_span_1_Template(rf, ctx) { if (rf & 1) {
201
+ i0.ɵɵelementStart(0, "span", 21);
164
202
  i0.ɵɵnamespaceSVG();
165
203
  i0.ɵɵelementStart(1, "svg");
166
204
  i0.ɵɵelement(2, "use");
167
205
  i0.ɵɵelementEnd()();
168
206
  } if (rf & 2) {
169
- const ctx_r1 = i0.ɵɵnextContext(2);
207
+ const ctx_r1 = i0.ɵɵnextContext(3);
170
208
  i0.ɵɵadvance(2);
171
209
  i0.ɵɵattribute("href", i0.ɵɵinterpolate2("assets/icons/", ctx_r1.currentNode.icon, ".svg#", ctx_r1.currentNode.icon));
172
210
  } }
173
- function PhoenixMenuItemComponent_ng_template_4_Template(rf, ctx) { if (rf & 1) {
174
- i0.ɵɵelementStart(0, "span", 11);
175
- i0.ɵɵtemplate(1, PhoenixMenuItemComponent_ng_template_4_span_1_Template, 3, 3, "span", 12);
176
- i0.ɵɵelementStart(2, "span", 13);
211
+ function PhoenixMenuItemComponent_ng_template_1_ng_template_4_Template(rf, ctx) { if (rf & 1) {
212
+ i0.ɵɵelementStart(0, "span", 18);
213
+ i0.ɵɵtemplate(1, PhoenixMenuItemComponent_ng_template_1_ng_template_4_span_1_Template, 3, 3, "span", 19);
214
+ i0.ɵɵelementStart(2, "span", 20);
177
215
  i0.ɵɵtext(3);
178
216
  i0.ɵɵelementEnd()();
179
217
  } if (rf & 2) {
180
- const ctx_r1 = i0.ɵɵnextContext();
218
+ const ctx_r1 = i0.ɵɵnextContext(2);
181
219
  i0.ɵɵadvance();
182
220
  i0.ɵɵproperty("ngIf", ctx_r1.currentNode.icon);
183
221
  i0.ɵɵadvance();
@@ -185,117 +223,200 @@ function PhoenixMenuItemComponent_ng_template_4_Template(rf, ctx) { if (rf & 1)
185
223
  i0.ɵɵadvance();
186
224
  i0.ɵɵtextInterpolate(ctx_r1.currentNode.name);
187
225
  } }
188
- function PhoenixMenuItemComponent_div_7_div_4_Template(rf, ctx) { if (rf & 1) {
189
- const _r5 = i0.ɵɵgetCurrentView();
190
- i0.ɵɵelementStart(0, "div", 20);
191
- i0.ɵɵlistener("click", function PhoenixMenuItemComponent_div_7_div_4_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.currentNode.configActive = false); });
226
+ function PhoenixMenuItemComponent_ng_template_1_div_7_ng_container_4_Template(rf, ctx) { if (rf & 1) {
227
+ i0.ɵɵelementContainer(0);
228
+ } }
229
+ function PhoenixMenuItemComponent_ng_template_1_div_7_Template(rf, ctx) { if (rf & 1) {
230
+ const _r6 = i0.ɵɵgetCurrentView();
231
+ i0.ɵɵelementStart(0, "div")(1, "button", 22);
232
+ i0.ɵɵlistener("click", function PhoenixMenuItemComponent_ng_template_1_div_7_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(2); ctx_r1.calculateConfigTop(); return i0.ɵɵresetView(ctx_r1.currentNode.configActive = !ctx_r1.currentNode.configActive); });
233
+ i0.ɵɵnamespaceSVG();
234
+ i0.ɵɵelementStart(2, "svg");
235
+ i0.ɵɵelement(3, "use", 23);
236
+ i0.ɵɵelementEnd()();
237
+ i0.ɵɵtemplate(4, PhoenixMenuItemComponent_ng_template_1_div_7_ng_container_4_Template, 1, 0, "ng-container", 10);
192
238
  i0.ɵɵelementEnd();
239
+ } if (rf & 2) {
240
+ i0.ɵɵnextContext(2);
241
+ const configPopup_r3 = i0.ɵɵreference(5);
242
+ i0.ɵɵadvance(4);
243
+ i0.ɵɵproperty("ngTemplateOutlet", configPopup_r3);
193
244
  } }
194
- function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_label_1_Template(rf, ctx) { if (rf & 1) {
195
- i0.ɵɵelementStart(0, "label", 26);
196
- i0.ɵɵtext(1);
245
+ function PhoenixMenuItemComponent_ng_template_1_button_8_Template(rf, ctx) { if (rf & 1) {
246
+ const _r7 = i0.ɵɵgetCurrentView();
247
+ i0.ɵɵelementStart(0, "button", 24);
248
+ i0.ɵɵlistener("click", function PhoenixMenuItemComponent_ng_template_1_button_8_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.currentNode.childrenActive = !ctx_r1.currentNode.childrenActive); });
249
+ i0.ɵɵnamespaceSVG();
250
+ i0.ɵɵelementStart(1, "svg");
251
+ i0.ɵɵelement(2, "use", 25);
252
+ i0.ɵɵelementEnd()();
253
+ } if (rf & 2) {
254
+ const ctx_r1 = i0.ɵɵnextContext(2);
255
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction1(1, _c1$7, ctx_r1.currentNode.childrenActive));
256
+ } }
257
+ function PhoenixMenuItemComponent_ng_template_1_Template(rf, ctx) { if (rf & 1) {
258
+ i0.ɵɵelementStart(0, "div", 11, 2)(2, "div", 12);
259
+ i0.ɵɵtemplate(3, PhoenixMenuItemComponent_ng_template_1_ng_container_3_Template, 3, 2, "ng-container", 13)(4, PhoenixMenuItemComponent_ng_template_1_ng_template_4_Template, 4, 3, "ng-template", null, 3, i0.ɵɵtemplateRefExtractor);
197
260
  i0.ɵɵelementEnd();
261
+ i0.ɵɵelementStart(6, "div", 14);
262
+ i0.ɵɵtemplate(7, PhoenixMenuItemComponent_ng_template_1_div_7_Template, 5, 1, "div", 15)(8, PhoenixMenuItemComponent_ng_template_1_button_8_Template, 3, 3, "button", 16);
263
+ i0.ɵɵelementEnd()();
198
264
  } if (rf & 2) {
199
- const config_r6 = i0.ɵɵnextContext(2).$implicit;
200
- i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction1(2, _c2$3, config_r6.type === "label"));
265
+ const nodeName_r5 = i0.ɵɵreference(5);
266
+ const ctx_r1 = i0.ɵɵnextContext();
267
+ i0.ɵɵadvance(3);
268
+ i0.ɵɵproperty("ngIf", ctx_r1.currentNode.onToggle)("ngIfElse", nodeName_r5);
269
+ i0.ɵɵadvance(4);
270
+ i0.ɵɵproperty("ngIf", ctx_r1.currentNode.configs.length > 0);
201
271
  i0.ɵɵadvance();
202
- i0.ɵɵtextInterpolate1(" ", config_r6.label, " ");
272
+ i0.ɵɵproperty("ngIf", ctx_r1.currentNode.children.length > 0);
203
273
  } }
204
- function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_mat_checkbox_1_Template(rf, ctx) { if (rf & 1) {
205
- const _r7 = i0.ɵɵgetCurrentView();
206
- i0.ɵɵelementStart(0, "mat-checkbox", 9);
207
- i0.ɵɵlistener("change", function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_mat_checkbox_1_Template_mat_checkbox_change_0_listener($event) { i0.ɵɵrestoreView(_r7); const config_r6 = i0.ɵɵnextContext(3).$implicit; config_r6.onChange($event.checked); return i0.ɵɵresetView(config_r6.isChecked = $event.checked); });
274
+ function PhoenixMenuItemComponent_div_3_div_1_app_phoenix_menu_item_1_Template(rf, ctx) { if (rf & 1) {
275
+ i0.ɵɵelement(0, "app-phoenix-menu-item", 30);
276
+ } if (rf & 2) {
277
+ const childNode_r8 = ctx.$implicit;
278
+ i0.ɵɵproperty("currentNode", childNode_r8);
279
+ } }
280
+ function PhoenixMenuItemComponent_div_3_div_1_Template(rf, ctx) { if (rf & 1) {
281
+ i0.ɵɵelementStart(0, "div", 29);
282
+ i0.ɵɵtemplate(1, PhoenixMenuItemComponent_div_3_div_1_app_phoenix_menu_item_1_Template, 1, 1, "app-phoenix-menu-item", 28);
208
283
  i0.ɵɵelementEnd();
209
284
  } if (rf & 2) {
210
- const config_r6 = i0.ɵɵnextContext(3).$implicit;
211
- i0.ɵɵproperty("checked", config_r6.isChecked);
285
+ const ctx_r1 = i0.ɵɵnextContext(2);
286
+ i0.ɵɵadvance();
287
+ i0.ɵɵproperty("ngForOf", ctx_r1.compactChildren);
212
288
  } }
213
- function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_app_config_slider_2_Template(rf, ctx) { if (rf & 1) {
214
- const _r8 = i0.ɵɵgetCurrentView();
215
- i0.ɵɵelementStart(0, "app-config-slider", 34);
216
- i0.ɵɵlistener("onChange", function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_app_config_slider_2_Template_app_config_slider_onChange_0_listener($event) { i0.ɵɵrestoreView(_r8); const config_r6 = i0.ɵɵnextContext(3).$implicit; config_r6.onChange($event); return i0.ɵɵresetView(config_r6.value = $event); });
289
+ function PhoenixMenuItemComponent_div_3_app_phoenix_menu_item_2_Template(rf, ctx) { if (rf & 1) {
290
+ i0.ɵɵelement(0, "app-phoenix-menu-item", 30);
291
+ } if (rf & 2) {
292
+ const childNode_r9 = ctx.$implicit;
293
+ i0.ɵɵproperty("currentNode", childNode_r9);
294
+ } }
295
+ function PhoenixMenuItemComponent_div_3_Template(rf, ctx) { if (rf & 1) {
296
+ i0.ɵɵelementStart(0, "div", 26);
297
+ i0.ɵɵtemplate(1, PhoenixMenuItemComponent_div_3_div_1_Template, 2, 1, "div", 27)(2, PhoenixMenuItemComponent_div_3_app_phoenix_menu_item_2_Template, 1, 1, "app-phoenix-menu-item", 28);
217
298
  i0.ɵɵelementEnd();
218
299
  } if (rf & 2) {
219
- const config_r6 = i0.ɵɵnextContext(3).$implicit;
220
- i0.ɵɵproperty("value", config_r6.value)("min", config_r6.min)("max", config_r6.max)("step", config_r6.step)("allowCustomValue", config_r6.allowCustomValue);
300
+ const ctx_r1 = i0.ɵɵnextContext();
301
+ i0.ɵɵadvance();
302
+ i0.ɵɵproperty("ngIf", ctx_r1.compactChildren.length > 0);
303
+ i0.ɵɵadvance();
304
+ i0.ɵɵproperty("ngForOf", ctx_r1.regularChildren);
221
305
  } }
222
- function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_button_3_Template(rf, ctx) { if (rf & 1) {
223
- const _r9 = i0.ɵɵgetCurrentView();
224
- i0.ɵɵelementStart(0, "button", 35);
225
- i0.ɵɵlistener("click", function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_button_3_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r9); const config_r6 = i0.ɵɵnextContext(3).$implicit; return i0.ɵɵresetView(config_r6.onClick()); });
306
+ function PhoenixMenuItemComponent_ng_template_4_div_0_Template(rf, ctx) { if (rf & 1) {
307
+ const _r10 = i0.ɵɵgetCurrentView();
308
+ i0.ɵɵelementStart(0, "div", 34);
309
+ i0.ɵɵlistener("click", function PhoenixMenuItemComponent_ng_template_4_div_0_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r10); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.currentNode.configActive = false); });
310
+ i0.ɵɵelementEnd();
311
+ } }
312
+ function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_label_1_Template(rf, ctx) { if (rf & 1) {
313
+ i0.ɵɵelementStart(0, "label", 40);
226
314
  i0.ɵɵtext(1);
227
315
  i0.ɵɵelementEnd();
228
316
  } if (rf & 2) {
229
- const config_r6 = i0.ɵɵnextContext(3).$implicit;
317
+ const config_r11 = i0.ɵɵnextContext(2).$implicit;
318
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction1(2, _c4$2, config_r11.type === "label"));
230
319
  i0.ɵɵadvance();
231
- i0.ɵɵtextInterpolate1(" ", config_r6.label, " ");
320
+ i0.ɵɵtextInterpolate1(" ", config_r11.label, " ");
232
321
  } }
233
- function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_input_4_Template(rf, ctx) { if (rf & 1) {
234
- const _r10 = i0.ɵɵgetCurrentView();
235
- i0.ɵɵelementStart(0, "input", 36);
236
- i0.ɵɵlistener("input", function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_input_4_Template_input_input_0_listener($event) { i0.ɵɵrestoreView(_r10); const config_r6 = i0.ɵɵnextContext(3).$implicit; config_r6.onChange($event.target.value); return i0.ɵɵresetView(config_r6.color = $event.target.value); });
322
+ function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_mat_checkbox_1_Template(rf, ctx) { if (rf & 1) {
323
+ const _r12 = i0.ɵɵgetCurrentView();
324
+ i0.ɵɵelementStart(0, "mat-checkbox", 17);
325
+ i0.ɵɵlistener("change", function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_mat_checkbox_1_Template_mat_checkbox_change_0_listener($event) { i0.ɵɵrestoreView(_r12); const config_r11 = i0.ɵɵnextContext(3).$implicit; config_r11.onChange($event.checked); return i0.ɵɵresetView(config_r11.isChecked = $event.checked); });
237
326
  i0.ɵɵelementEnd();
238
327
  } if (rf & 2) {
239
- const config_r6 = i0.ɵɵnextContext(3).$implicit;
240
- i0.ɵɵproperty("value", config_r6.color);
328
+ const config_r11 = i0.ɵɵnextContext(3).$implicit;
329
+ i0.ɵɵproperty("checked", config_r11.isChecked);
241
330
  } }
242
- function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_div_5_Template(rf, ctx) { if (rf & 1) {
243
- const _r11 = i0.ɵɵgetCurrentView();
244
- i0.ɵɵelementStart(0, "div", 37)(1, "div", 38)(2, "mat-checkbox", 39);
245
- i0.ɵɵtwoWayListener("ngModelChange", function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_div_5_Template_mat_checkbox_ngModelChange_2_listener($event) { i0.ɵɵrestoreView(_r11); const config_r6 = i0.ɵɵnextContext(3).$implicit; i0.ɵɵtwoWayBindingSet(config_r6.enableMin, $event) || (config_r6.enableMin = $event); return i0.ɵɵresetView($event); });
246
- i0.ɵɵlistener("change", function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_div_5_Template_mat_checkbox_change_2_listener($event) { i0.ɵɵrestoreView(_r11); const config_r6 = i0.ɵɵnextContext(3).$implicit; return i0.ɵɵresetView(config_r6.setEnableMin($event.checked)); });
331
+ function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_app_config_slider_2_Template(rf, ctx) { if (rf & 1) {
332
+ const _r13 = i0.ɵɵgetCurrentView();
333
+ i0.ɵɵelementStart(0, "app-config-slider", 48);
334
+ i0.ɵɵlistener("onChange", function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_app_config_slider_2_Template_app_config_slider_onChange_0_listener($event) { i0.ɵɵrestoreView(_r13); const config_r11 = i0.ɵɵnextContext(3).$implicit; config_r11.onChange($event); return i0.ɵɵresetView(config_r11.value = $event); });
335
+ i0.ɵɵelementEnd();
336
+ } if (rf & 2) {
337
+ const config_r11 = i0.ɵɵnextContext(3).$implicit;
338
+ i0.ɵɵproperty("value", config_r11.value)("min", config_r11.min)("max", config_r11.max)("step", config_r11.step)("allowCustomValue", config_r11.allowCustomValue);
339
+ } }
340
+ function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_button_3_Template(rf, ctx) { if (rf & 1) {
341
+ const _r14 = i0.ɵɵgetCurrentView();
342
+ i0.ɵɵelementStart(0, "button", 49);
343
+ i0.ɵɵlistener("click", function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_button_3_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r14); const config_r11 = i0.ɵɵnextContext(3).$implicit; return i0.ɵɵresetView(config_r11.onClick()); });
344
+ i0.ɵɵtext(1);
345
+ i0.ɵɵelementEnd();
346
+ } if (rf & 2) {
347
+ const config_r11 = i0.ɵɵnextContext(3).$implicit;
348
+ i0.ɵɵadvance();
349
+ i0.ɵɵtextInterpolate1(" ", config_r11.label, " ");
350
+ } }
351
+ function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_input_4_Template(rf, ctx) { if (rf & 1) {
352
+ const _r15 = i0.ɵɵgetCurrentView();
353
+ i0.ɵɵelementStart(0, "input", 50);
354
+ i0.ɵɵlistener("input", function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_input_4_Template_input_input_0_listener($event) { i0.ɵɵrestoreView(_r15); const config_r11 = i0.ɵɵnextContext(3).$implicit; config_r11.onChange($event.target.value); return i0.ɵɵresetView(config_r11.color = $event.target.value); });
247
355
  i0.ɵɵelementEnd();
248
- i0.ɵɵelementStart(3, "mat-checkbox", 39);
249
- i0.ɵɵtwoWayListener("ngModelChange", function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_div_5_Template_mat_checkbox_ngModelChange_3_listener($event) { i0.ɵɵrestoreView(_r11); const config_r6 = i0.ɵɵnextContext(3).$implicit; i0.ɵɵtwoWayBindingSet(config_r6.enableMax, $event) || (config_r6.enableMax = $event); return i0.ɵɵresetView($event); });
250
- i0.ɵɵlistener("change", function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_div_5_Template_mat_checkbox_change_3_listener($event) { i0.ɵɵrestoreView(_r11); const config_r6 = i0.ɵɵnextContext(3).$implicit; return i0.ɵɵresetView(config_r6.setEnableMax($event.checked)); });
356
+ } if (rf & 2) {
357
+ const config_r11 = i0.ɵɵnextContext(3).$implicit;
358
+ i0.ɵɵproperty("value", config_r11.color);
359
+ } }
360
+ function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_div_5_Template(rf, ctx) { if (rf & 1) {
361
+ const _r16 = i0.ɵɵgetCurrentView();
362
+ i0.ɵɵelementStart(0, "div", 51)(1, "div", 52)(2, "div", 53)(3, "mat-checkbox", 54);
363
+ i0.ɵɵtwoWayListener("ngModelChange", function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_div_5_Template_mat_checkbox_ngModelChange_3_listener($event) { i0.ɵɵrestoreView(_r16); const config_r11 = i0.ɵɵnextContext(3).$implicit; i0.ɵɵtwoWayBindingSet(config_r11.enableMin, $event) || (config_r11.enableMin = $event); return i0.ɵɵresetView($event); });
364
+ i0.ɵɵlistener("change", function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_div_5_Template_mat_checkbox_change_3_listener($event) { i0.ɵɵrestoreView(_r16); const config_r11 = i0.ɵɵnextContext(3).$implicit; return i0.ɵɵresetView(config_r11.setEnableMin($event.checked)); });
365
+ i0.ɵɵelementEnd();
366
+ i0.ɵɵelementStart(4, "input", 55);
367
+ i0.ɵɵtwoWayListener("ngModelChange", function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_div_5_Template_input_ngModelChange_4_listener($event) { i0.ɵɵrestoreView(_r16); const config_r11 = i0.ɵɵnextContext(3).$implicit; i0.ɵɵtwoWayBindingSet(config_r11.value, $event) || (config_r11.value = $event); return i0.ɵɵresetView($event); });
368
+ i0.ɵɵlistener("input", function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_div_5_Template_input_input_4_listener($event) { i0.ɵɵrestoreView(_r16); const config_r11 = i0.ɵɵnextContext(3).$implicit; config_r11.value = $event.target.value; return i0.ɵɵresetView(config_r11.onChange(config_r11)); });
251
369
  i0.ɵɵelementEnd()();
252
- i0.ɵɵelementStart(4, "div", 38)(5, "input", 40);
253
- i0.ɵɵtwoWayListener("ngModelChange", function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_div_5_Template_input_ngModelChange_5_listener($event) { i0.ɵɵrestoreView(_r11); const config_r6 = i0.ɵɵnextContext(3).$implicit; i0.ɵɵtwoWayBindingSet(config_r6.value, $event) || (config_r6.value = $event); return i0.ɵɵresetView($event); });
254
- i0.ɵɵlistener("input", function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_div_5_Template_input_input_5_listener($event) { i0.ɵɵrestoreView(_r11); const config_r6 = i0.ɵɵnextContext(3).$implicit; config_r6.value = $event.target.value; return i0.ɵɵresetView(config_r6.onChange(config_r6)); });
370
+ i0.ɵɵelementStart(5, "div", 53)(6, "mat-checkbox", 54);
371
+ i0.ɵɵtwoWayListener("ngModelChange", function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_div_5_Template_mat_checkbox_ngModelChange_6_listener($event) { i0.ɵɵrestoreView(_r16); const config_r11 = i0.ɵɵnextContext(3).$implicit; i0.ɵɵtwoWayBindingSet(config_r11.enableMax, $event) || (config_r11.enableMax = $event); return i0.ɵɵresetView($event); });
372
+ i0.ɵɵlistener("change", function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_div_5_Template_mat_checkbox_change_6_listener($event) { i0.ɵɵrestoreView(_r16); const config_r11 = i0.ɵɵnextContext(3).$implicit; return i0.ɵɵresetView(config_r11.setEnableMax($event.checked)); });
255
373
  i0.ɵɵelementEnd();
256
- i0.ɵɵelementStart(6, "input", 41);
257
- i0.ɵɵtwoWayListener("ngModelChange", function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_div_5_Template_input_ngModelChange_6_listener($event) { i0.ɵɵrestoreView(_r11); const config_r6 = i0.ɵɵnextContext(3).$implicit; i0.ɵɵtwoWayBindingSet(config_r6.highValue, $event) || (config_r6.highValue = $event); return i0.ɵɵresetView($event); });
258
- i0.ɵɵlistener("input", function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_div_5_Template_input_input_6_listener($event) { i0.ɵɵrestoreView(_r11); const config_r6 = i0.ɵɵnextContext(3).$implicit; config_r6.highValue = $event.target.value; return i0.ɵɵresetView(config_r6.onChange(config_r6)); });
259
- i0.ɵɵelementEnd()()();
374
+ i0.ɵɵelementStart(7, "input", 56);
375
+ i0.ɵɵtwoWayListener("ngModelChange", function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_div_5_Template_input_ngModelChange_7_listener($event) { i0.ɵɵrestoreView(_r16); const config_r11 = i0.ɵɵnextContext(3).$implicit; i0.ɵɵtwoWayBindingSet(config_r11.highValue, $event) || (config_r11.highValue = $event); return i0.ɵɵresetView($event); });
376
+ i0.ɵɵlistener("input", function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_div_5_Template_input_input_7_listener($event) { i0.ɵɵrestoreView(_r16); const config_r11 = i0.ɵɵnextContext(3).$implicit; config_r11.highValue = $event.target.value; return i0.ɵɵresetView(config_r11.onChange(config_r11)); });
377
+ i0.ɵɵelementEnd()()()();
260
378
  } if (rf & 2) {
261
- const config_r6 = i0.ɵɵnextContext(3).$implicit;
262
- i0.ɵɵadvance(2);
263
- i0.ɵɵtwoWayProperty("ngModel", config_r6.enableMin);
379
+ const config_r11 = i0.ɵɵnextContext(3).$implicit;
380
+ i0.ɵɵadvance(3);
381
+ i0.ɵɵtwoWayProperty("ngModel", config_r11.enableMin);
264
382
  i0.ɵɵadvance();
265
- i0.ɵɵtwoWayProperty("ngModel", config_r6.enableMax);
383
+ i0.ɵɵtwoWayProperty("ngModel", config_r11.value);
384
+ i0.ɵɵproperty("step", config_r11.step);
266
385
  i0.ɵɵadvance(2);
267
- i0.ɵɵtwoWayProperty("ngModel", config_r6.value);
386
+ i0.ɵɵtwoWayProperty("ngModel", config_r11.enableMax);
268
387
  i0.ɵɵadvance();
269
- i0.ɵɵtwoWayProperty("ngModel", config_r6.highValue);
388
+ i0.ɵɵtwoWayProperty("ngModel", config_r11.highValue);
389
+ i0.ɵɵproperty("step", config_r11.step);
270
390
  } }
271
- function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_select_6_option_1_Template(rf, ctx) { if (rf & 1) {
272
- i0.ɵɵelementStart(0, "option", 44);
391
+ function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_select_6_option_1_Template(rf, ctx) { if (rf & 1) {
392
+ i0.ɵɵelementStart(0, "option", 59);
273
393
  i0.ɵɵtext(1);
274
394
  i0.ɵɵelementEnd();
275
395
  } if (rf & 2) {
276
- const singleOption_r13 = ctx.$implicit;
277
- i0.ɵɵproperty("value", singleOption_r13);
396
+ const singleOption_r18 = ctx.$implicit;
397
+ i0.ɵɵproperty("value", singleOption_r18);
278
398
  i0.ɵɵadvance();
279
- i0.ɵɵtextInterpolate1(" ", singleOption_r13, " ");
399
+ i0.ɵɵtextInterpolate1(" ", singleOption_r18, " ");
280
400
  } }
281
- function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_select_6_Template(rf, ctx) { if (rf & 1) {
282
- const _r12 = i0.ɵɵgetCurrentView();
283
- i0.ɵɵelementStart(0, "select", 42);
284
- i0.ɵɵlistener("change", function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_select_6_Template_select_change_0_listener($event) { i0.ɵɵrestoreView(_r12); const config_r6 = i0.ɵɵnextContext(3).$implicit; return i0.ɵɵresetView(config_r6.onChange($event.target.value)); });
285
- i0.ɵɵtemplate(1, PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_select_6_option_1_Template, 2, 2, "option", 43);
401
+ function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_select_6_Template(rf, ctx) { if (rf & 1) {
402
+ const _r17 = i0.ɵɵgetCurrentView();
403
+ i0.ɵɵelementStart(0, "select", 57);
404
+ i0.ɵɵlistener("change", function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_select_6_Template_select_change_0_listener($event) { i0.ɵɵrestoreView(_r17); const config_r11 = i0.ɵɵnextContext(3).$implicit; return i0.ɵɵresetView(config_r11.onChange($event.target.value)); });
405
+ i0.ɵɵtemplate(1, PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_select_6_option_1_Template, 2, 2, "option", 58);
286
406
  i0.ɵɵelementEnd();
287
407
  } if (rf & 2) {
288
- const config_r6 = i0.ɵɵnextContext(3).$implicit;
408
+ const config_r11 = i0.ɵɵnextContext(3).$implicit;
409
+ i0.ɵɵproperty("value", config_r11.value ?? config_r11.options[0]);
289
410
  i0.ɵɵadvance();
290
- i0.ɵɵproperty("ngForOf", config_r6.options);
411
+ i0.ɵɵproperty("ngForOf", config_r11.options);
291
412
  } }
292
- function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_Template(rf, ctx) { if (rf & 1) {
293
- i0.ɵɵelementStart(0, "div", 27);
294
- i0.ɵɵtemplate(1, PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_mat_checkbox_1_Template, 1, 1, "mat-checkbox", 28)(2, PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_app_config_slider_2_Template, 1, 5, "app-config-slider", 29)(3, PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_button_3_Template, 2, 1, "button", 30)(4, PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_input_4_Template, 1, 1, "input", 31)(5, PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_div_5_Template, 7, 4, "div", 32)(6, PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_select_6_Template, 2, 1, "select", 33);
413
+ function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_Template(rf, ctx) { if (rf & 1) {
414
+ i0.ɵɵelementStart(0, "div", 41);
415
+ i0.ɵɵtemplate(1, PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_mat_checkbox_1_Template, 1, 1, "mat-checkbox", 42)(2, PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_app_config_slider_2_Template, 1, 5, "app-config-slider", 43)(3, PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_button_3_Template, 2, 1, "button", 44)(4, PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_input_4_Template, 1, 1, "input", 45)(5, PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_div_5_Template, 8, 6, "div", 46)(6, PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_select_6_Template, 2, 2, "select", 47);
295
416
  i0.ɵɵelementEnd();
296
417
  } if (rf & 2) {
297
- const config_r6 = i0.ɵɵnextContext(2).$implicit;
298
- i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction1(7, _c3$2, config_r6.type === "button"));
418
+ const config_r11 = i0.ɵɵnextContext(2).$implicit;
419
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction1(7, _c5$2, config_r11.type === "button"));
299
420
  i0.ɵɵadvance();
300
421
  i0.ɵɵproperty("ngSwitchCase", "checkbox");
301
422
  i0.ɵɵadvance();
@@ -309,76 +430,41 @@ function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_Template(rf,
309
430
  i0.ɵɵadvance();
310
431
  i0.ɵɵproperty("ngSwitchCase", "select");
311
432
  } }
312
- function PhoenixMenuItemComponent_div_7_ng_container_6_div_1_Template(rf, ctx) { if (rf & 1) {
313
- i0.ɵɵelementStart(0, "div", 23);
314
- i0.ɵɵtemplate(1, PhoenixMenuItemComponent_div_7_ng_container_6_div_1_label_1_Template, 2, 4, "label", 24)(2, PhoenixMenuItemComponent_div_7_ng_container_6_div_1_div_2_Template, 7, 9, "div", 25);
433
+ function PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_Template(rf, ctx) { if (rf & 1) {
434
+ i0.ɵɵelementStart(0, "div", 37);
435
+ i0.ɵɵtemplate(1, PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_label_1_Template, 2, 4, "label", 38)(2, PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_div_2_Template, 7, 9, "div", 39);
315
436
  i0.ɵɵelementEnd();
316
437
  } if (rf & 2) {
317
- const config_r6 = i0.ɵɵnextContext().$implicit;
438
+ const config_r11 = i0.ɵɵnextContext().$implicit;
439
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction1(3, _c3$2, config_r11.type === "rangeSlider"));
318
440
  i0.ɵɵadvance();
319
- i0.ɵɵproperty("ngIf", config_r6.type !== "button");
441
+ i0.ɵɵproperty("ngIf", config_r11.type !== "button");
320
442
  i0.ɵɵadvance();
321
- i0.ɵɵproperty("ngIf", config_r6.type !== "label");
443
+ i0.ɵɵproperty("ngIf", config_r11.type !== "label");
322
444
  } }
323
- function PhoenixMenuItemComponent_div_7_ng_container_6_Template(rf, ctx) { if (rf & 1) {
324
- i0.ɵɵelementContainerStart(0, 21);
325
- i0.ɵɵtemplate(1, PhoenixMenuItemComponent_div_7_ng_container_6_div_1_Template, 3, 2, "div", 22);
445
+ function PhoenixMenuItemComponent_ng_template_4_ng_container_2_Template(rf, ctx) { if (rf & 1) {
446
+ i0.ɵɵelementContainerStart(0, 35);
447
+ i0.ɵɵtemplate(1, PhoenixMenuItemComponent_ng_template_4_ng_container_2_div_1_Template, 3, 5, "div", 36);
326
448
  i0.ɵɵelementContainerEnd();
327
449
  } if (rf & 2) {
328
- const config_r6 = ctx.$implicit;
329
- i0.ɵɵproperty("ngSwitch", config_r6.type);
450
+ const config_r11 = ctx.$implicit;
451
+ i0.ɵɵproperty("ngSwitch", config_r11.type);
330
452
  i0.ɵɵadvance();
331
- i0.ɵɵproperty("ngIf", !config_r6.hidden);
453
+ i0.ɵɵproperty("ngIf", !config_r11.hidden);
332
454
  } }
333
- function PhoenixMenuItemComponent_div_7_Template(rf, ctx) { if (rf & 1) {
334
- const _r4 = i0.ɵɵgetCurrentView();
335
- i0.ɵɵelementStart(0, "div")(1, "button", 15);
336
- i0.ɵɵlistener("click", function PhoenixMenuItemComponent_div_7_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(); ctx_r1.calculateConfigTop(); return i0.ɵɵresetView(ctx_r1.currentNode.configActive = !ctx_r1.currentNode.configActive); });
337
- i0.ɵɵnamespaceSVG();
338
- i0.ɵɵelementStart(2, "svg");
339
- i0.ɵɵelement(3, "use", 16);
340
- i0.ɵɵelementEnd()();
341
- i0.ɵɵtemplate(4, PhoenixMenuItemComponent_div_7_div_4_Template, 1, 0, "div", 17);
342
- i0.ɵɵnamespaceHTML();
343
- i0.ɵɵelementStart(5, "div", 18);
344
- i0.ɵɵtemplate(6, PhoenixMenuItemComponent_div_7_ng_container_6_Template, 2, 2, "ng-container", 19);
345
- i0.ɵɵelementEnd()();
455
+ function PhoenixMenuItemComponent_ng_template_4_Template(rf, ctx) { if (rf & 1) {
456
+ i0.ɵɵtemplate(0, PhoenixMenuItemComponent_ng_template_4_div_0_Template, 1, 0, "div", 31);
457
+ i0.ɵɵelementStart(1, "div", 32);
458
+ i0.ɵɵtemplate(2, PhoenixMenuItemComponent_ng_template_4_ng_container_2_Template, 2, 2, "ng-container", 33);
459
+ i0.ɵɵelementEnd();
346
460
  } if (rf & 2) {
347
461
  const ctx_r1 = i0.ɵɵnextContext();
348
- i0.ɵɵadvance(4);
349
462
  i0.ɵɵproperty("ngIf", ctx_r1.currentNode.configActive);
350
463
  i0.ɵɵadvance();
351
- i0.ɵɵproperty("hidden", !ctx_r1.currentNode.configActive)("ngStyle", i0.ɵɵpureFunction1(4, _c1$7, ctx_r1.configTop));
464
+ i0.ɵɵproperty("hidden", !ctx_r1.currentNode.configActive)("ngStyle", i0.ɵɵpureFunction1(4, _c2$3, ctx_r1.configTop));
352
465
  i0.ɵɵadvance();
353
466
  i0.ɵɵproperty("ngForOf", ctx_r1.castConfigsToAny(ctx_r1.currentNode.configs));
354
467
  } }
355
- function PhoenixMenuItemComponent_button_8_Template(rf, ctx) { if (rf & 1) {
356
- const _r14 = i0.ɵɵgetCurrentView();
357
- i0.ɵɵelementStart(0, "button", 45);
358
- i0.ɵɵlistener("click", function PhoenixMenuItemComponent_button_8_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r14); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.currentNode.childrenActive = !ctx_r1.currentNode.childrenActive); });
359
- i0.ɵɵnamespaceSVG();
360
- i0.ɵɵelementStart(1, "svg");
361
- i0.ɵɵelement(2, "use", 46);
362
- i0.ɵɵelementEnd()();
363
- } if (rf & 2) {
364
- const ctx_r1 = i0.ɵɵnextContext();
365
- i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction1(1, _c4$2, ctx_r1.currentNode.childrenActive));
366
- } }
367
- function PhoenixMenuItemComponent_div_9_app_phoenix_menu_item_1_Template(rf, ctx) { if (rf & 1) {
368
- i0.ɵɵelement(0, "app-phoenix-menu-item", 49);
369
- } if (rf & 2) {
370
- const childNode_r15 = ctx.$implicit;
371
- i0.ɵɵproperty("currentNode", childNode_r15);
372
- } }
373
- function PhoenixMenuItemComponent_div_9_Template(rf, ctx) { if (rf & 1) {
374
- i0.ɵɵelementStart(0, "div", 47);
375
- i0.ɵɵtemplate(1, PhoenixMenuItemComponent_div_9_app_phoenix_menu_item_1_Template, 1, 1, "app-phoenix-menu-item", 48);
376
- i0.ɵɵelementEnd();
377
- } if (rf & 2) {
378
- const ctx_r1 = i0.ɵɵnextContext();
379
- i0.ɵɵadvance();
380
- i0.ɵɵproperty("ngForOf", ctx_r1.currentNode.children);
381
- } }
382
468
  class PhoenixMenuItemComponent {
383
469
  cdr;
384
470
  currentNode;
@@ -387,10 +473,41 @@ class PhoenixMenuItemComponent {
387
473
  constructor(cdr) {
388
474
  this.cdr = cdr;
389
475
  }
476
+ /**
477
+ * Whether a node is a leaf holding only configs (like the collections'
478
+ * "Draw/Cut/Color Options"), which is displayed as a compact icon button
479
+ * in a toolbar instead of a full menu row.
480
+ */
481
+ isCompactOptionsNode(node) {
482
+ return (!!node.icon &&
483
+ !node.onToggle &&
484
+ node.children.length === 0 &&
485
+ node.configs.length > 0);
486
+ }
487
+ get isCompact() {
488
+ return this.isCompactOptionsNode(this.currentNode);
489
+ }
490
+ /** Label for the compact icon button, e.g. "Draw Options" -> "Draw". */
491
+ get compactLabel() {
492
+ return this.currentNode.name.replace(/\s+Options$/, '');
493
+ }
494
+ get compactChildren() {
495
+ return this.currentNode.children.filter((child) => this.isCompactOptionsNode(child));
496
+ }
497
+ get regularChildren() {
498
+ return this.currentNode.children.filter((child) => !this.isCompactOptionsNode(child));
499
+ }
390
500
  calculateConfigTop() {
391
501
  if (this.phoenixMenuItem) {
392
- this.configTop =
393
- this.phoenixMenuItem.nativeElement.getBoundingClientRect().top;
502
+ const itemRect = this.phoenixMenuItem.nativeElement.getBoundingClientRect();
503
+ const dragContainer = document.querySelector('.phoenix-menu-drag-container');
504
+ if (dragContainer) {
505
+ const containerRect = dragContainer.getBoundingClientRect();
506
+ this.configTop = itemRect.top - containerRect.top;
507
+ }
508
+ else {
509
+ this.configTop = itemRect.top;
510
+ }
394
511
  this.cdr.detectChanges();
395
512
  }
396
513
  }
@@ -404,29 +521,18 @@ class PhoenixMenuItemComponent {
404
521
  } if (rf & 2) {
405
522
  let _t;
406
523
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.phoenixMenuItem = _t.first);
407
- } }, inputs: { currentNode: "currentNode" }, standalone: false, decls: 10, vars: 5, consts: [["phoenixMenuItem", ""], ["nodeName", ""], [1, "phoenix-menu-item"], [1, "phoenix-menu-item-left", "align-items-center"], [4, "ngIf", "ngIfElse"], [1, "phoenix-menu-item-right"], [4, "ngIf"], ["class", "icon-wrapper icon-button btn-blank item-expand", "data-testid", "dropdown", 3, "ngClass", "click", 4, "ngIf"], ["class", "phoenix-menu-children", 4, "ngIf"], [3, "change", "checked"], [4, "ngTemplateOutlet"], [1, "d-flex", "w-100", "align-items-center"], ["class", "icon-wrapper item-icon", 4, "ngIf"], [1, "item-name", 3, "title"], [1, "icon-wrapper", "item-icon"], [1, "icon-wrapper", "icon-button", "btn-blank", "item-settings", 3, "click"], ["href", "assets/icons/gear.svg#gear"], ["class", "item-config-backdrop", 3, "click", 4, "ngIf"], [1, "item-config", 3, "hidden", "ngStyle"], [3, "ngSwitch", 4, "ngFor", "ngForOf"], [1, "item-config-backdrop", 3, "click"], [3, "ngSwitch"], ["class", "item-config-single", 4, "ngIf"], [1, "item-config-single"], ["class", "item-config-label", "for", "item-config-field", 3, "ngClass", 4, "ngIf"], ["class", "item-config-data", 3, "ngClass", 4, "ngIf"], ["for", "item-config-field", 1, "item-config-label", 3, "ngClass"], [1, "item-config-data", 3, "ngClass"], [3, "checked", "change", 4, "ngSwitchCase"], [3, "value", "min", "max", "step", "allowCustomValue", "onChange", 4, "ngSwitchCase"], ["class", "w-100", "mat-button", "", "mat-stroked-button", "", 3, "click", 4, "ngSwitchCase"], ["id", "item-config-field", "type", "color", 3, "value", "input", 4, "ngSwitchCase"], ["class", "range-slider", 4, "ngSwitchCase"], ["id", "item-config-field", "class", "w-100", 3, "change", 4, "ngSwitchCase"], [3, "onChange", "value", "min", "max", "step", "allowCustomValue"], ["mat-button", "", "mat-stroked-button", "", 1, "w-100", 3, "click"], ["id", "item-config-field", "type", "color", 3, "input", "value"], [1, "range-slider"], [1, "range-slider-inputs", "d-flex", "justify-content-between"], [3, "ngModelChange", "change", "ngModel"], ["type", "number", "placeholder", "Min", "step", "config.step", 1, "form-control", "form-control-sm", 3, "ngModelChange", "input", "ngModel"], ["type", "number", "placeholder", "Max", "step", "config.step", 1, "form-control", "form-control-sm", 3, "ngModelChange", "input", "ngModel"], ["id", "item-config-field", 1, "w-100", 3, "change"], [3, "value", 4, "ngFor", "ngForOf"], [3, "value"], ["data-testid", "dropdown", 1, "icon-wrapper", "icon-button", "btn-blank", "item-expand", 3, "click", "ngClass"], ["href", "assets/icons/expand.svg#expand"], [1, "phoenix-menu-children"], [3, "currentNode", 4, "ngFor", "ngForOf"], [3, "currentNode"]], template: function PhoenixMenuItemComponent_Template(rf, ctx) { if (rf & 1) {
408
- i0.ɵɵelementStart(0, "div", 2, 0)(2, "div", 3);
409
- i0.ɵɵtemplate(3, PhoenixMenuItemComponent_ng_container_3_Template, 3, 2, "ng-container", 4)(4, PhoenixMenuItemComponent_ng_template_4_Template, 4, 3, "ng-template", null, 1, i0.ɵɵtemplateRefExtractor);
410
- i0.ɵɵelementEnd();
411
- i0.ɵɵelementStart(6, "div", 5);
412
- i0.ɵɵtemplate(7, PhoenixMenuItemComponent_div_7_Template, 7, 6, "div", 6)(8, PhoenixMenuItemComponent_button_8_Template, 3, 3, "button", 7);
413
- i0.ɵɵelementEnd()();
414
- i0.ɵɵtemplate(9, PhoenixMenuItemComponent_div_9_Template, 2, 1, "div", 8);
524
+ } }, inputs: { currentNode: "currentNode" }, standalone: false, decls: 6, vars: 3, consts: [["fullItem", ""], ["configPopup", ""], ["phoenixMenuItem", ""], ["nodeName", ""], ["class", "phoenix-menu-item-compact", 4, "ngIf", "ngIfElse"], ["class", "phoenix-menu-children", 4, "ngIf"], [1, "phoenix-menu-item-compact"], [1, "btn-blank", "compact-option-button", 3, "click", "title"], [1, "icon-wrapper"], [1, "compact-option-label"], [4, "ngTemplateOutlet"], [1, "phoenix-menu-item"], [1, "phoenix-menu-item-left", "align-items-center"], [4, "ngIf", "ngIfElse"], [1, "phoenix-menu-item-right"], [4, "ngIf"], ["class", "icon-wrapper icon-button btn-blank item-expand", "data-testid", "dropdown", 3, "ngClass", "click", 4, "ngIf"], [3, "change", "checked"], [1, "d-flex", "w-100", "align-items-center"], ["class", "icon-wrapper item-icon", 4, "ngIf"], [1, "item-name", 3, "title"], [1, "icon-wrapper", "item-icon"], [1, "icon-wrapper", "icon-button", "btn-blank", "item-settings", 3, "click"], ["href", "assets/icons/gear.svg#gear"], ["data-testid", "dropdown", 1, "icon-wrapper", "icon-button", "btn-blank", "item-expand", 3, "click", "ngClass"], ["href", "assets/icons/expand.svg#expand"], [1, "phoenix-menu-children"], ["class", "phoenix-menu-options-bar", 4, "ngIf"], [3, "currentNode", 4, "ngFor", "ngForOf"], [1, "phoenix-menu-options-bar"], [3, "currentNode"], ["class", "item-config-backdrop", 3, "click", 4, "ngIf"], [1, "item-config", 3, "hidden", "ngStyle"], [3, "ngSwitch", 4, "ngFor", "ngForOf"], [1, "item-config-backdrop", 3, "click"], [3, "ngSwitch"], ["class", "item-config-single", 3, "ngClass", 4, "ngIf"], [1, "item-config-single", 3, "ngClass"], ["class", "item-config-label", "for", "item-config-field", 3, "ngClass", 4, "ngIf"], ["class", "item-config-data", 3, "ngClass", 4, "ngIf"], ["for", "item-config-field", 1, "item-config-label", 3, "ngClass"], [1, "item-config-data", 3, "ngClass"], [3, "checked", "change", 4, "ngSwitchCase"], [3, "value", "min", "max", "step", "allowCustomValue", "onChange", 4, "ngSwitchCase"], ["class", "w-100", "mat-button", "", "mat-stroked-button", "", 3, "click", 4, "ngSwitchCase"], ["id", "item-config-field", "type", "color", 3, "value", "input", 4, "ngSwitchCase"], ["class", "range-slider", 4, "ngSwitchCase"], ["id", "item-config-field", "class", "w-100", 3, "value", "change", 4, "ngSwitchCase"], [3, "onChange", "value", "min", "max", "step", "allowCustomValue"], ["mat-button", "", "mat-stroked-button", "", 1, "w-100", 3, "click"], ["id", "item-config-field", "type", "color", 3, "input", "value"], [1, "range-slider"], [1, "range-slider-inputs", "d-flex", "align-items-center"], [1, "range-slider-side"], [3, "ngModelChange", "change", "ngModel"], ["type", "number", "placeholder", "Min", 1, "form-control", "form-control-sm", 3, "ngModelChange", "input", "ngModel", "step"], ["type", "number", "placeholder", "Max", 1, "form-control", "form-control-sm", 3, "ngModelChange", "input", "ngModel", "step"], ["id", "item-config-field", 1, "w-100", 3, "change", "value"], [3, "value", 4, "ngFor", "ngForOf"], [3, "value"]], template: function PhoenixMenuItemComponent_Template(rf, ctx) { if (rf & 1) {
525
+ i0.ɵɵtemplate(0, PhoenixMenuItemComponent_div_0_Template, 9, 6, "div", 4)(1, PhoenixMenuItemComponent_ng_template_1_Template, 9, 4, "ng-template", null, 0, i0.ɵɵtemplateRefExtractor)(3, PhoenixMenuItemComponent_div_3_Template, 3, 2, "div", 5)(4, PhoenixMenuItemComponent_ng_template_4_Template, 3, 6, "ng-template", null, 1, i0.ɵɵtemplateRefExtractor);
415
526
  } if (rf & 2) {
416
- const nodeName_r3 = i0.ɵɵreference(5);
527
+ const fullItem_r19 = i0.ɵɵreference(2);
528
+ i0.ɵɵproperty("ngIf", ctx.isCompact)("ngIfElse", fullItem_r19);
417
529
  i0.ɵɵadvance(3);
418
- i0.ɵɵproperty("ngIf", ctx.currentNode.onToggle)("ngIfElse", nodeName_r3);
419
- i0.ɵɵadvance(4);
420
- i0.ɵɵproperty("ngIf", ctx.currentNode.configs.length > 0);
421
- i0.ɵɵadvance();
422
- i0.ɵɵproperty("ngIf", ctx.currentNode.children.length > 0);
423
- i0.ɵɵadvance();
424
530
  i0.ɵɵproperty("ngIf", ctx.currentNode.childrenActive);
425
- } }, dependencies: [i2.NgClass, i2.NgForOf, i2.NgIf, i2.NgTemplateOutlet, i2.NgStyle, i2.NgSwitch, i2.NgSwitchCase, i3.MatButton, i4.NgSelectOption, i4.ɵNgSelectMultipleOption, i4.DefaultValueAccessor, i4.NumberValueAccessor, i4.NgControlStatus, i4.NgModel, i4$1.MatSlideToggle, i5.MatCheckbox, PhoenixMenuItemComponent, ConfigSliderComponent], styles: [".phoenix-menu-item{width:100%;padding:.6rem;background:var(--phoenix-background-color-secondary);color:var(--phoenix-text-color);font-size:.8rem;transition:all .5s;display:flex;justify-content:space-between;align-items:center}.phoenix-menu-item-left{display:flex;justify-content:flex-start;min-width:0;padding-right:.5rem;overflow:hidden}.phoenix-menu-item-left .item-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.phoenix-menu-item-left .item-icon{margin-right:.2rem}.phoenix-menu-item-right{display:flex;justify-content:flex-end}.phoenix-menu-item-right .item-expand.expanded{transform:scaleY(-1)}.phoenix-menu-item-right .item-settings{margin-right:.2rem}.phoenix-menu-item-right .item-config-backdrop{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:110}.phoenix-menu-item-right .item-config{position:absolute;margin-top:-5rem;right:100%;width:14rem;padding:.5rem;background:var(--phoenix-background-color-secondary);color:var(--phoenix-text-color-secondary);box-shadow:var(--phoenix-box-shadow);display:flex;flex-direction:column;z-index:120}.phoenix-menu-item-right .item-config>*{margin-bottom:.4rem}.phoenix-menu-item-right .item-config>*:last-child{margin-bottom:0}.phoenix-menu-item-right .item-config .item-config-single{display:flex;flex-direction:row}.phoenix-menu-item-right .item-config .item-config-single .item-config-label{width:30%}.phoenix-menu-item-right .item-config .item-config-single .item-config-data{flex-grow:1;padding-left:.5rem}.phoenix-menu-item .icon-wrapper{display:inline-block;width:1.3rem;height:1.3rem;padding:.23rem;transition:all .4s}.phoenix-menu-item .icon-wrapper.icon-button:hover{background:var(--phoenix-options-icon-bg);border-radius:40%;cursor:pointer}.phoenix-menu-item .icon-wrapper svg{width:100%;height:100%;vertical-align:top}.phoenix-menu-item .mat-slide-toggle-bar{width:30px!important}.phoenix-menu-item .mat-slide-toggle-thumb{width:15px!important;height:15px!important;position:absolute;top:2px}.phoenix-menu-item .mat-slider-horizontal{min-width:0;width:100%;height:20px}.phoenix-menu-item .mat-slider-horizontal .mat-slider-wrapper{top:10px}.phoenix-menu-item label.mat-checkbox-layout{margin:0}@media screen and (max-width: 768px){.phoenix-menu-item{font-size:.75rem;padding:.5rem}.phoenix-menu-item .icon-wrapper{width:1rem;height:1rem;padding:.15rem}}.phoenix-menu-children{margin-left:.5rem;border-left:1px solid var(--phoenix-accent)}.range-slider .range-slider-inputs{gap:20%}\n"], encapsulation: 2 });
531
+ } }, dependencies: [i2.NgClass, i2.NgForOf, i2.NgIf, i2.NgTemplateOutlet, i2.NgStyle, i2.NgSwitch, i2.NgSwitchCase, i3.MatButton, i4.NgSelectOption, i4.ɵNgSelectMultipleOption, i4.DefaultValueAccessor, i4.NumberValueAccessor, i4.NgControlStatus, i4.NgModel, i4$1.MatSlideToggle, i5.MatCheckbox, PhoenixMenuItemComponent, ConfigSliderComponent], styles: [".phoenix-menu-item{width:100%;padding:.6rem;background:var(--phoenix-background-color-secondary);color:var(--phoenix-text-color);font-size:.8rem;transition:all .5s;display:flex;justify-content:space-between;align-items:center}.phoenix-menu-item-left{display:flex;justify-content:flex-start;min-width:0;padding-right:.5rem;overflow:hidden}.phoenix-menu-item-left .item-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.phoenix-menu-item-left .item-icon{margin-right:.2rem}.phoenix-menu-item-right{display:flex;justify-content:flex-end}.phoenix-menu-item-right .item-expand.expanded{transform:scaleY(-1)}.phoenix-menu-item-right .item-settings{margin-right:.2rem}@media screen and (max-width:768px){.phoenix-menu-item{font-size:.75rem;padding:.5rem}}.phoenix-menu-item-compact{flex:1;min-width:0;color:var(--phoenix-text-color)}.phoenix-menu-item-compact .compact-option-button{width:100%;display:flex;flex-direction:column;align-items:center;gap:.15rem;padding:.3rem .2rem;color:inherit;border-radius:.3rem;transition:all .4s}.phoenix-menu-item-compact .compact-option-button:hover{background:var(--phoenix-options-icon-bg);cursor:pointer}.phoenix-menu-item-compact .compact-option-button .compact-option-label{font-size:.65rem;line-height:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.phoenix-menu-options-bar{display:flex;align-items:stretch;gap:.25rem;padding:.25rem .4rem;background:var(--phoenix-background-color-secondary)}.phoenix-menu-options-bar app-phoenix-menu-item{display:flex;flex:1;min-width:0}.phoenix-menu-item .item-config-backdrop,.phoenix-menu-item-compact .item-config-backdrop{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:110}.phoenix-menu-item .item-config,.phoenix-menu-item-compact .item-config{position:absolute;right:100%;width:14rem;padding:.5rem;background:var(--phoenix-background-color-secondary);color:var(--phoenix-text-color-secondary);box-shadow:var(--phoenix-box-shadow);display:flex;flex-direction:column;z-index:120;max-height:60vh;overflow-y:auto;font-size:.8rem}.phoenix-menu-item .item-config::-webkit-scrollbar,.phoenix-menu-item-compact .item-config::-webkit-scrollbar{width:4px}.phoenix-menu-item .item-config::-webkit-scrollbar-thumb,.phoenix-menu-item-compact .item-config::-webkit-scrollbar-thumb{background:var(--phoenix-text-color-secondary, #999);border-radius:4px}.phoenix-menu-item .item-config>*,.phoenix-menu-item-compact .item-config>*{margin-bottom:.4rem}.phoenix-menu-item .item-config>*:last-child,.phoenix-menu-item-compact .item-config>*:last-child{margin-bottom:0}.phoenix-menu-item .item-config .item-config-single,.phoenix-menu-item-compact .item-config .item-config-single{display:flex;flex-direction:row}.phoenix-menu-item .item-config .item-config-single .item-config-label,.phoenix-menu-item-compact .item-config .item-config-single .item-config-label{width:30%}.phoenix-menu-item .item-config .item-config-single .item-config-data,.phoenix-menu-item-compact .item-config .item-config-single .item-config-data{flex-grow:1;padding-left:.5rem;min-width:0}.phoenix-menu-item .item-config .item-config-single.range-slider-row,.phoenix-menu-item-compact .item-config .item-config-single.range-slider-row{align-items:center}.phoenix-menu-item .item-config .item-config-single.range-slider-row .item-config-label,.phoenix-menu-item-compact .item-config .item-config-single.range-slider-row .item-config-label{width:auto;min-width:1.6rem;flex-shrink:0;margin-bottom:0}.phoenix-menu-item .icon-wrapper,.phoenix-menu-item-compact .icon-wrapper{display:inline-block;width:1.3rem;height:1.3rem;padding:.23rem;transition:all .4s}.phoenix-menu-item .icon-wrapper.icon-button:hover,.phoenix-menu-item-compact .icon-wrapper.icon-button:hover{background:var(--phoenix-options-icon-bg);border-radius:40%;cursor:pointer}.phoenix-menu-item .icon-wrapper svg,.phoenix-menu-item-compact .icon-wrapper svg{width:100%;height:100%;vertical-align:top}.phoenix-menu-item .mat-slide-toggle-bar,.phoenix-menu-item-compact .mat-slide-toggle-bar{width:30px!important}.phoenix-menu-item .mat-slide-toggle-thumb,.phoenix-menu-item-compact .mat-slide-toggle-thumb{width:15px!important;height:15px!important;position:absolute;top:2px}.phoenix-menu-item .mat-slider-horizontal,.phoenix-menu-item-compact .mat-slider-horizontal{min-width:0;width:100%;height:20px}.phoenix-menu-item .mat-slider-horizontal .mat-slider-wrapper,.phoenix-menu-item-compact .mat-slider-horizontal .mat-slider-wrapper{top:10px}.phoenix-menu-item label.mat-checkbox-layout,.phoenix-menu-item-compact label.mat-checkbox-layout{margin:0}@media screen and (max-width:768px){.phoenix-menu-item .icon-wrapper,.phoenix-menu-item-compact .icon-wrapper{width:1rem;height:1rem;padding:.15rem}}.phoenix-menu-children{margin-left:.5rem;border-left:1px solid var(--phoenix-accent);max-height:35vh;overflow-y:auto}.phoenix-menu-children::-webkit-scrollbar{width:4px}.phoenix-menu-children::-webkit-scrollbar-thumb{background:var(--phoenix-text-color-secondary, #999);border-radius:4px}.range-slider .range-slider-inputs{gap:4px}.range-slider .range-slider-side{display:flex;align-items:center;flex:1;gap:3px;min-width:0}.range-slider .range-slider-side mat-checkbox{flex-shrink:0;--mdc-checkbox-state-layer-size: 24px}.range-slider .range-slider-side input[type=number]{min-width:0;width:100%;padding:.15rem .25rem;font-size:.75rem;appearance:textfield;-moz-appearance:textfield}.range-slider .range-slider-side input[type=number]::-webkit-outer-spin-button,.range-slider .range-slider-side input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"], encapsulation: 2 });
426
532
  }
427
533
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(PhoenixMenuItemComponent, [{
428
534
  type: Component,
429
- args: [{ standalone: false, selector: 'app-phoenix-menu-item', encapsulation: ViewEncapsulation.None, template: "<div class=\"phoenix-menu-item\" #phoenixMenuItem>\n <div class=\"phoenix-menu-item-left align-items-center\">\n <!-- If the item has some toggle function -->\n <ng-container *ngIf=\"currentNode.onToggle; else nodeName\">\n <mat-slide-toggle\n [checked]=\"currentNode.toggleState\"\n (change)=\"currentNode.toggleSelfAndDescendants($event.checked)\"\n >\n <ng-container *ngTemplateOutlet=\"nodeName\"></ng-container>\n </mat-slide-toggle>\n </ng-container>\n <!-- If the item has no toggle function -->\n <ng-template #nodeName>\n <span class=\"d-flex w-100 align-items-center\">\n <span *ngIf=\"currentNode.icon\" class=\"icon-wrapper item-icon\">\n <svg>\n <use\n attr.href=\"assets/icons/{{ currentNode.icon }}.svg#{{\n currentNode.icon\n }}\"\n ></use>\n </svg>\n </span>\n <span class=\"item-name\" [title]=\"currentNode.name\">{{\n currentNode.name\n }}</span>\n </span>\n </ng-template>\n </div>\n <div class=\"phoenix-menu-item-right\">\n <div *ngIf=\"currentNode.configs.length > 0\">\n <button\n class=\"icon-wrapper icon-button btn-blank item-settings\"\n (click)=\"\n calculateConfigTop();\n currentNode.configActive = !currentNode.configActive\n \"\n >\n <svg>\n <use href=\"assets/icons/gear.svg#gear\"></use>\n </svg>\n </button>\n <div\n class=\"item-config-backdrop\"\n *ngIf=\"currentNode.configActive\"\n (click)=\"currentNode.configActive = false\"\n ></div>\n <div\n class=\"item-config\"\n [hidden]=\"!currentNode.configActive\"\n [ngStyle]=\"{ 'top.px': configTop }\"\n >\n <ng-container\n *ngFor=\"let config of castConfigsToAny(currentNode.configs)\"\n [ngSwitch]=\"config.type\"\n >\n <!-- Config types -->\n <div class=\"item-config-single\" *ngIf=\"!config.hidden\">\n <label\n class=\"item-config-label\"\n for=\"item-config-field\"\n *ngIf=\"config.type !== 'button'\"\n [ngClass]=\"{ 'font-weight-bold w-100': config.type === 'label' }\"\n >\n {{ config.label }}\n </label>\n <div\n class=\"item-config-data\"\n [ngClass]=\"{ 'p-0': config.type === 'button' }\"\n *ngIf=\"config.type !== 'label'\"\n >\n <mat-checkbox\n *ngSwitchCase=\"'checkbox'\"\n [checked]=\"config.isChecked\"\n (change)=\"\n config.onChange($event.checked);\n config.isChecked = $event.checked\n \"\n ></mat-checkbox>\n\n <app-config-slider\n *ngSwitchCase=\"'slider'\"\n [value]=\"config.value\"\n (onChange)=\"config.onChange($event); config.value = $event\"\n [min]=\"config.min\"\n [max]=\"config.max\"\n [step]=\"config.step\"\n [allowCustomValue]=\"config.allowCustomValue\"\n >\n </app-config-slider>\n\n <button\n *ngSwitchCase=\"'button'\"\n class=\"w-100\"\n (click)=\"config.onClick()\"\n mat-button\n mat-stroked-button\n >\n {{ config.label }}\n </button>\n\n <input\n *ngSwitchCase=\"'color'\"\n id=\"item-config-field\"\n type=\"color\"\n [value]=\"config.color\"\n (input)=\"\n config.onChange($event.target.value);\n config.color = $event.target.value\n \"\n />\n\n <div *ngSwitchCase=\"'rangeSlider'\" class=\"range-slider\">\n <div class=\"range-slider-inputs d-flex justify-content-between\">\n <mat-checkbox\n [(ngModel)]=\"config.enableMin\"\n (change)=\"config.setEnableMin($event.checked)\"\n ></mat-checkbox>\n <mat-checkbox\n [(ngModel)]=\"config.enableMax\"\n (change)=\"config.setEnableMax($event.checked)\"\n ></mat-checkbox>\n </div>\n <div class=\"range-slider-inputs d-flex justify-content-between\">\n <input\n type=\"number\"\n placeholder=\"Min\"\n class=\"form-control form-control-sm\"\n [(ngModel)]=\"config.value\"\n (input)=\"\n config.value = $event.target.value;\n config.onChange(config)\n \"\n step=\"config.step\"\n />\n <input\n type=\"number\"\n placeholder=\"Max\"\n class=\"form-control form-control-sm\"\n [(ngModel)]=\"config.highValue\"\n (input)=\"\n config.highValue = $event.target.value;\n config.onChange(config)\n \"\n step=\"config.step\"\n />\n </div>\n </div>\n\n <select\n *ngSwitchCase=\"'select'\"\n id=\"item-config-field\"\n class=\"w-100\"\n (change)=\"config.onChange($event.target.value)\"\n >\n <option\n *ngFor=\"let singleOption of config.options\"\n [value]=\"singleOption\"\n >\n {{ singleOption }}\n </option>\n </select>\n </div>\n </div>\n <!-- Config types END -->\n </ng-container>\n </div>\n </div>\n <button\n class=\"icon-wrapper icon-button btn-blank item-expand\"\n *ngIf=\"currentNode.children.length > 0\"\n (click)=\"currentNode.childrenActive = !currentNode.childrenActive\"\n [ngClass]=\"{ expanded: currentNode.childrenActive }\"\n data-testid=\"dropdown\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n </div>\n</div>\n<div class=\"phoenix-menu-children\" *ngIf=\"currentNode.childrenActive\">\n <app-phoenix-menu-item\n *ngFor=\"let childNode of currentNode.children\"\n [currentNode]=\"childNode\"\n >\n </app-phoenix-menu-item>\n</div>\n", styles: [".phoenix-menu-item{width:100%;padding:.6rem;background:var(--phoenix-background-color-secondary);color:var(--phoenix-text-color);font-size:.8rem;transition:all .5s;display:flex;justify-content:space-between;align-items:center}.phoenix-menu-item-left{display:flex;justify-content:flex-start;min-width:0;padding-right:.5rem;overflow:hidden}.phoenix-menu-item-left .item-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.phoenix-menu-item-left .item-icon{margin-right:.2rem}.phoenix-menu-item-right{display:flex;justify-content:flex-end}.phoenix-menu-item-right .item-expand.expanded{transform:scaleY(-1)}.phoenix-menu-item-right .item-settings{margin-right:.2rem}.phoenix-menu-item-right .item-config-backdrop{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:110}.phoenix-menu-item-right .item-config{position:absolute;margin-top:-5rem;right:100%;width:14rem;padding:.5rem;background:var(--phoenix-background-color-secondary);color:var(--phoenix-text-color-secondary);box-shadow:var(--phoenix-box-shadow);display:flex;flex-direction:column;z-index:120}.phoenix-menu-item-right .item-config>*{margin-bottom:.4rem}.phoenix-menu-item-right .item-config>*:last-child{margin-bottom:0}.phoenix-menu-item-right .item-config .item-config-single{display:flex;flex-direction:row}.phoenix-menu-item-right .item-config .item-config-single .item-config-label{width:30%}.phoenix-menu-item-right .item-config .item-config-single .item-config-data{flex-grow:1;padding-left:.5rem}.phoenix-menu-item .icon-wrapper{display:inline-block;width:1.3rem;height:1.3rem;padding:.23rem;transition:all .4s}.phoenix-menu-item .icon-wrapper.icon-button:hover{background:var(--phoenix-options-icon-bg);border-radius:40%;cursor:pointer}.phoenix-menu-item .icon-wrapper svg{width:100%;height:100%;vertical-align:top}.phoenix-menu-item .mat-slide-toggle-bar{width:30px!important}.phoenix-menu-item .mat-slide-toggle-thumb{width:15px!important;height:15px!important;position:absolute;top:2px}.phoenix-menu-item .mat-slider-horizontal{min-width:0;width:100%;height:20px}.phoenix-menu-item .mat-slider-horizontal .mat-slider-wrapper{top:10px}.phoenix-menu-item label.mat-checkbox-layout{margin:0}@media screen and (max-width: 768px){.phoenix-menu-item{font-size:.75rem;padding:.5rem}.phoenix-menu-item .icon-wrapper{width:1rem;height:1rem;padding:.15rem}}.phoenix-menu-children{margin-left:.5rem;border-left:1px solid var(--phoenix-accent)}.range-slider .range-slider-inputs{gap:20%}\n"] }]
535
+ args: [{ standalone: false, selector: 'app-phoenix-menu-item', encapsulation: ViewEncapsulation.None, template: "<!-- Leaf nodes holding only configs (Draw/Cut/Color Options) are shown as a\n compact icon button; everything else as a full menu row. -->\n<div\n *ngIf=\"isCompact; else fullItem\"\n class=\"phoenix-menu-item-compact\"\n #phoenixMenuItem\n>\n <button\n class=\"btn-blank compact-option-button\"\n [title]=\"currentNode.name\"\n (click)=\"\n calculateConfigTop(); currentNode.configActive = !currentNode.configActive\n \"\n >\n <span class=\"icon-wrapper\">\n <svg>\n <use\n attr.href=\"assets/icons/{{ currentNode.icon }}.svg#{{\n currentNode.icon\n }}\"\n ></use>\n </svg>\n </span>\n <span class=\"compact-option-label\">{{ compactLabel }}</span>\n </button>\n <ng-container *ngTemplateOutlet=\"configPopup\"></ng-container>\n</div>\n\n<ng-template #fullItem>\n <div class=\"phoenix-menu-item\" #phoenixMenuItem>\n <div class=\"phoenix-menu-item-left align-items-center\">\n <!-- If the item has some toggle function -->\n <ng-container *ngIf=\"currentNode.onToggle; else nodeName\">\n <mat-slide-toggle\n [checked]=\"currentNode.toggleState\"\n (change)=\"currentNode.toggleSelfAndDescendants($event.checked)\"\n >\n <ng-container *ngTemplateOutlet=\"nodeName\"></ng-container>\n </mat-slide-toggle>\n </ng-container>\n <!-- If the item has no toggle function -->\n <ng-template #nodeName>\n <span class=\"d-flex w-100 align-items-center\">\n <span *ngIf=\"currentNode.icon\" class=\"icon-wrapper item-icon\">\n <svg>\n <use\n attr.href=\"assets/icons/{{ currentNode.icon }}.svg#{{\n currentNode.icon\n }}\"\n ></use>\n </svg>\n </span>\n <span class=\"item-name\" [title]=\"currentNode.name\">{{\n currentNode.name\n }}</span>\n </span>\n </ng-template>\n </div>\n <div class=\"phoenix-menu-item-right\">\n <div *ngIf=\"currentNode.configs.length > 0\">\n <button\n class=\"icon-wrapper icon-button btn-blank item-settings\"\n (click)=\"\n calculateConfigTop();\n currentNode.configActive = !currentNode.configActive\n \"\n >\n <svg>\n <use href=\"assets/icons/gear.svg#gear\"></use>\n </svg>\n </button>\n <ng-container *ngTemplateOutlet=\"configPopup\"></ng-container>\n </div>\n <button\n class=\"icon-wrapper icon-button btn-blank item-expand\"\n *ngIf=\"currentNode.children.length > 0\"\n (click)=\"currentNode.childrenActive = !currentNode.childrenActive\"\n [ngClass]=\"{ expanded: currentNode.childrenActive }\"\n data-testid=\"dropdown\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n </div>\n </div>\n</ng-template>\n\n<div class=\"phoenix-menu-children\" *ngIf=\"currentNode.childrenActive\">\n <div class=\"phoenix-menu-options-bar\" *ngIf=\"compactChildren.length > 0\">\n <app-phoenix-menu-item\n *ngFor=\"let childNode of compactChildren\"\n [currentNode]=\"childNode\"\n >\n </app-phoenix-menu-item>\n </div>\n <app-phoenix-menu-item\n *ngFor=\"let childNode of regularChildren\"\n [currentNode]=\"childNode\"\n >\n </app-phoenix-menu-item>\n</div>\n\n<!-- Config popup shared by the full row (gear button) and the compact icon button -->\n<ng-template #configPopup>\n <div\n class=\"item-config-backdrop\"\n *ngIf=\"currentNode.configActive\"\n (click)=\"currentNode.configActive = false\"\n ></div>\n <div\n class=\"item-config\"\n [hidden]=\"!currentNode.configActive\"\n [ngStyle]=\"{ 'top.px': configTop }\"\n >\n <ng-container\n *ngFor=\"let config of castConfigsToAny(currentNode.configs)\"\n [ngSwitch]=\"config.type\"\n >\n <!-- Config types -->\n <div\n class=\"item-config-single\"\n [ngClass]=\"{ 'range-slider-row': config.type === 'rangeSlider' }\"\n *ngIf=\"!config.hidden\"\n >\n <label\n class=\"item-config-label\"\n for=\"item-config-field\"\n *ngIf=\"config.type !== 'button'\"\n [ngClass]=\"{ 'font-weight-bold w-100': config.type === 'label' }\"\n >\n {{ config.label }}\n </label>\n <div\n class=\"item-config-data\"\n [ngClass]=\"{ 'p-0': config.type === 'button' }\"\n *ngIf=\"config.type !== 'label'\"\n >\n <mat-checkbox\n *ngSwitchCase=\"'checkbox'\"\n [checked]=\"config.isChecked\"\n (change)=\"\n config.onChange($event.checked); config.isChecked = $event.checked\n \"\n ></mat-checkbox>\n\n <app-config-slider\n *ngSwitchCase=\"'slider'\"\n [value]=\"config.value\"\n (onChange)=\"config.onChange($event); config.value = $event\"\n [min]=\"config.min\"\n [max]=\"config.max\"\n [step]=\"config.step\"\n [allowCustomValue]=\"config.allowCustomValue\"\n >\n </app-config-slider>\n\n <button\n *ngSwitchCase=\"'button'\"\n class=\"w-100\"\n (click)=\"config.onClick()\"\n mat-button\n mat-stroked-button\n >\n {{ config.label }}\n </button>\n\n <input\n *ngSwitchCase=\"'color'\"\n id=\"item-config-field\"\n type=\"color\"\n [value]=\"config.color\"\n (input)=\"\n config.onChange($event.target.value);\n config.color = $event.target.value\n \"\n />\n\n <div *ngSwitchCase=\"'rangeSlider'\" class=\"range-slider\">\n <div class=\"range-slider-inputs d-flex align-items-center\">\n <div class=\"range-slider-side\">\n <mat-checkbox\n [(ngModel)]=\"config.enableMin\"\n (change)=\"config.setEnableMin($event.checked)\"\n ></mat-checkbox>\n <input\n type=\"number\"\n placeholder=\"Min\"\n class=\"form-control form-control-sm\"\n [(ngModel)]=\"config.value\"\n (input)=\"\n config.value = $event.target.value; config.onChange(config)\n \"\n [step]=\"config.step\"\n />\n </div>\n <div class=\"range-slider-side\">\n <mat-checkbox\n [(ngModel)]=\"config.enableMax\"\n (change)=\"config.setEnableMax($event.checked)\"\n ></mat-checkbox>\n <input\n type=\"number\"\n placeholder=\"Max\"\n class=\"form-control form-control-sm\"\n [(ngModel)]=\"config.highValue\"\n (input)=\"\n config.highValue = $event.target.value;\n config.onChange(config)\n \"\n [step]=\"config.step\"\n />\n </div>\n </div>\n </div>\n\n <select\n *ngSwitchCase=\"'select'\"\n id=\"item-config-field\"\n class=\"w-100\"\n [value]=\"config.value ?? config.options[0]\"\n (change)=\"config.onChange($event.target.value)\"\n >\n <option\n *ngFor=\"let singleOption of config.options\"\n [value]=\"singleOption\"\n >\n {{ singleOption }}\n </option>\n </select>\n </div>\n </div>\n <!-- Config types END -->\n </ng-container>\n </div>\n</ng-template>\n", styles: [".phoenix-menu-item{width:100%;padding:.6rem;background:var(--phoenix-background-color-secondary);color:var(--phoenix-text-color);font-size:.8rem;transition:all .5s;display:flex;justify-content:space-between;align-items:center}.phoenix-menu-item-left{display:flex;justify-content:flex-start;min-width:0;padding-right:.5rem;overflow:hidden}.phoenix-menu-item-left .item-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.phoenix-menu-item-left .item-icon{margin-right:.2rem}.phoenix-menu-item-right{display:flex;justify-content:flex-end}.phoenix-menu-item-right .item-expand.expanded{transform:scaleY(-1)}.phoenix-menu-item-right .item-settings{margin-right:.2rem}@media screen and (max-width:768px){.phoenix-menu-item{font-size:.75rem;padding:.5rem}}.phoenix-menu-item-compact{flex:1;min-width:0;color:var(--phoenix-text-color)}.phoenix-menu-item-compact .compact-option-button{width:100%;display:flex;flex-direction:column;align-items:center;gap:.15rem;padding:.3rem .2rem;color:inherit;border-radius:.3rem;transition:all .4s}.phoenix-menu-item-compact .compact-option-button:hover{background:var(--phoenix-options-icon-bg);cursor:pointer}.phoenix-menu-item-compact .compact-option-button .compact-option-label{font-size:.65rem;line-height:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.phoenix-menu-options-bar{display:flex;align-items:stretch;gap:.25rem;padding:.25rem .4rem;background:var(--phoenix-background-color-secondary)}.phoenix-menu-options-bar app-phoenix-menu-item{display:flex;flex:1;min-width:0}.phoenix-menu-item .item-config-backdrop,.phoenix-menu-item-compact .item-config-backdrop{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:110}.phoenix-menu-item .item-config,.phoenix-menu-item-compact .item-config{position:absolute;right:100%;width:14rem;padding:.5rem;background:var(--phoenix-background-color-secondary);color:var(--phoenix-text-color-secondary);box-shadow:var(--phoenix-box-shadow);display:flex;flex-direction:column;z-index:120;max-height:60vh;overflow-y:auto;font-size:.8rem}.phoenix-menu-item .item-config::-webkit-scrollbar,.phoenix-menu-item-compact .item-config::-webkit-scrollbar{width:4px}.phoenix-menu-item .item-config::-webkit-scrollbar-thumb,.phoenix-menu-item-compact .item-config::-webkit-scrollbar-thumb{background:var(--phoenix-text-color-secondary, #999);border-radius:4px}.phoenix-menu-item .item-config>*,.phoenix-menu-item-compact .item-config>*{margin-bottom:.4rem}.phoenix-menu-item .item-config>*:last-child,.phoenix-menu-item-compact .item-config>*:last-child{margin-bottom:0}.phoenix-menu-item .item-config .item-config-single,.phoenix-menu-item-compact .item-config .item-config-single{display:flex;flex-direction:row}.phoenix-menu-item .item-config .item-config-single .item-config-label,.phoenix-menu-item-compact .item-config .item-config-single .item-config-label{width:30%}.phoenix-menu-item .item-config .item-config-single .item-config-data,.phoenix-menu-item-compact .item-config .item-config-single .item-config-data{flex-grow:1;padding-left:.5rem;min-width:0}.phoenix-menu-item .item-config .item-config-single.range-slider-row,.phoenix-menu-item-compact .item-config .item-config-single.range-slider-row{align-items:center}.phoenix-menu-item .item-config .item-config-single.range-slider-row .item-config-label,.phoenix-menu-item-compact .item-config .item-config-single.range-slider-row .item-config-label{width:auto;min-width:1.6rem;flex-shrink:0;margin-bottom:0}.phoenix-menu-item .icon-wrapper,.phoenix-menu-item-compact .icon-wrapper{display:inline-block;width:1.3rem;height:1.3rem;padding:.23rem;transition:all .4s}.phoenix-menu-item .icon-wrapper.icon-button:hover,.phoenix-menu-item-compact .icon-wrapper.icon-button:hover{background:var(--phoenix-options-icon-bg);border-radius:40%;cursor:pointer}.phoenix-menu-item .icon-wrapper svg,.phoenix-menu-item-compact .icon-wrapper svg{width:100%;height:100%;vertical-align:top}.phoenix-menu-item .mat-slide-toggle-bar,.phoenix-menu-item-compact .mat-slide-toggle-bar{width:30px!important}.phoenix-menu-item .mat-slide-toggle-thumb,.phoenix-menu-item-compact .mat-slide-toggle-thumb{width:15px!important;height:15px!important;position:absolute;top:2px}.phoenix-menu-item .mat-slider-horizontal,.phoenix-menu-item-compact .mat-slider-horizontal{min-width:0;width:100%;height:20px}.phoenix-menu-item .mat-slider-horizontal .mat-slider-wrapper,.phoenix-menu-item-compact .mat-slider-horizontal .mat-slider-wrapper{top:10px}.phoenix-menu-item label.mat-checkbox-layout,.phoenix-menu-item-compact label.mat-checkbox-layout{margin:0}@media screen and (max-width:768px){.phoenix-menu-item .icon-wrapper,.phoenix-menu-item-compact .icon-wrapper{width:1rem;height:1rem;padding:.15rem}}.phoenix-menu-children{margin-left:.5rem;border-left:1px solid var(--phoenix-accent);max-height:35vh;overflow-y:auto}.phoenix-menu-children::-webkit-scrollbar{width:4px}.phoenix-menu-children::-webkit-scrollbar-thumb{background:var(--phoenix-text-color-secondary, #999);border-radius:4px}.range-slider .range-slider-inputs{gap:4px}.range-slider .range-slider-side{display:flex;align-items:center;flex:1;gap:3px;min-width:0}.range-slider .range-slider-side mat-checkbox{flex-shrink:0;--mdc-checkbox-state-layer-size: 24px}.range-slider .range-slider-side input[type=number]{min-width:0;width:100%;padding:.15rem .25rem;font-size:.75rem;appearance:textfield;-moz-appearance:textfield}.range-slider .range-slider-side input[type=number]::-webkit-outer-spin-button,.range-slider .range-slider-side input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"] }]
430
536
  }], () => [{ type: i0.ChangeDetectorRef }], { currentNode: [{
431
537
  type: Input
432
538
  }], phoenixMenuItem: [{
@@ -435,25 +541,310 @@ class PhoenixMenuItemComponent {
435
541
  }] }); })();
436
542
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(PhoenixMenuItemComponent, { className: "PhoenixMenuItemComponent", filePath: "components/phoenix-menu/phoenix-menu-item/phoenix-menu-item.component.ts", lineNumber: 18 }); })();
437
543
 
544
+ class ShortcutsDialogComponent {
545
+ dialogRef;
546
+ constructor(dialogRef) {
547
+ this.dialogRef = dialogRef;
548
+ }
549
+ onClose() {
550
+ this.dialogRef.close();
551
+ }
552
+ static ɵfac = function ShortcutsDialogComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ShortcutsDialogComponent)(i0.ɵɵdirectiveInject(i1$1.MatDialogRef)); };
553
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ShortcutsDialogComponent, selectors: [["app-shortcuts-dialog"]], standalone: false, decls: 68, vars: 0, consts: [[1, "dialog"], ["mat-dialog-title", ""], ["mat-dialog-content", "", 1, "dialog-content", "pb-3"], [1, "text-center", "mt-2", "mb-3"], [1, "d-flex", "justify-content-between", "mb-2"], [1, "badge", "badge-secondary", "p-2"], [1, "text-center", "mb-3"], ["mat-dialog-actions", "", 1, "d-flex", "justify-content-end"], ["mat-button", "", 3, "click"]], template: function ShortcutsDialogComponent_Template(rf, ctx) { if (rf & 1) {
554
+ i0.ɵɵelementStart(0, "div", 0)(1, "h1", 1);
555
+ i0.ɵɵtext(2, "Keyboard Shortcuts");
556
+ i0.ɵɵelementEnd();
557
+ i0.ɵɵelementStart(3, "div", 2)(4, "p", 3)(5, "b");
558
+ i0.ɵɵtext(6, "Event & Session Controls");
559
+ i0.ɵɵelementEnd()();
560
+ i0.ɵɵelementStart(7, "div", 4)(8, "span");
561
+ i0.ɵɵtext(9, "Next event");
562
+ i0.ɵɵelementEnd();
563
+ i0.ɵɵelementStart(10, "span", 5);
564
+ i0.ɵɵtext(11, "Shift + ArrowRight");
565
+ i0.ɵɵelementEnd()();
566
+ i0.ɵɵelementStart(12, "div", 4)(13, "span");
567
+ i0.ɵɵtext(14, "Previous event");
568
+ i0.ɵɵelementEnd();
569
+ i0.ɵɵelementStart(15, "span", 5);
570
+ i0.ɵɵtext(16, "Shift + ArrowLeft");
571
+ i0.ɵɵelementEnd()();
572
+ i0.ɵɵelementStart(17, "div", 4)(18, "span");
573
+ i0.ɵɵtext(19, "Toggle session recording");
574
+ i0.ɵɵelementEnd();
575
+ i0.ɵɵelementStart(20, "span", 5);
576
+ i0.ɵɵtext(21, "Shift + S");
577
+ i0.ɵɵelementEnd()();
578
+ i0.ɵɵelement(22, "hr");
579
+ i0.ɵɵelementStart(23, "p", 6)(24, "b");
580
+ i0.ɵɵtext(25, "Camera & Viewport Controls");
581
+ i0.ɵɵelementEnd()();
582
+ i0.ɵɵelementStart(26, "div", 4)(27, "span");
583
+ i0.ɵɵtext(28, "Toggle auto-rotation");
584
+ i0.ɵɵelementEnd();
585
+ i0.ɵɵelementStart(29, "span", 5);
586
+ i0.ɵɵtext(30, "Shift + R");
587
+ i0.ɵɵelementEnd()();
588
+ i0.ɵɵelementStart(31, "div", 4)(32, "span");
589
+ i0.ɵɵtext(33, "Zoom in");
590
+ i0.ɵɵelementEnd();
591
+ i0.ɵɵelementStart(34, "span", 5);
592
+ i0.ɵɵtext(35, "Shift + +");
593
+ i0.ɵɵelementEnd()();
594
+ i0.ɵɵelementStart(36, "div", 4)(37, "span");
595
+ i0.ɵɵtext(38, "Zoom out");
596
+ i0.ɵɵelementEnd();
597
+ i0.ɵɵelementStart(39, "span", 5);
598
+ i0.ɵɵtext(40, "Shift + -");
599
+ i0.ɵɵelementEnd()();
600
+ i0.ɵɵelementStart(41, "div", 4)(42, "span");
601
+ i0.ɵɵtext(43, "Revert main camera");
602
+ i0.ɵɵelementEnd();
603
+ i0.ɵɵelementStart(44, "span", 5);
604
+ i0.ɵɵtext(45, "Shift + V");
605
+ i0.ɵɵelementEnd()();
606
+ i0.ɵɵelementStart(46, "div", 4)(47, "span");
607
+ i0.ɵɵtext(48, "Toggle local clipping");
608
+ i0.ɵɵelementEnd();
609
+ i0.ɵɵelementStart(49, "span", 5);
610
+ i0.ɵɵtext(50, "Shift + C");
611
+ i0.ɵɵelementEnd()();
612
+ i0.ɵɵelement(51, "hr");
613
+ i0.ɵɵelementStart(52, "p", 6)(53, "b");
614
+ i0.ɵɵtext(54, "UI & Theming Controls");
615
+ i0.ɵɵelementEnd()();
616
+ i0.ɵɵelementStart(55, "div", 4)(56, "span");
617
+ i0.ɵɵtext(57, "Toggle dark theme");
618
+ i0.ɵɵelementEnd();
619
+ i0.ɵɵelementStart(58, "span", 5);
620
+ i0.ɵɵtext(59, "Shift + T");
621
+ i0.ɵɵelementEnd()();
622
+ i0.ɵɵelementStart(60, "div", 4)(61, "span");
623
+ i0.ɵɵtext(62, "Preset views (if configured)");
624
+ i0.ɵɵelementEnd();
625
+ i0.ɵɵelementStart(63, "span", 5);
626
+ i0.ɵɵtext(64, "Shift + 1-9");
627
+ i0.ɵɵelementEnd()()();
628
+ i0.ɵɵelementStart(65, "div", 7)(66, "button", 8);
629
+ i0.ɵɵlistener("click", function ShortcutsDialogComponent_Template_button_click_66_listener() { return ctx.onClose(); });
630
+ i0.ɵɵtext(67, "Close");
631
+ i0.ɵɵelementEnd()()();
632
+ } }, dependencies: [i1$1.MatDialogTitle, i1$1.MatDialogActions, i1$1.MatDialogContent, i3.MatButton], styles: [".dialog[_ngcontent-%COMP%]{min-width:400px;max-height:80vh;background-color:var(--phoenix-background-color-secondary, #222);color:var(--phoenix-text-color, #fff)}.dialog[_ngcontent-%COMP%] .dialog-content[_ngcontent-%COMP%]{overflow-y:auto;max-height:calc(80vh - 120px)}.dialog[_ngcontent-%COMP%] .dialog-content[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{display:flex;align-items:center}"] });
633
+ }
634
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ShortcutsDialogComponent, [{
635
+ type: Component,
636
+ args: [{ standalone: false, selector: 'app-shortcuts-dialog', template: "<div class=\"dialog\">\n <h1 mat-dialog-title>Keyboard Shortcuts</h1>\n <div mat-dialog-content class=\"dialog-content pb-3\">\n <p class=\"text-center mt-2 mb-3\"><b>Event & Session Controls</b></p>\n <div class=\"d-flex justify-content-between mb-2\">\n <span>Next event</span>\n <span class=\"badge badge-secondary p-2\">Shift + ArrowRight</span>\n </div>\n <div class=\"d-flex justify-content-between mb-2\">\n <span>Previous event</span>\n <span class=\"badge badge-secondary p-2\">Shift + ArrowLeft</span>\n </div>\n <div class=\"d-flex justify-content-between mb-2\">\n <span>Toggle session recording</span>\n <span class=\"badge badge-secondary p-2\">Shift + S</span>\n </div>\n\n <hr />\n <p class=\"text-center mb-3\"><b>Camera & Viewport Controls</b></p>\n <div class=\"d-flex justify-content-between mb-2\">\n <span>Toggle auto-rotation</span>\n <span class=\"badge badge-secondary p-2\">Shift + R</span>\n </div>\n <div class=\"d-flex justify-content-between mb-2\">\n <span>Zoom in</span>\n <span class=\"badge badge-secondary p-2\">Shift + +</span>\n </div>\n <div class=\"d-flex justify-content-between mb-2\">\n <span>Zoom out</span>\n <span class=\"badge badge-secondary p-2\">Shift + -</span>\n </div>\n <div class=\"d-flex justify-content-between mb-2\">\n <span>Revert main camera</span>\n <span class=\"badge badge-secondary p-2\">Shift + V</span>\n </div>\n <div class=\"d-flex justify-content-between mb-2\">\n <span>Toggle local clipping</span>\n <span class=\"badge badge-secondary p-2\">Shift + C</span>\n </div>\n\n <hr />\n <p class=\"text-center mb-3\"><b>UI & Theming Controls</b></p>\n <div class=\"d-flex justify-content-between mb-2\">\n <span>Toggle dark theme</span>\n <span class=\"badge badge-secondary p-2\">Shift + T</span>\n </div>\n <div class=\"d-flex justify-content-between mb-2\">\n <span>Preset views (if configured)</span>\n <span class=\"badge badge-secondary p-2\">Shift + 1-9</span>\n </div>\n </div>\n <div mat-dialog-actions class=\"d-flex justify-content-end\">\n <button mat-button (click)=\"onClose()\">Close</button>\n </div>\n</div>\n", styles: [".dialog{min-width:400px;max-height:80vh;background-color:var(--phoenix-background-color-secondary, #222);color:var(--phoenix-text-color, #fff)}.dialog .dialog-content{overflow-y:auto;max-height:calc(80vh - 120px)}.dialog .dialog-content span{display:flex;align-items:center}\n"] }]
637
+ }], () => [{ type: i1$1.MatDialogRef }], null); })();
638
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ShortcutsDialogComponent, { className: "ShortcutsDialogComponent", filePath: "components/phoenix-menu/shortcuts-dialog/shortcuts-dialog.component.ts", lineNumber: 10 }); })();
639
+
438
640
  class PhoenixMenuComponent {
641
+ dialog;
439
642
  rootNode;
440
- static ɵfac = function PhoenixMenuComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || PhoenixMenuComponent)(); };
441
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: PhoenixMenuComponent, selectors: [["app-phoenix-menu"]], inputs: { rootNode: "rootNode" }, standalone: false, decls: 2, vars: 1, consts: [["id", "phoenixMenu"], [3, "currentNode"]], template: function PhoenixMenuComponent_Template(rf, ctx) { if (rf & 1) {
442
- i0.ɵɵelementStart(0, "div", 0);
443
- i0.ɵɵelement(1, "app-phoenix-menu-item", 1);
643
+ constructor(dialog) {
644
+ this.dialog = dialog;
645
+ }
646
+ openShortcutsDialog() {
647
+ this.dialog.open(ShortcutsDialogComponent, {
648
+ panelClass: 'dialog',
649
+ });
650
+ }
651
+ static ɵfac = function PhoenixMenuComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || PhoenixMenuComponent)(i0.ɵɵdirectiveInject(i1$1.MatDialog)); };
652
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: PhoenixMenuComponent, selectors: [["app-phoenix-menu"]], inputs: { rootNode: "rootNode" }, standalone: false, decls: 9, vars: 1, consts: [[1, "phoenix-menu-drag-container"], ["id", "phoenixMenu"], [3, "currentNode"], [1, "shortcuts-button-wrapper", "mt-2"], [1, "shortcuts-btn", 3, "click"], [2, "font-size", "16px", "width", "16px", "height", "16px", "vertical-align", "middle"], [2, "vertical-align", "middle"]], template: function PhoenixMenuComponent_Template(rf, ctx) { if (rf & 1) {
653
+ i0.ɵɵelementStart(0, "div", 0)(1, "div", 1);
654
+ i0.ɵɵelement(2, "app-phoenix-menu-item", 2);
655
+ i0.ɵɵelementEnd()();
656
+ i0.ɵɵelementStart(3, "div", 3)(4, "button", 4);
657
+ i0.ɵɵlistener("click", function PhoenixMenuComponent_Template_button_click_4_listener() { return ctx.openShortcutsDialog(); });
658
+ i0.ɵɵelementStart(5, "mat-icon", 5);
659
+ i0.ɵɵtext(6, "keyboard");
444
660
  i0.ɵɵelementEnd();
661
+ i0.ɵɵelementStart(7, "span", 6);
662
+ i0.ɵɵtext(8, "Shortcuts");
663
+ i0.ɵɵelementEnd()()();
445
664
  } if (rf & 2) {
446
- i0.ɵɵadvance();
665
+ i0.ɵɵadvance(2);
447
666
  i0.ɵɵproperty("currentNode", ctx.rootNode);
448
- } }, dependencies: [PhoenixMenuItemComponent], styles: ["[_nghost-%COMP%]{width:15rem;max-width:45%;position:absolute;top:5rem;right:1rem}#phoenixMenu[_ngcontent-%COMP%]{background:var(--phoenix-background-color-secondary);max-height:75vh;overflow-y:auto;box-shadow:var(--phoenix-box-shadow);z-index:100}@media screen and (max-width: 768px){#phoenixMenu[_ngcontent-%COMP%]{top:4rem}}"] });
667
+ } }, dependencies: [i2$2.MatIcon, PhoenixMenuItemComponent], styles: ["[_nghost-%COMP%]{width:15rem;max-width:45%;position:absolute;top:5rem;right:1rem}#phoenixMenu[_ngcontent-%COMP%]{background:var(--phoenix-background-color-secondary);max-height:75vh;overflow-y:auto;box-shadow:var(--phoenix-box-shadow);z-index:100}@media screen and (max-width:768px){#phoenixMenu[_ngcontent-%COMP%]{top:4rem}}.shortcuts-button-wrapper[_ngcontent-%COMP%]{text-align:right;pointer-events:auto}.shortcuts-btn[_ngcontent-%COMP%]{background-color:var(--phoenix-background-color-secondary);color:var(--phoenix-text-color);border:none;padding:5px 10px;border-radius:3px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;box-shadow:var(--phoenix-box-shadow);transition:background-color .2s ease;font-family:inherit;font-size:.8rem}.shortcuts-btn[_ngcontent-%COMP%]:hover{background-color:var(--phoenix-menu-item-hover-color, #444)}"] });
449
668
  }
450
669
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(PhoenixMenuComponent, [{
451
670
  type: Component,
452
- args: [{ standalone: false, selector: 'app-phoenix-menu', template: "<div id=\"phoenixMenu\">\n <app-phoenix-menu-item [currentNode]=\"rootNode\"></app-phoenix-menu-item>\n</div>\n", styles: [":host{width:15rem;max-width:45%;position:absolute;top:5rem;right:1rem}#phoenixMenu{background:var(--phoenix-background-color-secondary);max-height:75vh;overflow-y:auto;box-shadow:var(--phoenix-box-shadow);z-index:100}@media screen and (max-width: 768px){#phoenixMenu{top:4rem}}\n"] }]
453
- }], null, { rootNode: [{
671
+ args: [{ standalone: false, selector: 'app-phoenix-menu', template: "<div class=\"phoenix-menu-drag-container\">\n <div id=\"phoenixMenu\">\n <app-phoenix-menu-item [currentNode]=\"rootNode\"></app-phoenix-menu-item>\n </div>\n</div>\n<div class=\"shortcuts-button-wrapper mt-2\">\n <button class=\"shortcuts-btn\" (click)=\"openShortcutsDialog()\">\n <mat-icon\n style=\"font-size: 16px; width: 16px; height: 16px; vertical-align: middle\"\n >keyboard</mat-icon\n >\n <span style=\"vertical-align: middle\">Shortcuts</span>\n </button>\n</div>\n", styles: [":host{width:15rem;max-width:45%;position:absolute;top:5rem;right:1rem}#phoenixMenu{background:var(--phoenix-background-color-secondary);max-height:75vh;overflow-y:auto;box-shadow:var(--phoenix-box-shadow);z-index:100}@media screen and (max-width:768px){#phoenixMenu{top:4rem}}.shortcuts-button-wrapper{text-align:right;pointer-events:auto}.shortcuts-btn{background-color:var(--phoenix-background-color-secondary);color:var(--phoenix-text-color);border:none;padding:5px 10px;border-radius:3px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;box-shadow:var(--phoenix-box-shadow);transition:background-color .2s ease;font-family:inherit;font-size:.8rem}.shortcuts-btn:hover{background-color:var(--phoenix-menu-item-hover-color, #444)}\n"] }]
672
+ }], () => [{ type: i1$1.MatDialog }], { rootNode: [{
454
673
  type: Input
455
674
  }] }); })();
456
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(PhoenixMenuComponent, { className: "PhoenixMenuComponent", filePath: "components/phoenix-menu/phoenix-menu.component.ts", lineNumber: 10 }); })();
675
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(PhoenixMenuComponent, { className: "PhoenixMenuComponent", filePath: "components/phoenix-menu/phoenix-menu.component.ts", lineNumber: 12 }); })();
676
+
677
+ /**
678
+ * Service for displaying user-facing notifications with severity levels.
679
+ * Provides success, info, warning, and error notifications with
680
+ * configurable auto-dismiss durations. Replaces silent console errors
681
+ * with actionable user feedback across all Phoenix components.
682
+ */
683
+ class NotificationService {
684
+ /** Default auto-dismiss durations in milliseconds per severity level. */
685
+ defaultDurations = {
686
+ success: 5000,
687
+ info: 5000,
688
+ warning: 8000,
689
+ error: 0,
690
+ };
691
+ /** Active variable that stores the latest notification. */
692
+ notification = new ActiveVariable();
693
+ /**
694
+ * Subscribe to incoming notifications.
695
+ * @param callback Function called when a notification is emitted.
696
+ * @returns A function that can be called to unsubscribe.
697
+ */
698
+ subscribeToNotifications(callback) {
699
+ return this.notification.onUpdate(callback);
700
+ }
701
+ /**
702
+ * Display a success notification.
703
+ * Auto-dismisses after 5 seconds by default.
704
+ * @param message The message to display.
705
+ * @param duration Optional custom duration in ms. 0 means no auto-dismiss.
706
+ */
707
+ success(message, duration) {
708
+ this.notify(message, 'success', duration);
709
+ }
710
+ /**
711
+ * Display an info notification.
712
+ * Auto-dismisses after 5 seconds by default.
713
+ * @param message The message to display.
714
+ * @param duration Optional custom duration in ms. 0 means no auto-dismiss.
715
+ */
716
+ info(message, duration) {
717
+ this.notify(message, 'info', duration);
718
+ }
719
+ /**
720
+ * Display a warning notification.
721
+ * Auto-dismisses after 8 seconds by default.
722
+ * @param message The message to display.
723
+ * @param duration Optional custom duration in ms. 0 means no auto-dismiss.
724
+ */
725
+ warning(message, duration) {
726
+ this.notify(message, 'warning', duration);
727
+ }
728
+ /**
729
+ * Display an error notification.
730
+ * Does not auto-dismiss by default — requires manual dismissal.
731
+ * @param message The message to display.
732
+ * @param duration Optional custom duration in ms. 0 means no auto-dismiss.
733
+ */
734
+ error(message, duration) {
735
+ this.notify(message, 'error', duration);
736
+ }
737
+ /**
738
+ * Internal method to emit a notification.
739
+ * @param message The message to display.
740
+ * @param severity The severity level.
741
+ * @param duration Optional custom duration override.
742
+ */
743
+ notify(message, severity, duration) {
744
+ this.notification.update({
745
+ message,
746
+ severity,
747
+ duration: duration ?? this.defaultDurations[severity],
748
+ });
749
+ }
750
+ static ɵfac = function NotificationService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || NotificationService)(); };
751
+ static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: NotificationService, factory: NotificationService.ɵfac, providedIn: 'root' });
752
+ }
753
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(NotificationService, [{
754
+ type: Injectable,
755
+ args: [{
756
+ providedIn: 'root',
757
+ }]
758
+ }], null, null); })();
759
+
760
+ /**
761
+ * Component that subscribes to NotificationService and displays
762
+ * transient toast notifications via MatSnackBar.
763
+ *
764
+ * Severity levels map to CSS panel classes:
765
+ * - success → notification-success
766
+ * - info → notification-info
767
+ * - warning → notification-warning
768
+ * - error → notification-error
769
+ */
770
+ class NotificationToastComponent {
771
+ notificationService;
772
+ snackBar;
773
+ unsubscribe;
774
+ constructor(notificationService, snackBar) {
775
+ this.notificationService = notificationService;
776
+ this.snackBar = snackBar;
777
+ }
778
+ ngOnInit() {
779
+ this.unsubscribe = this.notificationService.subscribeToNotifications((notification) => {
780
+ this.snackBar.open(notification.message, 'Close', {
781
+ duration: notification.duration,
782
+ panelClass: [`notification-${notification.severity}`],
783
+ horizontalPosition: 'right',
784
+ verticalPosition: 'bottom',
785
+ });
786
+ });
787
+ }
788
+ ngOnDestroy() {
789
+ this.unsubscribe?.();
790
+ }
791
+ static ɵfac = function NotificationToastComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || NotificationToastComponent)(i0.ɵɵdirectiveInject(NotificationService), i0.ɵɵdirectiveInject(i2$3.MatSnackBar)); };
792
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: NotificationToastComponent, selectors: [["app-notification-toast"]], standalone: false, decls: 0, vars: 0, template: function NotificationToastComponent_Template(rf, ctx) { }, encapsulation: 2 });
793
+ }
794
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(NotificationToastComponent, [{
795
+ type: Component,
796
+ args: [{ standalone: false, selector: 'app-notification-toast', template: "<!-- NotificationToastComponent renders no visible DOM.\n It subscribes to NotificationService and delegates\n display to MatSnackBar. -->\n" }]
797
+ }], () => [{ type: NotificationService }, { type: i2$3.MatSnackBar }], null); })();
798
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(NotificationToastComponent, { className: "NotificationToastComponent", filePath: "components/ui-menu/notification-toast/notification-toast.component.ts", lineNumber: 21 }); })();
799
+
800
+ class DownloadAnimationDialogComponent {
801
+ data;
802
+ progress = 0;
803
+ progressSub;
804
+ constructor(data) {
805
+ this.data = data;
806
+ }
807
+ ngOnInit() {
808
+ this.progressSub = this.data.progress$.subscribe((val) => {
809
+ this.progress = val;
810
+ });
811
+ }
812
+ ngOnDestroy() {
813
+ // `progress$` is a BehaviorSubject that is never completed, so the
814
+ // subscription outlives the dialog unless it is dropped here.
815
+ this.progressSub?.unsubscribe();
816
+ }
817
+ static ɵfac = function DownloadAnimationDialogComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || DownloadAnimationDialogComponent)(i0.ɵɵdirectiveInject(MAT_DIALOG_DATA)); };
818
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: DownloadAnimationDialogComponent, selectors: [["app-download-animation-dialog"]], standalone: false, decls: 12, vars: 5, consts: [[1, "loader-wrapper", "d-flex", "flex-column", "justify-content-center", "align-items-center", "w-100", "h-100", "p-5", "text-center"], ["src", "assets/images/logo-small.svg", "alt", "Loader"], [1, "mt-5", "text-light"], [1, "text-muted"], [1, "loading-bar-container"], ["mode", "determinate", 3, "value"], [1, "progress-text", "text-light", "mt-2"]], template: function DownloadAnimationDialogComponent_Template(rf, ctx) { if (rf & 1) {
819
+ i0.ɵɵelementStart(0, "div", 0);
820
+ i0.ɵɵelement(1, "img", 1);
821
+ i0.ɵɵelementStart(2, "p", 2);
822
+ i0.ɵɵtext(3, " Generating Video...");
823
+ i0.ɵɵelement(4, "br");
824
+ i0.ɵɵelementStart(5, "small", 3);
825
+ i0.ɵɵtext(6, "(This may take a while)");
826
+ i0.ɵɵelementEnd()();
827
+ i0.ɵɵelementStart(7, "div", 4);
828
+ i0.ɵɵelement(8, "mat-progress-bar", 5);
829
+ i0.ɵɵelementStart(9, "div", 6);
830
+ i0.ɵɵtext(10);
831
+ i0.ɵɵpipe(11, "number");
832
+ i0.ɵɵelementEnd()()();
833
+ } if (rf & 2) {
834
+ i0.ɵɵadvance(8);
835
+ i0.ɵɵproperty("value", ctx.progress);
836
+ i0.ɵɵadvance(2);
837
+ i0.ɵɵtextInterpolate1(" ", i0.ɵɵpipeBind2(11, 2, ctx.progress, "1.0-0"), "% ");
838
+ } }, dependencies: [i1$2.MatProgressBar, i2.DecimalPipe], styles: [".loader-wrapper[_ngcontent-%COMP%]{background-color:#212529f2;border-radius:8px}.loader-wrapper[_ngcontent-%COMP%] img[_ngcontent-%COMP%]{width:100px;height:auto;animation:_ngcontent-%COMP%_pulse 2s infinite}.loader-wrapper[_ngcontent-%COMP%] .loading-bar-container[_ngcontent-%COMP%]{width:80%;margin-top:1rem}.loader-wrapper[_ngcontent-%COMP%] .loading-bar-container[_ngcontent-%COMP%] mat-progress-bar[_ngcontent-%COMP%]{height:10px;border-radius:5px}.loader-wrapper[_ngcontent-%COMP%] p[_ngcontent-%COMP%]{font-size:1.1rem;margin-bottom:0}@keyframes _ngcontent-%COMP%_pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.1);opacity:.8}to{transform:scale(1);opacity:1}}"] });
839
+ }
840
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DownloadAnimationDialogComponent, [{
841
+ type: Component,
842
+ args: [{ standalone: false, selector: 'app-download-animation-dialog', template: "<div\n class=\"loader-wrapper d-flex flex-column justify-content-center align-items-center w-100 h-100 p-5 text-center\"\n>\n <img src=\"assets/images/logo-small.svg\" alt=\"Loader\" />\n <p class=\"mt-5 text-light\">\n Generating Video...<br />\n <small class=\"text-muted\">(This may take a while)</small>\n </p>\n <div class=\"loading-bar-container\">\n <mat-progress-bar mode=\"determinate\" [value]=\"progress\"></mat-progress-bar>\n <div class=\"progress-text text-light mt-2\">\n {{ progress | number: '1.0-0' }}%\n </div>\n </div>\n</div>\n", styles: [".loader-wrapper{background-color:#212529f2;border-radius:8px}.loader-wrapper img{width:100px;height:auto;animation:pulse 2s infinite}.loader-wrapper .loading-bar-container{width:80%;margin-top:1rem}.loader-wrapper .loading-bar-container mat-progress-bar{height:10px;border-radius:5px}.loader-wrapper p{font-size:1.1rem;margin-bottom:0}@keyframes pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.1);opacity:.8}to{transform:scale(1);opacity:1}}\n"] }]
843
+ }], () => [{ type: undefined, decorators: [{
844
+ type: Inject,
845
+ args: [MAT_DIALOG_DATA]
846
+ }] }], null); })();
847
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(DownloadAnimationDialogComponent, { className: "DownloadAnimationDialogComponent", filePath: "components/ui-menu/animate-camera/download-animation-dialog/download-animation-dialog.component.ts", lineNumber: 11 }); })();
457
848
 
458
849
  /**
459
850
  * Service for all event display related functions.
@@ -538,16 +929,27 @@ class MenuToggleComponent {
538
929
  }] }); })();
539
930
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MenuToggleComponent, { className: "MenuToggleComponent", filePath: "components/ui-menu/menu-toggle/menu-toggle.component.ts", lineNumber: 9 }); })();
540
931
 
541
- function AnimateCameraComponent_button_2_Template(rf, ctx) { if (rf & 1) {
932
+ function AnimateCameraComponent_div_2_Template(rf, ctx) { if (rf & 1) {
542
933
  const _r2 = i0.ɵɵgetCurrentView();
543
- i0.ɵɵelementStart(0, "button", 2);
544
- i0.ɵɵlistener("click", function AnimateCameraComponent_button_2_Template_button_click_0_listener() { const preset_r3 = i0.ɵɵrestoreView(_r2).$implicit; const ctx_r3 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r3.animatePreset(preset_r3)); });
545
- i0.ɵɵtext(1);
934
+ i0.ɵɵelementStart(0, "div", 3);
935
+ i0.ɵɵlistener("click", function AnimateCameraComponent_div_2_Template_div_click_0_listener($event) { i0.ɵɵrestoreView(_r2); return i0.ɵɵresetView($event.stopPropagation()); });
936
+ i0.ɵɵelementStart(1, "span", 4);
937
+ i0.ɵɵtext(2);
546
938
  i0.ɵɵelementEnd();
939
+ i0.ɵɵelementStart(3, "span", 5)(4, "button", 6);
940
+ i0.ɵɵlistener("click", function AnimateCameraComponent_div_2_Template_button_click_4_listener() { const preset_r3 = i0.ɵɵrestoreView(_r2).$implicit; const ctx_r3 = i0.ɵɵnextContext(); const menuTrigger_r5 = i0.ɵɵreference(14); ctx_r3.playPreset(preset_r3); return i0.ɵɵresetView(menuTrigger_r5.closeMenu()); });
941
+ i0.ɵɵelementStart(5, "mat-icon");
942
+ i0.ɵɵtext(6, "play_arrow");
943
+ i0.ɵɵelementEnd()();
944
+ i0.ɵɵelementStart(7, "button", 7);
945
+ i0.ɵɵlistener("click", function AnimateCameraComponent_div_2_Template_button_click_7_listener() { const preset_r3 = i0.ɵɵrestoreView(_r2).$implicit; const ctx_r3 = i0.ɵɵnextContext(); const menuTrigger_r5 = i0.ɵɵreference(14); ctx_r3.downloadAnimation(preset_r3); return i0.ɵɵresetView(menuTrigger_r5.closeMenu()); });
946
+ i0.ɵɵelementStart(8, "mat-icon");
947
+ i0.ɵɵtext(9, "download");
948
+ i0.ɵɵelementEnd()()()();
547
949
  } if (rf & 2) {
548
950
  const preset_r3 = ctx.$implicit;
549
- i0.ɵɵadvance();
550
- i0.ɵɵtextInterpolate1(" ", preset_r3, " ");
951
+ i0.ɵɵadvance(2);
952
+ i0.ɵɵtextInterpolate(preset_r3);
551
953
  } }
552
954
  const defaultAnimationPresets = {
553
955
  'Cavern to ID': {
@@ -596,60 +998,138 @@ const defaultAnimationPresets = {
596
998
  };
597
999
  class AnimateCameraComponent {
598
1000
  eventDisplay;
1001
+ dialog;
599
1002
  animationPresets = defaultAnimationPresets;
600
1003
  animationPresetsKeys = Object.keys(this.animationPresets);
601
1004
  isAnimating = false;
602
- constructor(eventDisplay) {
1005
+ constructor(eventDisplay, dialog) {
603
1006
  this.eventDisplay = eventDisplay;
604
- // this.animationPresets = this.eventDisplay
605
- // .getUIManager()
606
- // .getPresetAnimations();
607
- // This is too late to fill the UI! FIXME. EJWM
1007
+ this.dialog = dialog;
1008
+ }
1009
+ playPreset(preset) {
1010
+ this.animatePreset(preset);
608
1011
  }
609
- animatePreset(preset) {
1012
+ playAnimateCamera() {
1013
+ this.animateCamera();
1014
+ }
1015
+ downloadAnimation(preset) {
1016
+ let totalDuration = 3000;
1017
+ if (preset && this.animationPresets[preset]) {
1018
+ totalDuration = this.animationPresets[preset].positions.reduce((acc, pos) => acc + pos.duration, 0);
1019
+ }
1020
+ const progress$ = new BehaviorSubject(0);
1021
+ const dialogRef = this.dialog.open(DownloadAnimationDialogComponent, {
1022
+ data: { progress$ },
1023
+ disableClose: true,
1024
+ panelClass: 'dialog-no-padding',
1025
+ });
1026
+ const startTime = Date.now();
1027
+ const interval = setInterval(() => {
1028
+ const elapsed = Date.now() - startTime;
1029
+ const progress = Math.min(100, Math.round((elapsed / totalDuration) * 100));
1030
+ progress$.next(progress);
1031
+ }, 100);
1032
+ const rendererManager = this.eventDisplay
1033
+ .getThreeManager()
1034
+ .getRendererManager();
1035
+ const canvas = rendererManager.getMainRenderer().domElement;
1036
+ const stream = canvas.captureStream(60);
1037
+ const recorder = new RecordRTC(stream, {
1038
+ type: 'video',
1039
+ mimeType: 'video/webm',
1040
+ });
1041
+ recorder.startRecording();
1042
+ const onEnd = () => {
1043
+ clearInterval(interval);
1044
+ progress$.next(100);
1045
+ recorder.stopRecording(() => {
1046
+ const blob = recorder.getBlob();
1047
+ RecordRTC.invokeSaveAsDialog(blob, `${preset || 'animation'}.webm`);
1048
+ dialogRef.close();
1049
+ });
1050
+ };
1051
+ // `animateCamera` refuses to start while another animation is running. If
1052
+ // it does not start, nothing will ever call `onEnd`, so the interval would
1053
+ // run forever behind a dialog the user cannot dismiss.
1054
+ const started = preset
1055
+ ? this.animatePreset(preset, onEnd)
1056
+ : this.animateCamera(onEnd);
1057
+ if (!started) {
1058
+ clearInterval(interval);
1059
+ recorder.stopRecording(() => dialogRef.close());
1060
+ }
1061
+ }
1062
+ /**
1063
+ * Animate through the given preset.
1064
+ * @returns Whether the animation was started.
1065
+ */
1066
+ animatePreset(preset, onEnd) {
610
1067
  this.setDetectorOpacity(0.2);
611
1068
  this.eventDisplay.animatePreset(this.animationPresets[preset], () => {
612
1069
  this.setDetectorOpacity(1);
1070
+ if (onEnd)
1071
+ onEnd();
613
1072
  });
1073
+ return true;
614
1074
  }
615
- animateCamera() {
616
- if (!this.isAnimating) {
617
- this.isAnimating = true;
618
- this.eventDisplay.animateThroughEvent([11976, 7262, 11927], 3000, () => {
619
- this.isAnimating = false;
620
- });
1075
+ /**
1076
+ * Animate the camera through the event.
1077
+ * @returns Whether the animation was started. It is refused while another
1078
+ * animation is already running.
1079
+ */
1080
+ animateCamera(onEnd) {
1081
+ if (this.isAnimating) {
1082
+ return false;
621
1083
  }
1084
+ this.isAnimating = true;
1085
+ this.eventDisplay.animateThroughEvent([11976, 7262, 11927], 3000, () => {
1086
+ this.isAnimating = false;
1087
+ if (onEnd)
1088
+ onEnd();
1089
+ });
1090
+ return true;
622
1091
  }
623
1092
  setDetectorOpacity(opacity) {
624
1093
  const sceneManager = this.eventDisplay.getThreeManager().getSceneManager();
625
1094
  const geometriesGroup = sceneManager.getObjectByName(SceneManager.GEOMETRIES_ID);
626
1095
  sceneManager.setGeometryOpacity(geometriesGroup, opacity);
627
1096
  }
628
- static ɵfac = function AnimateCameraComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || AnimateCameraComponent)(i0.ɵɵdirectiveInject(EventDisplayService)); };
629
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AnimateCameraComponent, selectors: [["app-animate-camera"]], inputs: { animationPresets: "animationPresets" }, standalone: false, decls: 6, vars: 2, consts: [["animationPresets", ""], ["mat-menu-item", "", 3, "click", 4, "ngFor", "ngForOf"], ["mat-menu-item", "", 3, "click"], ["tooltip", "Preset animations", "icon", "animate-camera", 3, "matMenuTriggerFor"]], template: function AnimateCameraComponent_Template(rf, ctx) { if (rf & 1) {
1097
+ static ɵfac = function AnimateCameraComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || AnimateCameraComponent)(i0.ɵɵdirectiveInject(EventDisplayService), i0.ɵɵdirectiveInject(i1$1.MatDialog)); };
1098
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AnimateCameraComponent, selectors: [["app-animate-camera"]], inputs: { animationPresets: "animationPresets" }, standalone: false, decls: 15, vars: 2, consts: [["animationPresets", ""], ["menuTrigger", "matMenuTrigger"], ["mat-menu-item", "", "class", "preset-row", 3, "click", 4, "ngFor", "ngForOf"], ["mat-menu-item", "", 1, "preset-row", 3, "click"], [1, "preset-name"], [1, "preset-actions"], ["mat-icon-button", "", "matTooltip", "Play", 3, "click"], ["mat-icon-button", "", "matTooltip", "Download", 3, "click"], ["tooltip", "Preset animations", "icon", "animate-camera", 3, "matMenuTriggerFor"]], template: function AnimateCameraComponent_Template(rf, ctx) { if (rf & 1) {
630
1099
  const _r1 = i0.ɵɵgetCurrentView();
631
1100
  i0.ɵɵelementStart(0, "mat-menu", null, 0);
632
- i0.ɵɵtemplate(2, AnimateCameraComponent_button_2_Template, 2, 1, "button", 1);
633
- i0.ɵɵelementStart(3, "button", 2);
634
- i0.ɵɵlistener("click", function AnimateCameraComponent_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.animateCamera()); });
635
- i0.ɵɵtext(4, "Animate camera");
1101
+ i0.ɵɵtemplate(2, AnimateCameraComponent_div_2_Template, 10, 1, "div", 2);
1102
+ i0.ɵɵelementStart(3, "div", 3);
1103
+ i0.ɵɵlistener("click", function AnimateCameraComponent_Template_div_click_3_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView($event.stopPropagation()); });
1104
+ i0.ɵɵelementStart(4, "span", 4);
1105
+ i0.ɵɵtext(5, "Animate camera");
1106
+ i0.ɵɵelementEnd();
1107
+ i0.ɵɵelementStart(6, "span", 5)(7, "button", 6);
1108
+ i0.ɵɵlistener("click", function AnimateCameraComponent_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r1); const menuTrigger_r5 = i0.ɵɵreference(14); ctx.playAnimateCamera(); return i0.ɵɵresetView(menuTrigger_r5.closeMenu()); });
1109
+ i0.ɵɵelementStart(8, "mat-icon");
1110
+ i0.ɵɵtext(9, "play_arrow");
636
1111
  i0.ɵɵelementEnd()();
637
- i0.ɵɵelement(5, "app-menu-toggle", 3);
1112
+ i0.ɵɵelementStart(10, "button", 7);
1113
+ i0.ɵɵlistener("click", function AnimateCameraComponent_Template_button_click_10_listener() { i0.ɵɵrestoreView(_r1); const menuTrigger_r5 = i0.ɵɵreference(14); ctx.downloadAnimation(); return i0.ɵɵresetView(menuTrigger_r5.closeMenu()); });
1114
+ i0.ɵɵelementStart(11, "mat-icon");
1115
+ i0.ɵɵtext(12, "download");
1116
+ i0.ɵɵelementEnd()()()()();
1117
+ i0.ɵɵelement(13, "app-menu-toggle", 8, 1);
638
1118
  } if (rf & 2) {
639
- const animationPresets_r5 = i0.ɵɵreference(1);
1119
+ const animationPresets_r6 = i0.ɵɵreference(1);
640
1120
  i0.ɵɵadvance(2);
641
1121
  i0.ɵɵproperty("ngForOf", ctx.animationPresetsKeys);
642
- i0.ɵɵadvance(3);
643
- i0.ɵɵproperty("matMenuTriggerFor", animationPresets_r5);
644
- } }, dependencies: [i2.NgForOf, i3$2.MatMenu, i3$2.MatMenuItem, i3$2.MatMenuTrigger, MenuToggleComponent], encapsulation: 2 });
1122
+ i0.ɵɵadvance(11);
1123
+ i0.ɵɵproperty("matMenuTriggerFor", animationPresets_r6);
1124
+ } }, dependencies: [i2.NgForOf, i3.MatIconButton, i3$1.MatTooltip, i4$2.MatMenu, i4$2.MatMenuItem, i4$2.MatMenuTrigger, i2$2.MatIcon, MenuToggleComponent], styles: [".preset-row[_ngcontent-%COMP%]{display:flex;justify-content:space-between;align-items:center;width:100%;padding-right:8px;cursor:default}.preset-row[_ngcontent-%COMP%] .preset-name[_ngcontent-%COMP%]{flex:1;cursor:pointer}.preset-row[_ngcontent-%COMP%] .preset-actions[_ngcontent-%COMP%]{display:flex;gap:4px}.preset-row[_ngcontent-%COMP%] .preset-actions[_ngcontent-%COMP%] button[_ngcontent-%COMP%]{width:32px;height:32px;line-height:32px;padding:0}.preset-row[_ngcontent-%COMP%] .preset-actions[_ngcontent-%COMP%] button[_ngcontent-%COMP%] mat-icon[_ngcontent-%COMP%]{font-size:18px;width:18px;height:18px;line-height:18px}"] });
645
1125
  }
646
1126
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AnimateCameraComponent, [{
647
1127
  type: Component,
648
- args: [{ standalone: false, selector: 'app-animate-camera', template: "<mat-menu #animationPresets>\n <button\n mat-menu-item\n *ngFor=\"let preset of animationPresetsKeys\"\n (click)=\"animatePreset(preset)\"\n >\n {{ preset }}\n </button>\n <button mat-menu-item (click)=\"animateCamera()\">Animate camera</button>\n</mat-menu>\n\n<app-menu-toggle\n [matMenuTriggerFor]=\"animationPresets\"\n tooltip=\"Preset animations\"\n icon=\"animate-camera\"\n>\n</app-menu-toggle>\n" }]
649
- }], () => [{ type: EventDisplayService }], { animationPresets: [{
1128
+ args: [{ standalone: false, selector: 'app-animate-camera', template: "<mat-menu #animationPresets>\n <div\n mat-menu-item\n *ngFor=\"let preset of animationPresetsKeys\"\n class=\"preset-row\"\n (click)=\"$event.stopPropagation()\"\n >\n <span class=\"preset-name\">{{ preset }}</span>\n <span class=\"preset-actions\">\n <button\n mat-icon-button\n (click)=\"playPreset(preset); menuTrigger.closeMenu()\"\n matTooltip=\"Play\"\n >\n <mat-icon>play_arrow</mat-icon>\n </button>\n <button\n mat-icon-button\n (click)=\"downloadAnimation(preset); menuTrigger.closeMenu()\"\n matTooltip=\"Download\"\n >\n <mat-icon>download</mat-icon>\n </button>\n </span>\n </div>\n <div mat-menu-item class=\"preset-row\" (click)=\"$event.stopPropagation()\">\n <span class=\"preset-name\">Animate camera</span>\n <span class=\"preset-actions\">\n <button\n mat-icon-button\n (click)=\"playAnimateCamera(); menuTrigger.closeMenu()\"\n matTooltip=\"Play\"\n >\n <mat-icon>play_arrow</mat-icon>\n </button>\n <button\n mat-icon-button\n (click)=\"downloadAnimation(); menuTrigger.closeMenu()\"\n matTooltip=\"Download\"\n >\n <mat-icon>download</mat-icon>\n </button>\n </span>\n </div>\n</mat-menu>\n\n<app-menu-toggle\n #menuTrigger=\"matMenuTrigger\"\n [matMenuTriggerFor]=\"animationPresets\"\n tooltip=\"Preset animations\"\n icon=\"animate-camera\"\n>\n</app-menu-toggle>\n", styles: [".preset-row{display:flex;justify-content:space-between;align-items:center;width:100%;padding-right:8px;cursor:default}.preset-row .preset-name{flex:1;cursor:pointer}.preset-row .preset-actions{display:flex;gap:4px}.preset-row .preset-actions button{width:32px;height:32px;line-height:32px;padding:0}.preset-row .preset-actions button mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}\n"] }]
1129
+ }], () => [{ type: EventDisplayService }, { type: i1$1.MatDialog }], { animationPresets: [{
650
1130
  type: Input
651
1131
  }] }); })();
652
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AnimateCameraComponent, { className: "AnimateCameraComponent", filePath: "components/ui-menu/animate-camera/animate-camera.component.ts", lineNumber: 60 }); })();
1132
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AnimateCameraComponent, { className: "AnimateCameraComponent", filePath: "components/ui-menu/animate-camera/animate-camera.component.ts", lineNumber: 64 }); })();
653
1133
 
654
1134
  class AnimateEventComponent {
655
1135
  eventDisplay;
@@ -719,59 +1199,70 @@ function OverlayComponent_div_2_div_1_Template(rf, ctx) { if (rf & 1) {
719
1199
  i0.ɵɵelement(2, "use");
720
1200
  i0.ɵɵelementEnd()();
721
1201
  } if (rf & 2) {
722
- const ctx_r1 = i0.ɵɵnextContext(2);
1202
+ const ctx_r2 = i0.ɵɵnextContext(2);
723
1203
  i0.ɵɵadvance(2);
724
- i0.ɵɵattribute("href", i0.ɵɵinterpolate2("assets/icons/", ctx_r1.icon, ".svg#", ctx_r1.icon));
1204
+ i0.ɵɵattribute("href", i0.ɵɵinterpolate2("assets/icons/", ctx_r2.icon, ".svg#", ctx_r2.icon));
725
1205
  } }
726
1206
  function OverlayComponent_div_2_Template(rf, ctx) { if (rf & 1) {
727
- const _r1 = i0.ɵɵgetCurrentView();
1207
+ const _r2 = i0.ɵɵgetCurrentView();
728
1208
  i0.ɵɵelementStart(0, "div", 6);
729
1209
  i0.ɵɵtemplate(1, OverlayComponent_div_2_div_1_Template, 3, 3, "div", 7);
730
1210
  i0.ɵɵelementStart(2, "h5", 8);
731
1211
  i0.ɵɵtext(3);
732
1212
  i0.ɵɵelementEnd();
733
1213
  i0.ɵɵelementStart(4, "button", 9);
734
- i0.ɵɵlistener("click", function OverlayComponent_div_2_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.showBody = !ctx_r1.showBody); });
1214
+ i0.ɵɵlistener("click", function OverlayComponent_div_2_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.toggleBodyAndEmit()); });
735
1215
  i0.ɵɵnamespaceSVG();
736
1216
  i0.ɵɵelementStart(5, "svg");
737
1217
  i0.ɵɵelement(6, "use", 10);
738
1218
  i0.ɵɵelementEnd()()();
739
1219
  } if (rf & 2) {
740
- const ctx_r1 = i0.ɵɵnextContext();
741
- i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction1(4, _c4$1, ctx_r1.showBody));
1220
+ const ctx_r2 = i0.ɵɵnextContext();
1221
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction1(4, _c4$1, ctx_r2.showBody));
742
1222
  i0.ɵɵadvance();
743
- i0.ɵɵproperty("ngIf", ctx_r1.icon);
1223
+ i0.ɵɵproperty("ngIf", ctx_r2.icon);
744
1224
  i0.ɵɵadvance(2);
745
- i0.ɵɵtextInterpolate(ctx_r1.overlayTitle);
1225
+ i0.ɵɵtextInterpolate(ctx_r2.overlayTitle);
746
1226
  i0.ɵɵadvance();
747
- i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction1(6, _c5$1, ctx_r1.showBody));
1227
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction1(6, _c5$1, ctx_r2.showBody));
748
1228
  } }
749
1229
  function OverlayComponent_div_3_Template(rf, ctx) { if (rf & 1) {
750
1230
  i0.ɵɵelementStart(0, "div", 12);
751
1231
  i0.ɵɵprojection(1);
752
1232
  i0.ɵɵelementEnd();
753
1233
  } if (rf & 2) {
754
- const ctx_r1 = i0.ɵɵnextContext();
755
- i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction1(1, _c6, ctx_r1.transparentBody));
1234
+ const ctx_r2 = i0.ɵɵnextContext();
1235
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction1(1, _c6, ctx_r2.transparentBody));
756
1236
  } }
757
1237
  function OverlayComponent_span_4_Template(rf, ctx) { if (rf & 1) {
758
- const _r3 = i0.ɵɵgetCurrentView();
1238
+ const _r4 = i0.ɵɵgetCurrentView();
759
1239
  i0.ɵɵelementStart(0, "span", 13, 1);
760
- i0.ɵɵlistener("cdkDragMoved", function OverlayComponent_span_4_Template_span_cdkDragMoved_0_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onResize()); });
1240
+ i0.ɵɵlistener("cdkDragMoved", function OverlayComponent_span_4_Template_span_cdkDragMoved_0_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onResize()); });
761
1241
  i0.ɵɵelementEnd();
762
1242
  } if (rf & 2) {
763
- const ctx_r1 = i0.ɵɵnextContext();
764
- i0.ɵɵproperty("hidden", !ctx_r1.showBody);
1243
+ const ctx_r2 = i0.ɵɵnextContext();
1244
+ i0.ɵɵproperty("hidden", !ctx_r2.showBody);
765
1245
  } }
1246
+ let maxZIndex = 100;
766
1247
  /**
767
1248
  * Component for overlay panel.
768
1249
  */
769
1250
  class OverlayComponent {
770
1251
  eventDisplay;
1252
+ elementRef;
771
1253
  /** Title of the overlay. */
772
1254
  overlayTitle;
773
1255
  /** If the overlay is open or not. */
774
- active = false;
1256
+ _active = false;
1257
+ set active(value) {
1258
+ this._active = value;
1259
+ if (value) {
1260
+ setTimeout(() => this.bringToFront(), 0);
1261
+ }
1262
+ }
1263
+ get active() {
1264
+ return this._active;
1265
+ }
775
1266
  /** Icon of the overlay header. */
776
1267
  icon;
777
1268
  /** If the overlay is resizable. */
@@ -799,13 +1290,31 @@ class OverlayComponent {
799
1290
  MIN_RES_WIDTH = 300;
800
1291
  /** Minimum resizable height */
801
1292
  MIN_RES_HEIGHT = 100;
802
- constructor(eventDisplay) {
1293
+ constructor(eventDisplay, elementRef) {
803
1294
  this.eventDisplay = eventDisplay;
1295
+ this.elementRef = elementRef;
1296
+ }
1297
+ ngOnInit() { }
1298
+ toggleBodyAndEmit() {
1299
+ this.showBody = !this.showBody;
1300
+ }
1301
+ /** Bring the overlay to the front. */
1302
+ bringToFront() {
1303
+ maxZIndex++;
1304
+ const wrapper = this.elementRef.nativeElement.closest('.cdk-global-overlay-wrapper');
1305
+ if (wrapper) {
1306
+ wrapper.style.zIndex = maxZIndex.toString();
1307
+ }
1308
+ const pane = this.elementRef.nativeElement.closest('.cdk-overlay-pane');
1309
+ if (pane) {
1310
+ pane.style.zIndex = maxZIndex.toString();
1311
+ }
804
1312
  }
805
1313
  /**
806
1314
  * Move the resizable handle to the bottom right after the component is created.
807
1315
  */
808
1316
  ngAfterViewInit() {
1317
+ this.bringToFront();
809
1318
  if (this.resizable) {
810
1319
  const resizeHandleElement = this.resizeHandleCorner.nativeElement;
811
1320
  resizeHandleElement.style.bottom = '0';
@@ -837,15 +1346,35 @@ class OverlayComponent {
837
1346
  if (this.keepAspectRatioFixed) {
838
1347
  height = width / this.aspectRatio;
839
1348
  }
840
- const oldratioW = width / this.overlayWindow.nativeElement.width;
841
- const oldratioH = height / this.overlayWindow.nativeElement.height;
842
- this.eventDisplay
843
- .getThreeManager()
844
- .getOverlayRenderer()
845
- .setSize(width, height);
846
- this.eventDisplay
847
- .getThreeManager()
848
- .syncOverlayViewPort(oldratioW, oldratioH);
1349
+ if (this.overlayWindow?.nativeElement) {
1350
+ const oldratioW = width / this.overlayWindow.nativeElement.width;
1351
+ const oldratioH = height / this.overlayWindow.nativeElement.height;
1352
+ this.eventDisplay
1353
+ .getThreeManager()
1354
+ .getOverlayRenderer()
1355
+ .setSize(width, height);
1356
+ this.eventDisplay
1357
+ .getThreeManager()
1358
+ .syncOverlayViewPort(oldratioW, oldratioH);
1359
+ }
1360
+ else {
1361
+ // Fallback for generic content that does not have a 3D canvas
1362
+ this.overlayCard.nativeElement.style.width = width + 'px';
1363
+ this.overlayCard.nativeElement.style.height = height + 'px';
1364
+ const contentWrapper = this.overlayCard.nativeElement.querySelector('.overlay-card-content');
1365
+ if (contentWrapper) {
1366
+ contentWrapper.style.flex = '1';
1367
+ contentWrapper.style.overflow = 'hidden';
1368
+ contentWrapper.style.maxHeight = 'none';
1369
+ const contentBody = contentWrapper.firstElementChild;
1370
+ if (contentBody) {
1371
+ contentBody.style.width = '100%';
1372
+ contentBody.style.height = '100%';
1373
+ contentBody.style.maxWidth = 'none';
1374
+ contentBody.style.maxHeight = 'none';
1375
+ }
1376
+ }
1377
+ }
849
1378
  }
850
1379
  }
851
1380
  /**
@@ -867,7 +1396,7 @@ class OverlayComponent {
867
1396
  const translateY = overlayRect.height - dragRect.height;
868
1397
  this.resizeHandleCorner.nativeElement.style.transform = `translate3d(${translateX}px, ${translateY}px, 0)`;
869
1398
  }
870
- static ɵfac = function OverlayComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || OverlayComponent)(i0.ɵɵdirectiveInject(EventDisplayService)); };
1399
+ static ɵfac = function OverlayComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || OverlayComponent)(i0.ɵɵdirectiveInject(EventDisplayService), i0.ɵɵdirectiveInject(i0.ElementRef)); };
871
1400
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: OverlayComponent, selectors: [["app-overlay"]], contentQueries: function OverlayComponent_ContentQueries(rf, ctx, dirIndex) { if (rf & 1) {
872
1401
  i0.ɵɵcontentQuery(dirIndex, _c0$c, 5);
873
1402
  } if (rf & 2) {
@@ -880,9 +1409,11 @@ class OverlayComponent {
880
1409
  let _t;
881
1410
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.overlayCard = _t.first);
882
1411
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.resizeHandleCorner = _t.first);
883
- } }, inputs: { overlayTitle: "overlayTitle", active: "active", icon: "icon", resizable: "resizable", transparentBody: "transparentBody", keepAspectRatioFixed: "keepAspectRatioFixed" }, standalone: false, ngContentSelectors: _c3$1, decls: 5, vars: 4, consts: [["overlayCard", ""], ["resizeHandleCorner", ""], ["cdkDrag", "", "cdkDragBoundary", "body", 1, "overlay-card", "card", 3, "hidden"], ["class", "card-header d-flex justify-content-between", "cdkDragHandle", "", 3, "ngClass", 4, "ngIf"], ["class", "overlay-card-content", 3, "ngClass", 4, "ngIf"], ["class", "resize-handle corner", "cdkDrag", "", 3, "hidden", "cdkDragMoved", 4, "ngIf"], ["cdkDragHandle", "", 1, "card-header", "d-flex", "justify-content-between", 3, "ngClass"], ["class", "align-self-center card-icon", 4, "ngIf"], [1, "align-self-center", "m-0", "px-2"], ["matTooltip", "Expand / Collapse", "matTooltipPosition", "right", 1, "align-self-center", "card-icon", "btn-blank", "expandCollapse", 3, "click", "ngClass"], ["href", "assets/icons/expand.svg#expand"], [1, "align-self-center", "card-icon"], [1, "overlay-card-content", 3, "ngClass"], ["cdkDrag", "", 1, "resize-handle", "corner", 3, "cdkDragMoved", "hidden"]], template: function OverlayComponent_Template(rf, ctx) { if (rf & 1) {
1412
+ } }, inputs: { overlayTitle: "overlayTitle", active: "active", icon: "icon", resizable: "resizable", transparentBody: "transparentBody", keepAspectRatioFixed: "keepAspectRatioFixed" }, standalone: false, ngContentSelectors: _c3$1, decls: 5, vars: 4, consts: [["overlayCard", ""], ["resizeHandleCorner", ""], ["cdkDrag", "", "cdkDragRootElement", ".cdk-overlay-pane", "cdkDragBoundary", "body", 1, "overlay-card", "card", 3, "mousedown", "touchstart", "hidden"], ["class", "card-header d-flex justify-content-between", "cdkDragHandle", "", 3, "ngClass", 4, "ngIf"], ["class", "overlay-card-content", 3, "ngClass", 4, "ngIf"], ["class", "resize-handle corner", "cdkDrag", "", 3, "hidden", "cdkDragMoved", 4, "ngIf"], ["cdkDragHandle", "", 1, "card-header", "d-flex", "justify-content-between", 3, "ngClass"], ["class", "align-self-center card-icon", 4, "ngIf"], [1, "align-self-center", "m-0", "px-2"], ["matTooltip", "Expand / Collapse", "matTooltipPosition", "right", 1, "align-self-center", "card-icon", "btn-blank", "expandCollapse", 3, "click", "ngClass"], ["href", "assets/icons/expand.svg#expand"], [1, "align-self-center", "card-icon"], [1, "overlay-card-content", 3, "ngClass"], ["cdkDrag", "", 1, "resize-handle", "corner", 3, "cdkDragMoved", "hidden"]], template: function OverlayComponent_Template(rf, ctx) { if (rf & 1) {
1413
+ const _r1 = i0.ɵɵgetCurrentView();
884
1414
  i0.ɵɵprojectionDef();
885
1415
  i0.ɵɵelementStart(0, "div", 2, 0);
1416
+ i0.ɵɵlistener("mousedown", function OverlayComponent_Template_div_mousedown_0_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.bringToFront()); })("touchstart", function OverlayComponent_Template_div_touchstart_0_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.bringToFront()); });
886
1417
  i0.ɵɵtemplate(2, OverlayComponent_div_2_Template, 7, 8, "div", 3)(3, OverlayComponent_div_3_Template, 2, 3, "div", 4)(4, OverlayComponent_span_4_Template, 2, 1, "span", 5);
887
1418
  i0.ɵɵelementEnd();
888
1419
  } if (rf & 2) {
@@ -893,12 +1424,12 @@ class OverlayComponent {
893
1424
  i0.ɵɵproperty("ngIf", ctx.showBody);
894
1425
  i0.ɵɵadvance();
895
1426
  i0.ɵɵproperty("ngIf", ctx.resizable);
896
- } }, dependencies: [i2.NgClass, i2.NgIf, i3$3.CdkDrag, i3$3.CdkDragHandle, i3$1.MatTooltip], styles: [".overlay-card.card{color:var(--phoenix-text-color);background:none;max-width:100vw;font-size:12px;min-width:300px;height:100%;box-shadow:none;border:none;position:relative;z-index:200}.overlay-card.card .card-header{width:12rem;background:var(--phoenix-background-color-secondary);box-shadow:var(--phoenix-box-shadow);padding:.4rem .8rem;border-radius:10px;border:none;cursor:move;text-align:center;transition:all .2s}.overlay-card.card .card-header.with-body{border-bottom-left-radius:0;border-bottom-right-radius:0}.overlay-card.card .card-header .card-icon{display:flex;height:.7rem;width:.7rem}.overlay-card.card .card-header h5{font-size:.9rem}.overlay-card.card .card-header .expandCollapse{cursor:pointer;transition:all .2s}.overlay-card.card .card-header .expandCollapse.expand{transform:scaleY(-1)}.overlay-card.card .card-header .expandCollapse svg{width:100%;height:100%}.overlay-card.card .overlay-card-content{background:var(--phoenix-background-color-secondary);box-shadow:var(--phoenix-box-shadow);border-radius:0 10px 10px;overflow:auto;height:100%}.overlay-card.card .overlay-card-content.transparent-bg{background:none}.overlay-card.card .list-group{color:var(--phoenix-text-color-secondary);background:var(--phoenix-background-color-secondary);max-height:40vh;overflow:auto}.overlay-card.card .list-group .list-group-item{background-color:var(--phoenix-background-color-secondary);border-color:var(--phoenix-border)}.overlay-card.card .resize-handle{width:1rem;height:1rem;cursor:nwse-resize;position:absolute}.overlay-card.card .resize-handle.corner{border-right:.2rem solid var(--phoenix-text-color);border-bottom:.2rem solid var(--phoenix-text-color)}\n"], encapsulation: 2 });
1427
+ } }, dependencies: [i2.NgClass, i2.NgIf, i3$2.CdkDrag, i3$2.CdkDragHandle, i3$1.MatTooltip], styles: [".overlay-card.card{color:var(--phoenix-text-color);background:none;max-width:95vw;font-size:12px;min-width:300px;height:auto;display:flex;flex-direction:column;box-shadow:none;border:none;position:relative;z-index:200}.overlay-card.card .card-header{width:12rem;background:var(--phoenix-background-color-secondary);box-shadow:var(--phoenix-box-shadow);padding:.4rem .8rem;border-radius:10px;border:none;cursor:move;text-align:center;transition:all .2s}.overlay-card.card .card-header.with-body{border-bottom-left-radius:0;border-bottom-right-radius:0}.overlay-card.card .card-header .card-icon{display:flex;height:.7rem;width:.7rem}.overlay-card.card .card-header h5{font-size:.9rem}.overlay-card.card .card-header .expandCollapse{cursor:pointer;transition:all .2s}.overlay-card.card .card-header .expandCollapse.expand{transform:scaleY(-1)}.overlay-card.card .card-header .expandCollapse svg{width:100%;height:100%}.overlay-card.card .overlay-card-content{background:var(--phoenix-background-color-secondary);box-shadow:var(--phoenix-box-shadow);border-radius:0 10px 10px;overflow:auto;height:100%;max-height:40vh}.overlay-card.card .overlay-card-content.transparent-bg{background:none}.overlay-card.card .list-group{color:var(--phoenix-text-color-secondary);background:var(--phoenix-background-color-secondary);max-height:40vh;overflow:auto}.overlay-card.card .list-group .list-group-item{background-color:var(--phoenix-background-color-secondary);border-color:var(--phoenix-border)}.overlay-card.card .resize-handle{width:1rem;height:1rem;cursor:nwse-resize;position:absolute}.overlay-card.card .resize-handle.corner{border-right:.2rem solid var(--phoenix-text-color);border-bottom:.2rem solid var(--phoenix-text-color)}\n"], encapsulation: 2 });
897
1428
  }
898
1429
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(OverlayComponent, [{
899
1430
  type: Component,
900
- args: [{ standalone: false, selector: 'app-overlay', encapsulation: ViewEncapsulation.None, template: "<div\n #overlayCard\n class=\"overlay-card card\"\n [hidden]=\"!active\"\n cdkDrag\n cdkDragBoundary=\"body\"\n>\n <div\n class=\"card-header d-flex justify-content-between\"\n [ngClass]=\"{ 'with-body': showBody }\"\n cdkDragHandle\n *ngIf=\"overlayTitle\"\n >\n <div class=\"align-self-center card-icon\" *ngIf=\"icon\">\n <svg>\n <use attr.href=\"assets/icons/{{ icon }}.svg#{{ icon }}\"></use>\n </svg>\n </div>\n <h5 class=\"align-self-center m-0 px-2\">{{ overlayTitle }}</h5>\n <button\n class=\"align-self-center card-icon btn-blank expandCollapse\"\n matTooltip=\"Expand / Collapse\"\n matTooltipPosition=\"right\"\n [ngClass]=\"{ expand: showBody }\"\n (click)=\"showBody = !showBody\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n </div>\n <div\n class=\"overlay-card-content\"\n *ngIf=\"showBody\"\n [ngClass]=\"{ 'transparent-bg': transparentBody }\"\n >\n <ng-content></ng-content>\n </div>\n <!-- Making the overlay resizable -->\n <span\n #resizeHandleCorner\n class=\"resize-handle corner\"\n *ngIf=\"resizable\"\n [hidden]=\"!showBody\"\n cdkDrag\n (cdkDragMoved)=\"onResize()\"\n ></span>\n</div>\n", styles: [".overlay-card.card{color:var(--phoenix-text-color);background:none;max-width:100vw;font-size:12px;min-width:300px;height:100%;box-shadow:none;border:none;position:relative;z-index:200}.overlay-card.card .card-header{width:12rem;background:var(--phoenix-background-color-secondary);box-shadow:var(--phoenix-box-shadow);padding:.4rem .8rem;border-radius:10px;border:none;cursor:move;text-align:center;transition:all .2s}.overlay-card.card .card-header.with-body{border-bottom-left-radius:0;border-bottom-right-radius:0}.overlay-card.card .card-header .card-icon{display:flex;height:.7rem;width:.7rem}.overlay-card.card .card-header h5{font-size:.9rem}.overlay-card.card .card-header .expandCollapse{cursor:pointer;transition:all .2s}.overlay-card.card .card-header .expandCollapse.expand{transform:scaleY(-1)}.overlay-card.card .card-header .expandCollapse svg{width:100%;height:100%}.overlay-card.card .overlay-card-content{background:var(--phoenix-background-color-secondary);box-shadow:var(--phoenix-box-shadow);border-radius:0 10px 10px;overflow:auto;height:100%}.overlay-card.card .overlay-card-content.transparent-bg{background:none}.overlay-card.card .list-group{color:var(--phoenix-text-color-secondary);background:var(--phoenix-background-color-secondary);max-height:40vh;overflow:auto}.overlay-card.card .list-group .list-group-item{background-color:var(--phoenix-background-color-secondary);border-color:var(--phoenix-border)}.overlay-card.card .resize-handle{width:1rem;height:1rem;cursor:nwse-resize;position:absolute}.overlay-card.card .resize-handle.corner{border-right:.2rem solid var(--phoenix-text-color);border-bottom:.2rem solid var(--phoenix-text-color)}\n"] }]
901
- }], () => [{ type: EventDisplayService }], { overlayTitle: [{
1431
+ args: [{ standalone: false, selector: 'app-overlay', encapsulation: ViewEncapsulation.None, template: "<div\n #overlayCard\n class=\"overlay-card card\"\n [hidden]=\"!active\"\n (mousedown)=\"bringToFront()\"\n (touchstart)=\"bringToFront()\"\n cdkDrag\n cdkDragRootElement=\".cdk-overlay-pane\"\n cdkDragBoundary=\"body\"\n>\n <div\n class=\"card-header d-flex justify-content-between\"\n [ngClass]=\"{ 'with-body': showBody }\"\n cdkDragHandle\n *ngIf=\"overlayTitle\"\n >\n <div class=\"align-self-center card-icon\" *ngIf=\"icon\">\n <svg>\n <use attr.href=\"assets/icons/{{ icon }}.svg#{{ icon }}\"></use>\n </svg>\n </div>\n <h5 class=\"align-self-center m-0 px-2\">{{ overlayTitle }}</h5>\n <button\n class=\"align-self-center card-icon btn-blank expandCollapse\"\n matTooltip=\"Expand / Collapse\"\n matTooltipPosition=\"right\"\n [ngClass]=\"{ expand: showBody }\"\n (click)=\"toggleBodyAndEmit()\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n </div>\n <div\n class=\"overlay-card-content\"\n *ngIf=\"showBody\"\n [ngClass]=\"{ 'transparent-bg': transparentBody }\"\n >\n <ng-content></ng-content>\n </div>\n <!-- Making the overlay resizable -->\n <span\n #resizeHandleCorner\n class=\"resize-handle corner\"\n *ngIf=\"resizable\"\n [hidden]=\"!showBody\"\n cdkDrag\n (cdkDragMoved)=\"onResize()\"\n ></span>\n</div>\n", styles: [".overlay-card.card{color:var(--phoenix-text-color);background:none;max-width:95vw;font-size:12px;min-width:300px;height:auto;display:flex;flex-direction:column;box-shadow:none;border:none;position:relative;z-index:200}.overlay-card.card .card-header{width:12rem;background:var(--phoenix-background-color-secondary);box-shadow:var(--phoenix-box-shadow);padding:.4rem .8rem;border-radius:10px;border:none;cursor:move;text-align:center;transition:all .2s}.overlay-card.card .card-header.with-body{border-bottom-left-radius:0;border-bottom-right-radius:0}.overlay-card.card .card-header .card-icon{display:flex;height:.7rem;width:.7rem}.overlay-card.card .card-header h5{font-size:.9rem}.overlay-card.card .card-header .expandCollapse{cursor:pointer;transition:all .2s}.overlay-card.card .card-header .expandCollapse.expand{transform:scaleY(-1)}.overlay-card.card .card-header .expandCollapse svg{width:100%;height:100%}.overlay-card.card .overlay-card-content{background:var(--phoenix-background-color-secondary);box-shadow:var(--phoenix-box-shadow);border-radius:0 10px 10px;overflow:auto;height:100%;max-height:40vh}.overlay-card.card .overlay-card-content.transparent-bg{background:none}.overlay-card.card .list-group{color:var(--phoenix-text-color-secondary);background:var(--phoenix-background-color-secondary);max-height:40vh;overflow:auto}.overlay-card.card .list-group .list-group-item{background-color:var(--phoenix-background-color-secondary);border-color:var(--phoenix-border)}.overlay-card.card .resize-handle{width:1rem;height:1rem;cursor:nwse-resize;position:absolute}.overlay-card.card .resize-handle.corner{border-right:.2rem solid var(--phoenix-text-color);border-bottom:.2rem solid var(--phoenix-text-color)}\n"] }]
1432
+ }], () => [{ type: EventDisplayService }, { type: i0.ElementRef }], { overlayTitle: [{
902
1433
  type: Input
903
1434
  }], active: [{
904
1435
  type: Input
@@ -920,7 +1451,7 @@ class OverlayComponent {
920
1451
  type: ContentChild,
921
1452
  args: ['overlayWindow']
922
1453
  }] }); })();
923
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(OverlayComponent, { className: "OverlayComponent", filePath: "components/ui-menu/overlay/overlay.component.ts", lineNumber: 25 }); })();
1454
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(OverlayComponent, { className: "OverlayComponent", filePath: "components/ui-menu/overlay/overlay.component.ts", lineNumber: 28 }); })();
924
1455
 
925
1456
  const _c0$b = (a0, a1, a2) => ({ "active-object": a0, "is-cut": a1, "d-none": a2 });
926
1457
  function CollectionsInfoOverlayComponent_div_1_div_1_optgroup_10_option_1_Template(rf, ctx) { if (rf & 1) {
@@ -1024,7 +1555,7 @@ function CollectionsInfoOverlayComponent_div_1_table_3_tr_11_td_13_Template(rf,
1024
1555
  const ctx_r1 = i0.ɵɵnextContext(3);
1025
1556
  i0.ɵɵattribute("data-label", ctx_r1.getPrettySymbol(column_r13));
1026
1557
  i0.ɵɵadvance();
1027
- i0.ɵɵtextInterpolate1(" ", object_r10[column_r13], " ");
1558
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.formatValue(object_r10[column_r13]), " ");
1028
1559
  } }
1029
1560
  function CollectionsInfoOverlayComponent_div_1_table_3_tr_11_Template(rf, ctx) { if (rf & 1) {
1030
1561
  const _r8 = i0.ɵɵgetCurrentView();
@@ -1114,6 +1645,7 @@ class CollectionsInfoOverlayComponent {
1114
1645
  'labelText',
1115
1646
  '_instanceId',
1116
1647
  '_position',
1648
+ 'index',
1117
1649
  ];
1118
1650
  hideInvisible;
1119
1651
  collections;
@@ -1128,13 +1660,17 @@ class CollectionsInfoOverlayComponent {
1128
1660
  this.eventDisplay = eventDisplay;
1129
1661
  }
1130
1662
  ngOnInit() {
1131
- this.unsubscribes.push(this.eventDisplay.listenToDisplayedEventChange(() => {
1663
+ const updateCollections = () => {
1132
1664
  const collectionsGrouped = this.eventDisplay.getCollections();
1133
- this.collections = Object.entries(collectionsGrouped).map(([type, collections]) => ({
1134
- type,
1135
- collections,
1136
- }));
1137
- }));
1665
+ if (collectionsGrouped) {
1666
+ this.collections = Object.entries(collectionsGrouped).map(([type, collections]) => ({
1667
+ type,
1668
+ collections,
1669
+ }));
1670
+ }
1671
+ };
1672
+ updateCollections();
1673
+ this.unsubscribes.push(this.eventDisplay.listenToDisplayedEventChange(() => updateCollections()));
1138
1674
  this.activeObject = this.eventDisplay.getActiveObjectId();
1139
1675
  this.unsubscribes.push(this.activeObject.onUpdate((value) => {
1140
1676
  if (document.getElementById(value)) {
@@ -1204,6 +1740,26 @@ class CollectionsInfoOverlayComponent {
1204
1740
  toggleInvisible(checked) {
1205
1741
  this.hideInvisible = checked;
1206
1742
  }
1743
+ formatValue(value) {
1744
+ if (typeof value === 'number') {
1745
+ return Number.isInteger(value) ? String(value) : value.toFixed(2);
1746
+ }
1747
+ if (Array.isArray(value)) {
1748
+ // Handles nested arrays too, e.g. track `pos`: an array of [x, y, z]
1749
+ // points, so each element is itself an array, not a number.
1750
+ return `[${value.map((v) => this.formatValue(v)).join(', ')}]`;
1751
+ }
1752
+ // Vector-like objects (e.g. THREE.Vector3), which otherwise stringify
1753
+ // to the unhelpful "[object Object]".
1754
+ if (value &&
1755
+ typeof value === 'object' &&
1756
+ typeof value.x === 'number' &&
1757
+ typeof value.y === 'number') {
1758
+ const parts = [value.x, value.y, value.z].filter((v) => typeof v === 'number');
1759
+ return `[${parts.map((v) => this.formatValue(v)).join(', ')}]`;
1760
+ }
1761
+ return String(value);
1762
+ }
1207
1763
  addLabel(index, uuid) {
1208
1764
  const labelValue = this.elementRef.nativeElement.querySelector(`#label${index}`).value;
1209
1765
  if (this.selectedCollection) {
@@ -1219,7 +1775,7 @@ class CollectionsInfoOverlayComponent {
1219
1775
  .getObjectByName(SceneManager.EVENT_DATA_ID);
1220
1776
  }
1221
1777
  static ɵfac = function CollectionsInfoOverlayComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || CollectionsInfoOverlayComponent)(i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(EventDisplayService)); };
1222
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: CollectionsInfoOverlayComponent, selectors: [["app-collections-info-overlay"]], inputs: { showObjectsInfo: "showObjectsInfo", excludedColumns: "excludedColumns" }, standalone: false, decls: 2, vars: 3, consts: [["overlayTitle", "Collections Info", "icon", "eventData", 3, "resizable", "active"], ["class", "collectionsInfo m-2", 4, "ngIf"], [1, "collectionsInfo", "m-2"], ["class", "collectionSelector mb-2 d-flex align-items-center", 4, "ngIf"], [1, "boxBody", "table-responsive"], ["id", "collectionTable", "class", "table table-borderless table-sm", 4, "ngIf"], ["class", "emptyBox", 4, "ngIf"], [1, "collectionSelector", "mb-2", "d-flex", "align-items-center"], [1, "d-flex", "align-items-center"], [1, "eventSelector"], ["for", "event"], ["id", "collection", "name", "collection", 3, "change"], ["value", "", "selected", "", "disabled", "", "hidden", ""], [3, "label", 4, "ngFor", "ngForOf"], [3, "change", 4, "ngIf"], [3, "label"], [3, "value", 4, "ngFor", "ngForOf"], [3, "value"], [3, "change"], ["id", "collectionTable", 1, "table", "table-borderless", "table-sm"], [4, "ngFor", "ngForOf"], [3, "ngClass", 4, "ngFor", "ngForOf"], [1, "head-wrapper"], [1, "sort-options"], [1, "icon-wrapper", "icon-button", "btn-blank", "up", 3, "click"], ["href", "assets/icons/expand.svg#expand"], [1, "icon-wrapper", "icon-button", "btn-blank", 3, "click"], [3, "ngClass"], ["data-label", "No."], ["data-label", "Selection"], ["class", "row justify-content-center icon-wrapper", 4, "ngIf"], ["data-label", "Label"], [1, "row", "m-0", "add-label"], [1, "col-10", "p-0"], ["type", "text", 1, "form-control", "form-control-sm", 3, "value", "id"], [1, "col-2", "p-0", "pl-2", "text-center", "icon-wrapper"], ["matTooltip", "Add, update or remove object label", 1, "object-select", "btn-blank", 3, "click"], ["href", "assets/icons/update.svg#update"], [1, "row", "justify-content-center", "icon-wrapper"], ["matTooltip", "Move camera to object", 1, "object-select", "btn-blank", 3, "click"], ["href", "assets/icons/views.svg#views"], ["matTooltip", "Highlight object", 1, "object-select", "btn-blank", 3, "click"], ["href", "assets/icons/cursor.svg#cursor"], [1, "emptyBox"]], template: function CollectionsInfoOverlayComponent_Template(rf, ctx) { if (rf & 1) {
1778
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: CollectionsInfoOverlayComponent, selectors: [["app-collections-info-overlay"]], inputs: { showObjectsInfo: "showObjectsInfo", excludedColumns: "excludedColumns" }, standalone: false, decls: 2, vars: 3, consts: [["overlayTitle", "Collections Info", "icon", "eventData", "accordionId", "right-dock", 3, "resizable", "active"], ["class", "collectionsInfo m-2", 4, "ngIf"], [1, "collectionsInfo", "m-2"], ["class", "collectionSelector mb-2 d-flex align-items-center", 4, "ngIf"], [1, "boxBody", "table-responsive"], ["id", "collectionTable", "class", "table table-borderless table-sm", 4, "ngIf"], ["class", "emptyBox", 4, "ngIf"], [1, "collectionSelector", "mb-2", "d-flex", "align-items-center"], [1, "d-flex", "align-items-center"], [1, "eventSelector"], ["for", "event"], ["id", "collection", "name", "collection", 3, "change"], ["value", "", "selected", "", "disabled", "", "hidden", ""], [3, "label", 4, "ngFor", "ngForOf"], [3, "change", 4, "ngIf"], [3, "label"], [3, "value", 4, "ngFor", "ngForOf"], [3, "value"], [3, "change"], ["id", "collectionTable", 1, "table", "table-borderless", "table-sm"], [4, "ngFor", "ngForOf"], [3, "ngClass", 4, "ngFor", "ngForOf"], [1, "head-wrapper"], [1, "sort-options"], [1, "icon-wrapper", "icon-button", "btn-blank", "up", 3, "click"], ["href", "assets/icons/expand.svg#expand"], [1, "icon-wrapper", "icon-button", "btn-blank", 3, "click"], [3, "ngClass"], ["data-label", "No."], ["data-label", "Selection"], ["class", "row justify-content-center icon-wrapper", 4, "ngIf"], ["data-label", "Label"], [1, "row", "m-0", "add-label"], [1, "col-10", "p-0"], ["type", "text", 1, "form-control", "form-control-sm", 3, "value", "id"], [1, "col-2", "p-0", "pl-2", "text-center", "icon-wrapper"], ["matTooltip", "Add, update or remove object label", 1, "object-select", "btn-blank", 3, "click"], ["href", "assets/icons/update.svg#update"], [1, "row", "justify-content-center", "icon-wrapper"], ["matTooltip", "Move camera to object", 1, "object-select", "btn-blank", 3, "click"], ["href", "assets/icons/views.svg#views"], ["matTooltip", "Highlight object", 1, "object-select", "btn-blank", 3, "click"], ["href", "assets/icons/cursor.svg#cursor"], [1, "emptyBox"]], template: function CollectionsInfoOverlayComponent_Template(rf, ctx) { if (rf & 1) {
1223
1779
  i0.ɵɵelementStart(0, "app-overlay", 0);
1224
1780
  i0.ɵɵtemplate(1, CollectionsInfoOverlayComponent_div_1_Template, 5, 3, "div", 1);
1225
1781
  i0.ɵɵelementEnd();
@@ -1227,11 +1783,11 @@ class CollectionsInfoOverlayComponent {
1227
1783
  i0.ɵɵproperty("resizable", true)("active", ctx.showObjectsInfo);
1228
1784
  i0.ɵɵadvance();
1229
1785
  i0.ɵɵproperty("ngIf", ctx.showObjectsInfo);
1230
- } }, dependencies: [i2.NgClass, i2.NgForOf, i2.NgIf, i3$1.MatTooltip, i4.NgSelectOption, i4.ɵNgSelectMultipleOption, i5.MatCheckbox, OverlayComponent], styles: ["@media screen and (max-width: 400px){.collectionsInfo[_ngcontent-%COMP%] table[_ngcontent-%COMP%], .collectionsInfo[_ngcontent-%COMP%] thead[_ngcontent-%COMP%], .collectionsInfo[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%], .collectionsInfo[_ngcontent-%COMP%] th[_ngcontent-%COMP%], .collectionsInfo[_ngcontent-%COMP%] td[_ngcontent-%COMP%], .collectionsInfo[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]{display:block}.collectionsInfo[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]{position:absolute;top:-9999px;left:-9999px}.collectionsInfo[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]{margin:0 0 1rem}.collectionsInfo[_ngcontent-%COMP%] td[_ngcontent-%COMP%]{border:none;position:relative;padding-left:50%;text-align:left}.collectionsInfo[_ngcontent-%COMP%] td[_ngcontent-%COMP%]:before{position:absolute;top:0;left:0;width:45%;padding-right:10px;white-space:nowrap;content:attr(data-label);font-weight:700}}.collectionsInfo[_ngcontent-%COMP%]{height:95%}.collectionsInfo[_ngcontent-%COMP%] .collectionSelector[_ngcontent-%COMP%]{height:15%;min-height:2rem}.collectionsInfo[_ngcontent-%COMP%] .collectionSelector[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{color:var(--phoenix-text-color-secondary);margin-right:1rem}.collectionsInfo[_ngcontent-%COMP%] .collectionSelector[_ngcontent-%COMP%] mat-checkbox[_ngcontent-%COMP%]{margin-left:1rem}.boxBody[_ngcontent-%COMP%]{height:85%;overflow:scroll}.boxBody[_ngcontent-%COMP%] p.emptyBox[_ngcontent-%COMP%]{max-width:21em}.boxBody[_ngcontent-%COMP%] .add-label[_ngcontent-%COMP%]{min-width:8rem}.boxBody[_ngcontent-%COMP%] .add-label[_ngcontent-%COMP%] input[_ngcontent-%COMP%]{padding:.1rem .3rem;font-size:.75rem}#collectionTable[_ngcontent-%COMP%]{position:relative;color:var(--phoenix-text-color-secondary)}#collectionTable[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%]{position:sticky;top:0;z-index:100;background:var(--phoenix-background-color-secondary)}#collectionTable[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%]{display:flex;align-items:center}#collectionTable[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%] .sort-options[_ngcontent-%COMP%]{display:flex;flex-direction:row}#collectionTable[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%] .sort-options[_ngcontent-%COMP%] .icon-wrapper[_ngcontent-%COMP%]{display:flex;width:1rem;height:1rem;padding:.2rem}#collectionTable[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%] .sort-options[_ngcontent-%COMP%] .icon-wrapper.up[_ngcontent-%COMP%]{transform:rotate(180deg)}#collectionTable[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%] .sort-options[_ngcontent-%COMP%] .icon-wrapper[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{width:100%;height:100%}#collectionTable[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] *[_ngcontent-%COMP%]{padding-right:1.2rem}#collectionTable[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] *[_ngcontent-%COMP%]:last-child{padding-right:0}#collectionTable[_ngcontent-%COMP%] tr.is-cut[_ngcontent-%COMP%]{opacity:.5}#collectionTable[_ngcontent-%COMP%] tr.active-object[_ngcontent-%COMP%]{color:var(--phoenix-background-color);background:var(--phoenix-text-color);box-shadow:0 0 15px var(--phoenix-text-color)}#collectionTable[_ngcontent-%COMP%] tr.active-object[_ngcontent-%COMP%] div.icon-wrapper[_ngcontent-%COMP%]{--phoenix-options-icon-path: var(--phoenix-background-color);--phoenix-options-icon-bg: var(--phoenix-text-color-hover)}#collectionTable[_ngcontent-%COMP%] tr.active-object[_ngcontent-%COMP%] .add-label[_ngcontent-%COMP%] input[_ngcontent-%COMP%]{--phoenix-text-color: var(--phoenix-background-color);--phoenix-background-color-tertiary: var( --phoenix-text-color-secondary );--phoenix-border: var(--phoenix-background-color)}#collectionTable[_ngcontent-%COMP%] td[_ngcontent-%COMP%]{min-height:20px}#collectionTable[_ngcontent-%COMP%] td[_ngcontent-%COMP%] .object-select[_ngcontent-%COMP%]{position:relative;width:1.6rem;height:1.6rem;margin-right:.4em;text-align:center;background-color:var(--phoenix-options-icon-bg);border-radius:10px;cursor:pointer}#collectionTable[_ngcontent-%COMP%] td[_ngcontent-%COMP%] .object-select[_ngcontent-%COMP%]:last-child{margin-right:0}#collectionTable[_ngcontent-%COMP%] td[_ngcontent-%COMP%] .object-select[_ngcontent-%COMP%]:hover{border:1px solid var(--phoenix-options-icon-path)}#collectionTable[_ngcontent-%COMP%] td[_ngcontent-%COMP%] .object-select[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{position:absolute;top:0;left:0;padding:.4rem;width:100%;height:100%;vertical-align:top}.eventSelector[_ngcontent-%COMP%]{display:flex}.eventSelector[_ngcontent-%COMP%] label[_ngcontent-%COMP%]{display:none;color:var(--phoenix-text-color-secondary)}.eventSelector[_ngcontent-%COMP%] select[_ngcontent-%COMP%]{width:9rem;padding:5px 10px;font-size:12px;border:1px solid rgba(88,88,88,.08);box-shadow:var(--phoenix-icon-shadow);background-color:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary)}"] });
1786
+ } }, dependencies: [i2.NgClass, i2.NgForOf, i2.NgIf, i3$1.MatTooltip, i4.NgSelectOption, i4.ɵNgSelectMultipleOption, i5.MatCheckbox, OverlayComponent], styles: ["@media screen and (max-width:400px){.collectionsInfo[_ngcontent-%COMP%] table[_ngcontent-%COMP%], .collectionsInfo[_ngcontent-%COMP%] thead[_ngcontent-%COMP%], .collectionsInfo[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%], .collectionsInfo[_ngcontent-%COMP%] th[_ngcontent-%COMP%], .collectionsInfo[_ngcontent-%COMP%] td[_ngcontent-%COMP%], .collectionsInfo[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]{display:block}.collectionsInfo[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]{position:absolute;top:-9999px;left:-9999px}.collectionsInfo[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]{margin:0 0 1rem}.collectionsInfo[_ngcontent-%COMP%] td[_ngcontent-%COMP%]{border:none;position:relative;padding-left:50%;text-align:left}.collectionsInfo[_ngcontent-%COMP%] td[_ngcontent-%COMP%]:before{position:absolute;top:0;left:0;width:45%;padding-right:10px;white-space:nowrap;content:attr(data-label);font-weight:700}}.collectionsInfo[_ngcontent-%COMP%]{height:95%}.collectionsInfo[_ngcontent-%COMP%] .collectionSelector[_ngcontent-%COMP%]{height:15%;min-height:2rem}.collectionsInfo[_ngcontent-%COMP%] .collectionSelector[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{color:var(--phoenix-text-color-secondary);margin-right:1rem}.collectionsInfo[_ngcontent-%COMP%] .collectionSelector[_ngcontent-%COMP%] mat-checkbox[_ngcontent-%COMP%]{margin-left:1rem}.boxBody[_ngcontent-%COMP%]{height:85%;overflow:scroll}.boxBody[_ngcontent-%COMP%] p.emptyBox[_ngcontent-%COMP%]{max-width:21em}.boxBody[_ngcontent-%COMP%] .add-label[_ngcontent-%COMP%]{min-width:8rem}.boxBody[_ngcontent-%COMP%] .add-label[_ngcontent-%COMP%] input[_ngcontent-%COMP%]{padding:.1rem .3rem;font-size:.75rem}#collectionTable[_ngcontent-%COMP%]{position:relative;color:var(--phoenix-text-color-secondary)}#collectionTable[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%]{position:sticky;top:0;z-index:100;background:var(--phoenix-background-color-secondary)}#collectionTable[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%]{display:flex;align-items:center}#collectionTable[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%] .sort-options[_ngcontent-%COMP%]{display:flex;flex-direction:row}#collectionTable[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%] .sort-options[_ngcontent-%COMP%] .icon-wrapper[_ngcontent-%COMP%]{display:flex;width:1rem;height:1rem;padding:.2rem}#collectionTable[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%] .sort-options[_ngcontent-%COMP%] .icon-wrapper.up[_ngcontent-%COMP%]{transform:rotate(180deg)}#collectionTable[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%] .sort-options[_ngcontent-%COMP%] .icon-wrapper[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{width:100%;height:100%}#collectionTable[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] *[_ngcontent-%COMP%]{padding-right:1.2rem}#collectionTable[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] *[_ngcontent-%COMP%]:last-child{padding-right:0}#collectionTable[_ngcontent-%COMP%] tr.is-cut[_ngcontent-%COMP%]{opacity:.5}#collectionTable[_ngcontent-%COMP%] tr.active-object[_ngcontent-%COMP%]{color:var(--phoenix-background-color);background:var(--phoenix-text-color);box-shadow:0 0 15px var(--phoenix-text-color)}#collectionTable[_ngcontent-%COMP%] tr.active-object[_ngcontent-%COMP%] div.icon-wrapper[_ngcontent-%COMP%]{--phoenix-options-icon-path: var(--phoenix-background-color);--phoenix-options-icon-bg: var(--phoenix-text-color-hover)}#collectionTable[_ngcontent-%COMP%] tr.active-object[_ngcontent-%COMP%] .add-label[_ngcontent-%COMP%] input[_ngcontent-%COMP%]{--phoenix-text-color: var(--phoenix-background-color);--phoenix-background-color-tertiary: var( --phoenix-text-color-secondary );--phoenix-border: var(--phoenix-background-color)}#collectionTable[_ngcontent-%COMP%] td[_ngcontent-%COMP%]{min-height:20px}#collectionTable[_ngcontent-%COMP%] td[_ngcontent-%COMP%] .object-select[_ngcontent-%COMP%]{position:relative;width:1.6rem;height:1.6rem;margin-right:.4em;text-align:center;background-color:var(--phoenix-options-icon-bg);border-radius:10px;cursor:pointer}#collectionTable[_ngcontent-%COMP%] td[_ngcontent-%COMP%] .object-select[_ngcontent-%COMP%]:last-child{margin-right:0}#collectionTable[_ngcontent-%COMP%] td[_ngcontent-%COMP%] .object-select[_ngcontent-%COMP%]:hover{border:1px solid var(--phoenix-options-icon-path)}#collectionTable[_ngcontent-%COMP%] td[_ngcontent-%COMP%] .object-select[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{position:absolute;top:0;left:0;padding:.4rem;width:100%;height:100%;vertical-align:top}.eventSelector[_ngcontent-%COMP%]{display:flex}.eventSelector[_ngcontent-%COMP%] label[_ngcontent-%COMP%]{display:none;color:var(--phoenix-text-color-secondary)}.eventSelector[_ngcontent-%COMP%] select[_ngcontent-%COMP%]{width:9rem;padding:5px 10px;font-size:12px;border:1px solid rgba(88,88,88,.08);box-shadow:var(--phoenix-icon-shadow);background-color:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary)}"] });
1231
1787
  }
1232
1788
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(CollectionsInfoOverlayComponent, [{
1233
1789
  type: Component,
1234
- args: [{ standalone: false, selector: 'app-collections-info-overlay', template: "<!-- Show objects -->\n<app-overlay\n overlayTitle=\"Collections Info\"\n icon=\"eventData\"\n [resizable]=\"true\"\n [active]=\"showObjectsInfo\"\n>\n <!-- Using ngIf to remove it from DOM since this panel requires heavy computing -->\n <div *ngIf=\"showObjectsInfo\" class=\"collectionsInfo m-2\">\n <div\n class=\"collectionSelector mb-2 d-flex align-items-center\"\n *ngIf=\"collections != null\"\n >\n <div class=\"d-flex align-items-center\">\n <span>Choose a collection: </span>\n <div class=\"eventSelector\">\n <label for=\"event\">Event</label>\n <select\n id=\"collection\"\n name=\"collection\"\n (change)=\"changeCollection($event.target.value)\"\n >\n <option value=\"\" selected disabled hidden>Choose Collection</option>\n <optgroup *ngFor=\"let group of collections\" [label]=\"group.type\">\n <option\n *ngFor=\"let collection of group.collections\"\n [value]=\"collection\"\n >\n {{ collection }}\n </option>\n </optgroup>\n </select>\n </div>\n <mat-checkbox\n *ngIf=\"showingCollection\"\n (change)=\"toggleInvisible($event.checked)\"\n >\n Hide Invisible Objects\n </mat-checkbox>\n </div>\n </div>\n\n <div class=\"boxBody table-responsive\">\n <table\n id=\"collectionTable\"\n class=\"table table-borderless table-sm\"\n *ngIf=\"showingCollection\"\n >\n <thead>\n <tr>\n <th>No.</th>\n <th>Selection</th>\n <th>Label</th>\n <th *ngFor=\"let column of collectionColumns\">\n <div class=\"head-wrapper\">\n <div>\n {{ getPrettySymbol(column) }}\n </div>\n <div class=\"sort-options\">\n <button\n class=\"icon-wrapper icon-button btn-blank up\"\n (click)=\"sort(column, 'dec')\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n <button\n class=\"icon-wrapper icon-button btn-blank\"\n (click)=\"sort(column, 'asc')\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n </div>\n </div>\n </th>\n </tr>\n </thead>\n <tbody>\n <tr\n *ngFor=\"let object of showingCollection; index as i\"\n [attr.id]=\"object.uuid\"\n [ngClass]=\"{\n 'active-object':\n activeObject && activeObject.value === object.uuid,\n 'is-cut': object.isCut,\n 'd-none': object.isCut && hideInvisible,\n }\"\n >\n <td data-label=\"No.\">#{{ i }}</td>\n <td data-label=\"Selection\">\n <div\n *ngIf=\"object.uuid\"\n class=\"row justify-content-center icon-wrapper\"\n >\n <button\n class=\"object-select btn-blank\"\n matTooltip=\"Move camera to object\"\n (click)=\"lookAtObject(object.uuid)\"\n >\n <svg>\n <use href=\"assets/icons/views.svg#views\"></use>\n </svg>\n </button>\n <button\n class=\"object-select btn-blank\"\n matTooltip=\"Highlight object\"\n (click)=\"highlightObject(object.uuid)\"\n >\n <svg>\n <use href=\"assets/icons/cursor.svg#cursor\"></use>\n </svg>\n </button>\n </div>\n </td>\n <td data-label=\"Label\">\n <div class=\"row m-0 add-label\">\n <div class=\"col-10 p-0\">\n <input\n class=\"form-control form-control-sm\"\n type=\"text\"\n value=\"{{ object.labelText }}\"\n [id]=\"'label' + i\"\n />\n </div>\n <div class=\"col-2 p-0 pl-2 text-center icon-wrapper\">\n <button\n class=\"object-select btn-blank\"\n matTooltip=\"Add, update or remove object label\"\n (click)=\"addLabel(i, object.uuid)\"\n >\n <svg>\n <use href=\"assets/icons/update.svg#update\"></use>\n </svg>\n </button>\n </div>\n </div>\n </td>\n <td\n *ngFor=\"let column of collectionColumns\"\n [attr.data-label]=\"getPrettySymbol(column)\"\n >\n {{ object[column] }}\n </td>\n </tr>\n </tbody>\n </table>\n <p class=\"emptyBox\" *ngIf=\"!collections\">\n Load event data to be able to display information about the collections\n here.\n </p>\n </div>\n </div>\n</app-overlay>\n", styles: ["@media screen and (max-width: 400px){.collectionsInfo table,.collectionsInfo thead,.collectionsInfo tbody,.collectionsInfo th,.collectionsInfo td,.collectionsInfo tr{display:block}.collectionsInfo thead tr{position:absolute;top:-9999px;left:-9999px}.collectionsInfo tr{margin:0 0 1rem}.collectionsInfo td{border:none;position:relative;padding-left:50%;text-align:left}.collectionsInfo td:before{position:absolute;top:0;left:0;width:45%;padding-right:10px;white-space:nowrap;content:attr(data-label);font-weight:700}}.collectionsInfo{height:95%}.collectionsInfo .collectionSelector{height:15%;min-height:2rem}.collectionsInfo .collectionSelector span{color:var(--phoenix-text-color-secondary);margin-right:1rem}.collectionsInfo .collectionSelector mat-checkbox{margin-left:1rem}.boxBody{height:85%;overflow:scroll}.boxBody p.emptyBox{max-width:21em}.boxBody .add-label{min-width:8rem}.boxBody .add-label input{padding:.1rem .3rem;font-size:.75rem}#collectionTable{position:relative;color:var(--phoenix-text-color-secondary)}#collectionTable thead tr th{position:sticky;top:0;z-index:100;background:var(--phoenix-background-color-secondary)}#collectionTable thead tr th .head-wrapper{display:flex;align-items:center}#collectionTable thead tr th .head-wrapper .sort-options{display:flex;flex-direction:row}#collectionTable thead tr th .head-wrapper .sort-options .icon-wrapper{display:flex;width:1rem;height:1rem;padding:.2rem}#collectionTable thead tr th .head-wrapper .sort-options .icon-wrapper.up{transform:rotate(180deg)}#collectionTable thead tr th .head-wrapper .sort-options .icon-wrapper svg{width:100%;height:100%}#collectionTable tr *{padding-right:1.2rem}#collectionTable tr *:last-child{padding-right:0}#collectionTable tr.is-cut{opacity:.5}#collectionTable tr.active-object{color:var(--phoenix-background-color);background:var(--phoenix-text-color);box-shadow:0 0 15px var(--phoenix-text-color)}#collectionTable tr.active-object div.icon-wrapper{--phoenix-options-icon-path: var(--phoenix-background-color);--phoenix-options-icon-bg: var(--phoenix-text-color-hover)}#collectionTable tr.active-object .add-label input{--phoenix-text-color: var(--phoenix-background-color);--phoenix-background-color-tertiary: var( --phoenix-text-color-secondary );--phoenix-border: var(--phoenix-background-color)}#collectionTable td{min-height:20px}#collectionTable td .object-select{position:relative;width:1.6rem;height:1.6rem;margin-right:.4em;text-align:center;background-color:var(--phoenix-options-icon-bg);border-radius:10px;cursor:pointer}#collectionTable td .object-select:last-child{margin-right:0}#collectionTable td .object-select:hover{border:1px solid var(--phoenix-options-icon-path)}#collectionTable td .object-select svg{position:absolute;top:0;left:0;padding:.4rem;width:100%;height:100%;vertical-align:top}.eventSelector{display:flex}.eventSelector label{display:none;color:var(--phoenix-text-color-secondary)}.eventSelector select{width:9rem;padding:5px 10px;font-size:12px;border:1px solid rgba(88,88,88,.08);box-shadow:var(--phoenix-icon-shadow);background-color:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary)}\n"] }]
1790
+ args: [{ standalone: false, selector: 'app-collections-info-overlay', template: "<!-- Show objects -->\n<app-overlay\n overlayTitle=\"Collections Info\"\n icon=\"eventData\"\n [resizable]=\"true\"\n [active]=\"showObjectsInfo\"\n accordionId=\"right-dock\"\n>\n <!-- Using ngIf to remove it from DOM since this panel requires heavy computing -->\n <div *ngIf=\"showObjectsInfo\" class=\"collectionsInfo m-2\">\n <div\n class=\"collectionSelector mb-2 d-flex align-items-center\"\n *ngIf=\"collections != null\"\n >\n <div class=\"d-flex align-items-center\">\n <span>Choose a collection: </span>\n <div class=\"eventSelector\">\n <label for=\"event\">Event</label>\n <select\n id=\"collection\"\n name=\"collection\"\n (change)=\"changeCollection($event.target.value)\"\n >\n <option value=\"\" selected disabled hidden>Choose Collection</option>\n <optgroup *ngFor=\"let group of collections\" [label]=\"group.type\">\n <option\n *ngFor=\"let collection of group.collections\"\n [value]=\"collection\"\n >\n {{ collection }}\n </option>\n </optgroup>\n </select>\n </div>\n <mat-checkbox\n *ngIf=\"showingCollection\"\n (change)=\"toggleInvisible($event.checked)\"\n >\n Hide Invisible Objects\n </mat-checkbox>\n </div>\n </div>\n\n <div class=\"boxBody table-responsive\">\n <table\n id=\"collectionTable\"\n class=\"table table-borderless table-sm\"\n *ngIf=\"showingCollection\"\n >\n <thead>\n <tr>\n <th>No.</th>\n <th>Selection</th>\n <th>Label</th>\n <th *ngFor=\"let column of collectionColumns\">\n <div class=\"head-wrapper\">\n <div>\n {{ getPrettySymbol(column) }}\n </div>\n <div class=\"sort-options\">\n <button\n class=\"icon-wrapper icon-button btn-blank up\"\n (click)=\"sort(column, 'dec')\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n <button\n class=\"icon-wrapper icon-button btn-blank\"\n (click)=\"sort(column, 'asc')\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n </div>\n </div>\n </th>\n </tr>\n </thead>\n <tbody>\n <tr\n *ngFor=\"let object of showingCollection; index as i\"\n [attr.id]=\"object.uuid\"\n [ngClass]=\"{\n 'active-object':\n activeObject && activeObject.value === object.uuid,\n 'is-cut': object.isCut,\n 'd-none': object.isCut && hideInvisible,\n }\"\n >\n <td data-label=\"No.\">#{{ i }}</td>\n <td data-label=\"Selection\">\n <div\n *ngIf=\"object.uuid\"\n class=\"row justify-content-center icon-wrapper\"\n >\n <button\n class=\"object-select btn-blank\"\n matTooltip=\"Move camera to object\"\n (click)=\"lookAtObject(object.uuid)\"\n >\n <svg>\n <use href=\"assets/icons/views.svg#views\"></use>\n </svg>\n </button>\n <button\n class=\"object-select btn-blank\"\n matTooltip=\"Highlight object\"\n (click)=\"highlightObject(object.uuid)\"\n >\n <svg>\n <use href=\"assets/icons/cursor.svg#cursor\"></use>\n </svg>\n </button>\n </div>\n </td>\n <td data-label=\"Label\">\n <div class=\"row m-0 add-label\">\n <div class=\"col-10 p-0\">\n <input\n class=\"form-control form-control-sm\"\n type=\"text\"\n value=\"{{ object.labelText }}\"\n [id]=\"'label' + i\"\n />\n </div>\n <div class=\"col-2 p-0 pl-2 text-center icon-wrapper\">\n <button\n class=\"object-select btn-blank\"\n matTooltip=\"Add, update or remove object label\"\n (click)=\"addLabel(i, object.uuid)\"\n >\n <svg>\n <use href=\"assets/icons/update.svg#update\"></use>\n </svg>\n </button>\n </div>\n </div>\n </td>\n <td\n *ngFor=\"let column of collectionColumns\"\n [attr.data-label]=\"getPrettySymbol(column)\"\n >\n {{ formatValue(object[column]) }}\n </td>\n </tr>\n </tbody>\n </table>\n <p class=\"emptyBox\" *ngIf=\"!collections\">\n Load event data to be able to display information about the collections\n here.\n </p>\n </div>\n </div>\n</app-overlay>\n", styles: ["@media screen and (max-width:400px){.collectionsInfo table,.collectionsInfo thead,.collectionsInfo tbody,.collectionsInfo th,.collectionsInfo td,.collectionsInfo tr{display:block}.collectionsInfo thead tr{position:absolute;top:-9999px;left:-9999px}.collectionsInfo tr{margin:0 0 1rem}.collectionsInfo td{border:none;position:relative;padding-left:50%;text-align:left}.collectionsInfo td:before{position:absolute;top:0;left:0;width:45%;padding-right:10px;white-space:nowrap;content:attr(data-label);font-weight:700}}.collectionsInfo{height:95%}.collectionsInfo .collectionSelector{height:15%;min-height:2rem}.collectionsInfo .collectionSelector span{color:var(--phoenix-text-color-secondary);margin-right:1rem}.collectionsInfo .collectionSelector mat-checkbox{margin-left:1rem}.boxBody{height:85%;overflow:scroll}.boxBody p.emptyBox{max-width:21em}.boxBody .add-label{min-width:8rem}.boxBody .add-label input{padding:.1rem .3rem;font-size:.75rem}#collectionTable{position:relative;color:var(--phoenix-text-color-secondary)}#collectionTable thead tr th{position:sticky;top:0;z-index:100;background:var(--phoenix-background-color-secondary)}#collectionTable thead tr th .head-wrapper{display:flex;align-items:center}#collectionTable thead tr th .head-wrapper .sort-options{display:flex;flex-direction:row}#collectionTable thead tr th .head-wrapper .sort-options .icon-wrapper{display:flex;width:1rem;height:1rem;padding:.2rem}#collectionTable thead tr th .head-wrapper .sort-options .icon-wrapper.up{transform:rotate(180deg)}#collectionTable thead tr th .head-wrapper .sort-options .icon-wrapper svg{width:100%;height:100%}#collectionTable tr *{padding-right:1.2rem}#collectionTable tr *:last-child{padding-right:0}#collectionTable tr.is-cut{opacity:.5}#collectionTable tr.active-object{color:var(--phoenix-background-color);background:var(--phoenix-text-color);box-shadow:0 0 15px var(--phoenix-text-color)}#collectionTable tr.active-object div.icon-wrapper{--phoenix-options-icon-path: var(--phoenix-background-color);--phoenix-options-icon-bg: var(--phoenix-text-color-hover)}#collectionTable tr.active-object .add-label input{--phoenix-text-color: var(--phoenix-background-color);--phoenix-background-color-tertiary: var( --phoenix-text-color-secondary );--phoenix-border: var(--phoenix-background-color)}#collectionTable td{min-height:20px}#collectionTable td .object-select{position:relative;width:1.6rem;height:1.6rem;margin-right:.4em;text-align:center;background-color:var(--phoenix-options-icon-bg);border-radius:10px;cursor:pointer}#collectionTable td .object-select:last-child{margin-right:0}#collectionTable td .object-select:hover{border:1px solid var(--phoenix-options-icon-path)}#collectionTable td .object-select svg{position:absolute;top:0;left:0;padding:.4rem;width:100%;height:100%;vertical-align:top}.eventSelector{display:flex}.eventSelector label{display:none;color:var(--phoenix-text-color-secondary)}.eventSelector select{width:9rem;padding:5px 10px;font-size:12px;border:1px solid rgba(88,88,88,.08);box-shadow:var(--phoenix-icon-shadow);background-color:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary)}\n"] }]
1235
1791
  }], () => [{ type: i0.ElementRef }, { type: EventDisplayService }], { showObjectsInfo: [{
1236
1792
  type: Input
1237
1793
  }], excludedColumns: [{
@@ -1247,7 +1803,12 @@ class CollectionsInfoComponent {
1247
1803
  this.overlay = overlay;
1248
1804
  }
1249
1805
  ngOnInit() {
1250
- const overlayRef = this.overlay.create();
1806
+ const positionStrategy = this.overlay
1807
+ .position()
1808
+ .global()
1809
+ .bottom('80px')
1810
+ .right('15px');
1811
+ const overlayRef = this.overlay.create({ positionStrategy });
1251
1812
  const overlayPortal = new ComponentPortal(CollectionsInfoOverlayComponent);
1252
1813
  this.overlayWindow = overlayRef.attach(overlayPortal);
1253
1814
  this.overlayWindow.instance.showObjectsInfo = this.showObjectsInfo;
@@ -1265,8 +1826,8 @@ class CollectionsInfoComponent {
1265
1826
  this.overlayWindow.instance.disableHighlighting();
1266
1827
  }
1267
1828
  }
1268
- static ɵfac = function CollectionsInfoComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || CollectionsInfoComponent)(i0.ɵɵdirectiveInject(i1$1.Overlay)); };
1269
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: CollectionsInfoComponent, selectors: [["app-collections-info"]], standalone: false, decls: 1, vars: 1, consts: [["tooltip", "Event data collections info", "icon", "eventData", 3, "click", "active"]], template: function CollectionsInfoComponent_Template(rf, ctx) { if (rf & 1) {
1829
+ static ɵfac = function CollectionsInfoComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || CollectionsInfoComponent)(i0.ɵɵdirectiveInject(i1$3.Overlay)); };
1830
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: CollectionsInfoComponent, selectors: [["app-collections-info"]], standalone: false, decls: 1, vars: 1, consts: [["tooltip", "Collections Info", "icon", "eventData", 3, "click", "active"]], template: function CollectionsInfoComponent_Template(rf, ctx) { if (rf & 1) {
1270
1831
  i0.ɵɵelementStart(0, "app-menu-toggle", 0);
1271
1832
  i0.ɵɵlistener("click", function CollectionsInfoComponent_Template_app_menu_toggle_click_0_listener() { return ctx.toggleOverlay(); });
1272
1833
  i0.ɵɵelementEnd();
@@ -1276,8 +1837,8 @@ class CollectionsInfoComponent {
1276
1837
  }
1277
1838
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(CollectionsInfoComponent, [{
1278
1839
  type: Component,
1279
- args: [{ standalone: false, selector: 'app-collections-info', template: "<app-menu-toggle\n tooltip=\"Event data collections info\"\n icon=\"eventData\"\n [active]=\"showObjectsInfo\"\n (click)=\"toggleOverlay()\"\n>\n</app-menu-toggle>\n" }]
1280
- }], () => [{ type: i1$1.Overlay }], null); })();
1840
+ args: [{ standalone: false, selector: 'app-collections-info', template: "<app-menu-toggle\n tooltip=\"Collections Info\"\n icon=\"eventData\"\n [active]=\"showObjectsInfo\"\n (click)=\"toggleOverlay()\"\n>\n</app-menu-toggle>\n" }]
1841
+ }], () => [{ type: i1$3.Overlay }], null); })();
1281
1842
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(CollectionsInfoComponent, { className: "CollectionsInfoComponent", filePath: "components/ui-menu/collections-info/collections-info.component.ts", lineNumber: 17 }); })();
1282
1843
 
1283
1844
  const _c0$a = a0 => ({ "active-object": a0 });
@@ -1431,7 +1992,7 @@ class GeometryBrowserOverlayComponent {
1431
1992
  this.eventDisplay.disableHighlighting();
1432
1993
  }
1433
1994
  static ɵfac = function GeometryBrowserOverlayComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || GeometryBrowserOverlayComponent)(i0.ɵɵdirectiveInject(EventDisplayService)); };
1434
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: GeometryBrowserOverlayComponent, selectors: [["app-geometry-browser-overlay"]], inputs: { browseDetectorParts: "browseDetectorParts" }, standalone: false, decls: 2, vars: 3, consts: [["overlayTitle", "Browse Geometry", "icon", "info", 3, "resizable", "active"], ["class", "collectionsInfo m-2", 4, "ngIf"], [1, "collectionsInfo", "m-2"], ["class", "collectionSelector mb-2 d-flex align-items-center", 4, "ngIf"], [1, "boxBody", "table-responsive"], ["id", "collectionTable", "class", "table table-borderless table-sm", 4, "ngIf"], ["class", "emptyBox", 4, "ngIf"], [1, "collectionSelector", "mb-2", "d-flex", "align-items-center"], [1, "d-flex", "align-items-center"], [1, "eventSelector"], ["id", "detectorCollection", "name", "detectorCollection", 3, "change"], ["value", "", "selected", "", "disabled", "", "hidden", ""], [3, "value", 4, "ngFor", "ngForOf"], [3, "value"], ["id", "collectionTable", 1, "table", "table-borderless", "table-sm"], [3, "ngClass", 4, "ngFor", "ngForOf"], [3, "ngClass"], ["class", "row justify-content-center icon-wrapper", 4, "ngIf"], [1, "row", "justify-content-center", "icon-wrapper"], ["matTooltip", "Move camera to object", 1, "object-select", "btn-blank", 3, "click"], ["href", "assets/icons/views.svg#views"], ["matTooltip", "Highlight object", 1, "object-select", "btn-blank", 3, "click"], ["href", "assets/icons/cursor.svg#cursor"], [1, "emptyBox"]], template: function GeometryBrowserOverlayComponent_Template(rf, ctx) { if (rf & 1) {
1995
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: GeometryBrowserOverlayComponent, selectors: [["app-geometry-browser-overlay"]], inputs: { browseDetectorParts: "browseDetectorParts" }, standalone: false, decls: 2, vars: 3, consts: [["overlayTitle", "Browse Geometry", "icon", "info", "accordionId", "right-dock", 3, "resizable", "active"], ["class", "collectionsInfo m-2", 4, "ngIf"], [1, "collectionsInfo", "m-2"], ["class", "collectionSelector mb-2 d-flex align-items-center", 4, "ngIf"], [1, "boxBody", "table-responsive"], ["id", "collectionTable", "class", "table table-borderless table-sm", 4, "ngIf"], ["class", "emptyBox", 4, "ngIf"], [1, "collectionSelector", "mb-2", "d-flex", "align-items-center"], [1, "d-flex", "align-items-center"], [1, "eventSelector"], ["id", "detectorCollection", "name", "detectorCollection", 3, "change"], ["value", "", "selected", "", "disabled", "", "hidden", ""], [3, "value", 4, "ngFor", "ngForOf"], [3, "value"], ["id", "collectionTable", 1, "table", "table-borderless", "table-sm"], [3, "ngClass", 4, "ngFor", "ngForOf"], [3, "ngClass"], ["class", "row justify-content-center icon-wrapper", 4, "ngIf"], [1, "row", "justify-content-center", "icon-wrapper"], ["matTooltip", "Move camera to object", 1, "object-select", "btn-blank", 3, "click"], ["href", "assets/icons/views.svg#views"], ["matTooltip", "Highlight object", 1, "object-select", "btn-blank", 3, "click"], ["href", "assets/icons/cursor.svg#cursor"], [1, "emptyBox"]], template: function GeometryBrowserOverlayComponent_Template(rf, ctx) { if (rf & 1) {
1435
1996
  i0.ɵɵelementStart(0, "app-overlay", 0);
1436
1997
  i0.ɵɵtemplate(1, GeometryBrowserOverlayComponent_div_1_Template, 5, 3, "div", 1);
1437
1998
  i0.ɵɵelementEnd();
@@ -1443,7 +2004,7 @@ class GeometryBrowserOverlayComponent {
1443
2004
  }
1444
2005
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(GeometryBrowserOverlayComponent, [{
1445
2006
  type: Component,
1446
- args: [{ standalone: false, selector: 'app-geometry-browser-overlay', template: "<!-- Show objects -->\n<app-overlay\n overlayTitle=\"Browse Geometry\"\n icon=\"info\"\n [resizable]=\"true\"\n [active]=\"browseDetectorParts\"\n>\n <!-- Using ngIf to remove it from DOM since this panel requires heavy computing -->\n <div *ngIf=\"browseDetectorParts\" class=\"collectionsInfo m-2\">\n <div\n class=\"collectionSelector mb-2 d-flex align-items-center\"\n *ngIf=\"children != null\"\n >\n <div class=\"d-flex align-items-center\">\n <span>Choose a collection: </span>\n <div class=\"eventSelector\">\n <select\n id=\"detectorCollection\"\n name=\"detectorCollection\"\n (change)=\"changeCollection($event.target.value)\"\n >\n <option value=\"\" selected disabled hidden>Choose Collection</option>\n <option *ngFor=\"let child of children\" [value]=\"child.name\">\n {{ child.name }}\n </option>\n </select>\n </div>\n </div>\n </div>\n\n <div class=\"boxBody table-responsive\">\n <table\n id=\"collectionTable\"\n class=\"table table-borderless table-sm\"\n *ngIf=\"showingCollection\"\n >\n <thead>\n <tr>\n <th>No.</th>\n <th>Selection</th>\n </tr>\n </thead>\n <tbody>\n <tr\n *ngFor=\"let object of showingCollection; index as i\"\n [attr.id]=\"object.uuid\"\n [ngClass]=\"{\n 'active-object':\n activeObject && activeObject.value === object.uuid,\n }\"\n >\n <td>#{{ i }}</td>\n <td>\n <div\n *ngIf=\"object.uuid\"\n class=\"row justify-content-center icon-wrapper\"\n >\n <button\n class=\"object-select btn-blank\"\n matTooltip=\"Move camera to object\"\n (click)=\"lookAtObject(object.uuid)\"\n >\n <svg>\n <use href=\"assets/icons/views.svg#views\"></use>\n </svg>\n </button>\n <button\n class=\"object-select btn-blank\"\n matTooltip=\"Highlight object\"\n (click)=\"highlightObject(object.uuid)\"\n >\n <svg>\n <use href=\"assets/icons/cursor.svg#cursor\"></use>\n </svg>\n </button>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n <p class=\"emptyBox\" *ngIf=\"!children\">\n Load event data to be able to display information about the collections\n here.\n </p>\n </div>\n </div>\n</app-overlay>\n", styles: [".collectionsInfo{height:95%}.collectionsInfo .collectionSelector{height:15%;min-height:2rem}.collectionsInfo .collectionSelector span{color:var(--phoenix-text-color-secondary);margin-right:1rem}.boxBody{height:85%;overflow:scroll}.boxBody p.emptyBox{max-width:21em}#collectionTable{position:relative;color:var(--phoenix-text-color-secondary)}#collectionTable thead tr th{position:sticky;top:0;z-index:100;background:var(--phoenix-background-color-secondary)}#collectionTable thead tr th .row{margin-right:10px;margin-left:-92px}#collectionTable thead tr th .icon-wrapper{display:flex;width:1rem;height:1rem;padding:.2rem}#collectionTable thead tr th .icon-wrapper svg{width:100%;height:100%}#collectionTable tr *{padding-right:1.2rem}#collectionTable tr *:last-child{padding-right:0}#collectionTable tr.active-object{color:var(--phoenix-background-color);background:var(--phoenix-text-color);box-shadow:0 0 15px var(--phoenix-text-color)}#collectionTable tr.active-object div.icon-wrapper{--phoenix-options-icon-path: var(--phoenix-background-color);--phoenix-options-icon-bg: var(--phoenix-text-color-hover)}#collectionTable td .object-select{position:relative;width:1.6rem;height:1.6rem;margin-right:.4em;text-align:center;background-color:var(--phoenix-options-icon-bg);border-radius:10px;cursor:pointer}#collectionTable td .object-select:last-child{margin-right:0}#collectionTable td .object-select:hover{border:1px solid var(--phoenix-options-icon-path)}#collectionTable td .object-select svg{position:absolute;top:0;left:0;padding:.4rem;width:100%;height:100%;vertical-align:top}.eventSelector{display:flex}.eventSelector label{display:none;color:var(--phoenix-text-color-secondary)}.eventSelector select{width:9rem;padding:5px 10px;font-size:12px;border:1px solid rgba(88,88,88,.08);box-shadow:var(--phoenix-icon-shadow);background-color:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary)}\n"] }]
2007
+ args: [{ standalone: false, selector: 'app-geometry-browser-overlay', template: "<!-- Show objects -->\n<app-overlay\n overlayTitle=\"Browse Geometry\"\n icon=\"info\"\n [resizable]=\"true\"\n [active]=\"browseDetectorParts\"\n accordionId=\"right-dock\"\n>\n <!-- Using ngIf to remove it from DOM since this panel requires heavy computing -->\n <div *ngIf=\"browseDetectorParts\" class=\"collectionsInfo m-2\">\n <div\n class=\"collectionSelector mb-2 d-flex align-items-center\"\n *ngIf=\"children != null\"\n >\n <div class=\"d-flex align-items-center\">\n <span>Choose a collection: </span>\n <div class=\"eventSelector\">\n <select\n id=\"detectorCollection\"\n name=\"detectorCollection\"\n (change)=\"changeCollection($event.target.value)\"\n >\n <option value=\"\" selected disabled hidden>Choose Collection</option>\n <option *ngFor=\"let child of children\" [value]=\"child.name\">\n {{ child.name }}\n </option>\n </select>\n </div>\n </div>\n </div>\n\n <div class=\"boxBody table-responsive\">\n <table\n id=\"collectionTable\"\n class=\"table table-borderless table-sm\"\n *ngIf=\"showingCollection\"\n >\n <thead>\n <tr>\n <th>No.</th>\n <th>Selection</th>\n </tr>\n </thead>\n <tbody>\n <tr\n *ngFor=\"let object of showingCollection; index as i\"\n [attr.id]=\"object.uuid\"\n [ngClass]=\"{\n 'active-object':\n activeObject && activeObject.value === object.uuid,\n }\"\n >\n <td>#{{ i }}</td>\n <td>\n <div\n *ngIf=\"object.uuid\"\n class=\"row justify-content-center icon-wrapper\"\n >\n <button\n class=\"object-select btn-blank\"\n matTooltip=\"Move camera to object\"\n (click)=\"lookAtObject(object.uuid)\"\n >\n <svg>\n <use href=\"assets/icons/views.svg#views\"></use>\n </svg>\n </button>\n <button\n class=\"object-select btn-blank\"\n matTooltip=\"Highlight object\"\n (click)=\"highlightObject(object.uuid)\"\n >\n <svg>\n <use href=\"assets/icons/cursor.svg#cursor\"></use>\n </svg>\n </button>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n <p class=\"emptyBox\" *ngIf=\"!children\">\n Load event data to be able to display information about the collections\n here.\n </p>\n </div>\n </div>\n</app-overlay>\n", styles: [".collectionsInfo{height:95%}.collectionsInfo .collectionSelector{height:15%;min-height:2rem}.collectionsInfo .collectionSelector span{color:var(--phoenix-text-color-secondary);margin-right:1rem}.boxBody{height:85%;overflow:scroll}.boxBody p.emptyBox{max-width:21em}#collectionTable{position:relative;color:var(--phoenix-text-color-secondary)}#collectionTable thead tr th{position:sticky;top:0;z-index:100;background:var(--phoenix-background-color-secondary)}#collectionTable thead tr th .row{margin-right:10px;margin-left:-92px}#collectionTable thead tr th .icon-wrapper{display:flex;width:1rem;height:1rem;padding:.2rem}#collectionTable thead tr th .icon-wrapper svg{width:100%;height:100%}#collectionTable tr *{padding-right:1.2rem}#collectionTable tr *:last-child{padding-right:0}#collectionTable tr.active-object{color:var(--phoenix-background-color);background:var(--phoenix-text-color);box-shadow:0 0 15px var(--phoenix-text-color)}#collectionTable tr.active-object div.icon-wrapper{--phoenix-options-icon-path: var(--phoenix-background-color);--phoenix-options-icon-bg: var(--phoenix-text-color-hover)}#collectionTable td .object-select{position:relative;width:1.6rem;height:1.6rem;margin-right:.4em;text-align:center;background-color:var(--phoenix-options-icon-bg);border-radius:10px;cursor:pointer}#collectionTable td .object-select:last-child{margin-right:0}#collectionTable td .object-select:hover{border:1px solid var(--phoenix-options-icon-path)}#collectionTable td .object-select svg{position:absolute;top:0;left:0;padding:.4rem;width:100%;height:100%;vertical-align:top}.eventSelector{display:flex}.eventSelector label{display:none;color:var(--phoenix-text-color-secondary)}.eventSelector select{width:9rem;padding:5px 10px;font-size:12px;border:1px solid rgba(88,88,88,.08);box-shadow:var(--phoenix-icon-shadow);background-color:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary)}\n"] }]
1447
2008
  }], () => [{ type: EventDisplayService }], { browseDetectorParts: [{
1448
2009
  type: Input
1449
2010
  }] }); })();
@@ -1457,7 +2018,12 @@ class GeometryBrowserComponent {
1457
2018
  this.overlay = overlay;
1458
2019
  }
1459
2020
  ngOnInit() {
1460
- const overlayRef = this.overlay.create();
2021
+ const positionStrategy = this.overlay
2022
+ .position()
2023
+ .global()
2024
+ .bottom('150px')
2025
+ .right('15px');
2026
+ const overlayRef = this.overlay.create({ positionStrategy });
1461
2027
  const overlayPortal = new ComponentPortal(GeometryBrowserOverlayComponent);
1462
2028
  this.overlayWindow = overlayRef.attach(overlayPortal);
1463
2029
  this.overlayWindow.instance.browseDetectorParts = this.browseDetectorParts;
@@ -1468,12 +2034,14 @@ class GeometryBrowserComponent {
1468
2034
  toggleOverlay() {
1469
2035
  this.browseDetectorParts = !this.browseDetectorParts;
1470
2036
  this.overlayWindow.instance.browseDetectorParts = this.browseDetectorParts;
1471
- // eslint-disable-next-line
1472
- this.browseDetectorParts
1473
- ? this.overlayWindow.instance.enableHighlighting()
1474
- : this.overlayWindow.instance.disableHighlighting();
2037
+ if (this.browseDetectorParts) {
2038
+ this.overlayWindow.instance.enableHighlighting();
2039
+ }
2040
+ else {
2041
+ this.overlayWindow.instance.disableHighlighting();
2042
+ }
1475
2043
  }
1476
- static ɵfac = function GeometryBrowserComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || GeometryBrowserComponent)(i0.ɵɵdirectiveInject(i1$1.Overlay)); };
2044
+ static ɵfac = function GeometryBrowserComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || GeometryBrowserComponent)(i0.ɵɵdirectiveInject(i1$3.Overlay)); };
1477
2045
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: GeometryBrowserComponent, selectors: [["app-geometry-browser"]], standalone: false, decls: 1, vars: 1, consts: [["tooltip", "Browse geometry", "icon", "top-cube", 3, "click", "active"]], template: function GeometryBrowserComponent_Template(rf, ctx) { if (rf & 1) {
1478
2046
  i0.ɵɵelementStart(0, "app-menu-toggle", 0);
1479
2047
  i0.ɵɵlistener("click", function GeometryBrowserComponent_Template_app_menu_toggle_click_0_listener() { return ctx.toggleOverlay(); });
@@ -1485,7 +2053,7 @@ class GeometryBrowserComponent {
1485
2053
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(GeometryBrowserComponent, [{
1486
2054
  type: Component,
1487
2055
  args: [{ standalone: false, selector: 'app-geometry-browser', template: "<app-menu-toggle\n tooltip=\"Browse geometry\"\n icon=\"top-cube\"\n [active]=\"browseDetectorParts\"\n (click)=\"toggleOverlay()\"\n>\n</app-menu-toggle>\n" }]
1488
- }], () => [{ type: i1$1.Overlay }], null); })();
2056
+ }], () => [{ type: i1$3.Overlay }], null); })();
1489
2057
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(GeometryBrowserComponent, { className: "GeometryBrowserComponent", filePath: "components/ui-menu/geometry-browser/geometry-browser.component.ts", lineNumber: 17 }); })();
1490
2058
 
1491
2059
  class DarkThemeComponent {
@@ -1502,7 +2070,7 @@ class DarkThemeComponent {
1502
2070
  this.eventDisplay.getUIManager().setDarkTheme(this.darkTheme);
1503
2071
  }
1504
2072
  static ɵfac = function DarkThemeComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || DarkThemeComponent)(i0.ɵɵdirectiveInject(EventDisplayService)); };
1505
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: DarkThemeComponent, selectors: [["app-dark-theme"]], standalone: false, decls: 1, vars: 1, consts: [["tooltip", "Dark theme", "icon", "dark", 3, "click", "active"]], template: function DarkThemeComponent_Template(rf, ctx) { if (rf & 1) {
2073
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: DarkThemeComponent, selectors: [["app-dark-theme"]], standalone: false, decls: 1, vars: 1, consts: [["tooltip", "Switch Dark/Light theme", "icon", "dark", 3, "click", "active"]], template: function DarkThemeComponent_Template(rf, ctx) { if (rf & 1) {
1506
2074
  i0.ɵɵelementStart(0, "app-menu-toggle", 0);
1507
2075
  i0.ɵɵlistener("click", function DarkThemeComponent_Template_app_menu_toggle_click_0_listener() { return ctx.setDarkTheme(); });
1508
2076
  i0.ɵɵelementEnd();
@@ -1512,7 +2080,7 @@ class DarkThemeComponent {
1512
2080
  }
1513
2081
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DarkThemeComponent, [{
1514
2082
  type: Component,
1515
- args: [{ standalone: false, selector: 'app-dark-theme', template: "<app-menu-toggle\n tooltip=\"Dark theme\"\n [active]=\"darkTheme\"\n icon=\"dark\"\n (click)=\"setDarkTheme()\"\n>\n</app-menu-toggle>\n" }]
2083
+ args: [{ standalone: false, selector: 'app-dark-theme', template: "<app-menu-toggle\n tooltip=\"Switch Dark/Light theme\"\n [active]=\"darkTheme\"\n icon=\"dark\"\n (click)=\"setDarkTheme()\"\n>\n</app-menu-toggle>\n" }]
1516
2084
  }], () => [{ type: EventDisplayService }], null); })();
1517
2085
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(DarkThemeComponent, { className: "DarkThemeComponent", filePath: "components/ui-menu/dark-theme/dark-theme.component.ts", lineNumber: 10 }); })();
1518
2086
 
@@ -1660,11 +2228,11 @@ class ExperimentInfoComponent {
1660
2228
  i0.ɵɵproperty("ngIf", ctx.tagline);
1661
2229
  i0.ɵɵadvance();
1662
2230
  i0.ɵɵproperty("ngForOf", ctx.experimentInfo);
1663
- } }, dependencies: [i2.NgForOf, i2.NgIf, i3$3.CdkDrag], styles: [".experimentInfo[_ngcontent-%COMP%]{position:absolute;top:5rem;left:1rem;display:flex;flex-direction:row;justify-items:flex-end;justify-content:flex-end;align-items:center;align-content:center;font-family:Courier New,Lucida Console,monospace;cursor:move}.experimentLogoWrapper[_ngcontent-%COMP%]{width:6rem}.experimentLogoWrapper[_ngcontent-%COMP%] .experimentLogo[_ngcontent-%COMP%]{width:100%;height:100%}.textInfo[_ngcontent-%COMP%]{margin-left:1rem}.textInfo[_ngcontent-%COMP%] p[_ngcontent-%COMP%]{color:var(--phoenix-text-color-secondary);font-size:.8rem;margin-bottom:.5rem;-webkit-user-select:none;user-select:none}@media screen and (max-width: 768px){.experimentInfo[_ngcontent-%COMP%]{top:4rem;max-width:40%}.experimentLogoWrapper[_ngcontent-%COMP%]{height:4rem}.textInfo[_ngcontent-%COMP%]{display:none}}"] });
2231
+ } }, dependencies: [i2.NgForOf, i2.NgIf, i3$2.CdkDrag], styles: [".experimentInfo[_ngcontent-%COMP%]{position:absolute;top:5rem;left:1rem;display:flex;flex-direction:row;justify-items:flex-end;justify-content:flex-end;align-items:center;align-content:center;font-family:Courier New,Lucida Console,monospace;cursor:move}.experimentLogoWrapper[_ngcontent-%COMP%]{width:6rem}.experimentLogoWrapper[_ngcontent-%COMP%] .experimentLogo[_ngcontent-%COMP%]{width:100%;height:100%}.textInfo[_ngcontent-%COMP%]{margin-left:1rem}.textInfo[_ngcontent-%COMP%] p[_ngcontent-%COMP%]{color:var(--phoenix-text-color-secondary);font-size:.8rem;margin-bottom:.5rem;-webkit-user-select:none;user-select:none}@media screen and (max-width:768px){.experimentInfo[_ngcontent-%COMP%]{top:4rem;max-width:40%}.experimentLogoWrapper[_ngcontent-%COMP%]{height:4rem}.textInfo[_ngcontent-%COMP%]{display:none}}"] });
1664
2232
  }
1665
2233
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ExperimentInfoComponent, [{
1666
2234
  type: Component,
1667
- args: [{ standalone: false, selector: 'app-experiment-info', template: "<div id=\"experimentInfo\" class=\"experimentInfo\" cdkDrag cdkDragBoundary=\"body\">\n <a\n class=\"experimentLogoWrapper\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n *ngIf=\"url\"\n [href]=\"url\"\n >\n <img class=\"experimentLogo\" [src]=\"logo\" alt=\"\" />\n </a>\n <div class=\"textInfo\">\n <p *ngIf=\"tagline\">\n <b>{{ tagline }}</b>\n </p>\n <p *ngFor=\"let infoField of experimentInfo\">\n <ng-container *ngIf=\"infoField.label\">\n <b>{{ infoField.label }}: </b>{{ infoField.value }}\n </ng-container>\n <ng-container *ngIf=\"!infoField.label\">\n {{ infoField }}\n </ng-container>\n </p>\n </div>\n</div>\n", styles: [".experimentInfo{position:absolute;top:5rem;left:1rem;display:flex;flex-direction:row;justify-items:flex-end;justify-content:flex-end;align-items:center;align-content:center;font-family:Courier New,Lucida Console,monospace;cursor:move}.experimentLogoWrapper{width:6rem}.experimentLogoWrapper .experimentLogo{width:100%;height:100%}.textInfo{margin-left:1rem}.textInfo p{color:var(--phoenix-text-color-secondary);font-size:.8rem;margin-bottom:.5rem;-webkit-user-select:none;user-select:none}@media screen and (max-width: 768px){.experimentInfo{top:4rem;max-width:40%}.experimentLogoWrapper{height:4rem}.textInfo{display:none}}\n"] }]
2235
+ args: [{ standalone: false, selector: 'app-experiment-info', template: "<div id=\"experimentInfo\" class=\"experimentInfo\" cdkDrag cdkDragBoundary=\"body\">\n <a\n class=\"experimentLogoWrapper\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n *ngIf=\"url\"\n [href]=\"url\"\n >\n <img class=\"experimentLogo\" [src]=\"logo\" alt=\"\" />\n </a>\n <div class=\"textInfo\">\n <p *ngIf=\"tagline\">\n <b>{{ tagline }}</b>\n </p>\n <p *ngFor=\"let infoField of experimentInfo\">\n <ng-container *ngIf=\"infoField.label\">\n <b>{{ infoField.label }}: </b>{{ infoField.value }}\n </ng-container>\n <ng-container *ngIf=\"!infoField.label\">\n {{ infoField }}\n </ng-container>\n </p>\n </div>\n</div>\n", styles: [".experimentInfo{position:absolute;top:5rem;left:1rem;display:flex;flex-direction:row;justify-items:flex-end;justify-content:flex-end;align-items:center;align-content:center;font-family:Courier New,Lucida Console,monospace;cursor:move}.experimentLogoWrapper{width:6rem}.experimentLogoWrapper .experimentLogo{width:100%;height:100%}.textInfo{margin-left:1rem}.textInfo p{color:var(--phoenix-text-color-secondary);font-size:.8rem;margin-bottom:.5rem;-webkit-user-select:none;user-select:none}@media screen and (max-width:768px){.experimentInfo{top:4rem;max-width:40%}.experimentLogoWrapper{height:4rem}.textInfo{display:none}}\n"] }]
1668
2236
  }], () => [{ type: EventDisplayService }], { url: [{
1669
2237
  type: Input
1670
2238
  }], logo: [{
@@ -1737,11 +2305,40 @@ class InfoPanelOverlayComponent {
1737
2305
  }] }); })();
1738
2306
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(InfoPanelOverlayComponent, { className: "InfoPanelOverlayComponent", filePath: "components/ui-menu/info-panel/info-panel-overlay/info-panel-overlay.component.ts", lineNumber: 14 }); })();
1739
2307
 
2308
+ class OverlayCascadeService {
2309
+ overlay;
2310
+ cascadeCount = 0;
2311
+ topOffset = 130;
2312
+ leftOffset = 15;
2313
+ step = 40;
2314
+ constructor(overlay) {
2315
+ this.overlay = overlay;
2316
+ }
2317
+ getCascadePositionStrategy() {
2318
+ const strategy = this.overlay
2319
+ .position()
2320
+ .global()
2321
+ .top(`${this.topOffset + this.cascadeCount * this.step}px`)
2322
+ .left(`${this.leftOffset + this.cascadeCount * this.step}px`);
2323
+ this.cascadeCount++;
2324
+ return strategy;
2325
+ }
2326
+ static ɵfac = function OverlayCascadeService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || OverlayCascadeService)(i0.ɵɵinject(i1$3.Overlay)); };
2327
+ static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: OverlayCascadeService, factory: OverlayCascadeService.ɵfac, providedIn: 'root' });
2328
+ }
2329
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(OverlayCascadeService, [{
2330
+ type: Injectable,
2331
+ args: [{
2332
+ providedIn: 'root',
2333
+ }]
2334
+ }], () => [{ type: i1$3.Overlay }], null); })();
2335
+
1740
2336
  /**
1741
2337
  * Component for toggling info panel overlay
1742
2338
  */
1743
2339
  class InfoPanelComponent {
1744
2340
  overlay;
2341
+ overlayCascade;
1745
2342
  /** @ignore */
1746
2343
  showInfoPanel = false;
1747
2344
  /** Information panel overlay window */
@@ -1750,14 +2347,16 @@ class InfoPanelComponent {
1750
2347
  * Create the info panel toggle
1751
2348
  * @param overlay Info panel overlay containing logger data
1752
2349
  */
1753
- constructor(overlay) {
2350
+ constructor(overlay, overlayCascade) {
1754
2351
  this.overlay = overlay;
2352
+ this.overlayCascade = overlayCascade;
1755
2353
  }
1756
2354
  /**
1757
2355
  * Create the info panel overlay
1758
2356
  */
1759
2357
  ngOnInit() {
1760
- const overlayRef = this.overlay.create();
2358
+ const positionStrategy = this.overlayCascade.getCascadePositionStrategy();
2359
+ const overlayRef = this.overlay.create({ positionStrategy });
1761
2360
  const overlayPortal = new ComponentPortal(InfoPanelOverlayComponent);
1762
2361
  this.overlayWindow = overlayRef.attach(overlayPortal);
1763
2362
  this.overlayWindow.instance.showInfoPanel = this.showInfoPanel;
@@ -1775,7 +2374,7 @@ class InfoPanelComponent {
1775
2374
  this.showInfoPanel = !this.showInfoPanel;
1776
2375
  this.overlayWindow.instance.showInfoPanel = this.showInfoPanel;
1777
2376
  }
1778
- static ɵfac = function InfoPanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || InfoPanelComponent)(i0.ɵɵdirectiveInject(i1$1.Overlay)); };
2377
+ static ɵfac = function InfoPanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || InfoPanelComponent)(i0.ɵɵdirectiveInject(i1$3.Overlay), i0.ɵɵdirectiveInject(OverlayCascadeService)); };
1779
2378
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: InfoPanelComponent, selectors: [["app-info-panel"]], standalone: false, decls: 1, vars: 1, consts: [["tooltip", "Info panel", "icon", "info-panel", 3, "click", "active"]], template: function InfoPanelComponent_Template(rf, ctx) { if (rf & 1) {
1780
2379
  i0.ɵɵelementStart(0, "app-menu-toggle", 0);
1781
2380
  i0.ɵɵlistener("click", function InfoPanelComponent_Template_app_menu_toggle_click_0_listener() { return ctx.toggleOverlay(); });
@@ -1787,8 +2386,8 @@ class InfoPanelComponent {
1787
2386
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(InfoPanelComponent, [{
1788
2387
  type: Component,
1789
2388
  args: [{ standalone: false, selector: 'app-info-panel', template: "<app-menu-toggle\n tooltip=\"Info panel\"\n icon=\"info-panel\"\n [active]=\"showInfoPanel\"\n (click)=\"toggleOverlay()\"\n>\n</app-menu-toggle>\n" }]
1790
- }], () => [{ type: i1$1.Overlay }], null); })();
1791
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(InfoPanelComponent, { className: "InfoPanelComponent", filePath: "components/ui-menu/info-panel/info-panel.component.ts", lineNumber: 20 }); })();
2389
+ }], () => [{ type: i1$3.Overlay }, { type: OverlayCascadeService }], null); })();
2390
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(InfoPanelComponent, { className: "InfoPanelComponent", filePath: "components/ui-menu/info-panel/info-panel.component.ts", lineNumber: 21 }); })();
1792
2391
 
1793
2392
  var EventDataFormat;
1794
2393
  (function (EventDataFormat) {
@@ -1796,6 +2395,7 @@ var EventDataFormat;
1796
2395
  EventDataFormat["EDM4HEPJSON"] = "EDM4HEPJSON";
1797
2396
  EventDataFormat["JIVEXML"] = "JIVEXML";
1798
2397
  EventDataFormat["PHYSLITE"] = "PHYSLITE";
2398
+ EventDataFormat["ATLASESD"] = "ATLASESD";
1799
2399
  EventDataFormat["IG"] = "IG";
1800
2400
  EventDataFormat["ZIP"] = "ZIP";
1801
2401
  })(EventDataFormat || (EventDataFormat = {}));
@@ -1820,15 +2420,18 @@ function IOOptionsDialogComponent_div_4_ng_container_5_Template(rf, ctx) { if (r
1820
2420
  i0.ɵɵelementEnd();
1821
2421
  i0.ɵɵelementStart(3, "button", 13);
1822
2422
  i0.ɵɵlistener("click", function IOOptionsDialogComponent_div_4_ng_container_5_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r2); const eventDataFileInput_r4 = i0.ɵɵreference(2); return i0.ɵɵresetView(eventDataFileInput_r4.click()); });
1823
- i0.ɵɵelement(4, "img", 31);
1824
- i0.ɵɵtext(5);
2423
+ i0.ɵɵnamespaceSVG();
2424
+ i0.ɵɵelementStart(4, "svg", 31);
2425
+ i0.ɵɵelement(5, "use", 32);
2426
+ i0.ɵɵelementEnd();
2427
+ i0.ɵɵtext(6);
1825
2428
  i0.ɵɵelementEnd();
1826
2429
  i0.ɵɵelementContainerEnd();
1827
2430
  } if (rf & 2) {
1828
2431
  const eventDataImportOption_r3 = ctx.$implicit;
1829
2432
  i0.ɵɵadvance();
1830
2433
  i0.ɵɵproperty("accept", eventDataImportOption_r3.accept);
1831
- i0.ɵɵadvance(4);
2434
+ i0.ɵɵadvance(5);
1832
2435
  i0.ɵɵtextInterpolate1(" Load ", eventDataImportOption_r3.fileType, " ");
1833
2436
  } }
1834
2437
  function IOOptionsDialogComponent_div_4_Template(rf, ctx) { if (rf & 1) {
@@ -1836,7 +2439,7 @@ function IOOptionsDialogComponent_div_4_Template(rf, ctx) { if (rf & 1) {
1836
2439
  i0.ɵɵtext(3, "Event data");
1837
2440
  i0.ɵɵelementEnd()();
1838
2441
  i0.ɵɵelementStart(4, "div", 11);
1839
- i0.ɵɵtemplate(5, IOOptionsDialogComponent_div_4_ng_container_5_Template, 6, 2, "ng-container", 29);
2442
+ i0.ɵɵtemplate(5, IOOptionsDialogComponent_div_4_ng_container_5_Template, 7, 2, "ng-container", 29);
1840
2443
  i0.ɵɵelementEnd()();
1841
2444
  } if (rf & 2) {
1842
2445
  const ctx_r4 = i0.ɵɵnextContext();
@@ -1846,6 +2449,7 @@ function IOOptionsDialogComponent_div_4_Template(rf, ctx) { if (rf & 1) {
1846
2449
  class IOOptionsDialogComponent {
1847
2450
  eventDisplay;
1848
2451
  dialogRef;
2452
+ notificationService;
1849
2453
  eventDataImportOptions = [EventDataFormat.JSON];
1850
2454
  eventDataOptionsWithHandler;
1851
2455
  supportedEventDataFormats = [
@@ -1853,12 +2457,16 @@ class IOOptionsDialogComponent {
1853
2457
  new ImportOption(EventDataFormat.EDM4HEPJSON, '.edm4hep.json', this.handleEDM4HEPJSONEventDataInput.bind(this), 'application/json'),
1854
2458
  new ImportOption(EventDataFormat.JIVEXML, '.xml', this.handleJiveXMLDataInput.bind(this), 'text/xml'),
1855
2459
  new ImportOption(EventDataFormat.ZIP, '.zip', this.handleZipEventDataInput.bind(this), '.zip'),
1856
- new ImportOption(EventDataFormat.PHYSLITE, '.root', this.handlePHYSLITEInput.bind(this), '.root'),
2460
+ // Both ROOT formats accept '.root', so the button labels have to say which
2461
+ // is which — the template renders "Load {{ fileType }}".
2462
+ new ImportOption(EventDataFormat.PHYSLITE, 'PHYSLITE (.root)', this.handlePHYSLITEInput.bind(this), '.root'),
2463
+ new ImportOption(EventDataFormat.ATLASESD, 'ATLAS ESD (.root)', this.handleATLASESDInput.bind(this), '.root'),
1857
2464
  new ImportOption(EventDataFormat.IG, '.ig', this.handleIgEventDataInput.bind(this), '.ig'),
1858
2465
  ];
1859
- constructor(eventDisplay, dialogRef) {
2466
+ constructor(eventDisplay, dialogRef, notificationService) {
1860
2467
  this.eventDisplay = eventDisplay;
1861
2468
  this.dialogRef = dialogRef;
2469
+ this.notificationService = notificationService;
1862
2470
  }
1863
2471
  ngOnInit() {
1864
2472
  this.eventDataOptionsWithHandler = this.supportedEventDataFormats.filter((eventDataFormat) => this.eventDataImportOptions.includes(eventDataFormat.format));
@@ -1897,6 +2505,7 @@ class IOOptionsDialogComponent {
1897
2505
  this.eventDisplay
1898
2506
  .getInfoLogger()
1899
2507
  .add('Could not parse JSON event file. Please ensure it is valid JSON.', 'Error');
2508
+ this.notificationService.error('Could not parse JSON event file. Please ensure it is valid JSON.');
1900
2509
  console.error('Error parsing JSON event file:', error);
1901
2510
  }
1902
2511
  };
@@ -1915,6 +2524,7 @@ class IOOptionsDialogComponent {
1915
2524
  this.eventDisplay
1916
2525
  .getInfoLogger()
1917
2526
  .add('Could not parse EDM4HEP JSON file. Please ensure it is valid JSON.', 'Error');
2527
+ this.notificationService.error('Could not parse EDM4HEP JSON file. Please ensure it is valid JSON.');
1918
2528
  console.error('Error parsing EDM4HEP JSON file:', error);
1919
2529
  }
1920
2530
  };
@@ -1965,7 +2575,15 @@ class IOOptionsDialogComponent {
1965
2575
  }
1966
2576
  async handleROOTInput(files) {
1967
2577
  const rootObjectName = prompt('Enter object name in ROOT file');
1968
- await this.eventDisplay.loadRootGeometry(URL.createObjectURL(files[0]), rootObjectName, files[0].name.split('.')[0]);
2578
+ // The object URL pins the whole file in memory, so release it once the
2579
+ // geometry has been read - ROOT files are routinely hundreds of MB.
2580
+ const objectUrl = URL.createObjectURL(files[0]);
2581
+ try {
2582
+ await this.eventDisplay.loadRootGeometry(objectUrl, rootObjectName, files[0].name.split('.')[0]);
2583
+ }
2584
+ finally {
2585
+ URL.revokeObjectURL(objectUrl);
2586
+ }
1969
2587
  this.onClose();
1970
2588
  }
1971
2589
  async handleRootJSONInput(files) {
@@ -1973,7 +2591,13 @@ class IOOptionsDialogComponent {
1973
2591
  return;
1974
2592
  }
1975
2593
  const name = files[0].name.split('.')[0];
1976
- await this.eventDisplay.loadRootJSONGeometry(URL.createObjectURL(files[0]), name);
2594
+ const objectUrl = URL.createObjectURL(files[0]);
2595
+ try {
2596
+ await this.eventDisplay.loadRootJSONGeometry(objectUrl, name);
2597
+ }
2598
+ finally {
2599
+ URL.revokeObjectURL(objectUrl);
2600
+ }
1977
2601
  this.onClose();
1978
2602
  }
1979
2603
  async handlePHYSLITEInput(files) {
@@ -2002,6 +2626,24 @@ class IOOptionsDialogComponent {
2002
2626
  this.eventDisplay
2003
2627
  .getInfoLogger()
2004
2628
  .add('Failed to load PHYSLITE file: ' + error.message, 'Error');
2629
+ this.notificationService.error('Failed to load PHYSLITE file: ' + error.message);
2630
+ }
2631
+ this.onClose();
2632
+ }
2633
+ async handleATLASESDInput(files) {
2634
+ if (!this.isFileOfExtension(files[0].name, 'root,root.1,pool.root,pool.root.1')) {
2635
+ return;
2636
+ }
2637
+ const loader = new ATLASESDLoader();
2638
+ try {
2639
+ const eventsData = await loader.getEventData(files[0]);
2640
+ this.eventDisplay.parsePhoenixEvents(eventsData);
2641
+ }
2642
+ catch (error) {
2643
+ this.eventDisplay
2644
+ .getInfoLogger()
2645
+ .add('Failed to load ATLAS ESD file: ' + error.message, 'Error');
2646
+ this.notificationService.error('Failed to load ATLAS ESD file: ' + error.message);
2005
2647
  }
2006
2648
  this.onClose();
2007
2649
  }
@@ -2026,6 +2668,7 @@ class IOOptionsDialogComponent {
2026
2668
  catch (error) {
2027
2669
  console.error('Error while reading zip', error);
2028
2670
  this.eventDisplay.getInfoLogger().add('Could not read zip file', 'Error');
2671
+ this.notificationService.error('Could not read zip file.');
2029
2672
  return;
2030
2673
  }
2031
2674
  // JSON event data
@@ -2059,6 +2702,7 @@ class IOOptionsDialogComponent {
2059
2702
  const errorMessage = `Failed to read file "${file.name}". The file may be corrupted, too large, or inaccessible.`;
2060
2703
  console.error('FileReader error:', reader.error);
2061
2704
  this.eventDisplay.getInfoLogger().add(errorMessage, 'Error');
2705
+ this.notificationService.error(errorMessage);
2062
2706
  };
2063
2707
  reader.readAsText(file);
2064
2708
  }
@@ -2078,6 +2722,7 @@ class IOOptionsDialogComponent {
2078
2722
  accepted;
2079
2723
  console.error('Error: ' + msg);
2080
2724
  this.eventDisplay.getInfoLogger().add(msg, 'Error');
2725
+ this.notificationService.error(msg);
2081
2726
  return false;
2082
2727
  }
2083
2728
  saveScene() {
@@ -2086,8 +2731,8 @@ class IOOptionsDialogComponent {
2086
2731
  exportOBJ() {
2087
2732
  this.eventDisplay.exportToOBJ();
2088
2733
  }
2089
- static ɵfac = function IOOptionsDialogComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || IOOptionsDialogComponent)(i0.ɵɵdirectiveInject(EventDisplayService), i0.ɵɵdirectiveInject(i1$2.MatDialogRef)); };
2090
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: IOOptionsDialogComponent, selectors: [["app-io-options-dialog"]], inputs: { eventDataImportOptions: "eventDataImportOptions" }, standalone: false, decls: 47, vars: 1, consts: [["objFileInput", ""], ["gltfFileInput", ""], ["rootFileInput", ""], ["rootJSONFileInput", ""], ["loadSceneInput", ""], ["eventDataFileInput", ""], [1, "dialog"], ["mat-dialog-title", ""], ["mat-dialog-content", "", 1, "dialog-content"], [4, "ngIf"], [1, "text-center"], [1, "row", "centered"], ["type", "file", "tabindex", "-1", "accept", ".obj", "name", "image", 1, "file-input", 3, "change"], [1, "file-input-button", 3, "click"], ["src", "assets/icons/obj.svg", "alt", "OBJ file format icon"], ["type", "file", "tabindex", "-1", "accept", ".gltf,.glb,.gltf.zip,.glb.zip", "name", "image", 1, "file-input", 3, "change"], ["src", "assets/icons/gltf.svg", "alt", "GLTF file format icon"], ["type", "file", "tabindex", "-1", "accept", ".root", 1, "file-input", 3, "change"], ["src", "assets/icons/file-import.svg", "alt", ""], ["type", "file", "tabindex", "-1", "accept", ".json.gz", 1, "file-input", 3, "change"], ["id", "exportScene", 1, "file-input-button", "load-export", 3, "click"], ["src", "assets/icons/save.svg", "alt", ""], ["type", "file", "tabindex", "-1", "accept", ".phnx", "name", "image", 1, "file-input", 3, "change"], ["id", "importScene", 1, "file-input-button", "load-export", 3, "click"], ["src", "assets/icons/upload.svg", "alt", ""], ["id", "exportOBJ", 1, "file-input-button", "export-button", 3, "click"], ["src", "assets/icons/obj.svg", "alt", ""], ["mat-dialog-actions", "", "align", "end"], ["mat-flat-button", "", "color", "primary", "cdkFocusInitial", "", 3, "click"], [4, "ngFor", "ngForOf"], ["type", "file", "tabindex", "-1", 1, "file-input", 3, "change", "accept"], ["src", "assets/icons/eventData.svg", "alt", "Event data icon"]], template: function IOOptionsDialogComponent_Template(rf, ctx) { if (rf & 1) {
2734
+ static ɵfac = function IOOptionsDialogComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || IOOptionsDialogComponent)(i0.ɵɵdirectiveInject(EventDisplayService), i0.ɵɵdirectiveInject(i1$1.MatDialogRef), i0.ɵɵdirectiveInject(NotificationService)); };
2735
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: IOOptionsDialogComponent, selectors: [["app-io-options-dialog"]], inputs: { eventDataImportOptions: "eventDataImportOptions" }, standalone: false, decls: 47, vars: 1, consts: [["objFileInput", ""], ["gltfFileInput", ""], ["rootFileInput", ""], ["rootJSONFileInput", ""], ["loadSceneInput", ""], ["eventDataFileInput", ""], [1, "dialog"], ["mat-dialog-title", ""], ["mat-dialog-content", "", 1, "dialog-content"], [4, "ngIf"], [1, "text-center"], [1, "row", "centered"], ["type", "file", "tabindex", "-1", "accept", ".obj", "name", "image", 1, "file-input", 3, "change"], [1, "file-input-button", 3, "click"], ["src", "assets/icons/obj.svg", "alt", "OBJ file format icon"], ["type", "file", "tabindex", "-1", "accept", ".gltf,.glb,.gltf.zip,.glb.zip", "name", "image", 1, "file-input", 3, "change"], ["src", "assets/icons/gltf.svg", "alt", "GLTF file format icon"], ["type", "file", "tabindex", "-1", "accept", ".root", 1, "file-input", 3, "change"], ["src", "assets/icons/file-import.svg", "alt", ""], ["type", "file", "tabindex", "-1", "accept", ".json.gz", 1, "file-input", 3, "change"], ["id", "exportScene", 1, "file-input-button", "load-export", 3, "click"], ["src", "assets/icons/save.svg", "alt", ""], ["type", "file", "tabindex", "-1", "accept", ".phnx", "name", "image", 1, "file-input", 3, "change"], ["id", "importScene", 1, "file-input-button", "load-export", 3, "click"], ["src", "assets/icons/upload.svg", "alt", ""], ["id", "exportOBJ", 1, "file-input-button", "export-button", 3, "click"], ["src", "assets/icons/obj.svg", "alt", ""], ["mat-dialog-actions", "", "align", "end"], ["mat-flat-button", "", "color", "primary", "cdkFocusInitial", "", 3, "click"], [4, "ngFor", "ngForOf"], ["type", "file", "tabindex", "-1", 1, "file-input", 3, "change", "accept"], [1, "item-icon"], ["href", "assets/icons/eventData.svg#eventData"]], template: function IOOptionsDialogComponent_Template(rf, ctx) { if (rf & 1) {
2091
2736
  const _r1 = i0.ɵɵgetCurrentView();
2092
2737
  i0.ɵɵelementStart(0, "div", 6)(1, "h1", 7);
2093
2738
  i0.ɵɵtext(2, "Import and export");
@@ -2157,15 +2802,15 @@ class IOOptionsDialogComponent {
2157
2802
  } if (rf & 2) {
2158
2803
  i0.ɵɵadvance(4);
2159
2804
  i0.ɵɵproperty("ngIf", ctx.eventDataImportOptions.length > 0);
2160
- } }, dependencies: [i2.NgForOf, i2.NgIf, i1$2.MatDialogTitle, i1$2.MatDialogActions, i1$2.MatDialogContent, i3.MatButton], styles: [".row[_ngcontent-%COMP%]{justify-content:center;margin:1rem 0}.file-input[_ngcontent-%COMP%]{width:.1px;height:.1px;opacity:0;overflow:hidden;position:absolute;z-index:-1}.file-input-button[_ngcontent-%COMP%]{font-size:1em;font-weight:700;color:#fff;background-color:#6eaece;padding:1em .5em;border:none;border-radius:10px;width:8rem;margin:0 .4rem}.file-input-button.load-export[_ngcontent-%COMP%]{padding:.8em .2em;width:7rem}.file-input-button.load-export[_ngcontent-%COMP%] img[_ngcontent-%COMP%]{max-height:1.2em}.file-input-button.export-button[_ngcontent-%COMP%]{background-color:#cb7133}.file-input-button.export-button[_ngcontent-%COMP%]:hover{background-color:#ad5b2d}.file-input-button[_ngcontent-%COMP%] img[_ngcontent-%COMP%]{max-height:2em;display:block;width:100%}.file-input-button[_ngcontent-%COMP%]:hover{background-color:#118ab2}#exportScene[_ngcontent-%COMP%]{background-color:#5bd99e}#exportScene[_ngcontent-%COMP%]:hover{background-color:#05c292}#importScene[_ngcontent-%COMP%]{background-color:#ecc25e}#importScene[_ngcontent-%COMP%]:hover{background-color:#d6b44d}"] });
2805
+ } }, dependencies: [i2.NgForOf, i2.NgIf, i1$1.MatDialogTitle, i1$1.MatDialogActions, i1$1.MatDialogContent, i3.MatButton], styles: [".row[_ngcontent-%COMP%]{justify-content:center;margin:1rem 0}.file-input[_ngcontent-%COMP%]{width:.1px;height:.1px;opacity:0;overflow:hidden;position:absolute;z-index:-1}.file-input-button[_ngcontent-%COMP%]{font-size:1em;font-weight:700;color:#fff;background-color:#6eaece;padding:1em .5em;border:none;border-radius:10px;width:8rem;margin:0 .4rem}.file-input-button.load-export[_ngcontent-%COMP%]{padding:.8em .2em;width:7rem}.file-input-button.load-export[_ngcontent-%COMP%] img[_ngcontent-%COMP%]{max-height:1.2em}.file-input-button.export-button[_ngcontent-%COMP%]{background-color:#cb7133}.file-input-button.export-button[_ngcontent-%COMP%]:hover{background-color:#ad5b2d}.file-input-button[_ngcontent-%COMP%] img[_ngcontent-%COMP%], .file-input-button[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{max-height:2em;display:block;width:100%}.file-input-button[_ngcontent-%COMP%]:hover{background-color:#118ab2}#exportScene[_ngcontent-%COMP%]{background-color:#5bd99e}#exportScene[_ngcontent-%COMP%]:hover{background-color:#05c292}#importScene[_ngcontent-%COMP%]{background-color:#ecc25e}#importScene[_ngcontent-%COMP%]:hover{background-color:#d6b44d}"] });
2161
2806
  }
2162
2807
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(IOOptionsDialogComponent, [{
2163
2808
  type: Component,
2164
- args: [{ standalone: false, selector: 'app-io-options-dialog', template: "<div class=\"dialog\">\n <h1 mat-dialog-title>Import and export</h1>\n <div mat-dialog-content class=\"dialog-content\">\n <div *ngIf=\"eventDataImportOptions.length > 0\">\n <p class=\"text-center\"><b>Event data</b></p>\n\n <div class=\"row centered\">\n <ng-container\n *ngFor=\"let eventDataImportOption of eventDataOptionsWithHandler\"\n >\n <input\n #eventDataFileInput\n type=\"file\"\n tabindex=\"-1\"\n class=\"file-input\"\n [accept]=\"eventDataImportOption.accept\"\n (change)=\"eventDataImportOption.handler($event.target.files)\"\n />\n <button\n class=\"file-input-button\"\n (click)=\"eventDataFileInput.click()\"\n >\n <img src=\"assets/icons/eventData.svg\" alt=\"Event data icon\" />\n Load {{ eventDataImportOption.fileType }}\n </button>\n </ng-container>\n </div>\n </div>\n\n <p class=\"text-center\"><b>Geometries</b></p>\n\n <div class=\"row centered\">\n <input\n #objFileInput\n type=\"file\"\n tabindex=\"-1\"\n class=\"file-input\"\n accept=\".obj\"\n name=\"image\"\n (change)=\"handleOBJInput($event.target.files)\"\n />\n <button class=\"file-input-button\" (click)=\"objFileInput.click()\">\n <img src=\"assets/icons/obj.svg\" alt=\"OBJ file format icon\" /> Load .obj\n </button>\n\n <input\n #gltfFileInput\n type=\"file\"\n tabindex=\"-1\"\n class=\"file-input\"\n accept=\".gltf,.glb,.gltf.zip,.glb.zip\"\n name=\"image\"\n (change)=\"handleGLTFInput($event.target.files)\"\n />\n <button class=\"file-input-button\" (click)=\"gltfFileInput.click()\">\n <img src=\"assets/icons/gltf.svg\" alt=\"GLTF file format icon\" /> Load\n .gltf/.glb\n </button>\n\n <input\n #rootFileInput\n type=\"file\"\n tabindex=\"-1\"\n class=\"file-input\"\n accept=\".root\"\n (change)=\"handleROOTInput($event.target.files)\"\n />\n <button class=\"file-input-button\" (click)=\"rootFileInput.click()\">\n <img src=\"assets/icons/file-import.svg\" alt=\"\" /> Load .root\n </button>\n\n <input\n #rootJSONFileInput\n type=\"file\"\n tabindex=\"-1\"\n class=\"file-input\"\n accept=\".json.gz\"\n (change)=\"handleRootJSONInput($event.target.files)\"\n />\n <button class=\"file-input-button\" (click)=\"rootJSONFileInput.click()\">\n <img src=\"assets/icons/file-import.svg\" alt=\"\" /> Load .json.gz\n </button>\n </div>\n\n <p class=\"text-center\"><b>Scene</b></p>\n\n <div class=\"row centered\">\n <button\n class=\"file-input-button load-export\"\n id=\"exportScene\"\n (click)=\"saveScene()\"\n >\n <img src=\"assets/icons/save.svg\" alt=\"\" /> Save scene\n </button>\n\n <input\n #loadSceneInput\n type=\"file\"\n tabindex=\"-1\"\n class=\"file-input\"\n accept=\".phnx\"\n name=\"image\"\n (change)=\"handlePhoenixInput($event.target.files)\"\n />\n <button\n class=\"file-input-button load-export\"\n id=\"importScene\"\n (click)=\"loadSceneInput.click()\"\n >\n <img src=\"assets/icons/upload.svg\" alt=\"\" /> Load scene\n </button>\n\n <button\n class=\"file-input-button export-button\"\n id=\"exportOBJ\"\n (click)=\"exportOBJ()\"\n >\n <img src=\"assets/icons/obj.svg\" alt=\"\" /> Save OBJ\n </button>\n </div>\n </div>\n\n <div mat-dialog-actions align=\"end\">\n <button mat-flat-button color=\"primary\" (click)=\"onClose()\" cdkFocusInitial>\n Close\n </button>\n </div>\n</div>\n", styles: [".row{justify-content:center;margin:1rem 0}.file-input{width:.1px;height:.1px;opacity:0;overflow:hidden;position:absolute;z-index:-1}.file-input-button{font-size:1em;font-weight:700;color:#fff;background-color:#6eaece;padding:1em .5em;border:none;border-radius:10px;width:8rem;margin:0 .4rem}.file-input-button.load-export{padding:.8em .2em;width:7rem}.file-input-button.load-export img{max-height:1.2em}.file-input-button.export-button{background-color:#cb7133}.file-input-button.export-button:hover{background-color:#ad5b2d}.file-input-button img{max-height:2em;display:block;width:100%}.file-input-button:hover{background-color:#118ab2}#exportScene{background-color:#5bd99e}#exportScene:hover{background-color:#05c292}#importScene{background-color:#ecc25e}#importScene:hover{background-color:#d6b44d}\n"] }]
2165
- }], () => [{ type: EventDisplayService }, { type: i1$2.MatDialogRef }], { eventDataImportOptions: [{
2809
+ args: [{ standalone: false, selector: 'app-io-options-dialog', template: "<div class=\"dialog\">\n <h1 mat-dialog-title>Import and export</h1>\n <div mat-dialog-content class=\"dialog-content\">\n <div *ngIf=\"eventDataImportOptions.length > 0\">\n <p class=\"text-center\"><b>Event data</b></p>\n\n <div class=\"row centered\">\n <ng-container\n *ngFor=\"let eventDataImportOption of eventDataOptionsWithHandler\"\n >\n <input\n #eventDataFileInput\n type=\"file\"\n tabindex=\"-1\"\n class=\"file-input\"\n [accept]=\"eventDataImportOption.accept\"\n (change)=\"eventDataImportOption.handler($event.target.files)\"\n />\n <button\n class=\"file-input-button\"\n (click)=\"eventDataFileInput.click()\"\n >\n <svg class=\"item-icon\">\n <use href=\"assets/icons/eventData.svg#eventData\"></use>\n </svg>\n Load {{ eventDataImportOption.fileType }}\n </button>\n </ng-container>\n </div>\n </div>\n\n <p class=\"text-center\"><b>Geometries</b></p>\n\n <div class=\"row centered\">\n <input\n #objFileInput\n type=\"file\"\n tabindex=\"-1\"\n class=\"file-input\"\n accept=\".obj\"\n name=\"image\"\n (change)=\"handleOBJInput($event.target.files)\"\n />\n <button class=\"file-input-button\" (click)=\"objFileInput.click()\">\n <img src=\"assets/icons/obj.svg\" alt=\"OBJ file format icon\" /> Load .obj\n </button>\n\n <input\n #gltfFileInput\n type=\"file\"\n tabindex=\"-1\"\n class=\"file-input\"\n accept=\".gltf,.glb,.gltf.zip,.glb.zip\"\n name=\"image\"\n (change)=\"handleGLTFInput($event.target.files)\"\n />\n <button class=\"file-input-button\" (click)=\"gltfFileInput.click()\">\n <img src=\"assets/icons/gltf.svg\" alt=\"GLTF file format icon\" /> Load\n .gltf/.glb\n </button>\n\n <input\n #rootFileInput\n type=\"file\"\n tabindex=\"-1\"\n class=\"file-input\"\n accept=\".root\"\n (change)=\"handleROOTInput($event.target.files)\"\n />\n <button class=\"file-input-button\" (click)=\"rootFileInput.click()\">\n <img src=\"assets/icons/file-import.svg\" alt=\"\" /> Load .root\n </button>\n\n <input\n #rootJSONFileInput\n type=\"file\"\n tabindex=\"-1\"\n class=\"file-input\"\n accept=\".json.gz\"\n (change)=\"handleRootJSONInput($event.target.files)\"\n />\n <button class=\"file-input-button\" (click)=\"rootJSONFileInput.click()\">\n <img src=\"assets/icons/file-import.svg\" alt=\"\" /> Load .json.gz\n </button>\n </div>\n\n <p class=\"text-center\"><b>Scene</b></p>\n\n <div class=\"row centered\">\n <button\n class=\"file-input-button load-export\"\n id=\"exportScene\"\n (click)=\"saveScene()\"\n >\n <img src=\"assets/icons/save.svg\" alt=\"\" /> Save scene\n </button>\n\n <input\n #loadSceneInput\n type=\"file\"\n tabindex=\"-1\"\n class=\"file-input\"\n accept=\".phnx\"\n name=\"image\"\n (change)=\"handlePhoenixInput($event.target.files)\"\n />\n <button\n class=\"file-input-button load-export\"\n id=\"importScene\"\n (click)=\"loadSceneInput.click()\"\n >\n <img src=\"assets/icons/upload.svg\" alt=\"\" /> Load scene\n </button>\n\n <button\n class=\"file-input-button export-button\"\n id=\"exportOBJ\"\n (click)=\"exportOBJ()\"\n >\n <img src=\"assets/icons/obj.svg\" alt=\"\" /> Save OBJ\n </button>\n </div>\n </div>\n\n <div mat-dialog-actions align=\"end\">\n <button mat-flat-button color=\"primary\" (click)=\"onClose()\" cdkFocusInitial>\n Close\n </button>\n </div>\n</div>\n", styles: [".row{justify-content:center;margin:1rem 0}.file-input{width:.1px;height:.1px;opacity:0;overflow:hidden;position:absolute;z-index:-1}.file-input-button{font-size:1em;font-weight:700;color:#fff;background-color:#6eaece;padding:1em .5em;border:none;border-radius:10px;width:8rem;margin:0 .4rem}.file-input-button.load-export{padding:.8em .2em;width:7rem}.file-input-button.load-export img{max-height:1.2em}.file-input-button.export-button{background-color:#cb7133}.file-input-button.export-button:hover{background-color:#ad5b2d}.file-input-button img,.file-input-button svg{max-height:2em;display:block;width:100%}.file-input-button:hover{background-color:#118ab2}#exportScene{background-color:#5bd99e}#exportScene:hover{background-color:#05c292}#importScene{background-color:#ecc25e}#importScene:hover{background-color:#d6b44d}\n"] }]
2810
+ }], () => [{ type: EventDisplayService }, { type: i1$1.MatDialogRef }, { type: NotificationService }], { eventDataImportOptions: [{
2166
2811
  type: Input
2167
2812
  }] }); })();
2168
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(IOOptionsDialogComponent, { className: "IOOptionsDialogComponent", filePath: "components/ui-menu/io-options/io-options-dialog/io-options-dialog.component.ts", lineNumber: 23 }); })();
2813
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(IOOptionsDialogComponent, { className: "IOOptionsDialogComponent", filePath: "components/ui-menu/io-options/io-options-dialog/io-options-dialog.component.ts", lineNumber: 25 }); })();
2169
2814
 
2170
2815
  class IoOptionsComponent {
2171
2816
  dialog;
@@ -2180,7 +2825,7 @@ class IoOptionsComponent {
2180
2825
  dialogRef.componentInstance.eventDataImportOptions =
2181
2826
  this.eventDataImportOptions;
2182
2827
  }
2183
- static ɵfac = function IoOptionsComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || IoOptionsComponent)(i0.ɵɵdirectiveInject(i1$2.MatDialog)); };
2828
+ static ɵfac = function IoOptionsComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || IoOptionsComponent)(i0.ɵɵdirectiveInject(i1$1.MatDialog)); };
2184
2829
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: IoOptionsComponent, selectors: [["app-io-options"]], inputs: { eventDataImportOptions: "eventDataImportOptions" }, standalone: false, decls: 1, vars: 1, consts: [["tooltip", "Import and export options", "icon", "import", 3, "click", "active"]], template: function IoOptionsComponent_Template(rf, ctx) { if (rf & 1) {
2185
2830
  i0.ɵɵelementStart(0, "app-menu-toggle", 0);
2186
2831
  i0.ɵɵlistener("click", function IoOptionsComponent_Template_app_menu_toggle_click_0_listener() { return ctx.openIODialog(); });
@@ -2192,7 +2837,7 @@ class IoOptionsComponent {
2192
2837
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(IoOptionsComponent, [{
2193
2838
  type: Component,
2194
2839
  args: [{ standalone: false, selector: 'app-io-options', template: "<app-menu-toggle\n tooltip=\"Import and export options\"\n icon=\"import\"\n [active]=\"false\"\n (click)=\"openIODialog()\"\n>\n</app-menu-toggle>\n" }]
2195
- }], () => [{ type: i1$2.MatDialog }], { eventDataImportOptions: [{
2840
+ }], () => [{ type: i1$1.MatDialog }], { eventDataImportOptions: [{
2196
2841
  type: Input
2197
2842
  }] }); })();
2198
2843
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(IoOptionsComponent, { className: "IoOptionsComponent", filePath: "components/ui-menu/io-options/io-options.component.ts", lineNumber: 15 }); })();
@@ -2253,44 +2898,56 @@ class ObjectClippingComponent {
2253
2898
  this.clippingEnabled = value;
2254
2899
  }
2255
2900
  static ɵfac = function ObjectClippingComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ObjectClippingComponent)(i0.ɵɵdirectiveInject(EventDisplayService)); };
2256
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ObjectClippingComponent, selectors: [["app-object-clipping"]], standalone: false, decls: 17, vars: 5, consts: [["menu", ""], ["clippingCheckbox", ""], ["startingAngleSlider", ""], ["openingAngleSlider", ""], [1, "mat-menu"], ["mat-menu-item", "", 3, "click"], [3, "click", "change", "checked"], ["mat-menu-item", "", 1, "slider-btn", 3, "focus"], ["min", "0", "max", "360", "step", "1", "thumbLabel", ""], ["matSliderThumb", "", 3, "valueChange", "value"], ["tooltip", "Geometry clipping", "icon", "clipping", 3, "matMenuTriggerFor", "active"]], template: function ObjectClippingComponent_Template(rf, ctx) { if (rf & 1) {
2901
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ObjectClippingComponent, selectors: [["app-object-clipping"]], standalone: false, decls: 23, vars: 7, consts: [["menu", ""], ["clippingCheckbox", ""], ["startingAngleSlider", ""], ["startAngleInput", ""], ["openingAngleSlider", ""], ["openingAngleInput", ""], [1, "mat-menu"], ["mat-menu-item", "", 3, "click"], [3, "click", "change", "checked"], [1, "angle-label"], ["mat-menu-item", "", 1, "slider-btn", 3, "click", "focus"], ["min", "0", "max", "360", "step", "1", "thumbLabel", ""], ["matSliderThumb", "", 3, "valueChange", "value"], ["type", "number", "min", "0", "max", "360", "step", "1", 1, "angle-input", 3, "click", "keydown", "change", "value"], ["tooltip", "Geometry clipping", "icon", "clipping", 3, "matMenuTriggerFor", "active"]], template: function ObjectClippingComponent_Template(rf, ctx) { if (rf & 1) {
2257
2902
  const _r1 = i0.ɵɵgetCurrentView();
2258
- i0.ɵɵelementStart(0, "mat-menu", 4, 0)(2, "button", 5);
2903
+ i0.ɵɵelementStart(0, "mat-menu", 6, 0)(2, "button", 7);
2259
2904
  i0.ɵɵlistener("click", function ObjectClippingComponent_Template_button_click_2_listener($event) { i0.ɵɵrestoreView(_r1); const clippingCheckbox_r2 = i0.ɵɵreference(4); $event.stopPropagation(); return i0.ɵɵresetView(clippingCheckbox_r2._inputElement.nativeElement.click()); });
2260
- i0.ɵɵelementStart(3, "mat-checkbox", 6, 1);
2905
+ i0.ɵɵelementStart(3, "mat-checkbox", 8, 1);
2261
2906
  i0.ɵɵlistener("click", function ObjectClippingComponent_Template_mat_checkbox_click_3_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView($event.stopPropagation()); })("change", function ObjectClippingComponent_Template_mat_checkbox_change_3_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.toggleClipping($event)); });
2262
- i0.ɵɵtext(5, " Clipping ");
2263
- i0.ɵɵelementEnd()();
2264
- i0.ɵɵelementStart(6, "button", 7);
2265
- i0.ɵɵlistener("focus", function ObjectClippingComponent_Template_button_focus_6_listener() { i0.ɵɵrestoreView(_r1); const startingAngleSlider_r3 = i0.ɵɵreference(9); return i0.ɵɵresetView(startingAngleSlider_r3.focus()); });
2266
- i0.ɵɵelementStart(7, "mat-slider", 8)(8, "input", 9, 2);
2267
- i0.ɵɵlistener("valueChange", function ObjectClippingComponent_Template_input_valueChange_8_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.changeStartClippingAngle($event)); });
2907
+ i0.ɵɵtext(5, " Clipping Enabled ");
2268
2908
  i0.ɵɵelementEnd()();
2269
- i0.ɵɵtext(10, " Start Angle ");
2909
+ i0.ɵɵelementStart(6, "div", 9);
2910
+ i0.ɵɵtext(7, "Start Angle");
2270
2911
  i0.ɵɵelementEnd();
2271
- i0.ɵɵelementStart(11, "button", 7);
2272
- i0.ɵɵlistener("focus", function ObjectClippingComponent_Template_button_focus_11_listener() { i0.ɵɵrestoreView(_r1); const openingAngleSlider_r4 = i0.ɵɵreference(14); return i0.ɵɵresetView(openingAngleSlider_r4.focus()); });
2273
- i0.ɵɵelementStart(12, "mat-slider", 8)(13, "input", 9, 3);
2274
- i0.ɵɵlistener("valueChange", function ObjectClippingComponent_Template_input_valueChange_13_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.changeOpeningClippingAngle($event)); });
2912
+ i0.ɵɵelementStart(8, "button", 10);
2913
+ i0.ɵɵlistener("click", function ObjectClippingComponent_Template_button_click_8_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView($event.stopPropagation()); })("focus", function ObjectClippingComponent_Template_button_focus_8_listener() { i0.ɵɵrestoreView(_r1); const startingAngleSlider_r3 = i0.ɵɵreference(11); return i0.ɵɵresetView(startingAngleSlider_r3.focus()); });
2914
+ i0.ɵɵelementStart(9, "mat-slider", 11)(10, "input", 12, 2);
2915
+ i0.ɵɵlistener("valueChange", function ObjectClippingComponent_Template_input_valueChange_10_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.changeStartClippingAngle($event)); });
2275
2916
  i0.ɵɵelementEnd()();
2276
- i0.ɵɵtext(15, " Opening Angle ");
2917
+ i0.ɵɵelementStart(12, "input", 13, 3);
2918
+ i0.ɵɵlistener("click", function ObjectClippingComponent_Template_input_click_12_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView($event.stopPropagation()); })("keydown", function ObjectClippingComponent_Template_input_keydown_12_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView($event.stopPropagation()); })("change", function ObjectClippingComponent_Template_input_change_12_listener() { i0.ɵɵrestoreView(_r1); const startAngleInput_r4 = i0.ɵɵreference(13); return i0.ɵɵresetView(ctx.changeStartClippingAngle(startAngleInput_r4.valueAsNumber)); });
2919
+ i0.ɵɵelementEnd()();
2920
+ i0.ɵɵelementStart(14, "div", 9);
2921
+ i0.ɵɵtext(15, "Opening Angle");
2922
+ i0.ɵɵelementEnd();
2923
+ i0.ɵɵelementStart(16, "button", 10);
2924
+ i0.ɵɵlistener("click", function ObjectClippingComponent_Template_button_click_16_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView($event.stopPropagation()); })("focus", function ObjectClippingComponent_Template_button_focus_16_listener() { i0.ɵɵrestoreView(_r1); const openingAngleSlider_r5 = i0.ɵɵreference(19); return i0.ɵɵresetView(openingAngleSlider_r5.focus()); });
2925
+ i0.ɵɵelementStart(17, "mat-slider", 11)(18, "input", 12, 4);
2926
+ i0.ɵɵlistener("valueChange", function ObjectClippingComponent_Template_input_valueChange_18_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.changeOpeningClippingAngle($event)); });
2277
2927
  i0.ɵɵelementEnd()();
2278
- i0.ɵɵelement(16, "app-menu-toggle", 10);
2928
+ i0.ɵɵelementStart(20, "input", 13, 5);
2929
+ i0.ɵɵlistener("click", function ObjectClippingComponent_Template_input_click_20_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView($event.stopPropagation()); })("keydown", function ObjectClippingComponent_Template_input_keydown_20_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView($event.stopPropagation()); })("change", function ObjectClippingComponent_Template_input_change_20_listener() { i0.ɵɵrestoreView(_r1); const openingAngleInput_r6 = i0.ɵɵreference(21); return i0.ɵɵresetView(ctx.changeOpeningClippingAngle(openingAngleInput_r6.valueAsNumber)); });
2930
+ i0.ɵɵelementEnd()()();
2931
+ i0.ɵɵelement(22, "app-menu-toggle", 14);
2279
2932
  } if (rf & 2) {
2280
- const menu_r5 = i0.ɵɵreference(1);
2933
+ const menu_r7 = i0.ɵɵreference(1);
2281
2934
  i0.ɵɵadvance(3);
2282
2935
  i0.ɵɵproperty("checked", ctx.clippingEnabled);
2283
- i0.ɵɵadvance(5);
2936
+ i0.ɵɵadvance(7);
2284
2937
  i0.ɵɵproperty("value", ctx.startClippingAngle);
2285
- i0.ɵɵadvance(5);
2938
+ i0.ɵɵadvance(2);
2939
+ i0.ɵɵproperty("value", ctx.startClippingAngle);
2940
+ i0.ɵɵadvance(6);
2941
+ i0.ɵɵproperty("value", ctx.openingClippingAngle);
2942
+ i0.ɵɵadvance(2);
2286
2943
  i0.ɵɵproperty("value", ctx.openingClippingAngle);
2287
- i0.ɵɵadvance(3);
2288
- i0.ɵɵproperty("matMenuTriggerFor", menu_r5)("active", ctx.clippingEnabled);
2289
- } }, dependencies: [i3$2.MatMenu, i3$2.MatMenuItem, i3$2.MatMenuTrigger, i2$1.MatSlider, i2$1.MatSliderThumb, i5.MatCheckbox, MenuToggleComponent], styles: [".slider-btn[_ngcontent-%COMP%]{overflow:visible}mat-slider[_ngcontent-%COMP%]{margin-left:.75rem;margin-right:.75rem}"] });
2944
+ i0.ɵɵadvance(2);
2945
+ i0.ɵɵproperty("matMenuTriggerFor", menu_r7)("active", ctx.clippingEnabled);
2946
+ } }, dependencies: [i4$2.MatMenu, i4$2.MatMenuItem, i4$2.MatMenuTrigger, i2$1.MatSlider, i2$1.MatSliderThumb, i5.MatCheckbox, MenuToggleComponent], styles: [".slider-btn[_ngcontent-%COMP%]{overflow:visible}.angle-label[_ngcontent-%COMP%]{padding:.5rem 1rem 0;font-size:.75rem;opacity:.6}mat-slider[_ngcontent-%COMP%]{margin-left:.75rem;margin-right:.75rem}.angle-input[_ngcontent-%COMP%]{width:4.5rem;margin-right:.5rem;padding:.25rem .35rem;text-align:right;box-sizing:border-box}"] });
2290
2947
  }
2291
2948
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ObjectClippingComponent, [{
2292
2949
  type: Component,
2293
- args: [{ standalone: false, selector: 'app-object-clipping', template: "<mat-menu class=\"mat-menu\" #menu>\n <button\n mat-menu-item\n (click)=\"\n $event.stopPropagation();\n clippingCheckbox._inputElement.nativeElement.click()\n \"\n >\n <mat-checkbox\n #clippingCheckbox\n [checked]=\"clippingEnabled\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleClipping($event)\"\n >\n Clipping\n </mat-checkbox>\n </button>\n <button\n class=\"slider-btn\"\n mat-menu-item\n (focus)=\"startingAngleSlider.focus()\"\n >\n <mat-slider min=\"0\" max=\"360\" step=\"1\" thumbLabel>\n <input\n #startingAngleSlider\n matSliderThumb\n [value]=\"startClippingAngle\"\n (valueChange)=\"changeStartClippingAngle($event)\"\n />\n </mat-slider>\n Start Angle\n </button>\n <button class=\"slider-btn\" mat-menu-item (focus)=\"openingAngleSlider.focus()\">\n <mat-slider min=\"0\" max=\"360\" step=\"1\" thumbLabel>\n <input\n #openingAngleSlider\n matSliderThumb\n [value]=\"openingClippingAngle\"\n (valueChange)=\"changeOpeningClippingAngle($event)\"\n />\n </mat-slider>\n Opening Angle\n </button>\n</mat-menu>\n\n<app-menu-toggle\n [matMenuTriggerFor]=\"menu\"\n tooltip=\"Geometry clipping\"\n icon=\"clipping\"\n [active]=\"clippingEnabled\"\n>\n</app-menu-toggle>\n", styles: [".slider-btn{overflow:visible}mat-slider{margin-left:.75rem;margin-right:.75rem}\n"] }]
2950
+ args: [{ standalone: false, selector: 'app-object-clipping', template: "<mat-menu class=\"mat-menu\" #menu>\n <button\n mat-menu-item\n (click)=\"\n $event.stopPropagation();\n clippingCheckbox._inputElement.nativeElement.click()\n \"\n >\n <mat-checkbox\n #clippingCheckbox\n [checked]=\"clippingEnabled\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleClipping($event)\"\n >\n Clipping Enabled\n </mat-checkbox>\n </button>\n <div class=\"angle-label\">Start Angle</div>\n <button\n class=\"slider-btn\"\n mat-menu-item\n (click)=\"$event.stopPropagation()\"\n (focus)=\"startingAngleSlider.focus()\"\n >\n <mat-slider min=\"0\" max=\"360\" step=\"1\" thumbLabel>\n <input\n #startingAngleSlider\n matSliderThumb\n [value]=\"startClippingAngle\"\n (valueChange)=\"changeStartClippingAngle($event)\"\n />\n </mat-slider>\n <input\n #startAngleInput\n class=\"angle-input\"\n type=\"number\"\n min=\"0\"\n max=\"360\"\n step=\"1\"\n [value]=\"startClippingAngle\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\"\n (change)=\"changeStartClippingAngle(startAngleInput.valueAsNumber)\"\n />\n </button>\n\n <div class=\"angle-label\">Opening Angle</div>\n <button\n class=\"slider-btn\"\n mat-menu-item\n (click)=\"$event.stopPropagation()\"\n (focus)=\"openingAngleSlider.focus()\"\n >\n <mat-slider min=\"0\" max=\"360\" step=\"1\" thumbLabel>\n <input\n #openingAngleSlider\n matSliderThumb\n [value]=\"openingClippingAngle\"\n (valueChange)=\"changeOpeningClippingAngle($event)\"\n />\n </mat-slider>\n <input\n #openingAngleInput\n class=\"angle-input\"\n type=\"number\"\n min=\"0\"\n max=\"360\"\n step=\"1\"\n [value]=\"openingClippingAngle\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\"\n (change)=\"changeOpeningClippingAngle(openingAngleInput.valueAsNumber)\"\n />\n </button>\n</mat-menu>\n\n<app-menu-toggle\n [matMenuTriggerFor]=\"menu\"\n tooltip=\"Geometry clipping\"\n icon=\"clipping\"\n [active]=\"clippingEnabled\"\n>\n</app-menu-toggle>\n", styles: [".slider-btn{overflow:visible}.angle-label{padding:.5rem 1rem 0;font-size:.75rem;opacity:.6}mat-slider{margin-left:.75rem;margin-right:.75rem}.angle-input{width:4.5rem;margin-right:.5rem;padding:.25rem .35rem;text-align:right;box-sizing:border-box}\n"] }]
2294
2951
  }], () => [{ type: EventDisplayService }], null); })();
2295
2952
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ObjectClippingComponent, { className: "ObjectClippingComponent", filePath: "components/ui-menu/object-clipping/object-clipping.component.ts", lineNumber: 11 }); })();
2296
2953
 
@@ -2402,7 +3059,7 @@ class ObjectSelectionComponent {
2402
3059
  this.overlayWindow.instance.hiddenSelectedInfo = this.hiddenSelectedInfo;
2403
3060
  this.eventDisplay.enableSelecting(!this.hiddenSelectedInfo);
2404
3061
  }
2405
- static ɵfac = function ObjectSelectionComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ObjectSelectionComponent)(i0.ɵɵdirectiveInject(i1$1.Overlay), i0.ɵɵdirectiveInject(EventDisplayService)); };
3062
+ static ɵfac = function ObjectSelectionComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ObjectSelectionComponent)(i0.ɵɵdirectiveInject(i1$3.Overlay), i0.ɵɵdirectiveInject(EventDisplayService)); };
2406
3063
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ObjectSelectionComponent, selectors: [["app-object-selection"]], standalone: false, decls: 1, vars: 1, consts: [["tooltip", "Object selection", "icon", "cursor", 3, "click", "active"]], template: function ObjectSelectionComponent_Template(rf, ctx) { if (rf & 1) {
2407
3064
  i0.ɵɵelementStart(0, "app-menu-toggle", 0);
2408
3065
  i0.ɵɵlistener("click", function ObjectSelectionComponent_Template_app_menu_toggle_click_0_listener() { return ctx.toggleOverlay(); });
@@ -2414,7 +3071,7 @@ class ObjectSelectionComponent {
2414
3071
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ObjectSelectionComponent, [{
2415
3072
  type: Component,
2416
3073
  args: [{ standalone: false, selector: 'app-object-selection', template: "<app-menu-toggle\n tooltip=\"Object selection\"\n icon=\"cursor\"\n [active]=\"!hiddenSelectedInfo\"\n (click)=\"toggleOverlay()\"\n>\n</app-menu-toggle>\n" }]
2417
- }], () => [{ type: i1$1.Overlay }, { type: EventDisplayService }], null); })();
3074
+ }], () => [{ type: i1$3.Overlay }, { type: EventDisplayService }], null); })();
2418
3075
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ObjectSelectionComponent, { className: "ObjectSelectionComponent", filePath: "components/ui-menu/object-selection/object-selection.component.ts", lineNumber: 18 }); })();
2419
3076
 
2420
3077
  const _c0$9 = ["overlayWindow"];
@@ -2542,7 +3199,7 @@ class OverlayViewComponent {
2542
3199
  this.showOverlay = !this.showOverlay;
2543
3200
  this.overlayWindow.instance.showOverlay = this.showOverlay;
2544
3201
  }
2545
- static ɵfac = function OverlayViewComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || OverlayViewComponent)(i0.ɵɵdirectiveInject(i1$1.Overlay)); };
3202
+ static ɵfac = function OverlayViewComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || OverlayViewComponent)(i0.ɵɵdirectiveInject(i1$3.Overlay)); };
2546
3203
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: OverlayViewComponent, selectors: [["app-overlay-view"]], standalone: false, decls: 1, vars: 1, consts: [["tooltip", "Overlay view", "icon", "overlay", 3, "click", "active"]], template: function OverlayViewComponent_Template(rf, ctx) { if (rf & 1) {
2547
3204
  i0.ɵɵelementStart(0, "app-menu-toggle", 0);
2548
3205
  i0.ɵɵlistener("click", function OverlayViewComponent_Template_app_menu_toggle_click_0_listener() { return ctx.toggleOverlay(); });
@@ -2554,7 +3211,7 @@ class OverlayViewComponent {
2554
3211
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(OverlayViewComponent, [{
2555
3212
  type: Component,
2556
3213
  args: [{ standalone: false, selector: 'app-overlay-view', template: "<app-menu-toggle\n tooltip=\"Overlay view\"\n icon=\"overlay\"\n [active]=\"showOverlay\"\n (click)=\"toggleOverlay()\"\n>\n</app-menu-toggle>\n" }]
2557
- }], () => [{ type: i1$1.Overlay }], null); })();
3214
+ }], () => [{ type: i1$3.Overlay }], null); })();
2558
3215
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(OverlayViewComponent, { className: "OverlayViewComponent", filePath: "components/ui-menu/overlay-view/overlay-view.component.ts", lineNumber: 17 }); })();
2559
3216
 
2560
3217
  const _c0$8 = a0 => ({ visible: a0 });
@@ -2632,7 +3289,7 @@ class TreeMenuItemComponent {
2632
3289
  i0.ɵɵtemplate(0, TreeMenuItemComponent_ng_container_0_Template, 8, 7, "ng-container", 0);
2633
3290
  } if (rf & 2) {
2634
3291
  i0.ɵɵproperty("ngIf", ctx.node);
2635
- } }, dependencies: [i2.NgClass, i2.NgForOf, i2.NgIf, i3.MatIconButton, i4$2.MatIcon, TreeMenuItemComponent], styles: [".header[_ngcontent-%COMP%]{cursor:pointer;user-select:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;display:flex;flex-direction:row;align-items:center}.header[_ngcontent-%COMP%] .node-name[_ngcontent-%COMP%], .header[_ngcontent-%COMP%] .mat-icon-rtl-mirror[_ngcontent-%COMP%]{color:var(--phoenix-text-color-secondary)}.header.visible[_ngcontent-%COMP%] .node-name[_ngcontent-%COMP%], .header.visible[_ngcontent-%COMP%] .mat-icon-rtl-mirror[_ngcontent-%COMP%]{color:var(--phoenix-accent)}.children[_ngcontent-%COMP%] li[_ngcontent-%COMP%]{list-style-type:none}"] });
3292
+ } }, dependencies: [i2.NgClass, i2.NgForOf, i2.NgIf, i3.MatIconButton, i2$2.MatIcon, TreeMenuItemComponent], styles: [".header[_ngcontent-%COMP%]{cursor:pointer;user-select:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;display:flex;flex-direction:row;align-items:center}.header[_ngcontent-%COMP%] .node-name[_ngcontent-%COMP%], .header[_ngcontent-%COMP%] .mat-icon-rtl-mirror[_ngcontent-%COMP%]{color:var(--phoenix-text-color-secondary)}.header.visible[_ngcontent-%COMP%] .node-name[_ngcontent-%COMP%], .header.visible[_ngcontent-%COMP%] .mat-icon-rtl-mirror[_ngcontent-%COMP%]{color:var(--phoenix-accent)}.children[_ngcontent-%COMP%] li[_ngcontent-%COMP%]{list-style-type:none}"] });
2636
3293
  }
2637
3294
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TreeMenuItemComponent, [{
2638
3295
  type: Component,
@@ -2752,21 +3409,49 @@ class CartesianGridConfigComponent {
2752
3409
  shiftCartesianGridByPointer() {
2753
3410
  this.shiftGrid = true;
2754
3411
  this.eventDisplay.getUIManager().shiftCartesianGridByPointer();
3412
+ // Drop any subscriptions from a previous shift before starting a new one.
3413
+ this.clearShiftSubscriptions();
2755
3414
  this.originChangedSub = this.eventDisplay
2756
3415
  .getThreeManager()
2757
3416
  .originChanged.subscribe((intersect) => {
2758
3417
  this.translateGrid(intersect);
2759
3418
  });
3419
+ // `stopShifting` may emit synchronously during `subscribe`, in which case
3420
+ // the handler runs before `stopShiftingSub` has been assigned. Track that
3421
+ // with a flag so the teardown still happens (and never dereferences null).
3422
+ let stoppedDuringSubscribe = false;
2760
3423
  this.stopShiftingSub = this.eventDisplay
2761
3424
  .getThreeManager()
2762
3425
  .stopShifting.subscribe((stop) => {
2763
- if (stop) {
2764
- this.originChangedSub.unsubscribe();
2765
- this.stopShiftingSub.unsubscribe();
3426
+ if (!stop) {
3427
+ return;
3428
+ }
3429
+ if (this.stopShiftingSub) {
3430
+ this.clearShiftSubscriptions();
3431
+ }
3432
+ else {
3433
+ stoppedDuringSubscribe = true;
2766
3434
  }
2767
3435
  });
3436
+ if (stoppedDuringSubscribe) {
3437
+ this.clearShiftSubscriptions();
3438
+ }
2768
3439
  this.onClose();
2769
3440
  }
3441
+ /**
3442
+ * Unsubscribe from the grid-shifting subscriptions, if any are active.
3443
+ */
3444
+ clearShiftSubscriptions() {
3445
+ this.originChangedSub?.unsubscribe();
3446
+ this.originChangedSub = null;
3447
+ this.stopShiftingSub?.unsubscribe();
3448
+ this.stopShiftingSub = null;
3449
+ }
3450
+ ngOnDestroy() {
3451
+ // The dialog closes as soon as pointer shifting starts, so without this the
3452
+ // subscriptions outlive the component and leak on every reopen.
3453
+ this.clearShiftSubscriptions();
3454
+ }
2770
3455
  shiftCartesianGridByValues(position) {
2771
3456
  this.translateGrid(position);
2772
3457
  this.eventDisplay.getThreeManager().originChangedEmit(position);
@@ -2818,7 +3503,7 @@ class CartesianGridConfigComponent {
2818
3503
  calcPlanes(dis) {
2819
3504
  return Math.max(0, 1 + 2 * Math.floor((dis * 10) / (this.scale * this.gridConfig.sparsity)));
2820
3505
  }
2821
- static ɵfac = function CartesianGridConfigComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || CartesianGridConfigComponent)(i0.ɵɵdirectiveInject(MAT_DIALOG_DATA), i0.ɵɵdirectiveInject(i1$2.MatDialogRef), i0.ɵɵdirectiveInject(EventDisplayService)); };
3506
+ static ɵfac = function CartesianGridConfigComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || CartesianGridConfigComponent)(i0.ɵɵdirectiveInject(MAT_DIALOG_DATA), i0.ɵɵdirectiveInject(i1$1.MatDialogRef), i0.ɵɵdirectiveInject(EventDisplayService)); };
2822
3507
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: CartesianGridConfigComponent, selectors: [["app-cartesian-grid-config"]], standalone: false, decls: 65, vars: 28, consts: [["xPos", ""], ["yPos", ""], ["zPos", ""], ["showXYPlanesCheckbox", ""], ["xyPlaneSlider", ""], ["showYZPlanesCheckbox", ""], ["yzPlaneSlider", ""], ["showZXPlanesCheckbox", ""], ["zxPlaneSlider", ""], ["sparsitySlider", ""], ["mat-dialog-title", ""], ["mat-dialog-content", "", 1, "container", "dialog-content"], [1, "item-config-single"], [1, "item-config-group"], ["for", "xPos", 1, "item-config-label"], ["type", "number", "placeholder", "Origin X", "name", "xPos", "id", "xPos", 1, "form-control", 3, "value"], ["for", "yPos", 1, "item-config-label"], ["type", "number", "placeholder", "Origin Y", "name", "yPos", "id", "yPos", 1, "form-control", 3, "value"], ["for", "zPos", 1, "item-config-label"], ["type", "number", "placeholder", "Origin Z", "name", "zPos", "id", "zPos", 1, "form-control", 3, "value"], ["mat-stroked-button", "", 3, "click", "disabled"], ["mat-menu-item", "", 1, "explain-button", 2, "margin", "0.5rem 0", 3, "click", "disabled"], [1, "explain-text"], ["mat-menu-item", "", 3, "click"], [3, "ngModelChange", "click", "change", "ngModel"], ["mat-menu-item", "", 1, "slider-btn", 3, "focus"], ["min", "-300", "step", "300", "thumbLabel", "", 3, "max"], ["matSliderThumb", "", 3, "input", "value"], ["min", "1", "max", "5", "step", "1", "thumbLabel", ""], ["mat-dialog-actions", "", "align", "end"], ["mat-flat-button", "", "color", "primary", "cdkFocusInitial", "", 3, "click"]], template: function CartesianGridConfigComponent_Template(rf, ctx) { if (rf & 1) {
2823
3508
  const _r1 = i0.ɵɵgetCurrentView();
2824
3509
  i0.ɵɵelementStart(0, "div")(1, "h1", 10);
@@ -2944,7 +3629,7 @@ class CartesianGridConfigComponent {
2944
3629
  i0.ɵɵproperty("value", ctx.gridConfig.sparsity);
2945
3630
  i0.ɵɵadvance(2);
2946
3631
  i0.ɵɵtextInterpolate1(" Sparsity (", ctx.gridConfig.sparsity, ") ");
2947
- } }, dependencies: [i1$2.MatDialogTitle, i1$2.MatDialogActions, i1$2.MatDialogContent, i3.MatButton, i4.NgControlStatus, i4.NgModel, i3$2.MatMenuItem, i2$1.MatSlider, i2$1.MatSliderThumb, i5.MatCheckbox, i2.DecimalPipe], styles: [".container[_ngcontent-%COMP%]{display:flex;flex-direction:row}.item-config-single[_ngcontent-%COMP%]{display:flex;flex-direction:column}.item-config-single[_ngcontent-%COMP%] .item-config-group[_ngcontent-%COMP%]{margin:.1rem 2rem}.item-config-single[_ngcontent-%COMP%] .item-config-group[_ngcontent-%COMP%] .item-config-label[_ngcontent-%COMP%]{display:inline;margin:1rem}.item-config-single[_ngcontent-%COMP%] .item-config-group[_ngcontent-%COMP%] .form-control[_ngcontent-%COMP%]{width:60%;display:inline}.item-config-single[_ngcontent-%COMP%] button[_ngcontent-%COMP%]{margin:.5rem 4rem}.item-config-single[_ngcontent-%COMP%] .explain-button[_ngcontent-%COMP%]{padding-top:1rem}.item-config-single[_ngcontent-%COMP%] .explain-text[_ngcontent-%COMP%]{width:20rem;opacity:.7;margin-top:.5rem;font-size:.9rem}"] });
3632
+ } }, dependencies: [i1$1.MatDialogTitle, i1$1.MatDialogActions, i1$1.MatDialogContent, i3.MatButton, i4.NgControlStatus, i4.NgModel, i4$2.MatMenuItem, i2$1.MatSlider, i2$1.MatSliderThumb, i5.MatCheckbox, i2.DecimalPipe], styles: [".container[_ngcontent-%COMP%]{display:flex;flex-direction:row}.item-config-single[_ngcontent-%COMP%]{display:flex;flex-direction:column}.item-config-single[_ngcontent-%COMP%] .item-config-group[_ngcontent-%COMP%]{margin:.1rem 2rem}.item-config-single[_ngcontent-%COMP%] .item-config-group[_ngcontent-%COMP%] .item-config-label[_ngcontent-%COMP%]{display:inline;margin:1rem}.item-config-single[_ngcontent-%COMP%] .item-config-group[_ngcontent-%COMP%] .form-control[_ngcontent-%COMP%]{width:60%;display:inline}.item-config-single[_ngcontent-%COMP%] button[_ngcontent-%COMP%]{margin:.5rem 4rem}.item-config-single[_ngcontent-%COMP%] .explain-button[_ngcontent-%COMP%]{padding-top:1rem}.item-config-single[_ngcontent-%COMP%] .explain-text[_ngcontent-%COMP%]{width:20rem;opacity:.7;margin-top:.5rem;font-size:.9rem}"] });
2948
3633
  }
2949
3634
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(CartesianGridConfigComponent, [{
2950
3635
  type: Component,
@@ -2952,7 +3637,7 @@ class CartesianGridConfigComponent {
2952
3637
  }], () => [{ type: undefined, decorators: [{
2953
3638
  type: Inject,
2954
3639
  args: [MAT_DIALOG_DATA]
2955
- }] }, { type: i1$2.MatDialogRef }, { type: EventDisplayService }], null); })();
3640
+ }] }, { type: i1$1.MatDialogRef }, { type: EventDisplayService }], null); })();
2956
3641
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(CartesianGridConfigComponent, { className: "CartesianGridConfigComponent", filePath: "components/ui-menu/view-options/cartesian-grid-config/cartesian-grid-config.component.ts", lineNumber: 14 }); })();
2957
3642
 
2958
3643
  function ViewOptionsComponent_button_29__svg_svg_1_Template(rf, ctx) { if (rf & 1) {
@@ -3043,9 +3728,11 @@ class ViewOptionsComponent {
3043
3728
  this.eventDisplay.getUIManager().show3DDistance(change.checked);
3044
3729
  }
3045
3730
  ngOnDestroy() {
3046
- this.sub.unsubscribe();
3731
+ // The component can be destroyed before ngOnInit has created the
3732
+ // subscription, so guard rather than dereferencing it unconditionally.
3733
+ this.sub?.unsubscribe();
3047
3734
  }
3048
- static ɵfac = function ViewOptionsComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ViewOptionsComponent)(i0.ɵɵdirectiveInject(EventDisplayService), i0.ɵɵdirectiveInject(i1$2.MatDialog)); };
3735
+ static ɵfac = function ViewOptionsComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ViewOptionsComponent)(i0.ɵɵdirectiveInject(EventDisplayService), i0.ɵɵdirectiveInject(i1$1.MatDialog)); };
3049
3736
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ViewOptionsComponent, selectors: [["app-view-options"]], viewQuery: function ViewOptionsComponent_Query(rf, ctx) { if (rf & 1) {
3050
3737
  i0.ɵɵviewQuery(MatMenuTrigger, 5);
3051
3738
  } if (rf & 2) {
@@ -3103,12 +3790,12 @@ class ViewOptionsComponent {
3103
3790
  i0.ɵɵproperty("ngForOf", ctx.views);
3104
3791
  i0.ɵɵadvance();
3105
3792
  i0.ɵɵproperty("matMenuTriggerFor", viewOptions_r10)("active", false);
3106
- } }, dependencies: [i2.NgForOf, i2.NgIf, i3$2.MatMenu, i3$2.MatMenuItem, i3$2.MatMenuTrigger, i5.MatCheckbox, MenuToggleComponent], styles: [".view-icon[_ngcontent-%COMP%]{width:1.2rem;height:1.2rem;margin-right:.5rem}.icon-wrapper[_ngcontent-%COMP%]{display:inline-block;width:1.5rem;height:1.5rem;padding:.23rem;transition:all .4s;transform:translateY(27%)}.icon-wrapper.icon-button[_ngcontent-%COMP%]:hover{background:var(--phoenix-options-icon-bg);border-radius:40%;cursor:pointer}.icon-wrapper[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{width:100%;height:100%;vertical-align:top}.item-settings[_ngcontent-%COMP%]{margin-right:.2rem;margin-left:.5rem}"] });
3793
+ } }, dependencies: [i2.NgForOf, i2.NgIf, i4$2.MatMenu, i4$2.MatMenuItem, i4$2.MatMenuTrigger, i5.MatCheckbox, MenuToggleComponent], styles: [".view-icon[_ngcontent-%COMP%]{width:1.2rem;height:1.2rem;margin-right:.5rem}.icon-wrapper[_ngcontent-%COMP%]{display:inline-block;width:1.5rem;height:1.5rem;padding:.23rem;transition:all .4s;transform:translateY(27%)}.icon-wrapper.icon-button[_ngcontent-%COMP%]:hover{background:var(--phoenix-options-icon-bg);border-radius:40%;cursor:pointer}.icon-wrapper[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{width:100%;height:100%;vertical-align:top}.item-settings[_ngcontent-%COMP%]{margin-right:.2rem;margin-left:.5rem}"] });
3107
3794
  }
3108
3795
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ViewOptionsComponent, [{
3109
3796
  type: Component,
3110
3797
  args: [{ standalone: false, selector: 'app-view-options', template: "<mat-menu #viewOptions>\n <button mat-menu-item>\n <mat-checkbox\n #showCartesianGridCheckbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"setCartesianGrid($event)\"\n >Show Cartesian Grid\n </mat-checkbox>\n <button\n class=\"icon-wrapper icon-button btn-blank item-settings\"\n (click)=\"openCartesianGridConfigDialog()\"\n >\n <svg>\n <use href=\"assets/icons/gear.svg#gear\"></use>\n </svg>\n </button>\n </button>\n <button\n mat-menu-item\n (click)=\"\n $event.stopPropagation();\n showEtaPhiGridCheckbox._inputElement.nativeElement.click()\n \"\n >\n <mat-checkbox\n #showEtaPhiGridCheckbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"setEtaPhiGrid($event)\"\n >Show Eta Phi Grid\n </mat-checkbox>\n </button>\n <button\n mat-menu-item\n (click)=\"\n $event.stopPropagation();\n showAxisCheckbox._inputElement.nativeElement.click()\n \"\n >\n <mat-checkbox\n #showAxisCheckbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"setAxis($event)\"\n >Show Axis\n </mat-checkbox>\n </button>\n <button\n mat-menu-item\n (click)=\"showLabelsCheckbox._inputElement.nativeElement.click()\"\n >\n <mat-checkbox\n #showLabelsCheckbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"showLabels($event)\"\n >Show Labels\n </mat-checkbox>\n </button>\n <button\n mat-menu-item\n (click)=\"\n $event.stopPropagation();\n showMouse3DPointsCheckbox._inputElement.nativeElement.click()\n \"\n >\n <mat-checkbox\n #showMouse3DPointsCheckbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"show3DMousePoints($event)\"\n >Show 3D Coordinates\n </mat-checkbox>\n </button>\n <button\n mat-menu-item\n (click)=\"\n $event.stopPropagation();\n show3DDistanceCheckbox._inputElement.nativeElement.click()\n \"\n >\n <mat-checkbox\n #show3DDistanceCheckbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleShowDistance($event)\"\n >Show 3D Distance\n </mat-checkbox>\n </button>\n <button\n mat-menu-item\n *ngFor=\"let view of views\"\n (click)=\"displayView($event, view)\"\n >\n <svg class=\"view-icon\" *ngIf=\"view.icon\">\n <use attr.href=\"assets/icons/{{ view.icon }}.svg#{{ view.icon }}\"></use>\n </svg>\n {{ view.name }}\n </button>\n</mat-menu>\n\n<app-menu-toggle\n [matMenuTriggerFor]=\"viewOptions\"\n tooltip=\"View options and Tools\"\n icon=\"views\"\n [active]=\"false\"\n>\n</app-menu-toggle>\n", styles: [".view-icon{width:1.2rem;height:1.2rem;margin-right:.5rem}.icon-wrapper{display:inline-block;width:1.5rem;height:1.5rem;padding:.23rem;transition:all .4s;transform:translateY(27%)}.icon-wrapper.icon-button:hover{background:var(--phoenix-options-icon-bg);border-radius:40%;cursor:pointer}.icon-wrapper svg{width:100%;height:100%;vertical-align:top}.item-settings{margin-right:.2rem;margin-left:.5rem}\n"] }]
3111
- }], () => [{ type: EventDisplayService }, { type: i1$2.MatDialog }], { trigger: [{
3798
+ }], () => [{ type: EventDisplayService }, { type: i1$1.MatDialog }], { trigger: [{
3112
3799
  type: ViewChild,
3113
3800
  args: [MatMenuTrigger]
3114
3801
  }] }); })();
@@ -3229,7 +3916,7 @@ class ArToggleComponent {
3229
3916
  i0.ɵɵproperty("ngIf", !ctx.arSupported);
3230
3917
  i0.ɵɵadvance();
3231
3918
  i0.ɵɵproperty("matMenuTriggerFor", arToggleOptions_r4)("disabled", !ctx.arSupported)("active", ctx.arActive)("tooltip", ctx.arSupported ? ctx.arActive ? "Exit AR" : "Enter AR" : "AR not supported");
3232
- } }, dependencies: [i2.NgIf, i3$2.MatMenu, i3$2.MatMenuItem, i3$2.MatMenuTrigger, MenuToggleComponent], encapsulation: 2 });
3919
+ } }, dependencies: [i2.NgIf, i4$2.MatMenu, i4$2.MatMenuItem, i4$2.MatMenuTrigger, MenuToggleComponent], encapsulation: 2 });
3233
3920
  }
3234
3921
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ArToggleComponent, [{
3235
3922
  type: Component,
@@ -3521,7 +4208,7 @@ class MakePictureComponent {
3521
4208
  i0.ɵɵtextInterpolate1(" ", ctx.buttonText(), " ");
3522
4209
  i0.ɵɵadvance();
3523
4210
  i0.ɵɵproperty("matMenuTriggerFor", menu_r5)("active", ctx.ssMode);
3524
- } }, dependencies: [i2.NgForOf, i4.NgControlStatus, i4.NgModel, i3$2.MatMenu, i3$2.MatMenuItem, i3$2.MatMenuTrigger, i5$1.MatRadioGroup, i5$1.MatRadioButton, MenuToggleComponent], styles: [".size-input{background:transparent;color:var(--phoenix-text-color);text-align:center}.size-input label{width:60px}.size-input input{background:transparent;color:var(--phoenix-text-color);width:80px}.badSize{border-color:red}.make-picture{text-align:center;display:block}.screenshot-mode span{padding:4px 8px;border:2px solid var(--phoenix-text-color);border-radius:8px;display:inline-block}.make-picture span{padding:5px 25px;border:2px solid var(--phoenix-text-color);border-radius:8px}#uiMenu,#phoenixMenu,#mainLogo,#statsElement{transition:opacity .4s}.ss-mode #uiMenu,.ss-mode #phoenixMenu,.ss-mode #mainLogo,.ss-mode #statsElement{visibility:hidden;opacity:0}\n"], encapsulation: 2 });
4211
+ } }, dependencies: [i2.NgForOf, i4.NgControlStatus, i4.NgModel, i4$2.MatMenu, i4$2.MatMenuItem, i4$2.MatMenuTrigger, i5$1.MatRadioGroup, i5$1.MatRadioButton, MenuToggleComponent], styles: [".size-input{background:transparent;color:var(--phoenix-text-color);text-align:center}.size-input label{width:60px}.size-input input{background:transparent;color:var(--phoenix-text-color);width:80px}.badSize{border-color:red}.make-picture{text-align:center;display:block}.screenshot-mode span{padding:4px 8px;border:2px solid var(--phoenix-text-color);border-radius:8px;display:inline-block}.make-picture span{padding:5px 25px;border:2px solid var(--phoenix-text-color);border-radius:8px}#uiMenu,#phoenixMenu,#mainLogo,#statsElement{transition:opacity .4s}.ss-mode #uiMenu,.ss-mode #phoenixMenu,.ss-mode #mainLogo,.ss-mode #statsElement{visibility:hidden;opacity:0}\n"], encapsulation: 2 });
3525
4212
  }
3526
4213
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MakePictureComponent, [{
3527
4214
  type: Component,
@@ -3766,7 +4453,7 @@ class ShareLinkDialogComponent {
3766
4453
  updateQRCode(link) {
3767
4454
  QRCode.toCanvas(this.qrcodeCanvas.nativeElement, link);
3768
4455
  }
3769
- static ɵfac = function ShareLinkDialogComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ShareLinkDialogComponent)(i0.ɵɵdirectiveInject(i1$2.MatDialogRef), i0.ɵɵdirectiveInject(EventDisplayService)); };
4456
+ static ɵfac = function ShareLinkDialogComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ShareLinkDialogComponent)(i0.ɵɵdirectiveInject(i1$1.MatDialogRef), i0.ɵɵdirectiveInject(EventDisplayService)); };
3770
4457
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ShareLinkDialogComponent, selectors: [["app-share-link-dialog"]], viewQuery: function ShareLinkDialogComponent_Query(rf, ctx) { if (rf & 1) {
3771
4458
  i0.ɵɵviewQuery(_c0$6, 5);
3772
4459
  } if (rf & 2) {
@@ -3855,12 +4542,12 @@ class ShareLinkDialogComponent {
3855
4542
  i0.ɵɵtextInterpolate(ctx.shareLink.value);
3856
4543
  i0.ɵɵadvance(7);
3857
4544
  i0.ɵɵtextInterpolate(ctx.embedLink);
3858
- } }, dependencies: [i1$2.MatDialogTitle, i1$2.MatDialogActions, i1$2.MatDialogContent, i3.MatButton, i4.NgSelectOption, i4.ɵNgSelectMultipleOption, i5.MatCheckbox], styles: [".share-box[_ngcontent-%COMP%]{width:30rem;max-width:100%;position:relative;background:var(--phoenix-background-color-tertiary);border:1px solid var(--phoenix-border);border-radius:.5rem;padding:1.25rem;word-wrap:break-word}.share-box[_ngcontent-%COMP%] .share-box-copy[_ngcontent-%COMP%]{position:absolute;top:-1px;right:-1px;background:transparent;color:inherit;cursor:pointer;padding:.25rem .5rem;font-size:.75rem;border:1px solid var(--phoenix-border);border-radius:.5rem}.share-box[_ngcontent-%COMP%] .share-box-copy[_ngcontent-%COMP%]:hover{background:var(--phoenix-border)}"] });
4545
+ } }, dependencies: [i1$1.MatDialogTitle, i1$1.MatDialogActions, i1$1.MatDialogContent, i3.MatButton, i4.NgSelectOption, i4.ɵNgSelectMultipleOption, i5.MatCheckbox], styles: [".share-box[_ngcontent-%COMP%]{width:30rem;max-width:100%;position:relative;background:var(--phoenix-background-color-tertiary);border:1px solid var(--phoenix-border);border-radius:.5rem;padding:1.25rem;word-wrap:break-word}.share-box[_ngcontent-%COMP%] .share-box-copy[_ngcontent-%COMP%]{position:absolute;top:-1px;right:-1px;background:transparent;color:inherit;cursor:pointer;padding:.25rem .5rem;font-size:.75rem;border:1px solid var(--phoenix-border);border-radius:.5rem}.share-box[_ngcontent-%COMP%] .share-box-copy[_ngcontent-%COMP%]:hover{background:var(--phoenix-border)}"] });
3859
4546
  }
3860
4547
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ShareLinkDialogComponent, [{
3861
4548
  type: Component,
3862
4549
  args: [{ standalone: false, selector: 'app-share-link-dialog', template: "<div class=\"dialog\">\n <h1 mat-dialog-title>Create shareable link</h1>\n <div mat-dialog-content class=\"dialog-content\">\n <h5>URL options</h5>\n <div class=\"form-group mt-2\">\n <label for=\"eventDataFile\">Event data file (URL or path)</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"eventDataFile\"\n placeholder=\"assets/files/event_data/atlaseventdump2.json\"\n [value]=\"urlOptions.file\"\n (input)=\"setOptionValue('file', $event.target.value)\"\n />\n </div>\n <div class=\"form-group\">\n <label for=\"eventDataType\">Event data type</label>\n <select\n class=\"form-control\"\n id=\"eventDataType\"\n (input)=\"setOptionValue('type', $event.target.value)\"\n >\n <option selected value=\"\">Select</option>\n <option value=\"jivexml\">JiveXML</option>\n <option value=\"json\">JSON</option>\n </select>\n </div>\n <div class=\"form-group\">\n <label for=\"configuration\">Configuration (URL or path)</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"configuration\"\n placeholder=\"assets/files/config/phoenix-config.json\"\n [value]=\"urlOptions.config\"\n (input)=\"setOptionValue('config', $event.target.value)\"\n />\n </div>\n <div class=\"form-group\">\n <mat-checkbox\n [checked]=\"urlOptions.hideWidgets\"\n (change)=\"setOptionValue('hideWidgets', $event.checked)\"\n >\n Hide all widgets\n </mat-checkbox>\n </div>\n <div class=\"form-group\">\n <mat-checkbox (change)=\"toggleViewState($event.checked)\">\n Include current view state (camera, visibility, clipping)\n </mat-checkbox>\n </div>\n\n <h5>Share</h5>\n <div class=\"share-box my-2\">\n <span>{{ shareLink.value }}</span>\n <button\n class=\"share-box-copy\"\n (click)=\"copyText(shareLink.value, $event.target)\"\n >\n COPY\n </button>\n </div>\n <h5>Embed</h5>\n <div class=\"share-box my-2\">\n <span>{{ embedLink }}</span>\n <button\n class=\"share-box-copy\"\n (click)=\"copyText(embedLink, $event.target)\"\n >\n COPY\n </button>\n </div>\n <h5>QR Code</h5>\n <div class=\"qrcode d-flex justify-content-center\">\n <canvas #qrcodeCanvas></canvas>\n </div>\n </div>\n <div mat-dialog-actions>\n <button mat-button (click)=\"onClose()\" cdkFocusInitial>Close</button>\n </div>\n</div>\n", styles: [".share-box{width:30rem;max-width:100%;position:relative;background:var(--phoenix-background-color-tertiary);border:1px solid var(--phoenix-border);border-radius:.5rem;padding:1.25rem;word-wrap:break-word}.share-box .share-box-copy{position:absolute;top:-1px;right:-1px;background:transparent;color:inherit;cursor:pointer;padding:.25rem .5rem;font-size:.75rem;border:1px solid var(--phoenix-border);border-radius:.5rem}.share-box .share-box-copy:hover{background:var(--phoenix-border)}\n"] }]
3863
- }], () => [{ type: i1$2.MatDialogRef }, { type: EventDisplayService }], { qrcodeCanvas: [{
4550
+ }], () => [{ type: i1$1.MatDialogRef }, { type: EventDisplayService }], { qrcodeCanvas: [{
3864
4551
  type: ViewChild,
3865
4552
  args: ['qrcodeCanvas']
3866
4553
  }] }); })();
@@ -3874,7 +4561,7 @@ class ShareLinkComponent {
3874
4561
  openShareLinkDialog() {
3875
4562
  this.dialog.open(ShareLinkDialogComponent);
3876
4563
  }
3877
- static ɵfac = function ShareLinkComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ShareLinkComponent)(i0.ɵɵdirectiveInject(i1$2.MatDialog)); };
4564
+ static ɵfac = function ShareLinkComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ShareLinkComponent)(i0.ɵɵdirectiveInject(i1$1.MatDialog)); };
3878
4565
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ShareLinkComponent, selectors: [["app-share-link"]], standalone: false, decls: 1, vars: 1, consts: [["tooltip", "Create shareable link", "icon", "share", 3, "click", "active"]], template: function ShareLinkComponent_Template(rf, ctx) { if (rf & 1) {
3879
4566
  i0.ɵɵelementStart(0, "app-menu-toggle", 0);
3880
4567
  i0.ɵɵlistener("click", function ShareLinkComponent_Template_app_menu_toggle_click_0_listener() { return ctx.openShareLinkDialog(); });
@@ -3886,7 +4573,7 @@ class ShareLinkComponent {
3886
4573
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ShareLinkComponent, [{
3887
4574
  type: Component,
3888
4575
  args: [{ standalone: false, selector: 'app-share-link', template: "<app-menu-toggle\n tooltip=\"Create shareable link\"\n icon=\"share\"\n [active]=\"false\"\n (click)=\"openShareLinkDialog()\"\n>\n</app-menu-toggle>\n" }]
3889
- }], () => [{ type: i1$2.MatDialog }], null); })();
4576
+ }], () => [{ type: i1$1.MatDialog }], null); })();
3890
4577
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ShareLinkComponent, { className: "ShareLinkComponent", filePath: "components/ui-menu/share-link/share-link.component.ts", lineNumber: 11 }); })();
3891
4578
 
3892
4579
  function FileExplorerComponent_cdk_tree_node_1_Template(rf, ctx) { if (rf & 1) {
@@ -3980,7 +4667,7 @@ class FileExplorerComponent {
3980
4667
  i0.ɵɵproperty("dataSource", ctx.dataSource)("treeControl", ctx.treeControl);
3981
4668
  i0.ɵɵadvance(2);
3982
4669
  i0.ɵɵproperty("cdkTreeNodeDefWhen", ctx.hasChildren);
3983
- } }, dependencies: [i3.MatButton, i2$2.CdkNestedTreeNode, i2$2.CdkTreeNodeDef, i2$2.CdkTreeNodeToggle, i2$2.CdkTree, i2$2.CdkTreeNode, i2$2.CdkTreeNodeOutlet], styles: [".file-tree-invisible[_ngcontent-%COMP%]{display:none}.tree-node[_ngcontent-%COMP%]{display:block;margin:.25rem 0}.tree-node[_ngcontent-%COMP%] .tree-node[_ngcontent-%COMP%]{padding-left:1rem}.tree-node-button[_ngcontent-%COMP%]{outline:none;padding:0 .75rem}.node-icon[_ngcontent-%COMP%]{width:1rem;height:1rem;margin-right:.25rem;margin-top:-2px}.node-icon.node-icon-filled[_ngcontent-%COMP%]{--icon-fill: var(--phoenix-options-icon-path, #fff)}"] });
4670
+ } }, dependencies: [i3.MatButton, i2$4.CdkNestedTreeNode, i2$4.CdkTreeNodeDef, i2$4.CdkTreeNodeToggle, i2$4.CdkTree, i2$4.CdkTreeNode, i2$4.CdkTreeNodeOutlet], styles: [".file-tree-invisible[_ngcontent-%COMP%]{display:none}.tree-node[_ngcontent-%COMP%]{display:block;margin:.25rem 0}.tree-node[_ngcontent-%COMP%] .tree-node[_ngcontent-%COMP%]{padding-left:1rem}.tree-node-button[_ngcontent-%COMP%]{outline:none;padding:0 .75rem}.node-icon[_ngcontent-%COMP%]{width:1rem;height:1rem;margin-right:.25rem;margin-top:-2px}.node-icon.node-icon-filled[_ngcontent-%COMP%]{--icon-fill: var(--phoenix-options-icon-path, #fff)}"] });
3984
4671
  }
3985
4672
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FileExplorerComponent, [{
3986
4673
  type: Component,
@@ -4187,7 +4874,7 @@ class EventDataExplorerDialogComponent {
4187
4874
  }
4188
4875
  return rootNode;
4189
4876
  }
4190
- static ɵfac = function EventDataExplorerDialogComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || EventDataExplorerDialogComponent)(i0.ɵɵdirectiveInject(EventDisplayService), i0.ɵɵdirectiveInject(FileLoaderService), i0.ɵɵdirectiveInject(i1$2.MatDialogRef), i0.ɵɵdirectiveInject(MAT_DIALOG_DATA)); };
4877
+ static ɵfac = function EventDataExplorerDialogComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || EventDataExplorerDialogComponent)(i0.ɵɵdirectiveInject(EventDisplayService), i0.ɵɵdirectiveInject(FileLoaderService), i0.ɵɵdirectiveInject(i1$1.MatDialogRef), i0.ɵɵdirectiveInject(MAT_DIALOG_DATA)); };
4191
4878
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: EventDataExplorerDialogComponent, selectors: [["app-event-data-explorer-dialog"]], standalone: false, decls: 11, vars: 3, consts: [[1, "dialog"], [1, "d-flex"], ["mat-dialog-title", ""], [4, "ngIf"], ["mat-dialog-content", "", 1, "dialog-content"], ["mat-align-tabs", "start", "animationDuration", "0ms", "disableRipple", "", "opacity", "transparent", 4, "ngIf"], ["mat-dialog-actions", ""], ["mat-button", "", "cdkFocusInitial", "", 3, "click"], ["mat-align-tabs", "start", "animationDuration", "0ms", "disableRipple", "", "opacity", "transparent"], ["label", "Event data"], [1, "py-3"], [3, "onFileSelect", "rootFileNode"], ["label", "Config"]], template: function EventDataExplorerDialogComponent_Template(rf, ctx) { if (rf & 1) {
4192
4879
  i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "h1", 2);
4193
4880
  i0.ɵɵtext(3, "Event data from directory");
@@ -4208,12 +4895,12 @@ class EventDataExplorerDialogComponent {
4208
4895
  i0.ɵɵproperty("ngIf", ctx.error);
4209
4896
  i0.ɵɵadvance();
4210
4897
  i0.ɵɵproperty("ngIf", !ctx.error);
4211
- } }, dependencies: [i2.NgIf, i1$2.MatDialogTitle, i1$2.MatDialogActions, i1$2.MatDialogContent, i3.MatButton, i6.MatTab, i6.MatTabGroup, FileExplorerComponent, RingLoaderComponent], styles: [".dialog[_ngcontent-%COMP%]{width:30rem;max-width:95%}.file-path-button[_ngcontent-%COMP%]{width:100%;color:var(--phoenix-text-color);background:var(--phoenix-background-color-tertiary);text-align:left;border-radius:1rem;margin-bottom:.5rem;padding:.5rem 1rem}.file-path-button[_ngcontent-%COMP%]:last-child{margin-bottom:0}.file-path-button[_ngcontent-%COMP%]:hover{background:var(--phoenix-background-color-secondary)}.file-path-button[_ngcontent-%COMP%]:focus{border:1px solid var(--phoenix-text-color-secondary);box-shadow:none}"] });
4898
+ } }, dependencies: [i2.NgIf, i1$1.MatDialogTitle, i1$1.MatDialogActions, i1$1.MatDialogContent, i3.MatButton, i6.MatTab, i6.MatTabGroup, FileExplorerComponent, RingLoaderComponent], styles: [".dialog[_ngcontent-%COMP%]{width:30rem;max-width:95%}.file-path-button[_ngcontent-%COMP%]{width:100%;color:var(--phoenix-text-color);background:var(--phoenix-background-color-tertiary);text-align:left;border-radius:1rem;margin-bottom:.5rem;padding:.5rem 1rem}.file-path-button[_ngcontent-%COMP%]:last-child{margin-bottom:0}.file-path-button[_ngcontent-%COMP%]:hover{background:var(--phoenix-background-color-secondary)}.file-path-button[_ngcontent-%COMP%]:focus{border:1px solid var(--phoenix-text-color-secondary);box-shadow:none}"] });
4212
4899
  }
4213
4900
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EventDataExplorerDialogComponent, [{
4214
4901
  type: Component,
4215
4902
  args: [{ standalone: false, selector: 'app-event-data-explorer-dialog', template: "<div class=\"dialog\">\n <div class=\"d-flex\">\n <h1 mat-dialog-title>Event data from directory</h1>\n <app-ring-loader *ngIf=\"loading\"></app-ring-loader>\n </div>\n <div mat-dialog-content class=\"dialog-content\">\n <div *ngIf=\"error\">Error processing request.</div>\n <mat-tab-group\n *ngIf=\"!error\"\n mat-align-tabs=\"start\"\n animationDuration=\"0ms\"\n disableRipple\n opacity=\"transparent\"\n >\n <mat-tab label=\"Event data\">\n <div class=\"py-3\">\n <app-file-explorer\n [rootFileNode]=\"eventDataFileNode\"\n (onFileSelect)=\"loadEvent($event)\"\n ></app-file-explorer>\n </div>\n </mat-tab>\n <mat-tab label=\"Config\">\n <div class=\"py-3\">\n <app-file-explorer\n [rootFileNode]=\"configFileNode\"\n (onFileSelect)=\"loadConfig($event)\"\n ></app-file-explorer>\n </div>\n </mat-tab>\n </mat-tab-group>\n </div>\n <div mat-dialog-actions>\n <button mat-button (click)=\"onClose()\" cdkFocusInitial>Close</button>\n </div>\n</div>\n", styles: [".dialog{width:30rem;max-width:95%}.file-path-button{width:100%;color:var(--phoenix-text-color);background:var(--phoenix-background-color-tertiary);text-align:left;border-radius:1rem;margin-bottom:.5rem;padding:.5rem 1rem}.file-path-button:last-child{margin-bottom:0}.file-path-button:hover{background:var(--phoenix-background-color-secondary)}.file-path-button:focus{border:1px solid var(--phoenix-text-color-secondary);box-shadow:none}\n"] }]
4216
- }], () => [{ type: EventDisplayService }, { type: FileLoaderService }, { type: i1$2.MatDialogRef }, { type: undefined, decorators: [{
4903
+ }], () => [{ type: EventDisplayService }, { type: FileLoaderService }, { type: i1$1.MatDialogRef }, { type: undefined, decorators: [{
4217
4904
  type: Inject,
4218
4905
  args: [MAT_DIALOG_DATA]
4219
4906
  }] }], null); })();
@@ -4232,7 +4919,7 @@ class EventDataExplorerComponent {
4232
4919
  },
4233
4920
  });
4234
4921
  }
4235
- static ɵfac = function EventDataExplorerComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || EventDataExplorerComponent)(i0.ɵɵdirectiveInject(i1$2.MatDialog)); };
4922
+ static ɵfac = function EventDataExplorerComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || EventDataExplorerComponent)(i0.ɵɵdirectiveInject(i1$1.MatDialog)); };
4236
4923
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: EventDataExplorerComponent, selectors: [["app-event-data-explorer"]], inputs: { apiURL: "apiURL" }, standalone: false, decls: 1, vars: 1, consts: [["tooltip", "Browse events in data directory", "icon", "server", 3, "click", "active"]], template: function EventDataExplorerComponent_Template(rf, ctx) { if (rf & 1) {
4237
4924
  i0.ɵɵelementStart(0, "app-menu-toggle", 0);
4238
4925
  i0.ɵɵlistener("click", function EventDataExplorerComponent_Template_app_menu_toggle_click_0_listener() { return ctx.openEventDataExplorerDialog(); });
@@ -4244,7 +4931,7 @@ class EventDataExplorerComponent {
4244
4931
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EventDataExplorerComponent, [{
4245
4932
  type: Component,
4246
4933
  args: [{ standalone: false, selector: 'app-event-data-explorer', template: "<app-menu-toggle\n tooltip=\"Browse events in data directory\"\n icon=\"server\"\n [active]=\"false\"\n (click)=\"openEventDataExplorerDialog()\"\n>\n</app-menu-toggle>\n" }]
4247
- }], () => [{ type: i1$2.MatDialog }], { apiURL: [{
4934
+ }], () => [{ type: i1$1.MatDialog }], { apiURL: [{
4248
4935
  type: Input
4249
4936
  }] }); })();
4250
4937
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EventDataExplorerComponent, { className: "EventDataExplorerComponent", filePath: "components/ui-menu/event-data-explorer/event-data-explorer.component.ts", lineNumber: 15 }); })();
@@ -4974,19 +5661,19 @@ class EventBrowserOverlayComponent {
4974
5661
  return this.loadingEventKey === summary.eventKey;
4975
5662
  }
4976
5663
  static ɵfac = function EventBrowserOverlayComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || EventBrowserOverlayComponent)(i0.ɵɵdirectiveInject(EventDisplayService)); };
4977
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: EventBrowserOverlayComponent, selectors: [["app-event-browser-overlay"]], inputs: { showEventBrowser: "showEventBrowser" }, standalone: false, decls: 2, vars: 2, consts: [["overlayTitle", "Event Browser", "icon", "event-browser", 3, "active"], ["class", "event-browser m-2", 4, "ngIf"], [1, "event-browser", "m-2"], ["class", "search-box mb-2 d-flex align-items-center", 4, "ngIf"], ["class", "filter-controls mb-2", 4, "ngIf"], ["class", "summary-count mb-1", 4, "ngIf"], ["tabindex", "0", 1, "boxBody", "table-responsive", 3, "keydown"], ["class", "table table-borderless table-sm", 4, "ngIf"], ["class", "emptyBox", 4, "ngIf"], ["class", "keyboard-hint mt-1", 4, "ngIf"], [1, "search-box", "mb-2", "d-flex", "align-items-center"], ["type", "text", "placeholder", "Jump to event number...", 1, "search-input", 3, "ngModelChange", "keydown.enter", "ngModel"], ["class", "search-hint", 4, "ngIf"], [1, "search-hint"], [1, "filter-controls", "mb-2"], [1, "d-flex", "align-items-center", "flex-wrap", "gap-1"], [1, "filter-label"], [1, "filter-select", 3, "ngModelChange", "ngModel"], ["value", "", "disabled", ""], [3, "value", 4, "ngFor", "ngForOf"], ["value", ">="], ["value", "<="], ["value", "="], ["type", "number", "placeholder", "value", "min", "0", 1, "filter-input", 3, "ngModelChange", "ngModel"], [1, "btn-filter", "btn-blank", 3, "click"], [1, "filter-separator"], ["type", "number", "placeholder", "GeV", 1, "filter-input", 3, "input"], ["class", "btn-filter btn-blank", 3, "click", 4, "ngIf"], ["class", "active-filters mt-1", 4, "ngIf"], [3, "value"], [1, "active-filters", "mt-1"], ["class", "filter-tag", 4, "ngFor", "ngForOf"], [1, "filter-tag"], [1, "btn-blank", "filter-remove", 3, "click"], [1, "summary-count", "mb-1"], [1, "table", "table-borderless", "table-sm"], [4, "ngFor", "ngForOf"], [1, "head-wrapper"], ["matTooltip", "Reconstructed physics objects (excl. detector readouts)"], [1, "sort-options"], [1, "icon-wrapper", "icon-button", "btn-blank", "up", 3, "click", "ngClass"], ["href", "assets/icons/expand.svg#expand"], [1, "icon-wrapper", "icon-button", "btn-blank", 3, "click", "ngClass"], [3, "id", "ngClass", "click", "dblclick", 4, "ngFor", "ngForOf"], [3, "click", "dblclick", "id", "ngClass"], [1, "event-cell"], [1, "event-label"], ["class", "run-label", 4, "ngIf"], [3, "ngClass", 4, "ngFor", "ngForOf"], [3, "ngClass"], [1, "btn-blank", "btn-load", 3, "click", "matTooltip"], ["class", "loading-spinner", 4, "ngIf"], ["class", "load-icon", 4, "ngIf"], [1, "run-label"], [1, "loading-spinner"], [1, "load-icon"], ["href", "assets/icons/views.svg#views"], [1, "emptyBox"], [1, "keyboard-hint", "mt-1"]], template: function EventBrowserOverlayComponent_Template(rf, ctx) { if (rf & 1) {
5664
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: EventBrowserOverlayComponent, selectors: [["app-event-browser-overlay"]], inputs: { showEventBrowser: "showEventBrowser" }, standalone: false, decls: 2, vars: 3, consts: [["overlayTitle", "Event Browser", "icon", "event-browser", "accordionId", "right-dock", 3, "resizable", "active"], ["class", "event-browser m-2", 4, "ngIf"], [1, "event-browser", "m-2"], ["class", "search-box mb-2 d-flex align-items-center", 4, "ngIf"], ["class", "filter-controls mb-2", 4, "ngIf"], ["class", "summary-count mb-1", 4, "ngIf"], ["tabindex", "0", 1, "boxBody", "table-responsive", 3, "keydown"], ["class", "table table-borderless table-sm", 4, "ngIf"], ["class", "emptyBox", 4, "ngIf"], ["class", "keyboard-hint mt-1", 4, "ngIf"], [1, "search-box", "mb-2", "d-flex", "align-items-center"], ["type", "text", "placeholder", "Jump to event number...", 1, "search-input", 3, "ngModelChange", "keydown.enter", "ngModel"], ["class", "search-hint", 4, "ngIf"], [1, "search-hint"], [1, "filter-controls", "mb-2"], [1, "d-flex", "align-items-center", "flex-wrap", "gap-1"], [1, "filter-label"], [1, "filter-select", 3, "ngModelChange", "ngModel"], ["value", "", "disabled", ""], [3, "value", 4, "ngFor", "ngForOf"], ["value", ">="], ["value", "<="], ["value", "="], ["type", "number", "placeholder", "value", "min", "0", 1, "filter-input", 3, "ngModelChange", "ngModel"], [1, "btn-filter", "btn-blank", 3, "click"], [1, "filter-separator"], ["type", "number", "placeholder", "GeV", 1, "filter-input", 3, "input"], ["class", "btn-filter btn-blank", 3, "click", 4, "ngIf"], ["class", "active-filters mt-1", 4, "ngIf"], [3, "value"], [1, "active-filters", "mt-1"], ["class", "filter-tag", 4, "ngFor", "ngForOf"], [1, "filter-tag"], [1, "btn-blank", "filter-remove", 3, "click"], [1, "summary-count", "mb-1"], [1, "table", "table-borderless", "table-sm"], [4, "ngFor", "ngForOf"], [1, "head-wrapper"], ["matTooltip", "Reconstructed physics objects (excl. detector readouts)"], [1, "sort-options"], [1, "icon-wrapper", "icon-button", "btn-blank", "up", 3, "click", "ngClass"], ["href", "assets/icons/expand.svg#expand"], [1, "icon-wrapper", "icon-button", "btn-blank", 3, "click", "ngClass"], [3, "id", "ngClass", "click", "dblclick", 4, "ngFor", "ngForOf"], [3, "click", "dblclick", "id", "ngClass"], [1, "event-cell"], [1, "event-label"], ["class", "run-label", 4, "ngIf"], [3, "ngClass", 4, "ngFor", "ngForOf"], [3, "ngClass"], [1, "btn-blank", "btn-load", 3, "click", "matTooltip"], ["class", "loading-spinner", 4, "ngIf"], ["class", "load-icon", 4, "ngIf"], [1, "run-label"], [1, "loading-spinner"], [1, "load-icon"], ["href", "assets/icons/views.svg#views"], [1, "emptyBox"], [1, "keyboard-hint", "mt-1"]], template: function EventBrowserOverlayComponent_Template(rf, ctx) { if (rf & 1) {
4978
5665
  i0.ɵɵelementStart(0, "app-overlay", 0);
4979
5666
  i0.ɵɵtemplate(1, EventBrowserOverlayComponent_div_1_Template, 8, 6, "div", 1);
4980
5667
  i0.ɵɵelementEnd();
4981
5668
  } if (rf & 2) {
4982
- i0.ɵɵproperty("active", ctx.showEventBrowser);
5669
+ i0.ɵɵproperty("resizable", true)("active", ctx.showEventBrowser);
4983
5670
  i0.ɵɵadvance();
4984
5671
  i0.ɵɵproperty("ngIf", ctx.showEventBrowser);
4985
5672
  } }, dependencies: [i2.NgClass, i2.NgForOf, i2.NgIf, i3$1.MatTooltip, i4.NgSelectOption, i4.ɵNgSelectMultipleOption, i4.DefaultValueAccessor, i4.NumberValueAccessor, i4.SelectControlValueAccessor, i4.NgControlStatus, i4.MinValidator, i4.NgModel, OverlayComponent], styles: [".event-browser[_ngcontent-%COMP%]{height:95%}.event-browser[_ngcontent-%COMP%] .search-box[_ngcontent-%COMP%] .search-input[_ngcontent-%COMP%]{flex:1;padding:4px 8px;font-size:12px;border:1px solid rgba(88,88,88,.15);box-shadow:var(--phoenix-icon-shadow);background-color:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary);border-radius:4px}.event-browser[_ngcontent-%COMP%] .search-box[_ngcontent-%COMP%] .search-input[_ngcontent-%COMP%]::placeholder{opacity:.5}.event-browser[_ngcontent-%COMP%] .search-box[_ngcontent-%COMP%] .search-hint[_ngcontent-%COMP%]{font-size:10px;color:var(--phoenix-text-color-secondary);opacity:.5;margin-left:.4rem;white-space:nowrap}.event-browser[_ngcontent-%COMP%] .filter-controls[_ngcontent-%COMP%] .filter-select[_ngcontent-%COMP%], .event-browser[_ngcontent-%COMP%] .filter-controls[_ngcontent-%COMP%] .filter-input[_ngcontent-%COMP%]{padding:3px 6px;font-size:11px;border:1px solid rgba(88,88,88,.08);box-shadow:var(--phoenix-icon-shadow);background-color:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary);border-radius:3px}.event-browser[_ngcontent-%COMP%] .filter-controls[_ngcontent-%COMP%] .filter-select[_ngcontent-%COMP%]{width:7rem}.event-browser[_ngcontent-%COMP%] .filter-controls[_ngcontent-%COMP%] .filter-input[_ngcontent-%COMP%]{width:4rem}.event-browser[_ngcontent-%COMP%] .filter-controls[_ngcontent-%COMP%] .btn-filter[_ngcontent-%COMP%]{padding:3px 8px;font-size:11px;color:var(--phoenix-text-color-secondary);border:1px solid rgba(88,88,88,.2);border-radius:3px;cursor:pointer}.event-browser[_ngcontent-%COMP%] .filter-controls[_ngcontent-%COMP%] .btn-filter[_ngcontent-%COMP%]:hover{background:#ffffff1a}.event-browser[_ngcontent-%COMP%] .filter-controls[_ngcontent-%COMP%] .filter-separator[_ngcontent-%COMP%]{color:var(--phoenix-text-color-secondary);opacity:.4;margin:0 .2rem}.event-browser[_ngcontent-%COMP%] .filter-controls[_ngcontent-%COMP%] .filter-label[_ngcontent-%COMP%]{font-size:11px;color:var(--phoenix-text-color-secondary)}.event-browser[_ngcontent-%COMP%] .filter-controls[_ngcontent-%COMP%] .gap-1[_ngcontent-%COMP%]{gap:.3rem}.event-browser[_ngcontent-%COMP%] .active-filters[_ngcontent-%COMP%] .filter-tag[_ngcontent-%COMP%]{display:inline-flex;align-items:center;padding:1px 6px;margin-right:.3rem;font-size:10px;color:var(--phoenix-text-color-secondary);background:#ffffff14;border-radius:3px}.event-browser[_ngcontent-%COMP%] .active-filters[_ngcontent-%COMP%] .filter-tag[_ngcontent-%COMP%] .filter-remove[_ngcontent-%COMP%]{margin-left:4px;font-size:12px;line-height:1;cursor:pointer;color:var(--phoenix-text-color-secondary);opacity:.6}.event-browser[_ngcontent-%COMP%] .active-filters[_ngcontent-%COMP%] .filter-tag[_ngcontent-%COMP%] .filter-remove[_ngcontent-%COMP%]:hover{opacity:1}.event-browser[_ngcontent-%COMP%] .summary-count[_ngcontent-%COMP%]{font-size:11px;color:var(--phoenix-text-color-secondary);opacity:.7}.boxBody[_ngcontent-%COMP%]{height:80%;overflow:auto;outline:none}.boxBody[_ngcontent-%COMP%] p.emptyBox[_ngcontent-%COMP%]{max-width:21em;color:var(--phoenix-text-color-secondary)}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%]{position:relative;color:var(--phoenix-text-color-secondary);font-size:12px}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%]{position:sticky;top:0;z-index:100;background:var(--phoenix-background-color-secondary);white-space:nowrap;font-size:11px}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%]{display:flex;align-items:center}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%] .sort-options[_ngcontent-%COMP%]{display:flex;flex-direction:row}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%] .sort-options[_ngcontent-%COMP%] .icon-wrapper[_ngcontent-%COMP%]{display:flex;width:.85rem;height:.85rem;padding:.15rem;opacity:.5;transition:opacity .15s}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%] .sort-options[_ngcontent-%COMP%] .icon-wrapper[_ngcontent-%COMP%]:hover, .event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%] .sort-options[_ngcontent-%COMP%] .icon-wrapper.sort-active[_ngcontent-%COMP%]{opacity:1}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%] .sort-options[_ngcontent-%COMP%] .icon-wrapper.up[_ngcontent-%COMP%]{transform:rotate(180deg)}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .head-wrapper[_ngcontent-%COMP%] .sort-options[_ngcontent-%COMP%] .icon-wrapper[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{width:100%;height:100%}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] *[_ngcontent-%COMP%]{padding-right:.8rem}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] *[_ngcontent-%COMP%]:last-child{padding-right:0}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]{cursor:pointer;transition:background .1s}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]:hover{background:#ffffff0d}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.selected-event[_ngcontent-%COMP%]{color:var(--phoenix-background-color);background:var(--phoenix-text-color);box-shadow:0 0 10px var(--phoenix-text-color)}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.loaded-event[_ngcontent-%COMP%]:not(.selected-event){background:#ffffff08}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.loaded-event[_ngcontent-%COMP%]:not(.selected-event) .event-label[_ngcontent-%COMP%]{font-weight:600}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] td[_ngcontent-%COMP%]{min-height:20px;white-space:nowrap}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] td.dim-zero[_ngcontent-%COMP%]{opacity:.25}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] .event-cell[_ngcontent-%COMP%] .event-label[_ngcontent-%COMP%]{font-size:12px;line-height:1.3}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] .event-cell[_ngcontent-%COMP%] .run-label[_ngcontent-%COMP%]{font-size:10px;opacity:.55;line-height:1.2}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] .btn-load[_ngcontent-%COMP%]{position:relative;width:1.4rem;height:1.4rem;text-align:center;background-color:var(--phoenix-options-icon-bg);border-radius:8px;cursor:pointer}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] .btn-load[_ngcontent-%COMP%]:hover{border:1px solid var(--phoenix-options-icon-path)}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] .btn-load[_ngcontent-%COMP%] .load-icon[_ngcontent-%COMP%]{position:absolute;top:0;left:0;padding:.3rem;width:100%;height:100%}.event-browser[_ngcontent-%COMP%] table[_ngcontent-%COMP%] .btn-load[_ngcontent-%COMP%] .loading-spinner[_ngcontent-%COMP%]{display:inline-block;width:10px;height:10px;border:2px solid rgba(255,255,255,.2);border-top-color:var(--phoenix-text-color-secondary);border-radius:50%;animation:_ngcontent-%COMP%_spin .7s linear infinite}@keyframes _ngcontent-%COMP%_spin{to{transform:rotate(360deg)}}.keyboard-hint[_ngcontent-%COMP%]{font-size:10px;color:var(--phoenix-text-color-secondary);opacity:.5}"] });
4986
5673
  }
4987
5674
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EventBrowserOverlayComponent, [{
4988
5675
  type: Component,
4989
- args: [{ standalone: false, selector: 'app-event-browser-overlay', template: "<app-overlay\n overlayTitle=\"Event Browser\"\n icon=\"event-browser\"\n [active]=\"showEventBrowser\"\n>\n <div *ngIf=\"showEventBrowser\" class=\"event-browser m-2\">\n <!-- Search box -->\n <div\n class=\"search-box mb-2 d-flex align-items-center\"\n *ngIf=\"allSummaries.length > 0\"\n >\n <input\n type=\"text\"\n class=\"search-input\"\n placeholder=\"Jump to event number...\"\n [ngModel]=\"searchQuery\"\n (ngModelChange)=\"updateSearch($event)\"\n (keydown.enter)=\"jumpToSearch()\"\n />\n <span class=\"search-hint\" *ngIf=\"searchQuery\">Enter to load</span>\n </div>\n\n <!-- Filter controls -->\n <div class=\"filter-controls mb-2\" *ngIf=\"allSummaries.length > 0\">\n <div class=\"d-flex align-items-center flex-wrap gap-1\">\n <span class=\"filter-label\">Filter:</span>\n <select class=\"filter-select\" [(ngModel)]=\"filterColumn\">\n <option value=\"\" disabled>Column</option>\n <option *ngFor=\"let col of columns\" [value]=\"col\">{{ col }}</option>\n </select>\n <select class=\"filter-select\" [(ngModel)]=\"filterOperator\">\n <option value=\">=\">&gt;=</option>\n <option value=\"<=\">&lt;=</option>\n <option value=\"=\">=</option>\n </select>\n <input\n type=\"number\"\n class=\"filter-input\"\n placeholder=\"value\"\n [(ngModel)]=\"filterValue\"\n min=\"0\"\n />\n <button class=\"btn-filter btn-blank\" (click)=\"addFilter()\">Add</button>\n <span class=\"filter-separator\">|</span>\n <span class=\"filter-label\">MET &gt;=</span>\n <input\n type=\"number\"\n class=\"filter-input\"\n placeholder=\"GeV\"\n (input)=\"updateMETFilter($event.target.value)\"\n />\n <button\n class=\"btn-filter btn-blank\"\n (click)=\"clearFilters()\"\n *ngIf=\"filters.length > 0 || metFilterMin !== null || searchQuery\"\n >\n Clear\n </button>\n </div>\n <!-- Active filters -->\n <div class=\"active-filters mt-1\" *ngIf=\"filters.length > 0\">\n <span class=\"filter-tag\" *ngFor=\"let filter of filters; index as i\">\n {{ getFilterLabel(filter) }}\n <button class=\"btn-blank filter-remove\" (click)=\"removeFilter(i)\">\n &times;\n </button>\n </span>\n </div>\n </div>\n\n <!-- Summary count -->\n <div class=\"summary-count mb-1\" *ngIf=\"allSummaries.length > 0\">\n {{ displayedSummaries.length }} of {{ allSummaries.length }} events\n </div>\n\n <!-- Event table -->\n <div\n class=\"boxBody table-responsive\"\n (keydown)=\"onTableKeydown($event)\"\n tabindex=\"0\"\n >\n <table\n class=\"table table-borderless table-sm\"\n *ngIf=\"allSummaries.length > 0\"\n >\n <thead>\n <tr>\n <th>Event</th>\n <th *ngFor=\"let col of columns\">\n <div class=\"head-wrapper\">\n <div>{{ col }}</div>\n <div class=\"sort-options\">\n <button\n class=\"icon-wrapper icon-button btn-blank up\"\n [ngClass]=\"{ 'sort-active': isSortActive(col, 'desc') }\"\n (click)=\"sort(col, 'desc')\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n <button\n class=\"icon-wrapper icon-button btn-blank\"\n [ngClass]=\"{ 'sort-active': isSortActive(col, 'asc') }\"\n (click)=\"sort(col, 'asc')\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n </div>\n </div>\n </th>\n <th>\n <div class=\"head-wrapper\">\n <div\n matTooltip=\"Reconstructed physics objects (excl. detector readouts)\"\n >\n Reco\n </div>\n <div class=\"sort-options\">\n <button\n class=\"icon-wrapper icon-button btn-blank up\"\n [ngClass]=\"{ 'sort-active': isSortActive('_reco', 'desc') }\"\n (click)=\"sortByReco('desc')\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n <button\n class=\"icon-wrapper icon-button btn-blank\"\n [ngClass]=\"{ 'sort-active': isSortActive('_reco', 'asc') }\"\n (click)=\"sortByReco('asc')\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n </div>\n </div>\n </th>\n <th>\n <div class=\"head-wrapper\">\n <div>MET</div>\n <div class=\"sort-options\">\n <button\n class=\"icon-wrapper icon-button btn-blank up\"\n [ngClass]=\"{ 'sort-active': isSortActive('_met', 'desc') }\"\n (click)=\"sortByMET('desc')\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n <button\n class=\"icon-wrapper icon-button btn-blank\"\n [ngClass]=\"{ 'sort-active': isSortActive('_met', 'asc') }\"\n (click)=\"sortByMET('asc')\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n </div>\n </div>\n </th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr\n *ngFor=\"let summary of displayedSummaries\"\n [id]=\"'event-row-' + summary.eventKey\"\n [ngClass]=\"{\n 'selected-event': selectedEventKey === summary.eventKey,\n 'loaded-event': isLoadedEvent(summary),\n }\"\n (click)=\"selectedEventKey = summary.eventKey\"\n (dblclick)=\"selectEvent(summary)\"\n >\n <td class=\"event-cell\">\n <div class=\"event-label\">{{ getEventLabel(summary) }}</div>\n <div class=\"run-label\" *ngIf=\"getRunLabel(summary)\">\n {{ getRunLabel(summary) }}\n </div>\n </td>\n <td\n *ngFor=\"let col of columns\"\n [ngClass]=\"{ 'dim-zero': getCount(summary, col) === 0 }\"\n >\n {{ formatNumber(getCount(summary, col)) }}\n </td>\n <td [ngClass]=\"{ 'dim-zero': getRecoTotal(summary) === 0 }\">\n {{ formatNumber(getRecoTotal(summary)) }}\n </td>\n <td>{{ formatMET(summary.met) }}</td>\n <td>\n <button\n class=\"btn-blank btn-load\"\n [matTooltip]=\"\n isLoadingEvent(summary) ? 'Loading...' : 'Load this event'\n \"\n (click)=\"selectEvent(summary); $event.stopPropagation()\"\n >\n <span\n *ngIf=\"isLoadingEvent(summary)\"\n class=\"loading-spinner\"\n ></span>\n <svg *ngIf=\"!isLoadingEvent(summary)\" class=\"load-icon\">\n <use href=\"assets/icons/views.svg#views\"></use>\n </svg>\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n <p class=\"emptyBox\" *ngIf=\"allSummaries.length === 0\">\n Load event data to browse events here.\n </p>\n </div>\n\n <!-- Keyboard hint -->\n <div class=\"keyboard-hint mt-1\" *ngIf=\"allSummaries.length > 0\">\n Arrow keys to navigate, Enter to load, Shift+Left/Right for prev/next\n </div>\n </div>\n</app-overlay>\n", styles: [".event-browser{height:95%}.event-browser .search-box .search-input{flex:1;padding:4px 8px;font-size:12px;border:1px solid rgba(88,88,88,.15);box-shadow:var(--phoenix-icon-shadow);background-color:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary);border-radius:4px}.event-browser .search-box .search-input::placeholder{opacity:.5}.event-browser .search-box .search-hint{font-size:10px;color:var(--phoenix-text-color-secondary);opacity:.5;margin-left:.4rem;white-space:nowrap}.event-browser .filter-controls .filter-select,.event-browser .filter-controls .filter-input{padding:3px 6px;font-size:11px;border:1px solid rgba(88,88,88,.08);box-shadow:var(--phoenix-icon-shadow);background-color:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary);border-radius:3px}.event-browser .filter-controls .filter-select{width:7rem}.event-browser .filter-controls .filter-input{width:4rem}.event-browser .filter-controls .btn-filter{padding:3px 8px;font-size:11px;color:var(--phoenix-text-color-secondary);border:1px solid rgba(88,88,88,.2);border-radius:3px;cursor:pointer}.event-browser .filter-controls .btn-filter:hover{background:#ffffff1a}.event-browser .filter-controls .filter-separator{color:var(--phoenix-text-color-secondary);opacity:.4;margin:0 .2rem}.event-browser .filter-controls .filter-label{font-size:11px;color:var(--phoenix-text-color-secondary)}.event-browser .filter-controls .gap-1{gap:.3rem}.event-browser .active-filters .filter-tag{display:inline-flex;align-items:center;padding:1px 6px;margin-right:.3rem;font-size:10px;color:var(--phoenix-text-color-secondary);background:#ffffff14;border-radius:3px}.event-browser .active-filters .filter-tag .filter-remove{margin-left:4px;font-size:12px;line-height:1;cursor:pointer;color:var(--phoenix-text-color-secondary);opacity:.6}.event-browser .active-filters .filter-tag .filter-remove:hover{opacity:1}.event-browser .summary-count{font-size:11px;color:var(--phoenix-text-color-secondary);opacity:.7}.boxBody{height:80%;overflow:auto;outline:none}.boxBody p.emptyBox{max-width:21em;color:var(--phoenix-text-color-secondary)}.event-browser table{position:relative;color:var(--phoenix-text-color-secondary);font-size:12px}.event-browser table thead tr th{position:sticky;top:0;z-index:100;background:var(--phoenix-background-color-secondary);white-space:nowrap;font-size:11px}.event-browser table thead tr th .head-wrapper{display:flex;align-items:center}.event-browser table thead tr th .head-wrapper .sort-options{display:flex;flex-direction:row}.event-browser table thead tr th .head-wrapper .sort-options .icon-wrapper{display:flex;width:.85rem;height:.85rem;padding:.15rem;opacity:.5;transition:opacity .15s}.event-browser table thead tr th .head-wrapper .sort-options .icon-wrapper:hover,.event-browser table thead tr th .head-wrapper .sort-options .icon-wrapper.sort-active{opacity:1}.event-browser table thead tr th .head-wrapper .sort-options .icon-wrapper.up{transform:rotate(180deg)}.event-browser table thead tr th .head-wrapper .sort-options .icon-wrapper svg{width:100%;height:100%}.event-browser table tr *{padding-right:.8rem}.event-browser table tr *:last-child{padding-right:0}.event-browser table tbody tr{cursor:pointer;transition:background .1s}.event-browser table tbody tr:hover{background:#ffffff0d}.event-browser table tbody tr.selected-event{color:var(--phoenix-background-color);background:var(--phoenix-text-color);box-shadow:0 0 10px var(--phoenix-text-color)}.event-browser table tbody tr.loaded-event:not(.selected-event){background:#ffffff08}.event-browser table tbody tr.loaded-event:not(.selected-event) .event-label{font-weight:600}.event-browser table td{min-height:20px;white-space:nowrap}.event-browser table td.dim-zero{opacity:.25}.event-browser table .event-cell .event-label{font-size:12px;line-height:1.3}.event-browser table .event-cell .run-label{font-size:10px;opacity:.55;line-height:1.2}.event-browser table .btn-load{position:relative;width:1.4rem;height:1.4rem;text-align:center;background-color:var(--phoenix-options-icon-bg);border-radius:8px;cursor:pointer}.event-browser table .btn-load:hover{border:1px solid var(--phoenix-options-icon-path)}.event-browser table .btn-load .load-icon{position:absolute;top:0;left:0;padding:.3rem;width:100%;height:100%}.event-browser table .btn-load .loading-spinner{display:inline-block;width:10px;height:10px;border:2px solid rgba(255,255,255,.2);border-top-color:var(--phoenix-text-color-secondary);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.keyboard-hint{font-size:10px;color:var(--phoenix-text-color-secondary);opacity:.5}\n"] }]
5676
+ args: [{ standalone: false, selector: 'app-event-browser-overlay', template: "<app-overlay\n overlayTitle=\"Event Browser\"\n icon=\"event-browser\"\n [resizable]=\"true\"\n [active]=\"showEventBrowser\"\n accordionId=\"right-dock\"\n>\n <div *ngIf=\"showEventBrowser\" class=\"event-browser m-2\">\n <!-- Search box -->\n <div\n class=\"search-box mb-2 d-flex align-items-center\"\n *ngIf=\"allSummaries.length > 0\"\n >\n <input\n type=\"text\"\n class=\"search-input\"\n placeholder=\"Jump to event number...\"\n [ngModel]=\"searchQuery\"\n (ngModelChange)=\"updateSearch($event)\"\n (keydown.enter)=\"jumpToSearch()\"\n />\n <span class=\"search-hint\" *ngIf=\"searchQuery\">Enter to load</span>\n </div>\n\n <!-- Filter controls -->\n <div class=\"filter-controls mb-2\" *ngIf=\"allSummaries.length > 0\">\n <div class=\"d-flex align-items-center flex-wrap gap-1\">\n <span class=\"filter-label\">Filter:</span>\n <select class=\"filter-select\" [(ngModel)]=\"filterColumn\">\n <option value=\"\" disabled>Column</option>\n <option *ngFor=\"let col of columns\" [value]=\"col\">{{ col }}</option>\n </select>\n <select class=\"filter-select\" [(ngModel)]=\"filterOperator\">\n <option value=\">=\">&gt;=</option>\n <option value=\"<=\">&lt;=</option>\n <option value=\"=\">=</option>\n </select>\n <input\n type=\"number\"\n class=\"filter-input\"\n placeholder=\"value\"\n [(ngModel)]=\"filterValue\"\n min=\"0\"\n />\n <button class=\"btn-filter btn-blank\" (click)=\"addFilter()\">Add</button>\n <span class=\"filter-separator\">|</span>\n <span class=\"filter-label\">MET &gt;=</span>\n <input\n type=\"number\"\n class=\"filter-input\"\n placeholder=\"GeV\"\n (input)=\"updateMETFilter($event.target.value)\"\n />\n <button\n class=\"btn-filter btn-blank\"\n (click)=\"clearFilters()\"\n *ngIf=\"filters.length > 0 || metFilterMin !== null || searchQuery\"\n >\n Clear\n </button>\n </div>\n <!-- Active filters -->\n <div class=\"active-filters mt-1\" *ngIf=\"filters.length > 0\">\n <span class=\"filter-tag\" *ngFor=\"let filter of filters; index as i\">\n {{ getFilterLabel(filter) }}\n <button class=\"btn-blank filter-remove\" (click)=\"removeFilter(i)\">\n &times;\n </button>\n </span>\n </div>\n </div>\n\n <!-- Summary count -->\n <div class=\"summary-count mb-1\" *ngIf=\"allSummaries.length > 0\">\n {{ displayedSummaries.length }} of {{ allSummaries.length }} events\n </div>\n\n <!-- Event table -->\n <div\n class=\"boxBody table-responsive\"\n (keydown)=\"onTableKeydown($event)\"\n tabindex=\"0\"\n >\n <table\n class=\"table table-borderless table-sm\"\n *ngIf=\"allSummaries.length > 0\"\n >\n <thead>\n <tr>\n <th>Event</th>\n <th *ngFor=\"let col of columns\">\n <div class=\"head-wrapper\">\n <div>{{ col }}</div>\n <div class=\"sort-options\">\n <button\n class=\"icon-wrapper icon-button btn-blank up\"\n [ngClass]=\"{ 'sort-active': isSortActive(col, 'desc') }\"\n (click)=\"sort(col, 'desc')\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n <button\n class=\"icon-wrapper icon-button btn-blank\"\n [ngClass]=\"{ 'sort-active': isSortActive(col, 'asc') }\"\n (click)=\"sort(col, 'asc')\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n </div>\n </div>\n </th>\n <th>\n <div class=\"head-wrapper\">\n <div\n matTooltip=\"Reconstructed physics objects (excl. detector readouts)\"\n >\n Reco\n </div>\n <div class=\"sort-options\">\n <button\n class=\"icon-wrapper icon-button btn-blank up\"\n [ngClass]=\"{ 'sort-active': isSortActive('_reco', 'desc') }\"\n (click)=\"sortByReco('desc')\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n <button\n class=\"icon-wrapper icon-button btn-blank\"\n [ngClass]=\"{ 'sort-active': isSortActive('_reco', 'asc') }\"\n (click)=\"sortByReco('asc')\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n </div>\n </div>\n </th>\n <th>\n <div class=\"head-wrapper\">\n <div>MET</div>\n <div class=\"sort-options\">\n <button\n class=\"icon-wrapper icon-button btn-blank up\"\n [ngClass]=\"{ 'sort-active': isSortActive('_met', 'desc') }\"\n (click)=\"sortByMET('desc')\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n <button\n class=\"icon-wrapper icon-button btn-blank\"\n [ngClass]=\"{ 'sort-active': isSortActive('_met', 'asc') }\"\n (click)=\"sortByMET('asc')\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n </div>\n </div>\n </th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr\n *ngFor=\"let summary of displayedSummaries\"\n [id]=\"'event-row-' + summary.eventKey\"\n [ngClass]=\"{\n 'selected-event': selectedEventKey === summary.eventKey,\n 'loaded-event': isLoadedEvent(summary),\n }\"\n (click)=\"selectedEventKey = summary.eventKey\"\n (dblclick)=\"selectEvent(summary)\"\n >\n <td class=\"event-cell\">\n <div class=\"event-label\">{{ getEventLabel(summary) }}</div>\n <div class=\"run-label\" *ngIf=\"getRunLabel(summary)\">\n {{ getRunLabel(summary) }}\n </div>\n </td>\n <td\n *ngFor=\"let col of columns\"\n [ngClass]=\"{ 'dim-zero': getCount(summary, col) === 0 }\"\n >\n {{ formatNumber(getCount(summary, col)) }}\n </td>\n <td [ngClass]=\"{ 'dim-zero': getRecoTotal(summary) === 0 }\">\n {{ formatNumber(getRecoTotal(summary)) }}\n </td>\n <td>{{ formatMET(summary.met) }}</td>\n <td>\n <button\n class=\"btn-blank btn-load\"\n [matTooltip]=\"\n isLoadingEvent(summary) ? 'Loading...' : 'Load this event'\n \"\n (click)=\"selectEvent(summary); $event.stopPropagation()\"\n >\n <span\n *ngIf=\"isLoadingEvent(summary)\"\n class=\"loading-spinner\"\n ></span>\n <svg *ngIf=\"!isLoadingEvent(summary)\" class=\"load-icon\">\n <use href=\"assets/icons/views.svg#views\"></use>\n </svg>\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n <p class=\"emptyBox\" *ngIf=\"allSummaries.length === 0\">\n Load event data to browse events here.\n </p>\n </div>\n\n <!-- Keyboard hint -->\n <div class=\"keyboard-hint mt-1\" *ngIf=\"allSummaries.length > 0\">\n Arrow keys to navigate, Enter to load, Shift+Left/Right for prev/next\n </div>\n </div>\n</app-overlay>\n", styles: [".event-browser{height:95%}.event-browser .search-box .search-input{flex:1;padding:4px 8px;font-size:12px;border:1px solid rgba(88,88,88,.15);box-shadow:var(--phoenix-icon-shadow);background-color:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary);border-radius:4px}.event-browser .search-box .search-input::placeholder{opacity:.5}.event-browser .search-box .search-hint{font-size:10px;color:var(--phoenix-text-color-secondary);opacity:.5;margin-left:.4rem;white-space:nowrap}.event-browser .filter-controls .filter-select,.event-browser .filter-controls .filter-input{padding:3px 6px;font-size:11px;border:1px solid rgba(88,88,88,.08);box-shadow:var(--phoenix-icon-shadow);background-color:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary);border-radius:3px}.event-browser .filter-controls .filter-select{width:7rem}.event-browser .filter-controls .filter-input{width:4rem}.event-browser .filter-controls .btn-filter{padding:3px 8px;font-size:11px;color:var(--phoenix-text-color-secondary);border:1px solid rgba(88,88,88,.2);border-radius:3px;cursor:pointer}.event-browser .filter-controls .btn-filter:hover{background:#ffffff1a}.event-browser .filter-controls .filter-separator{color:var(--phoenix-text-color-secondary);opacity:.4;margin:0 .2rem}.event-browser .filter-controls .filter-label{font-size:11px;color:var(--phoenix-text-color-secondary)}.event-browser .filter-controls .gap-1{gap:.3rem}.event-browser .active-filters .filter-tag{display:inline-flex;align-items:center;padding:1px 6px;margin-right:.3rem;font-size:10px;color:var(--phoenix-text-color-secondary);background:#ffffff14;border-radius:3px}.event-browser .active-filters .filter-tag .filter-remove{margin-left:4px;font-size:12px;line-height:1;cursor:pointer;color:var(--phoenix-text-color-secondary);opacity:.6}.event-browser .active-filters .filter-tag .filter-remove:hover{opacity:1}.event-browser .summary-count{font-size:11px;color:var(--phoenix-text-color-secondary);opacity:.7}.boxBody{height:80%;overflow:auto;outline:none}.boxBody p.emptyBox{max-width:21em;color:var(--phoenix-text-color-secondary)}.event-browser table{position:relative;color:var(--phoenix-text-color-secondary);font-size:12px}.event-browser table thead tr th{position:sticky;top:0;z-index:100;background:var(--phoenix-background-color-secondary);white-space:nowrap;font-size:11px}.event-browser table thead tr th .head-wrapper{display:flex;align-items:center}.event-browser table thead tr th .head-wrapper .sort-options{display:flex;flex-direction:row}.event-browser table thead tr th .head-wrapper .sort-options .icon-wrapper{display:flex;width:.85rem;height:.85rem;padding:.15rem;opacity:.5;transition:opacity .15s}.event-browser table thead tr th .head-wrapper .sort-options .icon-wrapper:hover,.event-browser table thead tr th .head-wrapper .sort-options .icon-wrapper.sort-active{opacity:1}.event-browser table thead tr th .head-wrapper .sort-options .icon-wrapper.up{transform:rotate(180deg)}.event-browser table thead tr th .head-wrapper .sort-options .icon-wrapper svg{width:100%;height:100%}.event-browser table tr *{padding-right:.8rem}.event-browser table tr *:last-child{padding-right:0}.event-browser table tbody tr{cursor:pointer;transition:background .1s}.event-browser table tbody tr:hover{background:#ffffff0d}.event-browser table tbody tr.selected-event{color:var(--phoenix-background-color);background:var(--phoenix-text-color);box-shadow:0 0 10px var(--phoenix-text-color)}.event-browser table tbody tr.loaded-event:not(.selected-event){background:#ffffff08}.event-browser table tbody tr.loaded-event:not(.selected-event) .event-label{font-weight:600}.event-browser table td{min-height:20px;white-space:nowrap}.event-browser table td.dim-zero{opacity:.25}.event-browser table .event-cell .event-label{font-size:12px;line-height:1.3}.event-browser table .event-cell .run-label{font-size:10px;opacity:.55;line-height:1.2}.event-browser table .btn-load{position:relative;width:1.4rem;height:1.4rem;text-align:center;background-color:var(--phoenix-options-icon-bg);border-radius:8px;cursor:pointer}.event-browser table .btn-load:hover{border:1px solid var(--phoenix-options-icon-path)}.event-browser table .btn-load .load-icon{position:absolute;top:0;left:0;padding:.3rem;width:100%;height:100%}.event-browser table .btn-load .loading-spinner{display:inline-block;width:10px;height:10px;border:2px solid rgba(255,255,255,.2);border-top-color:var(--phoenix-text-color-secondary);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.keyboard-hint{font-size:10px;color:var(--phoenix-text-color-secondary);opacity:.5}\n"] }]
4990
5677
  }], () => [{ type: EventDisplayService }], { showEventBrowser: [{
4991
5678
  type: Input
4992
5679
  }] }); })();
@@ -5000,7 +5687,12 @@ class EventBrowserComponent {
5000
5687
  this.overlay = overlay;
5001
5688
  }
5002
5689
  ngOnInit() {
5003
- const overlayRef = this.overlay.create();
5690
+ const positionStrategy = this.overlay
5691
+ .position()
5692
+ .global()
5693
+ .bottom('15px')
5694
+ .right('15px');
5695
+ const overlayRef = this.overlay.create({ positionStrategy });
5004
5696
  const overlayPortal = new ComponentPortal(EventBrowserOverlayComponent);
5005
5697
  this.overlayWindow = overlayRef.attach(overlayPortal);
5006
5698
  this.overlayWindow.instance.showEventBrowser = this.showEventBrowser;
@@ -5012,7 +5704,7 @@ class EventBrowserComponent {
5012
5704
  this.showEventBrowser = !this.showEventBrowser;
5013
5705
  this.overlayWindow.instance.showEventBrowser = this.showEventBrowser;
5014
5706
  }
5015
- static ɵfac = function EventBrowserComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || EventBrowserComponent)(i0.ɵɵdirectiveInject(i1$1.Overlay)); };
5707
+ static ɵfac = function EventBrowserComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || EventBrowserComponent)(i0.ɵɵdirectiveInject(i1$3.Overlay)); };
5016
5708
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: EventBrowserComponent, selectors: [["app-event-browser"]], standalone: false, decls: 1, vars: 1, consts: [["tooltip", "Event browser", "icon", "event-browser", 3, "click", "active"]], template: function EventBrowserComponent_Template(rf, ctx) { if (rf & 1) {
5017
5709
  i0.ɵɵelementStart(0, "app-menu-toggle", 0);
5018
5710
  i0.ɵɵlistener("click", function EventBrowserComponent_Template_app_menu_toggle_click_0_listener() { return ctx.toggleOverlay(); });
@@ -5024,7 +5716,7 @@ class EventBrowserComponent {
5024
5716
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EventBrowserComponent, [{
5025
5717
  type: Component,
5026
5718
  args: [{ standalone: false, selector: 'app-event-browser', template: "<app-menu-toggle\n tooltip=\"Event browser\"\n icon=\"event-browser\"\n [active]=\"showEventBrowser\"\n (click)=\"toggleOverlay()\"\n>\n</app-menu-toggle>\n" }]
5027
- }], () => [{ type: i1$1.Overlay }], null); })();
5719
+ }], () => [{ type: i1$3.Overlay }], null); })();
5028
5720
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EventBrowserComponent, { className: "EventBrowserComponent", filePath: "components/ui-menu/event-browser/event-browser.component.ts", lineNumber: 17 }); })();
5029
5721
 
5030
5722
  const _c0$3 = ["etaPhiCanvas"];
@@ -5062,6 +5754,8 @@ class EtaPhiPanelOverlayComponent {
5062
5754
  ngOnInit() {
5063
5755
  this.unsubscribes.push(this.eventDisplay.listenToDisplayedEventChange(() => {
5064
5756
  this.rebuildData();
5757
+ }), this.eventDisplay.listenToStateChange?.(() => {
5758
+ this.rebuildData();
5065
5759
  }));
5066
5760
  }
5067
5761
  ngAfterViewInit() {
@@ -5185,6 +5879,9 @@ class EtaPhiPanelOverlayComponent {
5185
5879
  if (!collections[caloType])
5186
5880
  continue;
5187
5881
  for (const collName of collections[caloType]) {
5882
+ if (this.eventDisplay.isCollectionVisible?.(collName) === false) {
5883
+ continue;
5884
+ }
5188
5885
  const items = this.eventDisplay.getCollection(collName);
5189
5886
  if (!items)
5190
5887
  continue;
@@ -5194,6 +5891,9 @@ class EtaPhiPanelOverlayComponent {
5194
5891
  }
5195
5892
  if (cell.energy <= energyThreshold)
5196
5893
  continue;
5894
+ if (this.eventDisplay.isItemVisible?.(collName, cell) === false) {
5895
+ continue;
5896
+ }
5197
5897
  const ei = Math.floor(((cell.eta - etaRange[0]) / (etaRange[1] - etaRange[0])) * etaBins);
5198
5898
  const pi = Math.floor(((cell.phi - phiRange[0]) / (phiRange[1] - phiRange[0])) * phiBins);
5199
5899
  if (ei >= 0 && ei < etaBins && pi >= 0 && pi < phiBins) {
@@ -5218,12 +5918,18 @@ class EtaPhiPanelOverlayComponent {
5218
5918
  // Jets
5219
5919
  if (collections['Jets']) {
5220
5920
  for (const collName of collections['Jets']) {
5921
+ if (this.eventDisplay.isCollectionVisible?.(collName) === false) {
5922
+ continue;
5923
+ }
5221
5924
  const jets = this.eventDisplay.getCollection(collName);
5222
5925
  if (!jets)
5223
5926
  continue;
5224
5927
  jets.forEach((jet, i) => {
5225
5928
  if (jet.eta == null || jet.phi == null)
5226
5929
  return;
5930
+ if (this.eventDisplay.isItemVisible?.(collName, jet) === false) {
5931
+ return;
5932
+ }
5227
5933
  this.markers.push({
5228
5934
  eta: jet.eta,
5229
5935
  phi: jet.phi,
@@ -5239,12 +5945,18 @@ class EtaPhiPanelOverlayComponent {
5239
5945
  // Muons
5240
5946
  if (collections['Muons']) {
5241
5947
  for (const collName of collections['Muons']) {
5948
+ if (this.eventDisplay.isCollectionVisible?.(collName) === false) {
5949
+ continue;
5950
+ }
5242
5951
  const muons = this.eventDisplay.getCollection(collName);
5243
5952
  if (!muons)
5244
5953
  continue;
5245
5954
  muons.forEach((mu, i) => {
5246
5955
  if (mu.eta == null || mu.phi == null)
5247
5956
  return;
5957
+ if (this.eventDisplay.isItemVisible?.(collName, mu) === false) {
5958
+ return;
5959
+ }
5248
5960
  this.markers.push({
5249
5961
  eta: mu.eta,
5250
5962
  phi: mu.phi,
@@ -5259,12 +5971,18 @@ class EtaPhiPanelOverlayComponent {
5259
5971
  // Electrons
5260
5972
  if (collections['Electrons']) {
5261
5973
  for (const collName of collections['Electrons']) {
5974
+ if (this.eventDisplay.isCollectionVisible?.(collName) === false) {
5975
+ continue;
5976
+ }
5262
5977
  const electrons = this.eventDisplay.getCollection(collName);
5263
5978
  if (!electrons)
5264
5979
  continue;
5265
5980
  electrons.forEach((el, i) => {
5266
5981
  if (el.eta == null || el.phi == null)
5267
5982
  return;
5983
+ if (this.eventDisplay.isItemVisible?.(collName, el) === false) {
5984
+ return;
5985
+ }
5268
5986
  this.markers.push({
5269
5987
  eta: el.eta,
5270
5988
  phi: el.phi,
@@ -5279,12 +5997,18 @@ class EtaPhiPanelOverlayComponent {
5279
5997
  // Photons
5280
5998
  if (collections['Photons']) {
5281
5999
  for (const collName of collections['Photons']) {
6000
+ if (this.eventDisplay.isCollectionVisible?.(collName) === false) {
6001
+ continue;
6002
+ }
5282
6003
  const photons = this.eventDisplay.getCollection(collName);
5283
6004
  if (!photons)
5284
6005
  continue;
5285
6006
  photons.forEach((ph, i) => {
5286
6007
  if (ph.eta == null || ph.phi == null)
5287
6008
  return;
6009
+ if (this.eventDisplay.isItemVisible?.(collName, ph) === false) {
6010
+ return;
6011
+ }
5288
6012
  this.markers.push({
5289
6013
  eta: ph.eta,
5290
6014
  phi: ph.phi,
@@ -5299,6 +6023,9 @@ class EtaPhiPanelOverlayComponent {
5299
6023
  // Tracks (derive eta/phi from dparams if needed)
5300
6024
  if (collections['Tracks']) {
5301
6025
  for (const collName of collections['Tracks']) {
6026
+ if (this.eventDisplay.isCollectionVisible?.(collName) === false) {
6027
+ continue;
6028
+ }
5302
6029
  const tracks = this.eventDisplay.getCollection(collName);
5303
6030
  if (!tracks)
5304
6031
  continue;
@@ -5312,6 +6039,9 @@ class EtaPhiPanelOverlayComponent {
5312
6039
  }
5313
6040
  if (eta == null || phi == null)
5314
6041
  return;
6042
+ if (this.eventDisplay.isItemVisible?.(collName, trk) === false) {
6043
+ return;
6044
+ }
5315
6045
  this.markers.push({
5316
6046
  eta,
5317
6047
  phi,
@@ -5326,12 +6056,18 @@ class EtaPhiPanelOverlayComponent {
5326
6056
  // MET
5327
6057
  if (collections['MissingEnergy']) {
5328
6058
  for (const collName of collections['MissingEnergy']) {
6059
+ if (this.eventDisplay.isCollectionVisible?.(collName) === false) {
6060
+ continue;
6061
+ }
5329
6062
  const metItems = this.eventDisplay.getCollection(collName);
5330
6063
  if (!metItems)
5331
6064
  continue;
5332
6065
  for (const met of metItems) {
5333
6066
  if (met.etx == null || met.ety == null)
5334
6067
  continue;
6068
+ if (this.eventDisplay.isItemVisible?.(collName, met) === false) {
6069
+ continue;
6070
+ }
5335
6071
  const metPhi = Math.atan2(met.ety, met.etx);
5336
6072
  const metMag = Math.sqrt(met.etx * met.etx + met.ety * met.ety);
5337
6073
  this.markers.push({
@@ -5666,13 +6402,16 @@ class EtaPhiPanelOverlayComponent {
5666
6402
 
5667
6403
  class EtaPhiPanelComponent {
5668
6404
  overlay;
6405
+ overlayCascade;
5669
6406
  showPanel = false;
5670
6407
  overlayWindow;
5671
- constructor(overlay) {
6408
+ constructor(overlay, overlayCascade) {
5672
6409
  this.overlay = overlay;
6410
+ this.overlayCascade = overlayCascade;
5673
6411
  }
5674
6412
  ngOnInit() {
5675
- const overlayRef = this.overlay.create();
6413
+ const positionStrategy = this.overlayCascade.getCascadePositionStrategy();
6414
+ const overlayRef = this.overlay.create({ positionStrategy });
5676
6415
  const overlayPortal = new ComponentPortal(EtaPhiPanelOverlayComponent);
5677
6416
  this.overlayWindow = overlayRef.attach(overlayPortal);
5678
6417
  this.overlayWindow.instance.showPanel = this.showPanel;
@@ -5684,7 +6423,7 @@ class EtaPhiPanelComponent {
5684
6423
  this.showPanel = !this.showPanel;
5685
6424
  this.overlayWindow.instance.showPanel = this.showPanel;
5686
6425
  }
5687
- static ɵfac = function EtaPhiPanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || EtaPhiPanelComponent)(i0.ɵɵdirectiveInject(i1$1.Overlay)); };
6426
+ static ɵfac = function EtaPhiPanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || EtaPhiPanelComponent)(i0.ɵɵdirectiveInject(i1$3.Overlay), i0.ɵɵdirectiveInject(OverlayCascadeService)); };
5688
6427
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: EtaPhiPanelComponent, selectors: [["app-eta-phi-panel"]], standalone: false, decls: 1, vars: 1, consts: [["tooltip", "Eta-phi energy map", "icon", "eta-phi", 3, "click", "active"]], template: function EtaPhiPanelComponent_Template(rf, ctx) { if (rf & 1) {
5689
6428
  i0.ɵɵelementStart(0, "app-menu-toggle", 0);
5690
6429
  i0.ɵɵlistener("click", function EtaPhiPanelComponent_Template_app_menu_toggle_click_0_listener() { return ctx.toggleOverlay(); });
@@ -5696,8 +6435,8 @@ class EtaPhiPanelComponent {
5696
6435
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EtaPhiPanelComponent, [{
5697
6436
  type: Component,
5698
6437
  args: [{ standalone: false, selector: 'app-eta-phi-panel', template: "<app-menu-toggle\n tooltip=\"Eta-phi energy map\"\n icon=\"eta-phi\"\n [active]=\"showPanel\"\n (click)=\"toggleOverlay()\"\n>\n</app-menu-toggle>\n" }]
5699
- }], () => [{ type: i1$1.Overlay }], null); })();
5700
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EtaPhiPanelComponent, { className: "EtaPhiPanelComponent", filePath: "components/ui-menu/eta-phi-panel/eta-phi-panel.component.ts", lineNumber: 17 }); })();
6438
+ }], () => [{ type: i1$3.Overlay }, { type: OverlayCascadeService }], null); })();
6439
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EtaPhiPanelComponent, { className: "EtaPhiPanelComponent", filePath: "components/ui-menu/eta-phi-panel/eta-phi-panel.component.ts", lineNumber: 18 }); })();
5701
6440
 
5702
6441
  function KinematicsPanelOverlayComponent_div_1_div_1_div_2_option_2_Template(rf, ctx) { if (rf & 1) {
5703
6442
  i0.ɵɵelementStart(0, "option", 17);
@@ -6227,11 +6966,11 @@ class KinematicsPanelOverlayComponent {
6227
6966
  i0.ɵɵproperty("overlayTitle", ctx.config.title)("resizable", true)("active", ctx.showKinematics);
6228
6967
  i0.ɵɵadvance();
6229
6968
  i0.ɵɵproperty("ngIf", ctx.showKinematics);
6230
- } }, dependencies: [i2.NgClass, i2.NgForOf, i2.NgIf, i3$1.MatTooltip, i4.NgSelectOption, i4.ɵNgSelectMultipleOption, OverlayComponent], styles: [".kinematics-panel[_ngcontent-%COMP%]{height:95%;display:flex;flex-direction:column}.panel-header[_ngcontent-%COMP%]{flex-shrink:0;padding:.5rem .75rem .25rem}.panel-header[_ngcontent-%COMP%] .header-top[_ngcontent-%COMP%]{display:flex;align-items:center;gap:.8rem;min-height:2rem}.panel-header[_ngcontent-%COMP%] .collection-selector[_ngcontent-%COMP%] select[_ngcontent-%COMP%]{padding:4px 8px;font-size:12px;border:1px solid rgba(88,88,88,.08);box-shadow:var(--phoenix-icon-shadow);background-color:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary)}.panel-header[_ngcontent-%COMP%] .collection-name[_ngcontent-%COMP%]{color:var(--phoenix-text-color-secondary);font-size:.85rem;font-weight:500}.panel-header[_ngcontent-%COMP%] .track-count[_ngcontent-%COMP%]{color:var(--phoenix-text-color-secondary);font-size:.75rem;opacity:.6;margin-left:auto}.panel-header[_ngcontent-%COMP%] .export-btn[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;padding:.3rem;border:none;background:var(--phoenix-options-icon-bg);border-radius:8px;cursor:pointer;flex-shrink:0}.panel-header[_ngcontent-%COMP%] .export-btn[_ngcontent-%COMP%]:hover{outline:1px solid var(--phoenix-options-icon-path)}.panel-header[_ngcontent-%COMP%] .export-btn[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{width:100%;height:100%}.filter-row[_ngcontent-%COMP%]{padding:.3rem 0}.filter-row[_ngcontent-%COMP%] label[_ngcontent-%COMP%]{display:flex;align-items:center;gap:.4rem;color:var(--phoenix-text-color-secondary);font-size:.75rem;opacity:.8}.filter-row[_ngcontent-%COMP%] .filter-input[_ngcontent-%COMP%]{width:4rem;padding:2px 6px;font-size:.75rem;border:1px solid rgba(255,255,255,.12);border-radius:4px;background:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary)}.filter-row[_ngcontent-%COMP%] .filter-input[_ngcontent-%COMP%]:focus{outline:1px solid var(--phoenix-text-color-secondary)}.filter-row[_ngcontent-%COMP%] .filter-unit[_ngcontent-%COMP%]{opacity:.5;font-size:.7rem}.table-container[_ngcontent-%COMP%]{flex:1;overflow:auto;outline:none}.kinematics-table[_ngcontent-%COMP%]{width:100%;border-collapse:collapse;color:var(--phoenix-text-color-secondary)}.kinematics-table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%]{position:sticky;top:0;z-index:10;background:var(--phoenix-background-color-secondary);padding:.4rem .7rem;font-size:.78rem;font-weight:600;white-space:nowrap;text-align:left;border-bottom:1px solid rgba(255,255,255,.08)}.kinematics-table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th.sortable[_ngcontent-%COMP%]{cursor:pointer;-webkit-user-select:none;user-select:none}.kinematics-table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th.sortable[_ngcontent-%COMP%]:hover, .kinematics-table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th.sorted[_ngcontent-%COMP%]{color:var(--phoenix-text-color)}.kinematics-table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .col-unit[_ngcontent-%COMP%]{opacity:.5;font-weight:400;font-size:.68rem;margin-left:.15rem}.kinematics-table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .sort-arrow[_ngcontent-%COMP%]{font-size:.55rem;margin-left:.2rem;opacity:.7}.kinematics-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]{cursor:pointer;transition:background .12s ease}.kinematics-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]:hover{background:#ffffff0a}.kinematics-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.active-object[_ngcontent-%COMP%]{color:var(--phoenix-background-color);background:var(--phoenix-text-color);box-shadow:0 0 12px var(--phoenix-text-color)}.kinematics-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.active-object[_ngcontent-%COMP%] .icon-btn[_ngcontent-%COMP%]{--phoenix-options-icon-path: var(--phoenix-background-color);--phoenix-options-icon-bg: var(--phoenix-text-color-hover)}.kinematics-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.is-cut[_ngcontent-%COMP%]{opacity:.35}.kinematics-table[_ngcontent-%COMP%] td[_ngcontent-%COMP%]{padding:.3rem .7rem;font-size:.78rem;font-variant-numeric:tabular-nums}.kinematics-table[_ngcontent-%COMP%] td.col-index[_ngcontent-%COMP%]{color:#fff6;width:2.5rem;text-align:right;padding-right:.4rem;white-space:nowrap}.kinematics-table[_ngcontent-%COMP%] td.col-action[_ngcontent-%COMP%]{width:2rem;padding:.2rem}.kinematics-table[_ngcontent-%COMP%] td.col-data[_ngcontent-%COMP%]{text-align:right}.charge-pos[_ngcontent-%COMP%]{color:#ff7b7b;font-weight:600}.charge-pos[_ngcontent-%COMP%]:before{content:\"+\"}.charge-neg[_ngcontent-%COMP%]{color:#7bb5ff;font-weight:600}.icon-btn[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:center;width:1.4rem;height:1.4rem;padding:.3rem;border:none;background:var(--phoenix-options-icon-bg);border-radius:8px;cursor:pointer}.icon-btn[_ngcontent-%COMP%]:hover{outline:1px solid var(--phoenix-options-icon-path)}.icon-btn[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{width:100%;height:100%}.empty-state[_ngcontent-%COMP%]{color:var(--phoenix-text-color-secondary);opacity:.5;text-align:center;padding:2rem;max-width:18em;margin:2rem auto;font-size:.85rem}"] });
6969
+ } }, dependencies: [i2.NgClass, i2.NgForOf, i2.NgIf, i3$1.MatTooltip, i4.NgSelectOption, i4.ɵNgSelectMultipleOption, OverlayComponent], styles: [".kinematics-panel[_ngcontent-%COMP%]{width:620px;height:450px;display:flex;flex-direction:column}.panel-header[_ngcontent-%COMP%]{flex-shrink:0;padding:.5rem .75rem .25rem}.panel-header[_ngcontent-%COMP%] .header-top[_ngcontent-%COMP%]{display:flex;align-items:center;gap:.8rem;min-height:2rem}.panel-header[_ngcontent-%COMP%] .collection-selector[_ngcontent-%COMP%] select[_ngcontent-%COMP%]{padding:4px 8px;font-size:12px;border:1px solid rgba(88,88,88,.08);box-shadow:var(--phoenix-icon-shadow);background-color:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary)}.panel-header[_ngcontent-%COMP%] .collection-name[_ngcontent-%COMP%]{color:var(--phoenix-text-color-secondary);font-size:.85rem;font-weight:500}.panel-header[_ngcontent-%COMP%] .track-count[_ngcontent-%COMP%]{color:var(--phoenix-text-color-secondary);font-size:.75rem;opacity:.6;margin-left:auto}.panel-header[_ngcontent-%COMP%] .export-btn[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;padding:.3rem;border:none;background:var(--phoenix-options-icon-bg);border-radius:8px;cursor:pointer;flex-shrink:0}.panel-header[_ngcontent-%COMP%] .export-btn[_ngcontent-%COMP%]:hover{outline:1px solid var(--phoenix-options-icon-path)}.panel-header[_ngcontent-%COMP%] .export-btn[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{width:100%;height:100%}.filter-row[_ngcontent-%COMP%]{padding:.3rem 0}.filter-row[_ngcontent-%COMP%] label[_ngcontent-%COMP%]{display:flex;align-items:center;gap:.4rem;color:var(--phoenix-text-color-secondary);font-size:.75rem;opacity:.8}.filter-row[_ngcontent-%COMP%] .filter-input[_ngcontent-%COMP%]{width:4rem;padding:2px 6px;font-size:.75rem;border:1px solid rgba(255,255,255,.12);border-radius:4px;background:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary)}.filter-row[_ngcontent-%COMP%] .filter-input[_ngcontent-%COMP%]:focus{outline:1px solid var(--phoenix-text-color-secondary)}.filter-row[_ngcontent-%COMP%] .filter-unit[_ngcontent-%COMP%]{opacity:.5;font-size:.7rem}.table-container[_ngcontent-%COMP%]{flex:1;overflow:auto;outline:none}.kinematics-table[_ngcontent-%COMP%]{width:100%;border-collapse:collapse;color:var(--phoenix-text-color-secondary)}.kinematics-table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%]{position:sticky;top:0;z-index:10;background:var(--phoenix-background-color-secondary);padding:.4rem .7rem;font-size:.78rem;font-weight:600;white-space:nowrap;text-align:left;border-bottom:1px solid rgba(255,255,255,.08)}.kinematics-table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th.sortable[_ngcontent-%COMP%]{cursor:pointer;-webkit-user-select:none;user-select:none}.kinematics-table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th.sortable[_ngcontent-%COMP%]:hover, .kinematics-table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th.sorted[_ngcontent-%COMP%]{color:var(--phoenix-text-color)}.kinematics-table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .col-unit[_ngcontent-%COMP%]{opacity:.5;font-weight:400;font-size:.68rem;margin-left:.15rem}.kinematics-table[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] th[_ngcontent-%COMP%] .sort-arrow[_ngcontent-%COMP%]{font-size:.55rem;margin-left:.2rem;opacity:.7}.kinematics-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]{cursor:pointer;transition:background .12s ease}.kinematics-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]:hover{background:#ffffff0a}.kinematics-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.active-object[_ngcontent-%COMP%]{color:var(--phoenix-background-color);background:var(--phoenix-text-color);box-shadow:0 0 12px var(--phoenix-text-color)}.kinematics-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.active-object[_ngcontent-%COMP%] .icon-btn[_ngcontent-%COMP%]{--phoenix-options-icon-path: var(--phoenix-background-color);--phoenix-options-icon-bg: var(--phoenix-text-color-hover)}.kinematics-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.is-cut[_ngcontent-%COMP%]{opacity:.35}.kinematics-table[_ngcontent-%COMP%] td[_ngcontent-%COMP%]{padding:.3rem .7rem;font-size:.78rem;font-variant-numeric:tabular-nums}.kinematics-table[_ngcontent-%COMP%] td.col-index[_ngcontent-%COMP%]{color:#fff6;width:2.5rem;text-align:right;padding-right:.4rem;white-space:nowrap}.kinematics-table[_ngcontent-%COMP%] td.col-action[_ngcontent-%COMP%]{width:2rem;padding:.2rem}.kinematics-table[_ngcontent-%COMP%] td.col-data[_ngcontent-%COMP%]{text-align:right}.charge-pos[_ngcontent-%COMP%]{color:#ff7b7b;font-weight:600}.charge-pos[_ngcontent-%COMP%]:before{content:\"+\"}.charge-neg[_ngcontent-%COMP%]{color:#7bb5ff;font-weight:600}.icon-btn[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:center;width:1.4rem;height:1.4rem;padding:.3rem;border:none;background:var(--phoenix-options-icon-bg);border-radius:8px;cursor:pointer}.icon-btn[_ngcontent-%COMP%]:hover{outline:1px solid var(--phoenix-options-icon-path)}.icon-btn[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{width:100%;height:100%}.empty-state[_ngcontent-%COMP%]{color:var(--phoenix-text-color-secondary);opacity:.5;text-align:center;padding:2rem;max-width:18em;margin:2rem auto;font-size:.85rem}"] });
6231
6970
  }
6232
6971
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(KinematicsPanelOverlayComponent, [{
6233
6972
  type: Component,
6234
- args: [{ standalone: false, selector: 'app-kinematics-panel-overlay', template: "<app-overlay\n [overlayTitle]=\"config.title\"\n icon=\"kinematics\"\n [resizable]=\"true\"\n [active]=\"showKinematics\"\n>\n <div *ngIf=\"showKinematics\" class=\"kinematics-panel\">\n <!-- Header: collection selector + filter + count + export -->\n <div class=\"panel-header\" *ngIf=\"collections?.length > 0\">\n <div class=\"header-top\">\n <div class=\"collection-selector\" *ngIf=\"collections.length > 1\">\n <select (change)=\"selectCollection($event.target.value)\">\n <option\n *ngFor=\"let c of collections\"\n [value]=\"c\"\n [selected]=\"c === selectedCollection\"\n >\n {{ c }}\n </option>\n </select>\n </div>\n <span class=\"collection-name\" *ngIf=\"collections.length === 1\">\n {{ selectedCollection }}\n </span>\n <span class=\"track-count\">\n {{ rows.length\n }}<span *ngIf=\"filterValue > 0\"> / {{ allRows.length }}</span> tracks\n </span>\n <button\n class=\"export-btn\"\n matTooltip=\"Export as TSV\"\n (click)=\"exportTSV()\"\n *ngIf=\"rows.length > 0\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"var(--phoenix-options-icon-path, #fff)\"\n >\n <path\n d=\"M10 3a.75.75 0 0 1 .75.75v7.69l2.22-2.22a.75.75 0 1 1 1.06 1.06l-3.5 3.5a.75.75 0 0 1-1.06 0l-3.5-3.5a.75.75 0 1 1 1.06-1.06l2.22 2.22V3.75A.75.75 0 0 1 10 3Z\"\n />\n <path\n d=\"M3 15.75a.75.75 0 0 1 .75-.75h12.5a.75.75 0 0 1 0 1.5H3.75a.75.75 0 0 1-.75-.75Z\"\n />\n </svg>\n </button>\n </div>\n\n <!-- Filter row -->\n <div class=\"filter-row\" *ngIf=\"filterCol\">\n <label>\n {{ filterCol.label }} &ge;\n <input\n type=\"number\"\n class=\"filter-input\"\n [value]=\"filterValue\"\n (input)=\"onFilterChange($event.target.value)\"\n step=\"1\"\n min=\"0\"\n />\n <span class=\"filter-unit\" *ngIf=\"filterCol.unit\">{{\n filterCol.unit\n }}</span>\n </label>\n </div>\n </div>\n\n <!-- Table with keyboard navigation -->\n <div\n class=\"table-container\"\n *ngIf=\"rows.length > 0\"\n tabindex=\"0\"\n (keydown)=\"onKeyDown($event)\"\n >\n <table class=\"kinematics-table\">\n <thead>\n <tr>\n <th class=\"col-index\">#</th>\n <th class=\"col-action\"></th>\n <th\n *ngFor=\"let col of config.columns\"\n class=\"col-data sortable\"\n [class.sorted]=\"sortColumn === col.id\"\n [matTooltip]=\"col.tooltip\"\n (click)=\"sort(col.id)\"\n >\n <span class=\"col-label\">{{ col.label }}</span>\n <span *ngIf=\"col.unit\" class=\"col-unit\">({{ col.unit }})</span>\n <span class=\"sort-arrow\" *ngIf=\"sortColumn === col.id\">\n {{ sortDirection === 'asc' ? '\\u25B2' : '\\u25BC' }}\n </span>\n </th>\n </tr>\n </thead>\n <tbody>\n <tr\n *ngFor=\"let row of rows; index as i\"\n [id]=\"'kin-' + row.uuid\"\n [class.active-object]=\"activeRowUuid === row.uuid\"\n [class.is-cut]=\"row.isCut\"\n (click)=\"selectTrack(row)\"\n >\n <td class=\"col-index\" [style.color]=\"getChargeColor(row)\">\n {{ i + 1 }}\n </td>\n <td class=\"col-action\">\n <button\n *ngIf=\"row.uuid\"\n class=\"icon-btn\"\n matTooltip=\"Look at track\"\n (click)=\"lookAtTrack(row.uuid, $event)\"\n >\n <svg>\n <use href=\"assets/icons/views.svg#views\"></use>\n </svg>\n </button>\n </td>\n <td\n *ngFor=\"let col of config.columns\"\n class=\"col-data\"\n [ngClass]=\"getChargeClass(col, row.values[col.id])\"\n >\n {{ formatValue(row.values[col.id], col) }}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Empty states -->\n <p\n class=\"empty-state\"\n *ngIf=\"collections?.length > 0 && rows.length === 0 && allRows.length > 0\"\n >\n No tracks above {{ filterCol?.label }} &ge; {{ filterValue }}\n {{ filterCol?.unit }}.\n </p>\n <p\n class=\"empty-state\"\n *ngIf=\"collections?.length > 0 && allRows.length === 0\"\n >\n No objects in this collection.\n </p>\n <p class=\"empty-state\" *ngIf=\"!collections || collections.length === 0\">\n Load event data to view track kinematics.\n </p>\n </div>\n</app-overlay>\n", styles: [".kinematics-panel{height:95%;display:flex;flex-direction:column}.panel-header{flex-shrink:0;padding:.5rem .75rem .25rem}.panel-header .header-top{display:flex;align-items:center;gap:.8rem;min-height:2rem}.panel-header .collection-selector select{padding:4px 8px;font-size:12px;border:1px solid rgba(88,88,88,.08);box-shadow:var(--phoenix-icon-shadow);background-color:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary)}.panel-header .collection-name{color:var(--phoenix-text-color-secondary);font-size:.85rem;font-weight:500}.panel-header .track-count{color:var(--phoenix-text-color-secondary);font-size:.75rem;opacity:.6;margin-left:auto}.panel-header .export-btn{display:flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;padding:.3rem;border:none;background:var(--phoenix-options-icon-bg);border-radius:8px;cursor:pointer;flex-shrink:0}.panel-header .export-btn:hover{outline:1px solid var(--phoenix-options-icon-path)}.panel-header .export-btn svg{width:100%;height:100%}.filter-row{padding:.3rem 0}.filter-row label{display:flex;align-items:center;gap:.4rem;color:var(--phoenix-text-color-secondary);font-size:.75rem;opacity:.8}.filter-row .filter-input{width:4rem;padding:2px 6px;font-size:.75rem;border:1px solid rgba(255,255,255,.12);border-radius:4px;background:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary)}.filter-row .filter-input:focus{outline:1px solid var(--phoenix-text-color-secondary)}.filter-row .filter-unit{opacity:.5;font-size:.7rem}.table-container{flex:1;overflow:auto;outline:none}.kinematics-table{width:100%;border-collapse:collapse;color:var(--phoenix-text-color-secondary)}.kinematics-table thead tr th{position:sticky;top:0;z-index:10;background:var(--phoenix-background-color-secondary);padding:.4rem .7rem;font-size:.78rem;font-weight:600;white-space:nowrap;text-align:left;border-bottom:1px solid rgba(255,255,255,.08)}.kinematics-table thead tr th.sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.kinematics-table thead tr th.sortable:hover,.kinematics-table thead tr th.sorted{color:var(--phoenix-text-color)}.kinematics-table thead tr th .col-unit{opacity:.5;font-weight:400;font-size:.68rem;margin-left:.15rem}.kinematics-table thead tr th .sort-arrow{font-size:.55rem;margin-left:.2rem;opacity:.7}.kinematics-table tbody tr{cursor:pointer;transition:background .12s ease}.kinematics-table tbody tr:hover{background:#ffffff0a}.kinematics-table tbody tr.active-object{color:var(--phoenix-background-color);background:var(--phoenix-text-color);box-shadow:0 0 12px var(--phoenix-text-color)}.kinematics-table tbody tr.active-object .icon-btn{--phoenix-options-icon-path: var(--phoenix-background-color);--phoenix-options-icon-bg: var(--phoenix-text-color-hover)}.kinematics-table tbody tr.is-cut{opacity:.35}.kinematics-table td{padding:.3rem .7rem;font-size:.78rem;font-variant-numeric:tabular-nums}.kinematics-table td.col-index{color:#fff6;width:2.5rem;text-align:right;padding-right:.4rem;white-space:nowrap}.kinematics-table td.col-action{width:2rem;padding:.2rem}.kinematics-table td.col-data{text-align:right}.charge-pos{color:#ff7b7b;font-weight:600}.charge-pos:before{content:\"+\"}.charge-neg{color:#7bb5ff;font-weight:600}.icon-btn{display:flex;align-items:center;justify-content:center;width:1.4rem;height:1.4rem;padding:.3rem;border:none;background:var(--phoenix-options-icon-bg);border-radius:8px;cursor:pointer}.icon-btn:hover{outline:1px solid var(--phoenix-options-icon-path)}.icon-btn svg{width:100%;height:100%}.empty-state{color:var(--phoenix-text-color-secondary);opacity:.5;text-align:center;padding:2rem;max-width:18em;margin:2rem auto;font-size:.85rem}\n"] }]
6973
+ args: [{ standalone: false, selector: 'app-kinematics-panel-overlay', template: "<app-overlay\n [overlayTitle]=\"config.title\"\n icon=\"kinematics\"\n [resizable]=\"true\"\n [active]=\"showKinematics\"\n>\n <div *ngIf=\"showKinematics\" class=\"kinematics-panel\">\n <!-- Header: collection selector + filter + count + export -->\n <div class=\"panel-header\" *ngIf=\"collections?.length > 0\">\n <div class=\"header-top\">\n <div class=\"collection-selector\" *ngIf=\"collections.length > 1\">\n <select (change)=\"selectCollection($event.target.value)\">\n <option\n *ngFor=\"let c of collections\"\n [value]=\"c\"\n [selected]=\"c === selectedCollection\"\n >\n {{ c }}\n </option>\n </select>\n </div>\n <span class=\"collection-name\" *ngIf=\"collections.length === 1\">\n {{ selectedCollection }}\n </span>\n <span class=\"track-count\">\n {{ rows.length\n }}<span *ngIf=\"filterValue > 0\"> / {{ allRows.length }}</span> tracks\n </span>\n <button\n class=\"export-btn\"\n matTooltip=\"Export as TSV\"\n (click)=\"exportTSV()\"\n *ngIf=\"rows.length > 0\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"var(--phoenix-options-icon-path, #fff)\"\n >\n <path\n d=\"M10 3a.75.75 0 0 1 .75.75v7.69l2.22-2.22a.75.75 0 1 1 1.06 1.06l-3.5 3.5a.75.75 0 0 1-1.06 0l-3.5-3.5a.75.75 0 1 1 1.06-1.06l2.22 2.22V3.75A.75.75 0 0 1 10 3Z\"\n />\n <path\n d=\"M3 15.75a.75.75 0 0 1 .75-.75h12.5a.75.75 0 0 1 0 1.5H3.75a.75.75 0 0 1-.75-.75Z\"\n />\n </svg>\n </button>\n </div>\n\n <!-- Filter row -->\n <div class=\"filter-row\" *ngIf=\"filterCol\">\n <label>\n {{ filterCol.label }} &ge;\n <input\n type=\"number\"\n class=\"filter-input\"\n [value]=\"filterValue\"\n (input)=\"onFilterChange($event.target.value)\"\n step=\"1\"\n min=\"0\"\n />\n <span class=\"filter-unit\" *ngIf=\"filterCol.unit\">{{\n filterCol.unit\n }}</span>\n </label>\n </div>\n </div>\n\n <!-- Table with keyboard navigation -->\n <div\n class=\"table-container\"\n *ngIf=\"rows.length > 0\"\n tabindex=\"0\"\n (keydown)=\"onKeyDown($event)\"\n >\n <table class=\"kinematics-table\">\n <thead>\n <tr>\n <th class=\"col-index\">#</th>\n <th class=\"col-action\"></th>\n <th\n *ngFor=\"let col of config.columns\"\n class=\"col-data sortable\"\n [class.sorted]=\"sortColumn === col.id\"\n [matTooltip]=\"col.tooltip\"\n (click)=\"sort(col.id)\"\n >\n <span class=\"col-label\">{{ col.label }}</span>\n <span *ngIf=\"col.unit\" class=\"col-unit\">({{ col.unit }})</span>\n <span class=\"sort-arrow\" *ngIf=\"sortColumn === col.id\">\n {{ sortDirection === 'asc' ? '\\u25B2' : '\\u25BC' }}\n </span>\n </th>\n </tr>\n </thead>\n <tbody>\n <tr\n *ngFor=\"let row of rows; index as i\"\n [id]=\"'kin-' + row.uuid\"\n [class.active-object]=\"activeRowUuid === row.uuid\"\n [class.is-cut]=\"row.isCut\"\n (click)=\"selectTrack(row)\"\n >\n <td class=\"col-index\" [style.color]=\"getChargeColor(row)\">\n {{ i + 1 }}\n </td>\n <td class=\"col-action\">\n <button\n *ngIf=\"row.uuid\"\n class=\"icon-btn\"\n matTooltip=\"Look at track\"\n (click)=\"lookAtTrack(row.uuid, $event)\"\n >\n <svg>\n <use href=\"assets/icons/views.svg#views\"></use>\n </svg>\n </button>\n </td>\n <td\n *ngFor=\"let col of config.columns\"\n class=\"col-data\"\n [ngClass]=\"getChargeClass(col, row.values[col.id])\"\n >\n {{ formatValue(row.values[col.id], col) }}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Empty states -->\n <p\n class=\"empty-state\"\n *ngIf=\"collections?.length > 0 && rows.length === 0 && allRows.length > 0\"\n >\n No tracks above {{ filterCol?.label }} &ge; {{ filterValue }}\n {{ filterCol?.unit }}.\n </p>\n <p\n class=\"empty-state\"\n *ngIf=\"collections?.length > 0 && allRows.length === 0\"\n >\n No objects in this collection.\n </p>\n <p class=\"empty-state\" *ngIf=\"!collections || collections.length === 0\">\n Load event data to view track kinematics.\n </p>\n </div>\n</app-overlay>\n", styles: [".kinematics-panel{width:620px;height:450px;display:flex;flex-direction:column}.panel-header{flex-shrink:0;padding:.5rem .75rem .25rem}.panel-header .header-top{display:flex;align-items:center;gap:.8rem;min-height:2rem}.panel-header .collection-selector select{padding:4px 8px;font-size:12px;border:1px solid rgba(88,88,88,.08);box-shadow:var(--phoenix-icon-shadow);background-color:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary)}.panel-header .collection-name{color:var(--phoenix-text-color-secondary);font-size:.85rem;font-weight:500}.panel-header .track-count{color:var(--phoenix-text-color-secondary);font-size:.75rem;opacity:.6;margin-left:auto}.panel-header .export-btn{display:flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;padding:.3rem;border:none;background:var(--phoenix-options-icon-bg);border-radius:8px;cursor:pointer;flex-shrink:0}.panel-header .export-btn:hover{outline:1px solid var(--phoenix-options-icon-path)}.panel-header .export-btn svg{width:100%;height:100%}.filter-row{padding:.3rem 0}.filter-row label{display:flex;align-items:center;gap:.4rem;color:var(--phoenix-text-color-secondary);font-size:.75rem;opacity:.8}.filter-row .filter-input{width:4rem;padding:2px 6px;font-size:.75rem;border:1px solid rgba(255,255,255,.12);border-radius:4px;background:var(--phoenix-background-color-tertiary);color:var(--phoenix-text-color-secondary)}.filter-row .filter-input:focus{outline:1px solid var(--phoenix-text-color-secondary)}.filter-row .filter-unit{opacity:.5;font-size:.7rem}.table-container{flex:1;overflow:auto;outline:none}.kinematics-table{width:100%;border-collapse:collapse;color:var(--phoenix-text-color-secondary)}.kinematics-table thead tr th{position:sticky;top:0;z-index:10;background:var(--phoenix-background-color-secondary);padding:.4rem .7rem;font-size:.78rem;font-weight:600;white-space:nowrap;text-align:left;border-bottom:1px solid rgba(255,255,255,.08)}.kinematics-table thead tr th.sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.kinematics-table thead tr th.sortable:hover,.kinematics-table thead tr th.sorted{color:var(--phoenix-text-color)}.kinematics-table thead tr th .col-unit{opacity:.5;font-weight:400;font-size:.68rem;margin-left:.15rem}.kinematics-table thead tr th .sort-arrow{font-size:.55rem;margin-left:.2rem;opacity:.7}.kinematics-table tbody tr{cursor:pointer;transition:background .12s ease}.kinematics-table tbody tr:hover{background:#ffffff0a}.kinematics-table tbody tr.active-object{color:var(--phoenix-background-color);background:var(--phoenix-text-color);box-shadow:0 0 12px var(--phoenix-text-color)}.kinematics-table tbody tr.active-object .icon-btn{--phoenix-options-icon-path: var(--phoenix-background-color);--phoenix-options-icon-bg: var(--phoenix-text-color-hover)}.kinematics-table tbody tr.is-cut{opacity:.35}.kinematics-table td{padding:.3rem .7rem;font-size:.78rem;font-variant-numeric:tabular-nums}.kinematics-table td.col-index{color:#fff6;width:2.5rem;text-align:right;padding-right:.4rem;white-space:nowrap}.kinematics-table td.col-action{width:2rem;padding:.2rem}.kinematics-table td.col-data{text-align:right}.charge-pos{color:#ff7b7b;font-weight:600}.charge-pos:before{content:\"+\"}.charge-neg{color:#7bb5ff;font-weight:600}.icon-btn{display:flex;align-items:center;justify-content:center;width:1.4rem;height:1.4rem;padding:.3rem;border:none;background:var(--phoenix-options-icon-bg);border-radius:8px;cursor:pointer}.icon-btn:hover{outline:1px solid var(--phoenix-options-icon-path)}.icon-btn svg{width:100%;height:100%}.empty-state{color:var(--phoenix-text-color-secondary);opacity:.5;text-align:center;padding:2rem;max-width:18em;margin:2rem auto;font-size:.85rem}\n"] }]
6235
6974
  }], () => [{ type: EventDisplayService }, { type: i0.ChangeDetectorRef }], { showKinematics: [{
6236
6975
  type: Input
6237
6976
  }], config: [{
@@ -6241,14 +6980,17 @@ class KinematicsPanelOverlayComponent {
6241
6980
 
6242
6981
  class KinematicsPanelComponent {
6243
6982
  overlay;
6983
+ overlayCascade;
6244
6984
  showKinematics = false;
6245
6985
  overlayWindow;
6246
6986
  config;
6247
- constructor(overlay) {
6987
+ constructor(overlay, overlayCascade) {
6248
6988
  this.overlay = overlay;
6989
+ this.overlayCascade = overlayCascade;
6249
6990
  }
6250
6991
  ngOnInit() {
6251
- const overlayRef = this.overlay.create();
6992
+ const positionStrategy = this.overlayCascade.getCascadePositionStrategy();
6993
+ const overlayRef = this.overlay.create({ positionStrategy });
6252
6994
  const overlayPortal = new ComponentPortal(KinematicsPanelOverlayComponent);
6253
6995
  this.overlayWindow = overlayRef.attach(overlayPortal);
6254
6996
  this.overlayWindow.instance.showKinematics = this.showKinematics;
@@ -6270,16 +7012,16 @@ class KinematicsPanelComponent {
6270
7012
  this.showKinematics = !this.showKinematics;
6271
7013
  this.overlayWindow.instance.showKinematics = this.showKinematics;
6272
7014
  }
6273
- static ɵfac = function KinematicsPanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || KinematicsPanelComponent)(i0.ɵɵdirectiveInject(i1$1.Overlay)); };
7015
+ static ɵfac = function KinematicsPanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || KinematicsPanelComponent)(i0.ɵɵdirectiveInject(i1$3.Overlay), i0.ɵɵdirectiveInject(OverlayCascadeService)); };
6274
7016
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: KinematicsPanelComponent, selectors: [["app-kinematics-panel"]], inputs: { config: "config" }, standalone: false, features: [i0.ɵɵNgOnChangesFeature], decls: 0, vars: 0, template: function KinematicsPanelComponent_Template(rf, ctx) { }, styles: ["@charset \"UTF-8\";"] });
6275
7017
  }
6276
7018
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(KinematicsPanelComponent, [{
6277
7019
  type: Component,
6278
7020
  args: [{ standalone: false, selector: 'app-kinematics-panel', template: "", styles: ["@charset \"UTF-8\";\n"] }]
6279
- }], () => [{ type: i1$1.Overlay }], { config: [{
7021
+ }], () => [{ type: i1$3.Overlay }, { type: OverlayCascadeService }], { config: [{
6280
7022
  type: Input
6281
7023
  }] }); })();
6282
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KinematicsPanelComponent, { className: "KinematicsPanelComponent", filePath: "components/ui-menu/kinematics-panel/kinematics-panel.component.ts", lineNumber: 21 }); })();
7024
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KinematicsPanelComponent, { className: "KinematicsPanelComponent", filePath: "components/ui-menu/kinematics-panel/kinematics-panel.component.ts", lineNumber: 22 }); })();
6283
7025
 
6284
7026
  const _c0$2 = ["infoPanel"];
6285
7027
  const _c1$2 = ["eventBrowser"];
@@ -6423,114 +7165,571 @@ class MoreInfoComponent {
6423
7165
  this.showInfoPanel = !this.showInfoPanel;
6424
7166
  this.infoPanel?.toggleOverlay();
6425
7167
  }
6426
- toggleEventBrowser() {
6427
- this.showEventBrowser = !this.showEventBrowser;
6428
- this.eventBrowser?.toggleOverlay();
7168
+ toggleEventBrowser() {
7169
+ this.showEventBrowser = !this.showEventBrowser;
7170
+ this.eventBrowser?.toggleOverlay();
7171
+ }
7172
+ toggleCollectionsInfo() {
7173
+ this.showCollectionsInfo = !this.showCollectionsInfo;
7174
+ this.collectionsInfo?.toggleOverlay();
7175
+ }
7176
+ toggleEtaPhiPanel() {
7177
+ this.showEtaPhiPanel = !this.showEtaPhiPanel;
7178
+ this.etaPhiPanel?.toggleOverlay();
7179
+ }
7180
+ toggleGeometryBrowser() {
7181
+ this.showGeometryBrowser = !this.showGeometryBrowser;
7182
+ this.geometryBrowser?.toggleOverlay();
7183
+ }
7184
+ toggleKinematicsPanel() {
7185
+ this.showKinematicsPanel = !this.showKinematicsPanel;
7186
+ this.kinematicsPanel?.toggleOverlay();
7187
+ }
7188
+ static ɵfac = function MoreInfoComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MoreInfoComponent)(); };
7189
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MoreInfoComponent, selectors: [["app-more-info"]], viewQuery: function MoreInfoComponent_Query(rf, ctx) { if (rf & 1) {
7190
+ i0.ɵɵviewQuery(_c0$2, 5);
7191
+ i0.ɵɵviewQuery(_c1$2, 5);
7192
+ i0.ɵɵviewQuery(_c2, 5);
7193
+ i0.ɵɵviewQuery(_c3, 5);
7194
+ i0.ɵɵviewQuery(_c4, 5);
7195
+ i0.ɵɵviewQuery(_c5, 5);
7196
+ } if (rf & 2) {
7197
+ let _t;
7198
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.infoPanel = _t.first);
7199
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.eventBrowser = _t.first);
7200
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.collectionsInfo = _t.first);
7201
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.etaPhiPanel = _t.first);
7202
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.geometryBrowser = _t.first);
7203
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.kinematicsPanel = _t.first);
7204
+ } }, inputs: { uiConfig: "uiConfig", kinematicsConfig: "kinematicsConfig" }, standalone: false, decls: 16, vars: 14, consts: [["moreInfo", ""], ["infoPanel", ""], ["eventBrowser", ""], ["collectionsInfo", ""], ["etaPhiPanel", ""], ["geometryBrowser", ""], ["kinematicsPanel", ""], [1, "hidden-panels"], [4, "ngIf"], [3, "config", 4, "ngIf"], ["mat-menu-item", "", 4, "ngIf"], ["tooltip", "More information", "icon", "info", 3, "matMenuTriggerFor", "active"], [3, "config"], ["mat-menu-item", ""], [3, "click", "change", "checked"], [1, "item-content"], [1, "item-icon"], ["href", "assets/icons/info-panel.svg#info-panel"], ["href", "assets/icons/event-browser.svg#event-browser"], ["href", "assets/icons/eventData.svg#eventData"], ["href", "assets/icons/eta-phi.svg#eta-phi"], ["href", "assets/icons/top-cube.svg#top-cube"], ["href", "assets/icons/kinematics.svg#kinematics"]], template: function MoreInfoComponent_Template(rf, ctx) { if (rf & 1) {
7205
+ i0.ɵɵelementStart(0, "div", 7);
7206
+ i0.ɵɵtemplate(1, MoreInfoComponent_app_info_panel_1_Template, 2, 0, "app-info-panel", 8)(2, MoreInfoComponent_app_event_browser_2_Template, 2, 0, "app-event-browser", 8)(3, MoreInfoComponent_app_collections_info_3_Template, 2, 0, "app-collections-info", 8)(4, MoreInfoComponent_app_eta_phi_panel_4_Template, 2, 0, "app-eta-phi-panel", 8)(5, MoreInfoComponent_app_geometry_browser_5_Template, 2, 0, "app-geometry-browser", 8)(6, MoreInfoComponent_app_kinematics_panel_6_Template, 2, 1, "app-kinematics-panel", 9);
7207
+ i0.ɵɵelementEnd();
7208
+ i0.ɵɵelementStart(7, "mat-menu", null, 0);
7209
+ i0.ɵɵtemplate(9, MoreInfoComponent_button_9_Template, 6, 1, "button", 10)(10, MoreInfoComponent_button_10_Template, 6, 1, "button", 10)(11, MoreInfoComponent_button_11_Template, 6, 1, "button", 10)(12, MoreInfoComponent_button_12_Template, 6, 1, "button", 10)(13, MoreInfoComponent_button_13_Template, 6, 1, "button", 10)(14, MoreInfoComponent_button_14_Template, 6, 1, "button", 10);
7210
+ i0.ɵɵelementEnd();
7211
+ i0.ɵɵelement(15, "app-menu-toggle", 11);
7212
+ } if (rf & 2) {
7213
+ const moreInfo_r8 = i0.ɵɵreference(8);
7214
+ i0.ɵɵadvance();
7215
+ i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showInfoPanel);
7216
+ i0.ɵɵadvance();
7217
+ i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showEventBrowser);
7218
+ i0.ɵɵadvance();
7219
+ i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showCollectionsInfo);
7220
+ i0.ɵɵadvance();
7221
+ i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showEtaPhiPanel);
7222
+ i0.ɵɵadvance();
7223
+ i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showGeometryBrowser);
7224
+ i0.ɵɵadvance();
7225
+ i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showKinematicsPanel);
7226
+ i0.ɵɵadvance(3);
7227
+ i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showInfoPanel);
7228
+ i0.ɵɵadvance();
7229
+ i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showEventBrowser);
7230
+ i0.ɵɵadvance();
7231
+ i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showCollectionsInfo);
7232
+ i0.ɵɵadvance();
7233
+ i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showEtaPhiPanel);
7234
+ i0.ɵɵadvance();
7235
+ i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showGeometryBrowser);
7236
+ i0.ɵɵadvance();
7237
+ i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showKinematicsPanel);
7238
+ i0.ɵɵadvance();
7239
+ i0.ɵɵproperty("matMenuTriggerFor", moreInfo_r8)("active", false);
7240
+ } }, dependencies: [i2.NgIf, i4$2.MatMenu, i4$2.MatMenuItem, i4$2.MatMenuTrigger, i5.MatCheckbox, CollectionsInfoComponent, GeometryBrowserComponent, MenuToggleComponent, InfoPanelComponent, EventBrowserComponent, EtaPhiPanelComponent, KinematicsPanelComponent], styles: [".hidden-panels[_ngcontent-%COMP%]{display:none}.item-content[_ngcontent-%COMP%]{display:inline-flex;align-items:center}.item-icon[_ngcontent-%COMP%]{inline-size:1.2rem;block-size:1.2rem;margin-inline-end:.5rem;fill:currentColor} .cdk-overlay-pane:has(.mat-mdc-menu-panel), .cdk-overlay-pane:has(.mat-menu-panel){z-index:99999!important}"] });
7241
+ }
7242
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MoreInfoComponent, [{
7243
+ type: Component,
7244
+ args: [{ standalone: false, selector: 'app-more-info', template: "<div class=\"hidden-panels\">\n <app-info-panel #infoPanel *ngIf=\"uiConfig?.showInfoPanel\"></app-info-panel>\n <app-event-browser\n #eventBrowser\n *ngIf=\"uiConfig?.showEventBrowser\"\n ></app-event-browser>\n <app-collections-info\n #collectionsInfo\n *ngIf=\"uiConfig?.showCollectionsInfo\"\n ></app-collections-info>\n <app-eta-phi-panel\n #etaPhiPanel\n *ngIf=\"uiConfig?.showEtaPhiPanel\"\n ></app-eta-phi-panel>\n <app-geometry-browser\n #geometryBrowser\n *ngIf=\"uiConfig?.showGeometryBrowser\"\n ></app-geometry-browser>\n <app-kinematics-panel\n #kinematicsPanel\n *ngIf=\"uiConfig?.showKinematicsPanel\"\n [config]=\"kinematicsConfig\"\n ></app-kinematics-panel>\n</div>\n\n<mat-menu #moreInfo>\n <button mat-menu-item *ngIf=\"uiConfig?.showInfoPanel\">\n <mat-checkbox\n [checked]=\"showInfoPanel\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleInfoPanel()\"\n >\n <span class=\"item-content\">\n <svg class=\"item-icon\">\n <use href=\"assets/icons/info-panel.svg#info-panel\"></use>\n </svg>\n Info Panel\n </span>\n </mat-checkbox>\n </button>\n <button mat-menu-item *ngIf=\"uiConfig?.showEventBrowser\">\n <mat-checkbox\n [checked]=\"showEventBrowser\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleEventBrowser()\"\n >\n <span class=\"item-content\">\n <svg class=\"item-icon\">\n <use href=\"assets/icons/event-browser.svg#event-browser\"></use>\n </svg>\n Event Browser\n </span>\n </mat-checkbox>\n </button>\n <button mat-menu-item *ngIf=\"uiConfig?.showCollectionsInfo\">\n <mat-checkbox\n [checked]=\"showCollectionsInfo\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleCollectionsInfo()\"\n >\n <span class=\"item-content\">\n <svg class=\"item-icon\">\n <use href=\"assets/icons/eventData.svg#eventData\"></use>\n </svg>\n Collections Info\n </span>\n </mat-checkbox>\n </button>\n <button mat-menu-item *ngIf=\"uiConfig?.showEtaPhiPanel\">\n <mat-checkbox\n [checked]=\"showEtaPhiPanel\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleEtaPhiPanel()\"\n >\n <span class=\"item-content\">\n <svg class=\"item-icon\">\n <use href=\"assets/icons/eta-phi.svg#eta-phi\"></use>\n </svg>\n Eta-Phi Panel\n </span>\n </mat-checkbox>\n </button>\n <button mat-menu-item *ngIf=\"uiConfig?.showGeometryBrowser\">\n <mat-checkbox\n [checked]=\"showGeometryBrowser\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleGeometryBrowser()\"\n >\n <span class=\"item-content\">\n <svg class=\"item-icon\">\n <use href=\"assets/icons/top-cube.svg#top-cube\"></use>\n </svg>\n Geometry Browser\n </span>\n </mat-checkbox>\n </button>\n <button mat-menu-item *ngIf=\"uiConfig?.showKinematicsPanel\">\n <mat-checkbox\n [checked]=\"showKinematicsPanel\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleKinematicsPanel()\"\n >\n <span class=\"item-content\">\n <svg class=\"item-icon\">\n <use href=\"assets/icons/kinematics.svg#kinematics\"></use>\n </svg>\n Track Kinematics\n </span>\n </mat-checkbox>\n </button>\n</mat-menu>\n\n<app-menu-toggle\n [matMenuTriggerFor]=\"moreInfo\"\n tooltip=\"More information\"\n icon=\"info\"\n [active]=\"false\"\n>\n</app-menu-toggle>\n", styles: [".hidden-panels{display:none}.item-content{display:inline-flex;align-items:center}.item-icon{inline-size:1.2rem;block-size:1.2rem;margin-inline-end:.5rem;fill:currentColor}::ng-deep .cdk-overlay-pane:has(.mat-mdc-menu-panel),::ng-deep .cdk-overlay-pane:has(.mat-menu-panel){z-index:99999!important}\n"] }]
7245
+ }], null, { uiConfig: [{
7246
+ type: Input
7247
+ }], kinematicsConfig: [{
7248
+ type: Input
7249
+ }], infoPanel: [{
7250
+ type: ViewChild,
7251
+ args: ['infoPanel']
7252
+ }], eventBrowser: [{
7253
+ type: ViewChild,
7254
+ args: ['eventBrowser']
7255
+ }], collectionsInfo: [{
7256
+ type: ViewChild,
7257
+ args: ['collectionsInfo']
7258
+ }], etaPhiPanel: [{
7259
+ type: ViewChild,
7260
+ args: ['etaPhiPanel']
7261
+ }], geometryBrowser: [{
7262
+ type: ViewChild,
7263
+ args: ['geometryBrowser']
7264
+ }], kinematicsPanel: [{
7265
+ type: ViewChild,
7266
+ args: ['kinematicsPanel']
7267
+ }] }); })();
7268
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MoreInfoComponent, { className: "MoreInfoComponent", filePath: "components/ui-menu/more-info/more-info.component.ts", lineNumber: 17 }); })();
7269
+
7270
+ function SessionPillComponent_div_0_ng_container_1_Template(rf, ctx) { if (rf & 1) {
7271
+ const _r1 = i0.ɵɵgetCurrentView();
7272
+ i0.ɵɵelementContainerStart(0);
7273
+ i0.ɵɵelement(1, "span", 3);
7274
+ i0.ɵɵelementStart(2, "span", 4);
7275
+ i0.ɵɵtext(3, "REC");
7276
+ i0.ɵɵelementEnd();
7277
+ i0.ɵɵelementStart(4, "span", 5);
7278
+ i0.ɵɵtext(5);
7279
+ i0.ɵɵelementEnd();
7280
+ i0.ɵɵelementStart(6, "span", 6);
7281
+ i0.ɵɵtext(7, "\u00B7");
7282
+ i0.ɵɵelementEnd();
7283
+ i0.ɵɵelementStart(8, "span", 7);
7284
+ i0.ɵɵtext(9);
7285
+ i0.ɵɵelementEnd();
7286
+ i0.ɵɵelementStart(10, "button", 8);
7287
+ i0.ɵɵlistener("click", function SessionPillComponent_div_0_ng_container_1_Template_button_click_10_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onStopRecording()); });
7288
+ i0.ɵɵtext(11, " \u25A0 ");
7289
+ i0.ɵɵelementEnd();
7290
+ i0.ɵɵelementContainerEnd();
7291
+ } if (rf & 2) {
7292
+ const ctx_r1 = i0.ɵɵnextContext(2);
7293
+ i0.ɵɵadvance(5);
7294
+ i0.ɵɵtextInterpolate(ctx_r1.durationLabel);
7295
+ i0.ɵɵadvance(4);
7296
+ i0.ɵɵtextInterpolate1("", ctx_r1.eventCount, " events");
7297
+ } }
7298
+ function SessionPillComponent_div_0_ng_container_2_Template(rf, ctx) { if (rf & 1) {
7299
+ const _r3 = i0.ɵɵgetCurrentView();
7300
+ i0.ɵɵelementContainerStart(0);
7301
+ i0.ɵɵelementStart(1, "span", 4);
7302
+ i0.ɵɵtext(2, "Saved");
7303
+ i0.ɵɵelementEnd();
7304
+ i0.ɵɵelementStart(3, "span", 5);
7305
+ i0.ɵɵtext(4);
7306
+ i0.ɵɵelementEnd();
7307
+ i0.ɵɵelementStart(5, "span", 6);
7308
+ i0.ɵɵtext(6, "\u00B7");
7309
+ i0.ɵɵelementEnd();
7310
+ i0.ɵɵelementStart(7, "span", 7);
7311
+ i0.ɵɵtext(8);
7312
+ i0.ɵɵelementEnd();
7313
+ i0.ɵɵelementStart(9, "button", 9);
7314
+ i0.ɵɵlistener("click", function SessionPillComponent_div_0_ng_container_2_Template_button_click_9_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onDownload()); });
7315
+ i0.ɵɵtext(10, " Download ");
7316
+ i0.ɵɵelementEnd();
7317
+ i0.ɵɵelementStart(11, "button", 10);
7318
+ i0.ɵɵlistener("click", function SessionPillComponent_div_0_ng_container_2_Template_button_click_11_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onCopyLink()); });
7319
+ i0.ɵɵtext(12);
7320
+ i0.ɵɵelementEnd();
7321
+ i0.ɵɵelementStart(13, "button", 11);
7322
+ i0.ɵɵlistener("click", function SessionPillComponent_div_0_ng_container_2_Template_button_click_13_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onDismiss()); });
7323
+ i0.ɵɵtext(14, " \u2715 ");
7324
+ i0.ɵɵelementEnd();
7325
+ i0.ɵɵelementContainerEnd();
7326
+ } if (rf & 2) {
7327
+ const ctx_r1 = i0.ɵɵnextContext(2);
7328
+ i0.ɵɵadvance(4);
7329
+ i0.ɵɵtextInterpolate(ctx_r1.durationLabel);
7330
+ i0.ɵɵadvance(4);
7331
+ i0.ɵɵtextInterpolate1("", ctx_r1.eventCount, " events");
7332
+ i0.ɵɵadvance(3);
7333
+ i0.ɵɵproperty("disabled", ctx_r1.linkCopied);
7334
+ i0.ɵɵadvance();
7335
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.linkCopied ? "Copied" : "Copy link", " ");
7336
+ } }
7337
+ function SessionPillComponent_div_0_ng_container_3_Template(rf, ctx) { if (rf & 1) {
7338
+ const _r4 = i0.ɵɵgetCurrentView();
7339
+ i0.ɵɵelementContainerStart(0);
7340
+ i0.ɵɵelementStart(1, "span", 4);
7341
+ i0.ɵɵtext(2, "Shared session");
7342
+ i0.ɵɵelementEnd();
7343
+ i0.ɵɵelementStart(3, "span", 5);
7344
+ i0.ɵɵtext(4);
7345
+ i0.ɵɵelementEnd();
7346
+ i0.ɵɵelementStart(5, "span", 6);
7347
+ i0.ɵɵtext(6, "\u00B7");
7348
+ i0.ɵɵelementEnd();
7349
+ i0.ɵɵelementStart(7, "span", 7);
7350
+ i0.ɵɵtext(8);
7351
+ i0.ɵɵelementEnd();
7352
+ i0.ɵɵelementStart(9, "button", 12);
7353
+ i0.ɵɵlistener("click", function SessionPillComponent_div_0_ng_container_3_Template_button_click_9_listener() { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onPlayPending()); });
7354
+ i0.ɵɵtext(10, " Play ");
7355
+ i0.ɵɵelementEnd();
7356
+ i0.ɵɵelementStart(11, "button", 13);
7357
+ i0.ɵɵlistener("click", function SessionPillComponent_div_0_ng_container_3_Template_button_click_11_listener() { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onDismiss()); });
7358
+ i0.ɵɵtext(12, " \u2715 ");
7359
+ i0.ɵɵelementEnd();
7360
+ i0.ɵɵelementContainerEnd();
7361
+ } if (rf & 2) {
7362
+ const ctx_r1 = i0.ɵɵnextContext(2);
7363
+ i0.ɵɵadvance(4);
7364
+ i0.ɵɵtextInterpolate(ctx_r1.pendingSummary);
7365
+ i0.ɵɵadvance(4);
7366
+ i0.ɵɵtextInterpolate1("from ", ctx_r1.state.sourceLabel);
7367
+ } }
7368
+ function SessionPillComponent_div_0_ng_container_4_button_9_Template(rf, ctx) { if (rf & 1) {
7369
+ const _r6 = i0.ɵɵgetCurrentView();
7370
+ i0.ɵɵelementStart(0, "button", 19);
7371
+ i0.ɵɵlistener("click", function SessionPillComponent_div_0_ng_container_4_button_9_Template_button_click_0_listener() { const s_r7 = i0.ɵɵrestoreView(_r6).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.onSpeed(s_r7)); });
7372
+ i0.ɵɵtext(1);
7373
+ i0.ɵɵelementEnd();
7374
+ } if (rf & 2) {
7375
+ const s_r7 = ctx.$implicit;
7376
+ const ctx_r1 = i0.ɵɵnextContext(3);
7377
+ i0.ɵɵclassProp("selected", ctx_r1.currentSpeed === s_r7);
7378
+ i0.ɵɵattribute("aria-label", "Speed " + s_r7 + "x")("aria-pressed", ctx_r1.currentSpeed === s_r7);
7379
+ i0.ɵɵadvance();
7380
+ i0.ɵɵtextInterpolate1(" ", s_r7, "\u00D7 ");
7381
+ } }
7382
+ function SessionPillComponent_div_0_ng_container_4_Template(rf, ctx) { if (rf & 1) {
7383
+ const _r5 = i0.ɵɵgetCurrentView();
7384
+ i0.ɵɵelementContainerStart(0);
7385
+ i0.ɵɵelementStart(1, "button", 14);
7386
+ i0.ɵɵlistener("click", function SessionPillComponent_div_0_ng_container_4_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onTogglePlayback()); });
7387
+ i0.ɵɵtext(2);
7388
+ i0.ɵɵelementEnd();
7389
+ i0.ɵɵelementStart(3, "span", 4);
7390
+ i0.ɵɵtext(4, "Replay");
7391
+ i0.ɵɵelementEnd();
7392
+ i0.ɵɵelementStart(5, "span", 5);
7393
+ i0.ɵɵtext(6);
7394
+ i0.ɵɵelementEnd();
7395
+ i0.ɵɵelementStart(7, "input", 15);
7396
+ i0.ɵɵlistener("input", function SessionPillComponent_div_0_ng_container_4_Template_input_input_7_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onScrub(+$event.target.value)); });
7397
+ i0.ɵɵelementEnd();
7398
+ i0.ɵɵelementStart(8, "span", 16);
7399
+ i0.ɵɵtemplate(9, SessionPillComponent_div_0_ng_container_4_button_9_Template, 2, 5, "button", 17);
7400
+ i0.ɵɵelementEnd();
7401
+ i0.ɵɵelementStart(10, "button", 18);
7402
+ i0.ɵɵlistener("click", function SessionPillComponent_div_0_ng_container_4_Template_button_click_10_listener() { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onDismiss()); });
7403
+ i0.ɵɵtext(11, " \u2715 ");
7404
+ i0.ɵɵelementEnd();
7405
+ i0.ɵɵelementContainerEnd();
7406
+ } if (rf & 2) {
7407
+ const ctx_r1 = i0.ɵɵnextContext(2);
7408
+ i0.ɵɵadvance();
7409
+ i0.ɵɵproperty("title", ctx_r1.state.kind === "playing" ? "Pause" : "Play");
7410
+ i0.ɵɵattribute("aria-label", ctx_r1.state.kind === "playing" ? "Pause" : "Play");
7411
+ i0.ɵɵadvance();
7412
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.state.kind === "playing" ? "\u275A\u275A" : "\u25BA", " ");
7413
+ i0.ɵɵadvance(4);
7414
+ i0.ɵɵtextInterpolate(ctx_r1.playbackLabel);
7415
+ i0.ɵɵadvance();
7416
+ i0.ɵɵproperty("max", ctx_r1.playerDuration)("value", ctx_r1.state.kind === "finished" ? ctx_r1.playerDuration : ctx_r1.state.currentTime ?? 0);
7417
+ i0.ɵɵadvance(2);
7418
+ i0.ɵɵproperty("ngForOf", ctx_r1.speeds);
7419
+ } }
7420
+ function SessionPillComponent_div_0_ng_container_5_Template(rf, ctx) { if (rf & 1) {
7421
+ const _r8 = i0.ɵɵgetCurrentView();
7422
+ i0.ɵɵelementContainerStart(0);
7423
+ i0.ɵɵelementStart(1, "span", 20);
7424
+ i0.ɵɵtext(2, "Session error");
7425
+ i0.ɵɵelementEnd();
7426
+ i0.ɵɵelementStart(3, "span", 5);
7427
+ i0.ɵɵtext(4);
7428
+ i0.ɵɵelementEnd();
7429
+ i0.ɵɵelementStart(5, "button", 11);
7430
+ i0.ɵɵlistener("click", function SessionPillComponent_div_0_ng_container_5_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onDismiss()); });
7431
+ i0.ɵɵtext(6, " \u2715 ");
7432
+ i0.ɵɵelementEnd();
7433
+ i0.ɵɵelementContainerEnd();
7434
+ } if (rf & 2) {
7435
+ const ctx_r1 = i0.ɵɵnextContext(2);
7436
+ i0.ɵɵadvance(4);
7437
+ i0.ɵɵtextInterpolate(ctx_r1.state.message);
7438
+ } }
7439
+ function SessionPillComponent_div_0_Template(rf, ctx) { if (rf & 1) {
7440
+ i0.ɵɵelementStart(0, "div", 1);
7441
+ i0.ɵɵtemplate(1, SessionPillComponent_div_0_ng_container_1_Template, 12, 2, "ng-container", 2)(2, SessionPillComponent_div_0_ng_container_2_Template, 15, 4, "ng-container", 2)(3, SessionPillComponent_div_0_ng_container_3_Template, 13, 2, "ng-container", 2)(4, SessionPillComponent_div_0_ng_container_4_Template, 12, 7, "ng-container", 2)(5, SessionPillComponent_div_0_ng_container_5_Template, 7, 1, "ng-container", 2);
7442
+ i0.ɵɵelementEnd();
7443
+ } if (rf & 2) {
7444
+ const ctx_r1 = i0.ɵɵnextContext();
7445
+ i0.ɵɵattribute("data-kind", ctx_r1.state.kind);
7446
+ i0.ɵɵadvance();
7447
+ i0.ɵɵproperty("ngIf", ctx_r1.state.kind === "recording");
7448
+ i0.ɵɵadvance();
7449
+ i0.ɵɵproperty("ngIf", ctx_r1.state.kind === "recorded");
7450
+ i0.ɵɵadvance();
7451
+ i0.ɵɵproperty("ngIf", ctx_r1.state.kind === "pending");
7452
+ i0.ɵɵadvance();
7453
+ i0.ɵɵproperty("ngIf", ctx_r1.state.kind === "playing" || ctx_r1.state.kind === "paused" || ctx_r1.state.kind === "finished");
7454
+ i0.ɵɵadvance();
7455
+ i0.ɵɵproperty("ngIf", ctx_r1.state.kind === "error");
7456
+ } }
7457
+ const SPEEDS = [0.5, 1, 2, 4];
7458
+ /**
7459
+ * Floating session pill (#883). Renders only while the session manager is
7460
+ * recording, paused, finished, or playing a replay. Idle state hides the
7461
+ * pill entirely so it adds zero footprint to the toolbar.
7462
+ */
7463
+ class SessionPillComponent {
7464
+ eventDisplay;
7465
+ cdr;
7466
+ /** Current session-manager state, kept in component for template binding. */
7467
+ state = { kind: 'idle' };
7468
+ /** Cached duration label for the recording elapsed clock. */
7469
+ durationLabel = '00:00';
7470
+ /** Cached current/total label for the playback clock. */
7471
+ playbackLabel = '00:00 / 00:00';
7472
+ /** Cached event count for the recording badge. */
7473
+ eventCount = 0;
7474
+ /** Last computed scrub percent (0-100). */
7475
+ scrubPercent = 0;
7476
+ /** Saved copy-link feedback flag (resets after a tick). */
7477
+ linkCopied = false;
7478
+ /** Available playback speeds for the speed selector. */
7479
+ speeds = SPEEDS;
7480
+ sessionManager;
7481
+ unsubscribeState = null;
7482
+ recordingClockId = null;
7483
+ copiedResetTimer = null;
7484
+ constructor(eventDisplay, cdr) {
7485
+ this.eventDisplay = eventDisplay;
7486
+ this.cdr = cdr;
7487
+ }
7488
+ ngOnInit() {
7489
+ this.sessionManager = this.eventDisplay.getSessionManager();
7490
+ this.state = this.sessionManager.state.value ?? { kind: 'idle' };
7491
+ this.unsubscribeState = this.sessionManager.state.onUpdate((next) => {
7492
+ this.state = next;
7493
+ this.refreshDerived();
7494
+ this.cdr.detectChanges();
7495
+ });
7496
+ this.refreshDerived();
7497
+ }
7498
+ ngOnDestroy() {
7499
+ if (this.unsubscribeState) {
7500
+ this.unsubscribeState();
7501
+ this.unsubscribeState = null;
7502
+ }
7503
+ if (this.recordingClockId) {
7504
+ clearInterval(this.recordingClockId);
7505
+ this.recordingClockId = null;
7506
+ }
7507
+ if (this.copiedResetTimer) {
7508
+ clearTimeout(this.copiedResetTimer);
7509
+ this.copiedResetTimer = null;
7510
+ }
7511
+ }
7512
+ /** True when the pill should be visible. Idle state hides it. */
7513
+ get visible() {
7514
+ return this.state.kind !== 'idle';
6429
7515
  }
6430
- toggleCollectionsInfo() {
6431
- this.showCollectionsInfo = !this.showCollectionsInfo;
6432
- this.collectionsInfo?.toggleOverlay();
7516
+ /** Stop an active recording from the pill's stop button. */
7517
+ onStopRecording() {
7518
+ this.sessionManager.stopRecording();
6433
7519
  }
6434
- toggleEtaPhiPanel() {
6435
- this.showEtaPhiPanel = !this.showEtaPhiPanel;
6436
- this.etaPhiPanel?.toggleOverlay();
7520
+ /** Dismiss a recorded/finished/errored state and return to idle. */
7521
+ onDismiss() {
7522
+ if (this.state.kind === 'recorded') {
7523
+ this.sessionManager.clearRecorded();
7524
+ }
7525
+ else {
7526
+ this.sessionManager.stopPlayback();
7527
+ }
6437
7528
  }
6438
- toggleGeometryBrowser() {
6439
- this.showGeometryBrowser = !this.showGeometryBrowser;
6440
- this.geometryBrowser?.toggleOverlay();
7529
+ /** Start a pending (link-supplied) session after explicit user confirmation. */
7530
+ onPlayPending() {
7531
+ this.sessionManager.playPending();
6441
7532
  }
6442
- toggleKinematicsPanel() {
6443
- this.showKinematicsPanel = !this.showKinematicsPanel;
6444
- this.kinematicsPanel?.toggleOverlay();
7533
+ /** Source label + duration/event summary for the pending confirm prompt. */
7534
+ get pendingSummary() {
7535
+ if (this.state.kind !== 'pending')
7536
+ return '';
7537
+ const { session } = this.state;
7538
+ return `${formatClock(session.duration)} · ${session.events.length} events`;
6445
7539
  }
6446
- static ɵfac = function MoreInfoComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MoreInfoComponent)(); };
6447
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MoreInfoComponent, selectors: [["app-more-info"]], viewQuery: function MoreInfoComponent_Query(rf, ctx) { if (rf & 1) {
6448
- i0.ɵɵviewQuery(_c0$2, 5);
6449
- i0.ɵɵviewQuery(_c1$2, 5);
6450
- i0.ɵɵviewQuery(_c2, 5);
6451
- i0.ɵɵviewQuery(_c3, 5);
6452
- i0.ɵɵviewQuery(_c4, 5);
6453
- i0.ɵɵviewQuery(_c5, 5);
6454
- } if (rf & 2) {
6455
- let _t;
6456
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.infoPanel = _t.first);
6457
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.eventBrowser = _t.first);
6458
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.collectionsInfo = _t.first);
6459
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.etaPhiPanel = _t.first);
6460
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.geometryBrowser = _t.first);
6461
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.kinematicsPanel = _t.first);
6462
- } }, inputs: { uiConfig: "uiConfig", kinematicsConfig: "kinematicsConfig" }, standalone: false, decls: 16, vars: 14, consts: [["moreInfo", ""], ["infoPanel", ""], ["eventBrowser", ""], ["collectionsInfo", ""], ["etaPhiPanel", ""], ["geometryBrowser", ""], ["kinematicsPanel", ""], [1, "hidden-panels"], [4, "ngIf"], [3, "config", 4, "ngIf"], ["mat-menu-item", "", 4, "ngIf"], ["tooltip", "More information", "icon", "info", 3, "matMenuTriggerFor", "active"], [3, "config"], ["mat-menu-item", ""], [3, "click", "change", "checked"], [1, "item-content"], [1, "item-icon"], ["href", "assets/icons/info-panel.svg#info-panel"], ["href", "assets/icons/event-browser.svg#event-browser"], ["href", "assets/icons/eventData.svg#eventData"], ["href", "assets/icons/eta-phi.svg#eta-phi"], ["href", "assets/icons/top-cube.svg#top-cube"], ["href", "assets/icons/kinematics.svg#kinematics"]], template: function MoreInfoComponent_Template(rf, ctx) { if (rf & 1) {
6463
- i0.ɵɵelementStart(0, "div", 7);
6464
- i0.ɵɵtemplate(1, MoreInfoComponent_app_info_panel_1_Template, 2, 0, "app-info-panel", 8)(2, MoreInfoComponent_app_event_browser_2_Template, 2, 0, "app-event-browser", 8)(3, MoreInfoComponent_app_collections_info_3_Template, 2, 0, "app-collections-info", 8)(4, MoreInfoComponent_app_eta_phi_panel_4_Template, 2, 0, "app-eta-phi-panel", 8)(5, MoreInfoComponent_app_geometry_browser_5_Template, 2, 0, "app-geometry-browser", 8)(6, MoreInfoComponent_app_kinematics_panel_6_Template, 2, 1, "app-kinematics-panel", 9);
6465
- i0.ɵɵelementEnd();
6466
- i0.ɵɵelementStart(7, "mat-menu", null, 0);
6467
- i0.ɵɵtemplate(9, MoreInfoComponent_button_9_Template, 6, 1, "button", 10)(10, MoreInfoComponent_button_10_Template, 6, 1, "button", 10)(11, MoreInfoComponent_button_11_Template, 6, 1, "button", 10)(12, MoreInfoComponent_button_12_Template, 6, 1, "button", 10)(13, MoreInfoComponent_button_13_Template, 6, 1, "button", 10)(14, MoreInfoComponent_button_14_Template, 6, 1, "button", 10);
6468
- i0.ɵɵelementEnd();
6469
- i0.ɵɵelement(15, "app-menu-toggle", 11);
7540
+ /** Toggle play/pause on the active player. */
7541
+ onTogglePlayback() {
7542
+ if (this.state.kind === 'playing') {
7543
+ this.sessionManager.pause();
7544
+ }
7545
+ else if (this.state.kind === 'paused' || this.state.kind === 'finished') {
7546
+ this.sessionManager.play();
7547
+ }
7548
+ }
7549
+ /** Scrub to the playhead position the user clicked. */
7550
+ onScrub(value) {
7551
+ this.sessionManager.seek(value);
7552
+ }
7553
+ /** Change the active player's speed. */
7554
+ onSpeed(speed) {
7555
+ this.sessionManager.setSpeed(speed);
7556
+ }
7557
+ /** Download the recorded session as a .phnxreplay JSON file. */
7558
+ onDownload() {
7559
+ const blob = this.sessionManager.getDownloadBlob();
7560
+ if (!blob)
7561
+ return;
7562
+ const url = URL.createObjectURL(blob);
7563
+ const a = document.createElement('a');
7564
+ a.href = url;
7565
+ a.download = `phoenix-session-${Date.now()}.phnxreplay`;
7566
+ document.body.appendChild(a);
7567
+ a.click();
7568
+ document.body.removeChild(a);
7569
+ URL.revokeObjectURL(url);
7570
+ }
7571
+ /**
7572
+ * Copy a ?replay=... share link to the clipboard. The base64 is precomputed
7573
+ * when recording stops, so the common path writes synchronously inside the
7574
+ * click gesture (the async Clipboard API rejects writes that happen after an
7575
+ * await, which expires the user activation). Falls back to execCommand.
7576
+ */
7577
+ onCopyLink() {
7578
+ const cached = this.sessionManager.getCachedSharableBase64();
7579
+ if (cached) {
7580
+ this.copyShareLink(cached);
7581
+ return;
7582
+ }
7583
+ // Encode not finished yet (very fast click): compute then copy via the
7584
+ // synchronous execCommand fallback, which tolerates the post-await case.
7585
+ this.sessionManager.getSharableBase64().then((base64) => {
7586
+ if (base64)
7587
+ this.copyShareLink(base64);
7588
+ });
7589
+ }
7590
+ /** Build the share URL for a base64 payload and copy it to the clipboard. */
7591
+ copyShareLink(base64) {
7592
+ const url = new URL(window.location.href);
7593
+ url.searchParams.set('replay', base64);
7594
+ const text = url.toString();
7595
+ const onCopied = () => {
7596
+ this.linkCopied = true;
7597
+ if (this.copiedResetTimer)
7598
+ clearTimeout(this.copiedResetTimer);
7599
+ this.copiedResetTimer = setTimeout(() => {
7600
+ this.linkCopied = false;
7601
+ this.cdr.detectChanges();
7602
+ }, 2000);
7603
+ this.cdr.detectChanges();
7604
+ };
7605
+ if (navigator.clipboard?.writeText) {
7606
+ navigator.clipboard.writeText(text).then(onCopied, () => {
7607
+ if (this.legacyCopy(text))
7608
+ onCopied();
7609
+ });
7610
+ }
7611
+ else if (this.legacyCopy(text)) {
7612
+ onCopied();
7613
+ }
7614
+ }
7615
+ /**
7616
+ * Legacy clipboard copy via a temporary textarea + execCommand. Used as a
7617
+ * fallback when the async Clipboard API is unavailable or rejected. Mirrors
7618
+ * the share-link dialog's proven approach.
7619
+ * @param text The text to copy.
7620
+ * @returns True if the copy command reported success.
7621
+ */
7622
+ legacyCopy(text) {
7623
+ const el = document.createElement('textarea');
7624
+ el.value = text;
7625
+ el.setAttribute('readonly', '');
7626
+ el.style.position = 'fixed';
7627
+ el.style.opacity = '0';
7628
+ document.body.appendChild(el);
7629
+ el.select();
7630
+ let ok = false;
7631
+ try {
7632
+ ok = document.execCommand('copy');
7633
+ }
7634
+ catch {
7635
+ ok = false;
7636
+ }
7637
+ document.body.removeChild(el);
7638
+ return ok;
7639
+ }
7640
+ /** Active player duration (ms) for the scrub bar max value. */
7641
+ get playerDuration() {
7642
+ if ('player' in this.state && this.state.player) {
7643
+ return this.state.player.duration;
7644
+ }
7645
+ if (this.state.kind === 'recorded')
7646
+ return this.state.session.duration;
7647
+ return 0;
7648
+ }
7649
+ /** Active player current speed for the selector highlight. */
7650
+ get currentSpeed() {
7651
+ if ('player' in this.state && this.state.player) {
7652
+ return this.state.player.speed;
7653
+ }
7654
+ return 1;
7655
+ }
7656
+ refreshDerived() {
7657
+ if (this.state.kind === 'recording') {
7658
+ this.startRecordingClock();
7659
+ this.updateRecordingDerived();
7660
+ return;
7661
+ }
7662
+ this.stopRecordingClock();
7663
+ if (this.state.kind === 'recorded') {
7664
+ this.eventCount = this.state.session.events.length;
7665
+ this.durationLabel = formatClock(this.state.session.duration);
7666
+ this.playbackLabel = '';
7667
+ this.scrubPercent = 0;
7668
+ return;
7669
+ }
7670
+ if (this.state.kind === 'playing' ||
7671
+ this.state.kind === 'paused' ||
7672
+ this.state.kind === 'finished') {
7673
+ const player = this.state.player;
7674
+ const current = this.state.kind === 'finished' ? player.duration : player.currentTime;
7675
+ this.eventCount = player.eventCount;
7676
+ this.playbackLabel = `${formatClock(current)} / ${formatClock(player.duration)}`;
7677
+ this.scrubPercent =
7678
+ player.duration > 0 ? (current / player.duration) * 100 : 0;
7679
+ return;
7680
+ }
7681
+ }
7682
+ startRecordingClock() {
7683
+ if (this.recordingClockId)
7684
+ return;
7685
+ this.recordingClockId = setInterval(() => {
7686
+ this.updateRecordingDerived();
7687
+ this.cdr.detectChanges();
7688
+ }, 250);
7689
+ }
7690
+ stopRecordingClock() {
7691
+ if (this.recordingClockId) {
7692
+ clearInterval(this.recordingClockId);
7693
+ this.recordingClockId = null;
7694
+ }
7695
+ }
7696
+ updateRecordingDerived() {
7697
+ if (this.state.kind !== 'recording')
7698
+ return;
7699
+ const recorder = this.state.recorder;
7700
+ this.durationLabel = formatClock(recorder.duration);
7701
+ this.eventCount = recorder.eventCount;
7702
+ }
7703
+ static ɵfac = function SessionPillComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || SessionPillComponent)(i0.ɵɵdirectiveInject(EventDisplayService), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
7704
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: SessionPillComponent, selectors: [["app-session-pill"]], standalone: false, decls: 1, vars: 1, consts: [["class", "session-pill", 4, "ngIf"], [1, "session-pill"], [4, "ngIf"], ["aria-label", "Recording", 1, "rec-dot"], [1, "label"], [1, "time"], [1, "dot-sep"], [1, "count"], ["type", "button", "title", "Stop recording (Shift+S)", "aria-label", "Stop recording", 1, "action", "stop", 3, "click"], ["type", "button", "title", "Download .phnxreplay file", "aria-label", "Download session", 1, "action", 3, "click"], ["type", "button", "title", "Copy a ?replay= share link", "aria-label", "Copy share link", 1, "action", 3, "click", "disabled"], ["type", "button", "title", "Dismiss", "aria-label", "Dismiss", 1, "action", "close", 3, "click"], ["type", "button", "title", "Play this shared session", "aria-label", "Play shared session", 1, "action", "play-pending", 3, "click"], ["type", "button", "title", "Dismiss", "aria-label", "Dismiss shared session", 1, "action", "close", 3, "click"], ["type", "button", 1, "action", "play", 3, "click", "title"], ["type", "range", "min", "0", "step", "50", "aria-label", "Replay scrub bar", 1, "scrub", 3, "input", "max", "value"], [1, "speeds"], ["type", "button", "class", "speed", 3, "selected", "click", 4, "ngFor", "ngForOf"], ["type", "button", "title", "Stop replay", "aria-label", "Stop replay", 1, "action", "close", 3, "click"], ["type", "button", 1, "speed", 3, "click"], [1, "label", "error"]], template: function SessionPillComponent_Template(rf, ctx) { if (rf & 1) {
7705
+ i0.ɵɵtemplate(0, SessionPillComponent_div_0_Template, 6, 6, "div", 0);
6470
7706
  } if (rf & 2) {
6471
- const moreInfo_r8 = i0.ɵɵreference(8);
6472
- i0.ɵɵadvance();
6473
- i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showInfoPanel);
6474
- i0.ɵɵadvance();
6475
- i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showEventBrowser);
6476
- i0.ɵɵadvance();
6477
- i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showCollectionsInfo);
6478
- i0.ɵɵadvance();
6479
- i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showEtaPhiPanel);
6480
- i0.ɵɵadvance();
6481
- i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showGeometryBrowser);
6482
- i0.ɵɵadvance();
6483
- i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showKinematicsPanel);
6484
- i0.ɵɵadvance(3);
6485
- i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showInfoPanel);
6486
- i0.ɵɵadvance();
6487
- i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showEventBrowser);
6488
- i0.ɵɵadvance();
6489
- i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showCollectionsInfo);
6490
- i0.ɵɵadvance();
6491
- i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showEtaPhiPanel);
6492
- i0.ɵɵadvance();
6493
- i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showGeometryBrowser);
6494
- i0.ɵɵadvance();
6495
- i0.ɵɵproperty("ngIf", ctx.uiConfig == null ? null : ctx.uiConfig.showKinematicsPanel);
6496
- i0.ɵɵadvance();
6497
- i0.ɵɵproperty("matMenuTriggerFor", moreInfo_r8)("active", false);
6498
- } }, dependencies: [i2.NgIf, i3$2.MatMenu, i3$2.MatMenuItem, i3$2.MatMenuTrigger, i5.MatCheckbox, CollectionsInfoComponent, GeometryBrowserComponent, MenuToggleComponent, InfoPanelComponent, EventBrowserComponent, EtaPhiPanelComponent, KinematicsPanelComponent], styles: [".hidden-panels[_ngcontent-%COMP%]{display:none}.item-content[_ngcontent-%COMP%]{display:inline-flex;align-items:center}.item-icon[_ngcontent-%COMP%]{inline-size:1.2rem;block-size:1.2rem;margin-inline-end:.5rem;fill:currentColor}"] });
7707
+ i0.ɵɵproperty("ngIf", ctx.visible);
7708
+ } }, dependencies: [i2.NgForOf, i2.NgIf], styles: [".session-pill[_ngcontent-%COMP%]{position:fixed;bottom:80px;left:16px;z-index:1100;display:flex;align-items:center;gap:10px;padding:8px 14px;background:#14161ed1;color:#e8eaf2;border-radius:999px;border:1px solid rgba(255,255,255,.08);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:0 6px 18px #00000059;font-size:13px;font-weight:500;letter-spacing:.2px;-webkit-user-select:none;user-select:none;max-width:80vw}.session-pill[_ngcontent-%COMP%] .label[_ngcontent-%COMP%]{font-weight:600;letter-spacing:.4px}.session-pill[_ngcontent-%COMP%] .label.error[_ngcontent-%COMP%]{color:#ff7676}.session-pill[_ngcontent-%COMP%] .time[_ngcontent-%COMP%], .session-pill[_ngcontent-%COMP%] .count[_ngcontent-%COMP%]{font-variant-numeric:tabular-nums;opacity:.9}.session-pill[_ngcontent-%COMP%] .dot-sep[_ngcontent-%COMP%]{opacity:.4}.session-pill[_ngcontent-%COMP%] .rec-dot[_ngcontent-%COMP%]{width:10px;height:10px;border-radius:50%;background:#ff4040;box-shadow:0 0 6px #ff404099;animation:_ngcontent-%COMP%_phoenixSessionPulse 1.2s infinite ease-in-out}.session-pill[_ngcontent-%COMP%] .action[_ngcontent-%COMP%]{background:#ffffff0f;color:inherit;border:1px solid rgba(255,255,255,.1);border-radius:999px;padding:4px 12px;cursor:pointer;font-size:12px;font-weight:600;letter-spacing:.3px;transition:background .12s ease,border-color .12s ease}.session-pill[_ngcontent-%COMP%] .action[_ngcontent-%COMP%]:hover{background:#ffffff1f;border-color:#ffffff2e}.session-pill[_ngcontent-%COMP%] .action[_ngcontent-%COMP%]:disabled{opacity:.6;cursor:default}.session-pill[_ngcontent-%COMP%] .action.stop[_ngcontent-%COMP%]{background:#ff40402e;border-color:#ff404066}.session-pill[_ngcontent-%COMP%] .action.stop[_ngcontent-%COMP%]:hover{background:#ff404047}.session-pill[_ngcontent-%COMP%] .action.close[_ngcontent-%COMP%]{width:24px;height:24px;padding:0;display:inline-flex;align-items:center;justify-content:center}.session-pill[_ngcontent-%COMP%] .action.play[_ngcontent-%COMP%]{width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;font-size:11px}.session-pill[_ngcontent-%COMP%] .scrub[_ngcontent-%COMP%]{width:180px;accent-color:#80c1ff}.session-pill[_ngcontent-%COMP%] .speeds[_ngcontent-%COMP%]{display:inline-flex;gap:4px}.session-pill[_ngcontent-%COMP%] .speeds[_ngcontent-%COMP%] .speed[_ngcontent-%COMP%]{background:transparent;color:inherit;border:1px solid rgba(255,255,255,.12);border-radius:999px;padding:3px 8px;cursor:pointer;font-size:11px;font-weight:600;opacity:.65;transition:opacity .12s ease,background .12s ease}.session-pill[_ngcontent-%COMP%] .speeds[_ngcontent-%COMP%] .speed[_ngcontent-%COMP%]:hover{opacity:1}.session-pill[_ngcontent-%COMP%] .speeds[_ngcontent-%COMP%] .speed.selected[_ngcontent-%COMP%]{background:#80c1ff2e;border-color:#80c1ff80;opacity:1}@keyframes _ngcontent-%COMP%_phoenixSessionPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.65;transform:scale(.85)}}"] });
6499
7709
  }
6500
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MoreInfoComponent, [{
7710
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SessionPillComponent, [{
6501
7711
  type: Component,
6502
- args: [{ standalone: false, selector: 'app-more-info', template: "<!-- Sub-components kept outside the mat-menu so their overlays persist when the menu closes -->\n<div class=\"hidden-panels\">\n <app-info-panel #infoPanel *ngIf=\"uiConfig?.showInfoPanel\"></app-info-panel>\n <app-event-browser\n #eventBrowser\n *ngIf=\"uiConfig?.showEventBrowser\"\n ></app-event-browser>\n <app-collections-info\n #collectionsInfo\n *ngIf=\"uiConfig?.showCollectionsInfo\"\n ></app-collections-info>\n <app-eta-phi-panel\n #etaPhiPanel\n *ngIf=\"uiConfig?.showEtaPhiPanel\"\n ></app-eta-phi-panel>\n <app-geometry-browser\n #geometryBrowser\n *ngIf=\"uiConfig?.showGeometryBrowser\"\n ></app-geometry-browser>\n <app-kinematics-panel\n #kinematicsPanel\n *ngIf=\"uiConfig?.showKinematicsPanel\"\n [config]=\"kinematicsConfig\"\n ></app-kinematics-panel>\n</div>\n\n<mat-menu #moreInfo>\n <button mat-menu-item *ngIf=\"uiConfig?.showInfoPanel\">\n <mat-checkbox\n [checked]=\"showInfoPanel\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleInfoPanel()\"\n >\n <span class=\"item-content\">\n <svg class=\"item-icon\">\n <use href=\"assets/icons/info-panel.svg#info-panel\"></use>\n </svg>\n Info Panel\n </span>\n </mat-checkbox>\n </button>\n <button mat-menu-item *ngIf=\"uiConfig?.showEventBrowser\">\n <mat-checkbox\n [checked]=\"showEventBrowser\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleEventBrowser()\"\n >\n <span class=\"item-content\">\n <svg class=\"item-icon\">\n <use href=\"assets/icons/event-browser.svg#event-browser\"></use>\n </svg>\n Event Browser\n </span>\n </mat-checkbox>\n </button>\n <button mat-menu-item *ngIf=\"uiConfig?.showCollectionsInfo\">\n <mat-checkbox\n [checked]=\"showCollectionsInfo\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleCollectionsInfo()\"\n >\n <span class=\"item-content\">\n <svg class=\"item-icon\">\n <use href=\"assets/icons/eventData.svg#eventData\"></use>\n </svg>\n Collections Info\n </span>\n </mat-checkbox>\n </button>\n <button mat-menu-item *ngIf=\"uiConfig?.showEtaPhiPanel\">\n <mat-checkbox\n [checked]=\"showEtaPhiPanel\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleEtaPhiPanel()\"\n >\n <span class=\"item-content\">\n <svg class=\"item-icon\">\n <use href=\"assets/icons/eta-phi.svg#eta-phi\"></use>\n </svg>\n Eta-Phi Panel\n </span>\n </mat-checkbox>\n </button>\n <button mat-menu-item *ngIf=\"uiConfig?.showGeometryBrowser\">\n <mat-checkbox\n [checked]=\"showGeometryBrowser\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleGeometryBrowser()\"\n >\n <span class=\"item-content\">\n <svg class=\"item-icon\">\n <use href=\"assets/icons/top-cube.svg#top-cube\"></use>\n </svg>\n Geometry Browser\n </span>\n </mat-checkbox>\n </button>\n <button mat-menu-item *ngIf=\"uiConfig?.showKinematicsPanel\">\n <mat-checkbox\n [checked]=\"showKinematicsPanel\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleKinematicsPanel()\"\n >\n <span class=\"item-content\">\n <svg class=\"item-icon\">\n <use href=\"assets/icons/kinematics.svg#kinematics\"></use>\n </svg>\n Track Kinematics\n </span>\n </mat-checkbox>\n </button>\n</mat-menu>\n\n<app-menu-toggle\n [matMenuTriggerFor]=\"moreInfo\"\n tooltip=\"More information\"\n icon=\"info\"\n [active]=\"false\"\n>\n</app-menu-toggle>\n", styles: [".hidden-panels{display:none}.item-content{display:inline-flex;align-items:center}.item-icon{inline-size:1.2rem;block-size:1.2rem;margin-inline-end:.5rem;fill:currentColor}\n"] }]
6503
- }], null, { uiConfig: [{
6504
- type: Input
6505
- }], kinematicsConfig: [{
6506
- type: Input
6507
- }], infoPanel: [{
6508
- type: ViewChild,
6509
- args: ['infoPanel']
6510
- }], eventBrowser: [{
6511
- type: ViewChild,
6512
- args: ['eventBrowser']
6513
- }], collectionsInfo: [{
6514
- type: ViewChild,
6515
- args: ['collectionsInfo']
6516
- }], etaPhiPanel: [{
6517
- type: ViewChild,
6518
- args: ['etaPhiPanel']
6519
- }], geometryBrowser: [{
6520
- type: ViewChild,
6521
- args: ['geometryBrowser']
6522
- }], kinematicsPanel: [{
6523
- type: ViewChild,
6524
- args: ['kinematicsPanel']
6525
- }] }); })();
6526
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MoreInfoComponent, { className: "MoreInfoComponent", filePath: "components/ui-menu/more-info/more-info.component.ts", lineNumber: 17 }); })();
7712
+ args: [{ standalone: false, selector: 'app-session-pill', template: "<div class=\"session-pill\" *ngIf=\"visible\" [attr.data-kind]=\"state.kind\">\n <!-- Recording -->\n <ng-container *ngIf=\"state.kind === 'recording'\">\n <span class=\"rec-dot\" aria-label=\"Recording\"></span>\n <span class=\"label\">REC</span>\n <span class=\"time\">{{ durationLabel }}</span>\n <span class=\"dot-sep\">\u00B7</span>\n <span class=\"count\">{{ eventCount }} events</span>\n <button\n type=\"button\"\n class=\"action stop\"\n (click)=\"onStopRecording()\"\n title=\"Stop recording (Shift+S)\"\n aria-label=\"Stop recording\"\n >\n \u25A0\n </button>\n </ng-container>\n\n <!-- Recorded (offer download / share) -->\n <ng-container *ngIf=\"state.kind === 'recorded'\">\n <span class=\"label\">Saved</span>\n <span class=\"time\">{{ durationLabel }}</span>\n <span class=\"dot-sep\">\u00B7</span>\n <span class=\"count\">{{ eventCount }} events</span>\n <button\n type=\"button\"\n class=\"action\"\n (click)=\"onDownload()\"\n title=\"Download .phnxreplay file\"\n aria-label=\"Download session\"\n >\n Download\n </button>\n <button\n type=\"button\"\n class=\"action\"\n (click)=\"onCopyLink()\"\n [disabled]=\"linkCopied\"\n title=\"Copy a ?replay= share link\"\n aria-label=\"Copy share link\"\n >\n {{ linkCopied ? 'Copied' : 'Copy link' }}\n </button>\n <button\n type=\"button\"\n class=\"action close\"\n (click)=\"onDismiss()\"\n title=\"Dismiss\"\n aria-label=\"Dismiss\"\n >\n \u2715\n </button>\n </ng-container>\n\n <!-- Pending: link-supplied session awaiting explicit user confirmation -->\n <ng-container *ngIf=\"state.kind === 'pending'\">\n <span class=\"label\">Shared session</span>\n <span class=\"time\">{{ pendingSummary }}</span>\n <span class=\"dot-sep\">\u00B7</span>\n <span class=\"count\">from {{ state.sourceLabel }}</span>\n <button\n type=\"button\"\n class=\"action play-pending\"\n (click)=\"onPlayPending()\"\n title=\"Play this shared session\"\n aria-label=\"Play shared session\"\n >\n Play\n </button>\n <button\n type=\"button\"\n class=\"action close\"\n (click)=\"onDismiss()\"\n title=\"Dismiss\"\n aria-label=\"Dismiss shared session\"\n >\n \u2715\n </button>\n </ng-container>\n\n <!-- Playing / Paused / Finished -->\n <ng-container\n *ngIf=\"\n state.kind === 'playing' ||\n state.kind === 'paused' ||\n state.kind === 'finished'\n \"\n >\n <button\n type=\"button\"\n class=\"action play\"\n (click)=\"onTogglePlayback()\"\n [title]=\"state.kind === 'playing' ? 'Pause' : 'Play'\"\n [attr.aria-label]=\"state.kind === 'playing' ? 'Pause' : 'Play'\"\n >\n {{ state.kind === 'playing' ? '\u275A\u275A' : '\u25BA' }}\n </button>\n <span class=\"label\">Replay</span>\n <span class=\"time\">{{ playbackLabel }}</span>\n <input\n type=\"range\"\n class=\"scrub\"\n min=\"0\"\n [max]=\"playerDuration\"\n step=\"50\"\n [value]=\"\n state.kind === 'finished' ? playerDuration : (state.currentTime ?? 0)\n \"\n (input)=\"onScrub(+$any($event.target).value)\"\n aria-label=\"Replay scrub bar\"\n />\n <span class=\"speeds\">\n <button\n type=\"button\"\n class=\"speed\"\n *ngFor=\"let s of speeds\"\n [class.selected]=\"currentSpeed === s\"\n (click)=\"onSpeed(s)\"\n [attr.aria-label]=\"'Speed ' + s + 'x'\"\n [attr.aria-pressed]=\"currentSpeed === s\"\n >\n {{ s }}\u00D7\n </button>\n </span>\n <button\n type=\"button\"\n class=\"action close\"\n (click)=\"onDismiss()\"\n title=\"Stop replay\"\n aria-label=\"Stop replay\"\n >\n \u2715\n </button>\n </ng-container>\n\n <!-- Error -->\n <ng-container *ngIf=\"state.kind === 'error'\">\n <span class=\"label error\">Session error</span>\n <span class=\"time\">{{ state.message }}</span>\n <button\n type=\"button\"\n class=\"action close\"\n (click)=\"onDismiss()\"\n title=\"Dismiss\"\n aria-label=\"Dismiss\"\n >\n \u2715\n </button>\n </ng-container>\n</div>\n", styles: [".session-pill{position:fixed;bottom:80px;left:16px;z-index:1100;display:flex;align-items:center;gap:10px;padding:8px 14px;background:#14161ed1;color:#e8eaf2;border-radius:999px;border:1px solid rgba(255,255,255,.08);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:0 6px 18px #00000059;font-size:13px;font-weight:500;letter-spacing:.2px;-webkit-user-select:none;user-select:none;max-width:80vw}.session-pill .label{font-weight:600;letter-spacing:.4px}.session-pill .label.error{color:#ff7676}.session-pill .time,.session-pill .count{font-variant-numeric:tabular-nums;opacity:.9}.session-pill .dot-sep{opacity:.4}.session-pill .rec-dot{width:10px;height:10px;border-radius:50%;background:#ff4040;box-shadow:0 0 6px #ff404099;animation:phoenixSessionPulse 1.2s infinite ease-in-out}.session-pill .action{background:#ffffff0f;color:inherit;border:1px solid rgba(255,255,255,.1);border-radius:999px;padding:4px 12px;cursor:pointer;font-size:12px;font-weight:600;letter-spacing:.3px;transition:background .12s ease,border-color .12s ease}.session-pill .action:hover{background:#ffffff1f;border-color:#ffffff2e}.session-pill .action:disabled{opacity:.6;cursor:default}.session-pill .action.stop{background:#ff40402e;border-color:#ff404066}.session-pill .action.stop:hover{background:#ff404047}.session-pill .action.close{width:24px;height:24px;padding:0;display:inline-flex;align-items:center;justify-content:center}.session-pill .action.play{width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;font-size:11px}.session-pill .scrub{width:180px;accent-color:#80c1ff}.session-pill .speeds{display:inline-flex;gap:4px}.session-pill .speeds .speed{background:transparent;color:inherit;border:1px solid rgba(255,255,255,.12);border-radius:999px;padding:3px 8px;cursor:pointer;font-size:11px;font-weight:600;opacity:.65;transition:opacity .12s ease,background .12s ease}.session-pill .speeds .speed:hover{opacity:1}.session-pill .speeds .speed.selected{background:#80c1ff2e;border-color:#80c1ff80;opacity:1}@keyframes phoenixSessionPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.65;transform:scale(.85)}}\n"] }]
7713
+ }], () => [{ type: EventDisplayService }, { type: i0.ChangeDetectorRef }], null); })();
7714
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SessionPillComponent, { className: "SessionPillComponent", filePath: "components/session-pill/session-pill.component.ts", lineNumber: 23 }); })();
7715
+ function formatClock(ms) {
7716
+ if (!isFinite(ms) || ms < 0)
7717
+ return '00:00';
7718
+ const totalSeconds = Math.floor(ms / 1000);
7719
+ const minutes = Math.floor(totalSeconds / 60);
7720
+ const seconds = totalSeconds % 60;
7721
+ return `${pad(minutes)}:${pad(seconds)}`;
7722
+ }
7723
+ function pad(n) {
7724
+ return n < 10 ? `0${n}` : String(n);
7725
+ }
6527
7726
 
6528
7727
  const _c0$1 = ["*"];
6529
7728
  const _c1$1 = a0 => ({ "ui-menu-hidden": a0 });
6530
7729
  class UiMenuWrapperComponent {
6531
7730
  hideUIMenu = false;
6532
7731
  static ɵfac = function UiMenuWrapperComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || UiMenuWrapperComponent)(); };
6533
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: UiMenuWrapperComponent, selectors: [["app-ui-menu-wrapper"]], standalone: false, ngContentSelectors: _c0$1, decls: 6, vars: 5, consts: [["id", "uiMenu"], ["id", "hideUIMenu", "matTooltipPosition", "above", 3, "click", "matTooltip", "ngClass"], ["href", "assets/icons/expand.svg#expand"], ["id", "optionsPanel", 3, "hidden"]], template: function UiMenuWrapperComponent_Template(rf, ctx) { if (rf & 1) {
7732
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: UiMenuWrapperComponent, selectors: [["app-ui-menu-wrapper"]], standalone: false, ngContentSelectors: _c0$1, decls: 7, vars: 5, consts: [["id", "uiMenu"], ["id", "hideUIMenu", "type", "button", "aria-label", "Toggle toolbar visibility", "matTooltipPosition", "above", 3, "click", "matTooltip", "ngClass"], ["href", "assets/icons/expand.svg#expand"], ["id", "optionsPanel", 3, "hidden"]], template: function UiMenuWrapperComponent_Template(rf, ctx) { if (rf & 1) {
6534
7733
  i0.ɵɵprojectionDef();
6535
7734
  i0.ɵɵelementStart(0, "div", 0)(1, "button", 1);
6536
7735
  i0.ɵɵlistener("click", function UiMenuWrapperComponent_Template_button_click_1_listener() { return ctx.hideUIMenu = !ctx.hideUIMenu; });
@@ -6542,16 +7741,17 @@ class UiMenuWrapperComponent {
6542
7741
  i0.ɵɵelementStart(4, "div", 3);
6543
7742
  i0.ɵɵprojection(5);
6544
7743
  i0.ɵɵelementEnd()();
7744
+ i0.ɵɵelement(6, "app-session-pill");
6545
7745
  } if (rf & 2) {
6546
7746
  i0.ɵɵadvance();
6547
7747
  i0.ɵɵproperty("matTooltip", ctx.hideUIMenu ? "Show toolbar" : "Hide toolbar")("ngClass", i0.ɵɵpureFunction1(3, _c1$1, ctx.hideUIMenu));
6548
7748
  i0.ɵɵadvance(3);
6549
7749
  i0.ɵɵproperty("hidden", ctx.hideUIMenu);
6550
- } }, dependencies: [i2.NgClass, i3$1.MatTooltip], styles: ["#uiMenu[_ngcontent-%COMP%]{position:absolute;bottom:1rem;width:100%;box-sizing:border-box;display:flex;flex-direction:column;align-items:center}#optionsPanel[_ngcontent-%COMP%]{display:flex;align-items:center;background:var(--phoenix-background-color-secondary);border:1px solid var(--phoenix-background-color-tertiary);box-shadow:var(--phoenix-box-shadow);border-radius:30px;max-width:95%;padding:.3rem 1rem;overflow-x:scroll;z-index:90}#hideUIMenu[_ngcontent-%COMP%]{background:var(--phoenix-background-color-secondary);height:1.2rem;width:3rem;border:1px solid var(--phoenix-border);border-bottom:none;box-shadow:var(--phoenix-box-shadow);border-top-left-radius:15px;border-top-right-radius:15px;padding:3px}#hideUIMenu[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{height:100%;width:100%;vertical-align:top;transition:all .3s}#hideUIMenu.ui-menu-hidden[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{transform:scaleY(-1);transition:all .3s}"] });
7750
+ } }, dependencies: [i2.NgClass, i3$1.MatTooltip, SessionPillComponent], styles: ["#uiMenu[_ngcontent-%COMP%]{position:absolute;bottom:1rem;width:100%;box-sizing:border-box;display:flex;flex-direction:column;align-items:center}#optionsPanel[_ngcontent-%COMP%]{display:flex;align-items:center;background:var(--phoenix-background-color-secondary);border:1px solid var(--phoenix-background-color-tertiary);box-shadow:var(--phoenix-box-shadow);border-radius:30px;max-width:95%;padding:.3rem 1rem;overflow-x:scroll;z-index:90}#hideUIMenu[_ngcontent-%COMP%]{background:var(--phoenix-background-color-secondary);height:1.2rem;width:3rem;border:1px solid var(--phoenix-border);border-bottom:none;box-shadow:var(--phoenix-box-shadow);border-top-left-radius:15px;border-top-right-radius:15px;padding:3px}#hideUIMenu[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{height:100%;width:100%;vertical-align:top;transition:all .3s}#hideUIMenu.ui-menu-hidden[_ngcontent-%COMP%] svg[_ngcontent-%COMP%]{transform:scaleY(-1);transition:all .3s}"] });
6551
7751
  }
6552
7752
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(UiMenuWrapperComponent, [{
6553
7753
  type: Component,
6554
- args: [{ standalone: false, selector: 'app-ui-menu-wrapper', template: "<div id=\"uiMenu\">\n <button\n id=\"hideUIMenu\"\n [matTooltip]=\"hideUIMenu ? 'Show toolbar' : 'Hide toolbar'\"\n matTooltipPosition=\"above\"\n (click)=\"hideUIMenu = !hideUIMenu\"\n [ngClass]=\"{ 'ui-menu-hidden': hideUIMenu }\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n <div id=\"optionsPanel\" [hidden]=\"hideUIMenu\">\n <ng-content></ng-content>\n </div>\n</div>\n", styles: ["#uiMenu{position:absolute;bottom:1rem;width:100%;box-sizing:border-box;display:flex;flex-direction:column;align-items:center}#optionsPanel{display:flex;align-items:center;background:var(--phoenix-background-color-secondary);border:1px solid var(--phoenix-background-color-tertiary);box-shadow:var(--phoenix-box-shadow);border-radius:30px;max-width:95%;padding:.3rem 1rem;overflow-x:scroll;z-index:90}#hideUIMenu{background:var(--phoenix-background-color-secondary);height:1.2rem;width:3rem;border:1px solid var(--phoenix-border);border-bottom:none;box-shadow:var(--phoenix-box-shadow);border-top-left-radius:15px;border-top-right-radius:15px;padding:3px}#hideUIMenu svg{height:100%;width:100%;vertical-align:top;transition:all .3s}#hideUIMenu.ui-menu-hidden svg{transform:scaleY(-1);transition:all .3s}\n"] }]
7754
+ args: [{ standalone: false, selector: 'app-ui-menu-wrapper', template: "<div id=\"uiMenu\">\n <button\n id=\"hideUIMenu\"\n type=\"button\"\n aria-label=\"Toggle toolbar visibility\"\n [matTooltip]=\"hideUIMenu ? 'Show toolbar' : 'Hide toolbar'\"\n matTooltipPosition=\"above\"\n (click)=\"hideUIMenu = !hideUIMenu\"\n [ngClass]=\"{ 'ui-menu-hidden': hideUIMenu }\"\n >\n <svg>\n <use href=\"assets/icons/expand.svg#expand\"></use>\n </svg>\n </button>\n <div id=\"optionsPanel\" [hidden]=\"hideUIMenu\">\n <ng-content></ng-content>\n </div>\n</div>\n<app-session-pill></app-session-pill>\n", styles: ["#uiMenu{position:absolute;bottom:1rem;width:100%;box-sizing:border-box;display:flex;flex-direction:column;align-items:center}#optionsPanel{display:flex;align-items:center;background:var(--phoenix-background-color-secondary);border:1px solid var(--phoenix-background-color-tertiary);box-shadow:var(--phoenix-box-shadow);border-radius:30px;max-width:95%;padding:.3rem 1rem;overflow-x:scroll;z-index:90}#hideUIMenu{background:var(--phoenix-background-color-secondary);height:1.2rem;width:3rem;border:1px solid var(--phoenix-border);border-bottom:none;box-shadow:var(--phoenix-box-shadow);border-top-left-radius:15px;border-top-right-radius:15px;padding:3px}#hideUIMenu svg{height:100%;width:100%;vertical-align:top;transition:all .3s}#hideUIMenu.ui-menu-hidden svg{transform:scaleY(-1);transition:all .3s}\n"] }]
6555
7755
  }], null, null); })();
6556
7756
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(UiMenuWrapperComponent, { className: "UiMenuWrapperComponent", filePath: "components/ui-menu/ui-menu-wrapper/ui-menu-wrapper.component.ts", lineNumber: 9 }); })();
6557
7757
 
@@ -6979,13 +8179,27 @@ class MasterclassPanelOverlayComponent {
6979
8179
  // ── Step 1: Collection ──────────────────────────────────
6980
8180
  loadCollections() {
6981
8181
  const grouped = this.eventDisplay.getCollections();
8182
+ // Restrict to taggable types (default 'Tracks') so detector-level
8183
+ // collections (hits like CSCs, calo cells) do not show up. See #923.
8184
+ const types = this.config?.collectionTypes ?? ['Tracks'];
6982
8185
  this.collectionNames = [];
6983
- for (const [, names] of Object.entries(grouped)) {
6984
- this.collectionNames.push(...names);
8186
+ for (const type of types) {
8187
+ if (grouped[type]) {
8188
+ this.collectionNames.push(...grouped[type]);
8189
+ }
6985
8190
  }
6986
- if (this.collectionNames.length > 0 && !this.selectedCollection) {
8191
+ if (this.collectionNames.length === 0) {
8192
+ this.selectedCollection = '';
8193
+ this.collectionItems = [];
8194
+ return;
8195
+ }
8196
+ if (!this.selectedCollection ||
8197
+ !this.collectionNames.includes(this.selectedCollection)) {
6987
8198
  this.selectCollection(this.collectionNames[0]);
6988
8199
  }
8200
+ else {
8201
+ this.selectCollection(this.selectedCollection);
8202
+ }
6989
8203
  }
6990
8204
  selectCollection(name) {
6991
8205
  this.selectedCollection = name;
@@ -7166,7 +8380,7 @@ class MasterclassPanelComponent {
7166
8380
  this.showPanel = !this.showPanel;
7167
8381
  this.overlayWindow.instance.showPanel = this.showPanel;
7168
8382
  }
7169
- static ɵfac = function MasterclassPanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MasterclassPanelComponent)(i0.ɵɵdirectiveInject(i1$1.Overlay)); };
8383
+ static ɵfac = function MasterclassPanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MasterclassPanelComponent)(i0.ɵɵdirectiveInject(i1$3.Overlay)); };
7170
8384
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MasterclassPanelComponent, selectors: [["app-masterclass-panel"]], inputs: { config: "config" }, standalone: false, features: [i0.ɵɵNgOnChangesFeature], decls: 1, vars: 1, consts: [["tooltip", "Masterclass: tag particles and calculate invariant mass", "icon", "physics", 3, "click", "active"]], template: function MasterclassPanelComponent_Template(rf, ctx) { if (rf & 1) {
7171
8385
  i0.ɵɵelementStart(0, "app-menu-toggle", 0);
7172
8386
  i0.ɵɵlistener("click", function MasterclassPanelComponent_Template_app_menu_toggle_click_0_listener() { return ctx.toggleOverlay(); });
@@ -7178,7 +8392,7 @@ class MasterclassPanelComponent {
7178
8392
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MasterclassPanelComponent, [{
7179
8393
  type: Component,
7180
8394
  args: [{ standalone: false, selector: 'app-masterclass-panel', template: "<app-menu-toggle\n tooltip=\"Masterclass: tag particles and calculate invariant mass\"\n icon=\"physics\"\n [active]=\"showPanel\"\n (click)=\"toggleOverlay()\"\n>\n</app-menu-toggle>\n" }]
7181
- }], () => [{ type: i1$1.Overlay }], { config: [{
8395
+ }], () => [{ type: i1$3.Overlay }], { config: [{
7182
8396
  type: Input
7183
8397
  }] }); })();
7184
8398
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MasterclassPanelComponent, { className: "MasterclassPanelComponent", filePath: "components/ui-menu/masterclass-panel/masterclass-panel.component.ts", lineNumber: 21 }); })();
@@ -7213,36 +8427,15 @@ class ErrorMessageService {
7213
8427
 
7214
8428
  const _c0 = a0 => ({ "load-complete": a0 });
7215
8429
  const _c1 = a0 => ({ width: a0 });
7216
- function LoaderComponent_ng_container_1_div_7_Template(rf, ctx) { if (rf & 1) {
7217
- i0.ɵɵelementStart(0, "div", 7);
8430
+ function LoaderComponent_div_7_Template(rf, ctx) { if (rf & 1) {
8431
+ i0.ɵɵelementStart(0, "div", 5);
7218
8432
  i0.ɵɵelement(1, "span");
7219
8433
  i0.ɵɵelementEnd();
7220
8434
  } if (rf & 2) {
7221
- const ctx_r0 = i0.ɵɵnextContext(2);
8435
+ const ctx_r0 = i0.ɵɵnextContext();
7222
8436
  i0.ɵɵadvance();
7223
8437
  i0.ɵɵstyleMap(i0.ɵɵpureFunction1(2, _c1, ctx_r0.progress + "%"));
7224
8438
  } }
7225
- function LoaderComponent_ng_container_1_Template(rf, ctx) { if (rf & 1) {
7226
- i0.ɵɵelementContainerStart(0);
7227
- i0.ɵɵelement(1, "img", 3);
7228
- i0.ɵɵelementStart(2, "p", 4);
7229
- i0.ɵɵtext(3, " Loading...");
7230
- i0.ɵɵelement(4, "br");
7231
- i0.ɵɵelementStart(5, "small", 5);
7232
- i0.ɵɵtext(6, "(This may take a while)");
7233
- i0.ɵɵelementEnd()();
7234
- i0.ɵɵtemplate(7, LoaderComponent_ng_container_1_div_7_Template, 2, 4, "div", 6);
7235
- i0.ɵɵelementContainerEnd();
7236
- } if (rf & 2) {
7237
- const ctx_r0 = i0.ɵɵnextContext();
7238
- i0.ɵɵadvance(7);
7239
- i0.ɵɵproperty("ngIf", ctx_r0.progress !== undefined);
7240
- } }
7241
- function LoaderComponent_ng_template_2_Template(rf, ctx) { if (rf & 1) {
7242
- i0.ɵɵelementStart(0, "p");
7243
- i0.ɵɵtext(1, " An error occurred while loading the application. Please try refreshing the page. ");
7244
- i0.ɵɵelementEnd();
7245
- } }
7246
8439
  class LoaderComponent {
7247
8440
  errorMessageService;
7248
8441
  loaded = false;
@@ -7261,20 +8454,26 @@ class LoaderComponent {
7261
8454
  this.unsubscribeError();
7262
8455
  }
7263
8456
  static ɵfac = function LoaderComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || LoaderComponent)(i0.ɵɵdirectiveInject(ErrorMessageService)); };
7264
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: LoaderComponent, selectors: [["app-loader"]], inputs: { loaded: "loaded", progress: "progress" }, standalone: false, decls: 4, vars: 5, consts: [["errorContainer", ""], [1, "loader-wrapper", "d-flex", "position-absolute", "flex-column", "justify-content-center", "align-items-center", "w-100", "h-100", "p-5", "text-center", 3, "ngClass"], [4, "ngIf", "ngIfElse"], ["src", "assets/images/logo-small.svg", "alt", "Loader"], [1, "mt-5"], [1, "text-muted"], ["class", "loading-bar", 4, "ngIf"], [1, "loading-bar"]], template: function LoaderComponent_Template(rf, ctx) { if (rf & 1) {
7265
- i0.ɵɵelementStart(0, "div", 1);
7266
- i0.ɵɵtemplate(1, LoaderComponent_ng_container_1_Template, 8, 1, "ng-container", 2)(2, LoaderComponent_ng_template_2_Template, 2, 0, "ng-template", null, 0, i0.ɵɵtemplateRefExtractor);
8457
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: LoaderComponent, selectors: [["app-loader"]], inputs: { loaded: "loaded", progress: "progress" }, standalone: false, decls: 8, vars: 4, consts: [[1, "loader-wrapper", "d-flex", "position-absolute", "flex-column", "justify-content-center", "align-items-center", "w-100", "h-100", "p-5", "text-center", 3, "ngClass"], ["src", "assets/images/logo-small.svg", "alt", "Loader"], [1, "mt-5"], [1, "text-muted"], ["class", "loading-bar", 4, "ngIf"], [1, "loading-bar"]], template: function LoaderComponent_Template(rf, ctx) { if (rf & 1) {
8458
+ i0.ɵɵelementStart(0, "div", 0);
8459
+ i0.ɵɵelement(1, "img", 1);
8460
+ i0.ɵɵelementStart(2, "p", 2);
8461
+ i0.ɵɵtext(3, " Loading...");
8462
+ i0.ɵɵelement(4, "br");
8463
+ i0.ɵɵelementStart(5, "small", 3);
8464
+ i0.ɵɵtext(6, "(This may take a while)");
8465
+ i0.ɵɵelementEnd()();
8466
+ i0.ɵɵtemplate(7, LoaderComponent_div_7_Template, 2, 4, "div", 4);
7267
8467
  i0.ɵɵelementEnd();
7268
8468
  } if (rf & 2) {
7269
- const errorContainer_r2 = i0.ɵɵreference(3);
7270
- i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction1(3, _c0, ctx.loaded));
7271
- i0.ɵɵadvance();
7272
- i0.ɵɵproperty("ngIf", ctx.error === undefined)("ngIfElse", errorContainer_r2);
8469
+ i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction1(2, _c0, ctx.loaded));
8470
+ i0.ɵɵadvance(7);
8471
+ i0.ɵɵproperty("ngIf", ctx.progress !== undefined);
7273
8472
  } }, dependencies: [i2.NgClass, i2.NgIf], styles: [".loader-wrapper[_ngcontent-%COMP%]{background:var(--phoenix-background-color);z-index:9998}.loader-wrapper.load-complete[_ngcontent-%COMP%]{display:none!important}.loader-wrapper[_ngcontent-%COMP%] img[_ngcontent-%COMP%]{width:5rem;height:5rem;animation:loader 2s infinite}.loader-wrapper[_ngcontent-%COMP%] p[_ngcontent-%COMP%]{color:var(--phoenix-text-color)}.loader-wrapper[_ngcontent-%COMP%] .loading-bar[_ngcontent-%COMP%]{width:15rem;max-width:90%;height:.5rem;background:var(--phoenix-background-color-tertiary);border-radius:2.5rem;overflow:hidden}.loader-wrapper[_ngcontent-%COMP%] .loading-bar[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{display:block;height:100%;border-radius:2.5rem;background:linear-gradient(to right,var(--phoenix-primary),var(--phoenix-secondary));transition:all .5s}@keyframes _ngcontent-%COMP%_app-loader_loader{0%,to{transform:scale(.1) rotate(0)}95%{transform:scale(1) rotate(15deg)}}"] });
7274
8473
  }
7275
8474
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(LoaderComponent, [{
7276
8475
  type: Component,
7277
- args: [{ standalone: false, selector: 'app-loader', template: "<div\n class=\"loader-wrapper d-flex position-absolute flex-column justify-content-center align-items-center w-100 h-100 p-5 text-center\"\n [ngClass]=\"{ 'load-complete': loaded }\"\n>\n <ng-container *ngIf=\"error === undefined; else errorContainer\">\n <img src=\"assets/images/logo-small.svg\" alt=\"Loader\" />\n <p class=\"mt-5\">\n Loading...<br />\n <small class=\"text-muted\">(This may take a while)</small>\n </p>\n <div class=\"loading-bar\" *ngIf=\"progress !== undefined\">\n <span [style]=\"{ width: progress + '%' }\"></span>\n </div>\n </ng-container>\n <ng-template #errorContainer>\n <p>\n An error occurred while loading the application. Please try refreshing the\n page.\n </p>\n </ng-template>\n</div>\n", styles: [".loader-wrapper{background:var(--phoenix-background-color);z-index:9998}.loader-wrapper.load-complete{display:none!important}.loader-wrapper img{width:5rem;height:5rem;animation:loader 2s infinite}.loader-wrapper p{color:var(--phoenix-text-color)}.loader-wrapper .loading-bar{width:15rem;max-width:90%;height:.5rem;background:var(--phoenix-background-color-tertiary);border-radius:2.5rem;overflow:hidden}.loader-wrapper .loading-bar span{display:block;height:100%;border-radius:2.5rem;background:linear-gradient(to right,var(--phoenix-primary),var(--phoenix-secondary));transition:all .5s}@keyframes app-loader_loader{0%,to{transform:scale(.1) rotate(0)}95%{transform:scale(1) rotate(15deg)}}\n"] }]
8476
+ args: [{ standalone: false, selector: 'app-loader', template: "<div\n class=\"loader-wrapper d-flex position-absolute flex-column justify-content-center align-items-center w-100 h-100 p-5 text-center\"\n [ngClass]=\"{ 'load-complete': loaded }\"\n>\n <img src=\"assets/images/logo-small.svg\" alt=\"Loader\" />\n <p class=\"mt-5\">\n Loading...<br />\n <small class=\"text-muted\">(This may take a while)</small>\n </p>\n <div class=\"loading-bar\" *ngIf=\"progress !== undefined\">\n <span [style]=\"{ width: progress + '%' }\"></span>\n </div>\n</div>\n", styles: [".loader-wrapper{background:var(--phoenix-background-color);z-index:9998}.loader-wrapper.load-complete{display:none!important}.loader-wrapper img{width:5rem;height:5rem;animation:loader 2s infinite}.loader-wrapper p{color:var(--phoenix-text-color)}.loader-wrapper .loading-bar{width:15rem;max-width:90%;height:.5rem;background:var(--phoenix-background-color-tertiary);border-radius:2.5rem;overflow:hidden}.loader-wrapper .loading-bar span{display:block;height:100%;border-radius:2.5rem;background:linear-gradient(to right,var(--phoenix-primary),var(--phoenix-secondary));transition:all .5s}@keyframes app-loader_loader{0%,to{transform:scale(.1) rotate(0)}95%{transform:scale(1) rotate(15deg)}}\n"] }]
7278
8477
  }], () => [{ type: ErrorMessageService }], { loaded: [{
7279
8478
  type: Input
7280
8479
  }], progress: [{
@@ -7348,6 +8547,7 @@ class EmbedMenuComponent {
7348
8547
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EmbedMenuComponent, { className: "EmbedMenuComponent", filePath: "components/embed-menu/embed-menu.component.ts", lineNumber: 9 }); })();
7349
8548
 
7350
8549
  const PHOENIX_COMPONENTS = [
8550
+ NotificationToastComponent,
7351
8551
  NavComponent,
7352
8552
  UiMenuWrapperComponent,
7353
8553
  UiMenuComponent,
@@ -7381,6 +8581,7 @@ const PHOENIX_COMPONENTS = [
7381
8581
  PhoenixMenuItemComponent,
7382
8582
  ConfigSliderComponent,
7383
8583
  AnimateCameraComponent,
8584
+ DownloadAnimationDialogComponent,
7384
8585
  AnimateEventComponent,
7385
8586
  VrToggleComponent,
7386
8587
  ArToggleComponent,
@@ -7405,18 +8606,21 @@ const PHOENIX_COMPONENTS = [
7405
8606
  KinematicsPanelOverlayComponent,
7406
8607
  MasterclassPanelComponent,
7407
8608
  MasterclassPanelOverlayComponent,
8609
+ SessionPillComponent,
8610
+ ShortcutsDialogComponent,
7408
8611
  ];
7409
8612
  class PhoenixUIModule {
7410
8613
  static ɵfac = function PhoenixUIModule_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || PhoenixUIModule)(); };
7411
8614
  static ɵmod = /*@__PURE__*/ i0.ɵɵdefineNgModule({ type: PhoenixUIModule });
7412
8615
  static ɵinj = /*@__PURE__*/ i0.ɵɵdefineInjector({ providers: [
8616
+ NotificationService,
7413
8617
  EventDisplayService,
7414
8618
  ErrorMessageService,
7415
8619
  {
7416
8620
  provide: ErrorHandler,
7417
8621
  useClass: GlobalErrorHandler,
7418
8622
  },
7419
- ], imports: [// Correct: standalone items must be in imports
8623
+ ], imports: [MatSnackBarModule, // Correct: standalone items must be in imports
7420
8624
  CommonModule,
7421
8625
  RouterModule,
7422
8626
  DragDropModule,
@@ -7433,13 +8637,15 @@ class PhoenixUIModule {
7433
8637
  MatCheckboxModule,
7434
8638
  MatIconModule,
7435
8639
  CdkTreeModule,
7436
- MatTabsModule] });
8640
+ MatTabsModule,
8641
+ MatProgressBarModule] });
7437
8642
  }
7438
8643
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(PhoenixUIModule, [{
7439
8644
  type: NgModule,
7440
8645
  args: [{
7441
8646
  declarations: PHOENIX_COMPONENTS,
7442
8647
  imports: [
8648
+ MatSnackBarModule,
7443
8649
  AttributePipe, // Correct: standalone items must be in imports
7444
8650
  CommonModule,
7445
8651
  RouterModule,
@@ -7458,12 +8664,14 @@ class PhoenixUIModule {
7458
8664
  MatIconModule,
7459
8665
  CdkTreeModule,
7460
8666
  MatTabsModule,
8667
+ MatProgressBarModule,
7461
8668
  ],
7462
8669
  exports: [
7463
8670
  ...PHOENIX_COMPONENTS,
7464
8671
  AttributePipe, // Export it so components in other modules can use it
7465
8672
  ],
7466
8673
  providers: [
8674
+ NotificationService,
7467
8675
  EventDisplayService,
7468
8676
  ErrorMessageService,
7469
8677
  {
@@ -7473,7 +8681,8 @@ class PhoenixUIModule {
7473
8681
  ],
7474
8682
  }]
7475
8683
  }], null, null); })();
7476
- (function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵɵsetNgModuleScope(PhoenixUIModule, { declarations: [NavComponent,
8684
+ (function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵɵsetNgModuleScope(PhoenixUIModule, { declarations: [NotificationToastComponent,
8685
+ NavComponent,
7477
8686
  UiMenuWrapperComponent,
7478
8687
  UiMenuComponent,
7479
8688
  MoreInfoComponent,
@@ -7506,6 +8715,7 @@ class PhoenixUIModule {
7506
8715
  PhoenixMenuItemComponent,
7507
8716
  ConfigSliderComponent,
7508
8717
  AnimateCameraComponent,
8718
+ DownloadAnimationDialogComponent,
7509
8719
  AnimateEventComponent,
7510
8720
  VrToggleComponent,
7511
8721
  ArToggleComponent,
@@ -7529,7 +8739,10 @@ class PhoenixUIModule {
7529
8739
  KinematicsPanelComponent,
7530
8740
  KinematicsPanelOverlayComponent,
7531
8741
  MasterclassPanelComponent,
7532
- MasterclassPanelOverlayComponent], imports: [AttributePipe, // Correct: standalone items must be in imports
8742
+ MasterclassPanelOverlayComponent,
8743
+ SessionPillComponent,
8744
+ ShortcutsDialogComponent], imports: [MatSnackBarModule,
8745
+ AttributePipe, // Correct: standalone items must be in imports
7533
8746
  CommonModule,
7534
8747
  RouterModule,
7535
8748
  DragDropModule,
@@ -7546,7 +8759,9 @@ class PhoenixUIModule {
7546
8759
  MatCheckboxModule,
7547
8760
  MatIconModule,
7548
8761
  CdkTreeModule,
7549
- MatTabsModule], exports: [NavComponent,
8762
+ MatTabsModule,
8763
+ MatProgressBarModule], exports: [NotificationToastComponent,
8764
+ NavComponent,
7550
8765
  UiMenuWrapperComponent,
7551
8766
  UiMenuComponent,
7552
8767
  MoreInfoComponent,
@@ -7579,6 +8794,7 @@ class PhoenixUIModule {
7579
8794
  PhoenixMenuItemComponent,
7580
8795
  ConfigSliderComponent,
7581
8796
  AnimateCameraComponent,
8797
+ DownloadAnimationDialogComponent,
7582
8798
  AnimateEventComponent,
7583
8799
  VrToggleComponent,
7584
8800
  ArToggleComponent,
@@ -7602,7 +8818,9 @@ class PhoenixUIModule {
7602
8818
  KinematicsPanelComponent,
7603
8819
  KinematicsPanelOverlayComponent,
7604
8820
  MasterclassPanelComponent,
7605
- MasterclassPanelOverlayComponent, AttributePipe] }); })();
8821
+ MasterclassPanelOverlayComponent,
8822
+ SessionPillComponent,
8823
+ ShortcutsDialogComponent, AttributePipe] }); })();
7606
8824
  i0.ɵɵsetComponentScope(UiMenuComponent, [i2.NgIf, UiMenuWrapperComponent,
7607
8825
  MoreInfoComponent,
7608
8826
  IoOptionsComponent,
@@ -7624,94 +8842,11 @@ i0.ɵɵsetComponentScope(UiMenuComponent, [i2.NgIf, UiMenuWrapperComponent,
7624
8842
  ShareLinkComponent,
7625
8843
  MasterclassPanelComponent], []);
7626
8844
 
7627
- /**
7628
- * Service for displaying user-facing notifications with severity levels.
7629
- * Provides success, info, warning, and error notifications with
7630
- * configurable auto-dismiss durations. Replaces silent console errors
7631
- * with actionable user feedback across all Phoenix components.
7632
- */
7633
- class NotificationService {
7634
- /** Default auto-dismiss durations in milliseconds per severity level. */
7635
- defaultDurations = {
7636
- success: 5000,
7637
- info: 5000,
7638
- warning: 8000,
7639
- error: 0,
7640
- };
7641
- /** Active variable that stores the latest notification. */
7642
- notification = new ActiveVariable();
7643
- /**
7644
- * Subscribe to incoming notifications.
7645
- * @param callback Function called when a notification is emitted.
7646
- * @returns A function that can be called to unsubscribe.
7647
- */
7648
- subscribeToNotifications(callback) {
7649
- return this.notification.onUpdate(callback);
7650
- }
7651
- /**
7652
- * Display a success notification.
7653
- * Auto-dismisses after 5 seconds by default.
7654
- * @param message The message to display.
7655
- * @param duration Optional custom duration in ms. 0 means no auto-dismiss.
7656
- */
7657
- success(message, duration) {
7658
- this.notify(message, 'success', duration);
7659
- }
7660
- /**
7661
- * Display an info notification.
7662
- * Auto-dismisses after 5 seconds by default.
7663
- * @param message The message to display.
7664
- * @param duration Optional custom duration in ms. 0 means no auto-dismiss.
7665
- */
7666
- info(message, duration) {
7667
- this.notify(message, 'info', duration);
7668
- }
7669
- /**
7670
- * Display a warning notification.
7671
- * Auto-dismisses after 8 seconds by default.
7672
- * @param message The message to display.
7673
- * @param duration Optional custom duration in ms. 0 means no auto-dismiss.
7674
- */
7675
- warning(message, duration) {
7676
- this.notify(message, 'warning', duration);
7677
- }
7678
- /**
7679
- * Display an error notification.
7680
- * Does not auto-dismiss by default — requires manual dismissal.
7681
- * @param message The message to display.
7682
- * @param duration Optional custom duration in ms. 0 means no auto-dismiss.
7683
- */
7684
- error(message, duration) {
7685
- this.notify(message, 'error', duration);
7686
- }
7687
- /**
7688
- * Internal method to emit a notification.
7689
- * @param message The message to display.
7690
- * @param severity The severity level.
7691
- * @param duration Optional custom duration override.
7692
- */
7693
- notify(message, severity, duration) {
7694
- this.notification.update({
7695
- message,
7696
- severity,
7697
- duration: duration ?? this.defaultDurations[severity],
7698
- });
7699
- }
7700
- static ɵfac = function NotificationService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || NotificationService)(); };
7701
- static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: NotificationService, factory: NotificationService.ɵfac, providedIn: 'root' });
7702
- }
7703
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(NotificationService, [{
7704
- type: Injectable,
7705
- args: [{
7706
- providedIn: 'root',
7707
- }]
7708
- }], null, null); })();
7709
-
7710
8845
  // Components
7711
8846
 
7712
8847
  /**
7713
8848
  * Generated bundle index. Do not edit.
7714
8849
  */
7715
8850
 
7716
- export { AnimateCameraComponent, AnimateEventComponent, ArToggleComponent, AttributePipe, AutoRotateComponent, CartesianGridConfigComponent, CollectionsInfoComponent, CollectionsInfoOverlayComponent, ConfigSliderComponent, CycleEventsComponent, DarkThemeComponent, EmbedMenuComponent, EtaPhiPanelComponent, EtaPhiPanelOverlayComponent, EventBrowserComponent, EventBrowserOverlayComponent, EventDataExplorerComponent, EventDataExplorerDialogComponent, EventDataFormat, EventDisplayService, EventSelectorComponent, ExperimentInfoComponent, ExperimentLinkComponent, FileEvent, FileExplorerComponent, FileNode, GeometryBrowserComponent, GeometryBrowserOverlayComponent, IOOptionsDialogComponent, ImportOption, InfoPanelComponent, InfoPanelOverlayComponent, IoOptionsComponent, KinematicsPanelComponent, KinematicsPanelOverlayComponent, LoaderComponent, MainViewToggleComponent, MakePictureComponent, MasterclassPanelComponent, MasterclassPanelOverlayComponent, MenuToggleComponent, MoreInfoComponent, NavComponent, NotificationService, ObjectClippingComponent, ObjectSelectionComponent, ObjectSelectionOverlayComponent, OverlayComponent, OverlayViewComponent, OverlayViewWindowComponent, PerformanceToggleComponent, PhoenixMenuComponent, PhoenixMenuItemComponent, PhoenixUIModule, RingLoaderComponent, SSModeComponent, ShareLinkComponent, ShareLinkDialogComponent, TreeMenuComponent, TreeMenuItemComponent, UiMenuComponent, UiMenuWrapperComponent, ViewOptionsComponent, VrToggleComponent, ZoomControlsComponent, defaultAnimationPresets, defaultUIMenuConfig };
8851
+ export { AnimateCameraComponent, AnimateEventComponent, ArToggleComponent, AttributePipe, AutoRotateComponent, CartesianGridConfigComponent, CollectionsInfoComponent, CollectionsInfoOverlayComponent, ConfigSliderComponent, CycleEventsComponent, DarkThemeComponent, DownloadAnimationDialogComponent, EmbedMenuComponent, EtaPhiPanelComponent, EtaPhiPanelOverlayComponent, EventBrowserComponent, EventBrowserOverlayComponent, EventDataExplorerComponent, EventDataExplorerDialogComponent, EventDataFormat, EventDisplayService, EventSelectorComponent, ExperimentInfoComponent, ExperimentLinkComponent, FileEvent, FileExplorerComponent, FileNode, GeometryBrowserComponent, GeometryBrowserOverlayComponent, IOOptionsDialogComponent, ImportOption, InfoPanelComponent, InfoPanelOverlayComponent, IoOptionsComponent, KinematicsPanelComponent, KinematicsPanelOverlayComponent, LoaderComponent, MainViewToggleComponent, MakePictureComponent, MasterclassPanelComponent, MasterclassPanelOverlayComponent, MenuToggleComponent, MoreInfoComponent, NavComponent, NotificationService, NotificationToastComponent, ObjectClippingComponent, ObjectSelectionComponent, ObjectSelectionOverlayComponent, OverlayCascadeService, OverlayComponent, OverlayViewComponent, OverlayViewWindowComponent, PerformanceToggleComponent, PhoenixMenuComponent, PhoenixMenuItemComponent, PhoenixUIModule, RingLoaderComponent, SSModeComponent, SessionPillComponent, ShareLinkComponent, ShareLinkDialogComponent, ShortcutsDialogComponent, TreeMenuComponent, TreeMenuItemComponent, UiMenuComponent, UiMenuWrapperComponent, ViewOptionsComponent, VrToggleComponent, ZoomControlsComponent, defaultAnimationPresets, defaultUIMenuConfig };
7717
8852
  //# sourceMappingURL=phoenix-ui-components.mjs.map