@jax-data-science/sci-image-visualizer 0.3.2 → 0.4.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.
Files changed (93) hide show
  1. package/README.md +215 -11
  2. package/esm2022/index.mjs +20 -1
  3. package/esm2022/lib/contracts/display-types.mjs +20 -2
  4. package/esm2022/lib/contracts/image.contract.mjs +1 -1
  5. package/esm2022/lib/contracts/plot-type.mjs +36 -1
  6. package/esm2022/lib/contracts/ports/spatial-data.port.mjs +3 -0
  7. package/esm2022/lib/contracts/spatial-dataset.contract.mjs +45 -0
  8. package/esm2022/lib/contracts/visualizer.contract.mjs +1 -1
  9. package/esm2022/lib/implementations/napari-js/napari-axes-labels.mjs +10 -19
  10. package/esm2022/lib/implementations/napari-js/napari-region-overlay.mjs +71 -7
  11. package/esm2022/lib/implementations/napari-js/napari-scale-bar.mjs +1 -1
  12. package/esm2022/lib/implementations/napari-js/napari-spatial-tooltip.mjs +96 -0
  13. package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +1934 -55
  14. package/esm2022/lib/implementations/napari-js/napari-zoom.mjs +18 -0
  15. package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +5 -3
  16. package/esm2022/lib/implementations/osd/osd-region-overlay.mjs +3 -4
  17. package/esm2022/lib/implementations/osd/osd-zoom.mjs +20 -0
  18. package/esm2022/lib/implementations/plotly/omics-trace-builders.mjs +555 -0
  19. package/esm2022/lib/implementations/plotly/plotly.service.mjs +13 -7
  20. package/esm2022/lib/implementations/spatial-data-http/spatial-data-http.service.mjs +304 -0
  21. package/esm2022/lib/implementations/spatial-data-http/spatial-wire.mjs +179 -0
  22. package/esm2022/lib/routing-visualizer.service.mjs +223 -12
  23. package/esm2022/lib/spatial/embedding-compute.mjs +142 -0
  24. package/esm2022/lib/spatial/gene-search.mjs +77 -0
  25. package/esm2022/lib/spatial/spatial-density.mjs +224 -0
  26. package/esm2022/lib/spatial/spatial-encoding.mjs +232 -0
  27. package/esm2022/lib/spatial/spatial-expression.mjs +257 -0
  28. package/esm2022/lib/spatial/spatial-framing.mjs +60 -0
  29. package/esm2022/lib/spatial/spatial-heatmap.mjs +185 -0
  30. package/esm2022/lib/spatial/spatial-hover.mjs +79 -0
  31. package/esm2022/lib/spatial/spatial-sections.mjs +96 -0
  32. package/esm2022/lib/spatial/spatial-selection.mjs +264 -0
  33. package/esm2022/lib/spatial/spatial-volume-image.mjs +163 -0
  34. package/esm2022/lib/spatial/tsne-worker.mjs +10 -0
  35. package/esm2022/lib/spatial/world-grid.mjs +77 -0
  36. package/esm2022/lib/spatial-controls/spatial-charts/spatial-charts.component.mjs +1278 -0
  37. package/esm2022/lib/spatial-controls/spatial-controls.component.mjs +621 -0
  38. package/esm2022/lib/store/spatial-selection.service.mjs +47 -0
  39. package/esm2022/lib/store/visualizer-store.service.mjs +23 -1
  40. package/esm2022/lib/toolbar/segmentation/cellpose-segmenter.service.mjs +2 -2
  41. package/esm2022/lib/toolbar/segmentation/sam-model-registry.mjs +9 -9
  42. package/esm2022/lib/toolbar/toolbar.component.mjs +30 -7
  43. package/esm2022/lib/visualization.module.mjs +23 -5
  44. package/esm2022/lib/visualizer.component.mjs +283 -22
  45. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs → jax-data-science-sci-image-visualizer-onnx-sam-session-BwP-A11m.mjs} +6 -4
  46. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs.map → jax-data-science-sci-image-visualizer-onnx-sam-session-BwP-A11m.mjs.map} +1 -1
  47. package/fesm2022/jax-data-science-sci-image-visualizer-tsne-worker-B_nXDGf0.mjs +12 -0
  48. package/fesm2022/jax-data-science-sci-image-visualizer-tsne-worker-B_nXDGf0.mjs.map +1 -0
  49. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +15282 -7829
  50. package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
  51. package/fesm2022/tsne.worker.js +19091 -0
  52. package/index.d.ts +15 -0
  53. package/lib/contracts/display-types.d.ts +131 -0
  54. package/lib/contracts/image.contract.d.ts +7 -0
  55. package/lib/contracts/plot-type.d.ts +42 -2
  56. package/lib/contracts/ports/spatial-data.port.d.ts +90 -0
  57. package/lib/contracts/spatial-dataset.contract.d.ts +281 -0
  58. package/lib/contracts/visualizer.contract.d.ts +99 -1
  59. package/lib/implementations/napari-js/napari-axes-labels.d.ts +9 -7
  60. package/lib/implementations/napari-js/napari-region-overlay.d.ts +38 -4
  61. package/lib/implementations/napari-js/napari-scale-bar.d.ts +7 -3
  62. package/lib/implementations/napari-js/napari-spatial-tooltip.d.ts +32 -0
  63. package/lib/implementations/napari-js/napari-visualizer.service.d.ts +497 -16
  64. package/lib/implementations/napari-js/napari-zoom.d.ts +15 -0
  65. package/lib/implementations/osd/osd-zoom.d.ts +19 -0
  66. package/lib/implementations/plotly/omics-trace-builders.d.ts +193 -0
  67. package/lib/implementations/plotly/plotly.service.d.ts +9 -0
  68. package/lib/implementations/spatial-data-http/spatial-data-http.service.d.ts +123 -0
  69. package/lib/implementations/spatial-data-http/spatial-wire.d.ts +115 -0
  70. package/lib/routing-visualizer.service.d.ts +45 -6
  71. package/lib/spatial/embedding-compute.d.ts +73 -0
  72. package/lib/spatial/gene-search.d.ts +48 -0
  73. package/lib/spatial/spatial-density.d.ts +73 -0
  74. package/lib/spatial/spatial-encoding.d.ts +155 -0
  75. package/lib/spatial/spatial-expression.d.ts +144 -0
  76. package/lib/spatial/spatial-framing.d.ts +37 -0
  77. package/lib/spatial/spatial-heatmap.d.ts +111 -0
  78. package/lib/spatial/spatial-hover.d.ts +49 -0
  79. package/lib/spatial/spatial-sections.d.ts +59 -0
  80. package/lib/spatial/spatial-selection.d.ts +113 -0
  81. package/lib/spatial/spatial-volume-image.d.ts +67 -0
  82. package/lib/spatial/tsne-worker.d.ts +7 -0
  83. package/lib/spatial/world-grid.d.ts +58 -0
  84. package/lib/spatial-controls/spatial-charts/spatial-charts.component.d.ts +439 -0
  85. package/lib/spatial-controls/spatial-controls.component.d.ts +257 -0
  86. package/lib/store/spatial-selection.service.d.ts +27 -0
  87. package/lib/store/visualizer-store.service.d.ts +13 -0
  88. package/lib/toolbar/toolbar.component.d.ts +19 -3
  89. package/lib/visualization.module.d.ts +28 -24
  90. package/lib/visualizer.component.d.ts +73 -2
  91. package/package.json +7 -3
  92. package/src/lib/assets/spatial-omics-3d.svg +31 -0
  93. package/src/lib/assets/spatial-omics.svg +27 -0
@@ -0,0 +1,621 @@
1
+ import { Component, EventEmitter, Inject, Input, Output, ViewChild, } from '@angular/core';
2
+ import { Subscription, combineLatest } from 'rxjs';
3
+ import { VISUALIZER } from '../contracts/visualizer.contract';
4
+ import { SpatialChartsComponent } from './spatial-charts/spatial-charts.component';
5
+ import { DEFAULT_SPATIAL_VIEW, } from '../contracts/display-types';
6
+ import { SPATIAL_3D_MAX_CATEGORIES, spatialContinuousLut, } from '../spatial/spatial-encoding';
7
+ import { emptySelection, } from '../spatial/spatial-selection';
8
+ import { searchGeneNames } from '../spatial/gene-search';
9
+ import * as i0 from "@angular/core";
10
+ import * as i1 from "@angular/common";
11
+ import * as i2 from "@angular/forms";
12
+ import * as i3 from "primeng/api";
13
+ import * as i4 from "primeng/button";
14
+ import * as i5 from "primeng/tooltip";
15
+ import * as i6 from "primeng/dropdown";
16
+ import * as i7 from "primeng/slider";
17
+ import * as i8 from "primeng/treeselect";
18
+ import * as i9 from "primeng/dialog";
19
+ import * as i10 from "primeng/checkbox";
20
+ import * as i11 from "./spatial-charts/spatial-charts.component";
21
+ /** A gene name as a dropdown option. Objects rather than bare strings because the
22
+ * dropdown filters on a named field (`filterBy="label"`), which a string has not. */
23
+ const geneOption = (name) => ({
24
+ label: name,
25
+ value: name,
26
+ });
27
+ /** Per-instance id source — see {@link SpatialControlsComponent.chartsBodyId}. */
28
+ let controlsInstanceSeq = 0;
29
+ /** Outlier clipping presets, as `[lo, hi]` percentile fractions. */
30
+ const CLIP_OPTIONS = [
31
+ { label: 'None', value: [0, 1] },
32
+ { label: '1%', value: [0.01, 0.99] },
33
+ { label: '2%', value: [0.02, 0.98] },
34
+ { label: '5%', value: [0.05, 0.95] },
35
+ ];
36
+ /**
37
+ * Spatial-omics controls: a non-modal, resizable, draggable dialog for choosing
38
+ * what the observation markers are coloured by, and how they are drawn.
39
+ *
40
+ * Depends only on {@link ISpatialControls}, reached through the `VISUALIZER`
41
+ * contract token — never a concrete backend — mirroring how
42
+ * `ChannelHistogramComponent` depends only on `CHANNEL_HISTOGRAM_API`. When no
43
+ * host has bound `SPATIAL_DATA_PORT`, `getSpatialControls()` returns null and
44
+ * the dialog renders an explanatory empty state rather than dead controls.
45
+ *
46
+ * The legend swatches and the continuous colour bar are built with the SAME
47
+ * functions the renderer uses (`categoryColors` → `resolveCategoryColors`,
48
+ * `spatialContinuousLut`), so the key cannot drift from what is on screen.
49
+ */
50
+ export class SpatialControlsComponent {
51
+ /**
52
+ * Re-fit the chart after the dialog is resized.
53
+ *
54
+ * Plotly does not follow a container resize on its own — its `responsive`
55
+ * option listens for WINDOW resizes only, so dragging the dialog's handle
56
+ * reaches it through nothing. PrimeNG tells us when the drag ends, which is a
57
+ * far more direct signal than watching boxes, and firing once on release means
58
+ * no relayout churn during the drag.
59
+ */
60
+ onResizeEnd() {
61
+ this.charts?.resize();
62
+ }
63
+ constructor(viz) {
64
+ this.viz = viz;
65
+ /** Dialog visibility, two-way bound so a toolbar button can open it. */
66
+ this.visible = false;
67
+ /** Set while the 3D cloud is the active mode. Changes what the ROI selection
68
+ * MEANS — a screen-space lasso cutting through the cloud's full depth rather
69
+ * than a shape in tissue coordinates — so the hint below it says so. */
70
+ this.is3d = false;
71
+ this.visibleChange = new EventEmitter();
72
+ this.clipOptions = CLIP_OPTIONS;
73
+ /** Null when the host bound no `SPATIAL_DATA_PORT`. */
74
+ this.controls = null;
75
+ this.dataset = null;
76
+ this.view = { ...DEFAULT_SPATIAL_VIEW };
77
+ /** Colour-by column choices — "None" plus every column the dataset declares. */
78
+ this.columnOptions = [];
79
+ this.selectedColumn = null;
80
+ /** Virtual-scrolled only past this many options: the scroller earns its
81
+ * complexity for a few thousand names, and for eight it adds only overhead —
82
+ * a virtual viewport that short swallows the clicks it is meant to forward. */
83
+ this.geneVirtualScrollFrom = 200;
84
+ /**
85
+ * Gene picker: a filterable dropdown rather than a free-text typeahead, so the
86
+ * options are visible before anything is typed and each keystroke narrows a list
87
+ * the user can see.
88
+ *
89
+ * `geneOptions` is the full name list when the dataset inlines it (a targeted
90
+ * panel: 8 for the ABC demo, 300–5,000 for Xenium/CosMx). A whole-transcriptome
91
+ * dataset does not ship its ~31k names, so there the list is what the port's last
92
+ * search returned and filtering is server-side — same control either way.
93
+ */
94
+ this.geneOptions = [];
95
+ /**
96
+ * Every gene the dataset inlined, whether or not it is currently an option.
97
+ *
98
+ * Held as plain strings and searched per keystroke. A whole-transcriptome dataset
99
+ * inlines ~18k names — cheap to keep, ruinous to hand a dropdown all at once — so
100
+ * {@link geneOptions} is only ever the best few hundred of these.
101
+ */
102
+ this.geneNames = [];
103
+ this.selectedGene = null;
104
+ /** True when the options come from the port per keystroke rather than a resident
105
+ * list, which changes what an empty list means (nothing matched *yet*). */
106
+ this.genesAreRemote = false;
107
+ this.geneSearchFailed = false;
108
+ /** Categorical key, or null when the active colouring is continuous. */
109
+ this.legend = null;
110
+ /** CSS gradient for a continuous colouring, or null when categorical. */
111
+ this.colorBarCss = null;
112
+ /** Current selection — drives the count, the Clear button and the muting. */
113
+ this.selection = emptySelection();
114
+ /** The legend row whose category is currently selected, for highlighting. */
115
+ this.selectedCategory = null;
116
+ /** An ROI selection that matched nothing, so the UI can say so rather than
117
+ * looking like the button did nothing. */
118
+ this.selectionMissed = false;
119
+ /**
120
+ * Whether the Distribution section is expanded. Collapsed by default: the
121
+ * panel's primary job is the colour controls, and the chart roughly doubles
122
+ * its height.
123
+ */
124
+ this.chartsOpen = false;
125
+ /** Per-instance id for the collapsible body, so `aria-controls` points at one
126
+ * element and expanding the second panel cannot scroll the first one's chart. */
127
+ this.chartsBodyId = `sc-charts-body-${++controlsInstanceSeq}`;
128
+ /**
129
+ * The dataset's imaged section positions, or null when its z is continuous
130
+ * rather than sectioned. Read once per dataset — the scan walks the z of every
131
+ * observation, so it must not sit in a template getter.
132
+ */
133
+ this.sections = null;
134
+ /**
135
+ * Colormap tree for the continuous colour scale, and the node currently picked
136
+ * from it. The library's own `COLORMAP_OPTIONS`, shown with the same swatches
137
+ * the image's colormap picker uses — choosing a gradient is a visual decision,
138
+ * so a list of names would be the wrong control.
139
+ */
140
+ this.colormapOptions = [];
141
+ this.selectedColormapNode = null;
142
+ this.colormap = null;
143
+ this.reverse = false;
144
+ /** Guards the gene typeahead and the legend/colour-bar rebuild: both are async
145
+ * and both are driven by input the user changes faster than they resolve. */
146
+ this.geneSearchToken = 0;
147
+ this.keyToken = 0;
148
+ this.subs = new Subscription();
149
+ /** The ceiling itself, for the message. */
150
+ this.cloudPaletteLimit = SPATIAL_3D_MAX_CATEGORIES;
151
+ }
152
+ ngOnInit() {
153
+ this.controls = this.viz.getSpatialControls?.() ?? null;
154
+ this.colormapOptions = this.viz.getColormapOptions?.() ?? [];
155
+ if (!this.controls)
156
+ return;
157
+ this.subs.add(this.controls.getDataset$().subscribe((dataset) => {
158
+ this.dataset = dataset;
159
+ this.columnOptions = [
160
+ { label: 'None (flat colour)', value: null },
161
+ ...(dataset?.columns ?? []).map((c) => ({ label: this.columnLabel(c), value: c.name })),
162
+ ];
163
+ // A new dataset almost certainly has different columns; drop stale UI state.
164
+ this.selectedGene = null;
165
+ this.geneSearchFailed = false;
166
+ const names = dataset?.features?.names;
167
+ this.genesAreRemote = !!dataset?.features && !names;
168
+ this.geneNames = names ? [...names] : [];
169
+ // The head of the list, not all of it: see `geneNames`.
170
+ this.geneOptions = searchGeneNames(this.geneNames, '').map(geneOption);
171
+ this.sections = this.controls?.sampledSections() ?? null;
172
+ void this.refreshKey();
173
+ }));
174
+ this.subs.add(this.controls.getViewState$().subscribe((view) => {
175
+ this.view = view;
176
+ this.selectedColumn = view.colorBy?.kind === 'column' ? view.colorBy.name : null;
177
+ this.selectedGene = view.colorBy?.kind === 'feature' ? view.colorBy.name : null;
178
+ this.selectedColormapNode = this.colormapNodeFor(view.continuousColormap);
179
+ void this.refreshKey();
180
+ }));
181
+ this.subs.add(this.controls.getSelection$().subscribe((selection) => {
182
+ this.selection = selection;
183
+ if (selection.count === 0)
184
+ this.selectedCategory = null;
185
+ }));
186
+ // The colour bar must use the colormap the renderer is using.
187
+ this.subs.add(combineLatest([this.viz.getColormap(), this.viz.getReverseScale()])
188
+ .subscribe(([colormap, reverse]) => {
189
+ this.colormap = colormap ?? null;
190
+ this.reverse = !!reverse;
191
+ void this.refreshKey();
192
+ }));
193
+ }
194
+ ngOnDestroy() {
195
+ this.subs.unsubscribe();
196
+ }
197
+ onVisibleChange(value) {
198
+ this.visible = value;
199
+ this.visibleChange.emit(value);
200
+ }
201
+ // ── colour source ───────────────────────────────────────────────────────
202
+ /** Column dropdown. Choosing a column supersedes any gene selection. */
203
+ onColumn(name) {
204
+ this.selectedColumn = name;
205
+ if (!name) {
206
+ this.controls?.clearColorBy();
207
+ this.selectedGene = null;
208
+ return;
209
+ }
210
+ this.selectedGene = null;
211
+ this.controls?.colorByColumn(name);
212
+ }
213
+ /**
214
+ * A keystroke in the gene dropdown's filter box.
215
+ *
216
+ * With the names resident the dropdown filters them itself and this only clears a
217
+ * stale failure. Without them there is nothing to filter, so the query goes to the
218
+ * port and its answer BECOMES the option list — the same control, filtering one
219
+ * hop further away.
220
+ */
221
+ async onGeneFilter(query) {
222
+ this.geneSearchFailed = false;
223
+ if (!this.genesAreRemote) {
224
+ // Resident names: search them here and materialise only the top matches. The
225
+ // dropdown's own filter then runs over those and agrees — they were chosen by the
226
+ // same query — so the control behaves as if it still held the whole list.
227
+ this.geneOptions = searchGeneNames(this.geneNames, query).map(geneOption);
228
+ return;
229
+ }
230
+ if (!this.controls)
231
+ return;
232
+ // Typing outruns the lookup, so a slow answer for an earlier query would
233
+ // replace the options for the text now in the box — including a failure, which
234
+ // would wrongly mark the current query as failed.
235
+ const mine = ++this.geneSearchToken;
236
+ if (!query) {
237
+ this.geneOptions = [];
238
+ return;
239
+ }
240
+ try {
241
+ const names = await this.controls.searchFeatures(query, 50);
242
+ if (mine !== this.geneSearchToken)
243
+ return;
244
+ this.geneOptions = names.map(geneOption);
245
+ }
246
+ catch {
247
+ if (mine !== this.geneSearchToken)
248
+ return;
249
+ // A failed lookup must not wedge the control — show none and say so.
250
+ this.geneOptions = [];
251
+ this.geneSearchFailed = true;
252
+ }
253
+ }
254
+ /** A gene was picked; it supersedes any column selection. */
255
+ onGene(name) {
256
+ this.selectedGene = name;
257
+ if (!name) {
258
+ this.controls?.clearColorBy();
259
+ return;
260
+ }
261
+ this.selectedColumn = null;
262
+ this.controls?.colorByFeature(name);
263
+ }
264
+ // ── selection ───────────────────────────────────────────────────────────
265
+ /** Select every observation inside the drawn regions (their union). */
266
+ selectFromRegions() {
267
+ const count = this.controls?.selectFromRegions() ?? 0;
268
+ this.selectedCategory = null;
269
+ this.selectionMissed = count === 0;
270
+ }
271
+ /** Legend click: select one category. Clicking the active row clears it, so a
272
+ * second click is an undo rather than a no-op. */
273
+ async selectCategory(index) {
274
+ const by = this.view.colorBy;
275
+ if (!this.controls || by?.kind !== 'column')
276
+ return;
277
+ if (this.selectedCategory === index) {
278
+ this.clearSelection();
279
+ return;
280
+ }
281
+ try {
282
+ await this.controls.selectCategory(by.name, index);
283
+ this.selectedCategory = index;
284
+ this.selectionMissed = false;
285
+ }
286
+ catch {
287
+ this.selectedCategory = null;
288
+ }
289
+ }
290
+ /**
291
+ * Expand or collapse the distribution section.
292
+ *
293
+ * Expanding SCROLLS it into view, because the panel is taller than the viewport
294
+ * once this section is open and the chart is its last row: expanding it drew a
295
+ * chart ~300 px below the fold, which reads as the section being empty. The
296
+ * scroll is deferred a task so the chart's own deferred first draw has given the
297
+ * body its height.
298
+ */
299
+ toggleCharts() {
300
+ this.chartsOpen = !this.chartsOpen;
301
+ if (!this.chartsOpen)
302
+ return;
303
+ setTimeout(() => {
304
+ document.getElementById(this.chartsBodyId)
305
+ ?.scrollIntoView({ block: 'end', behavior: 'smooth' });
306
+ }, 0);
307
+ }
308
+ clearSelection() {
309
+ this.controls?.clearSelection();
310
+ this.selectedCategory = null;
311
+ this.selectionMissed = false;
312
+ }
313
+ /** True while a selection is active — everything else renders muted. */
314
+ get hasSelection() {
315
+ return this.selection.count > 0;
316
+ }
317
+ // ── display ─────────────────────────────────────────────────────────────
318
+ // PrimeNG's slider reports `number | undefined`; ignore the empty case rather
319
+ // than writing `undefined` into the store and rendering NaN-sized markers.
320
+ onPointScale(value) {
321
+ if (value === undefined)
322
+ return;
323
+ this.controls?.setViewState({ pointScale: value });
324
+ }
325
+ onOpacity(value) {
326
+ if (value === undefined)
327
+ return;
328
+ this.controls?.setViewState({ opacity: value });
329
+ }
330
+ onLogScale(on) {
331
+ this.controls?.setViewState({ logScale: on });
332
+ }
333
+ /**
334
+ * Set when the active categorical colouring has more categories than the 3D
335
+ * cloud can keep apart, so the points are drawing FLAT.
336
+ *
337
+ * Said in the panel rather than left to a console warning: `subclass` (338) is
338
+ * served precisely because the density volumes and the 2D view can render it, and
339
+ * a user who picks it in the cloud and sees one colour deserves to know both why
340
+ * and what to do instead.
341
+ */
342
+ get exceedsCloudPalette() {
343
+ return this.is3d && (this.legend?.length ?? 0) > SPATIAL_3D_MAX_CATEGORIES;
344
+ }
345
+ onGeneMap(on) {
346
+ this.controls?.setViewState({ geneMap: on });
347
+ }
348
+ /** 3D: smooth the per-section sheets along z into a continuous volume. */
349
+ onGeneMapVolume(on) {
350
+ this.controls?.setViewState({ geneMapVolume: on });
351
+ }
352
+ /** 3D: restrict the sheets to one imaged section. */
353
+ onGeneMapOneSection(on) {
354
+ if (!on) {
355
+ this.controls?.setViewState({ geneMapSection: null });
356
+ return;
357
+ }
358
+ const middle = this.sections ? Math.floor((this.sections.length - 1) / 2) : 0;
359
+ this.controls?.setViewState({ geneMapSection: middle });
360
+ }
361
+ onGeneMapSection(value) {
362
+ if (value === undefined)
363
+ return;
364
+ this.controls?.setViewState({ geneMapSection: value });
365
+ }
366
+ get geneMapOneSection() {
367
+ return this.view.geneMapSection != null;
368
+ }
369
+ /** "12 of 53" for the gene map's own section, 1-based like the cloud's. */
370
+ get geneMapSectionLabel() {
371
+ const total = this.sections?.length ?? 0;
372
+ if (!total)
373
+ return '';
374
+ const at = Math.max(0, Math.min(total - 1, this.view.geneMapSection ?? 0));
375
+ return `${at + 1} of ${total}`;
376
+ }
377
+ onGeneMapSmoothing(value) {
378
+ if (value === undefined)
379
+ return;
380
+ this.controls?.setViewState({ geneMapSmoothing: value });
381
+ }
382
+ onGeneMapOpacity(value) {
383
+ if (value === undefined)
384
+ return;
385
+ this.controls?.setViewState({ geneMapOpacity: value });
386
+ }
387
+ /**
388
+ * What the 3D gene map is currently showing, said plainly — the sheets are a
389
+ * measurement and the volume is an estimate, and the panel has to be the place
390
+ * that says which one is on screen.
391
+ */
392
+ get geneMapVolumeNote() {
393
+ const total = this.sections?.length ?? 0;
394
+ if (this.view.geneMapVolume) {
395
+ return 'Interpolated along z: the planes between the imaged sections carry an '
396
+ + 'ESTIMATE, smoothed from their neighbours\' mean. Nothing is drawn beyond the '
397
+ + 'outermost section.';
398
+ }
399
+ if (this.geneMapOneSection) {
400
+ return 'One imaged section\'s field — measured, not interpolated. Hide the '
401
+ + 'observations to read it, or leave them on to check the field against them.';
402
+ }
403
+ return `One field per imaged section${total ? ` (${total})` : ''}, at its own depth, `
404
+ + 'with the gaps between sections empty. Kernel-weighted mean per cell, not a sum.';
405
+ }
406
+ /** True while a gene is the colour source — the only thing a gene map can map. */
407
+ get canMapGene() {
408
+ return this.view.colorBy?.kind === 'feature';
409
+ }
410
+ // ── what the 3D scene draws ─────────────────────────────────────────────
411
+ // The volume, the cloud and the density volumes share one space, so each one
412
+ // hides the others to some degree. Independent toggles because the useful views
413
+ // are the combinations, not a single "3D mode".
414
+ onShowVolume(on) {
415
+ this.controls?.setViewState({ showVolume: on });
416
+ }
417
+ onVolumeOpacity(value) {
418
+ if (value === undefined)
419
+ return;
420
+ this.controls?.setViewState({ volumeOpacity: value });
421
+ }
422
+ onShowPoints(on) {
423
+ this.controls?.setViewState({ showPoints: on });
424
+ }
425
+ /** The "one section at a time" switch: null restores the whole stack. */
426
+ onOneSection(on) {
427
+ if (!on) {
428
+ this.controls?.setViewState({ pointSection: null });
429
+ return;
430
+ }
431
+ // Open in the middle of the stack rather than on the first section, which for
432
+ // a brain is a nearly empty olfactory-bulb slide.
433
+ const middle = this.sections ? Math.floor((this.sections.length - 1) / 2) : 0;
434
+ this.controls?.setViewState({ pointSection: middle });
435
+ }
436
+ onPointSection(value) {
437
+ if (value === undefined)
438
+ return;
439
+ this.controls?.setViewState({ pointSection: value });
440
+ }
441
+ /** True while the cloud is restricted to a single section. */
442
+ get oneSection() {
443
+ return this.view.pointSection != null;
444
+ }
445
+ /** Highest section index the slider can reach. */
446
+ get lastSection() {
447
+ return Math.max(0, (this.sections?.length ?? 1) - 1);
448
+ }
449
+ /** "12 of 53" — 1-based, because the sections are slides, not array slots. */
450
+ get sectionLabel() {
451
+ const total = this.sections?.length ?? 0;
452
+ if (!total)
453
+ return '';
454
+ const at = Math.max(0, Math.min(total - 1, this.view.pointSection ?? 0));
455
+ return `${at + 1} of ${total}`;
456
+ }
457
+ /** Whether this dataset has sections to pick from at all. */
458
+ get isSectioned() {
459
+ return (this.sections?.length ?? 0) > 1;
460
+ }
461
+ onDensityVolume(on) {
462
+ this.controls?.setViewState({ densityVolume: on });
463
+ }
464
+ onDensitySmoothing(value) {
465
+ if (value === undefined)
466
+ return;
467
+ this.controls?.setViewState({ densitySmoothing: value });
468
+ }
469
+ /** What the density volumes are actually showing, said plainly — an estimate is
470
+ * only honest if the reader knows it is one, and which clusters are in view. */
471
+ get densityNote() {
472
+ const capped = `the ${SpatialControlsComponent.DENSITY_MAX_CLUSTERS} largest clusters`;
473
+ const what = this.legend ? capped : this.hasSelection ? 'the selected cells' : 'all cells';
474
+ return `Density estimate over ${what} — smoothed between the imaged sections, `
475
+ + 'not measured cells. Lower Opacity to read the fields under the cloud.';
476
+ }
477
+ /** Mirrors the renderer's cap, for the note only. */
478
+ static { this.DENSITY_MAX_CLUSTERS = 6; }
479
+ onClip(value) {
480
+ this.controls?.setViewState({ percentileClip: value });
481
+ }
482
+ reset() {
483
+ this.controls?.setViewState({ ...DEFAULT_SPATIAL_VIEW });
484
+ this.clearSelection();
485
+ this.selectedColumn = null;
486
+ this.selectedGene = null;
487
+ }
488
+ /**
489
+ * Description of the active column, when it has one. Surfaced because a
490
+ * DERIVED column (k-means clusters, QC totals computed at conversion) must not
491
+ * read as though it came with the data.
492
+ */
493
+ get activeDescription() {
494
+ const by = this.view.colorBy;
495
+ if (!by || by.kind !== 'column')
496
+ return null;
497
+ return this.dataset?.columns.find((c) => c.name === by.name)?.description ?? null;
498
+ }
499
+ /** Label for the current colouring, for the key's heading. */
500
+ get colorByLabel() {
501
+ const by = this.view.colorBy;
502
+ if (!by)
503
+ return 'Flat colour';
504
+ return by.kind === 'feature' ? `Gene · ${by.name}` : by.name;
505
+ }
506
+ /** True when the active colouring is a categorical column (drives the key). */
507
+ get isCategorical() {
508
+ return this.legend !== null;
509
+ }
510
+ /** Whether log/clip apply — they are continuous-only knobs. */
511
+ get isContinuous() {
512
+ return !!this.view.colorBy && this.legend === null;
513
+ }
514
+ // ── internals ───────────────────────────────────────────────────────────
515
+ columnLabel(c) {
516
+ const kind = c.kind === 'categorical'
517
+ ? `${c.categories.length} categories`
518
+ : c.unit ?? 'continuous';
519
+ return `${c.name} — ${kind}`;
520
+ }
521
+ /** Rebuild the legend or colour bar for the current colouring. */
522
+ async refreshKey() {
523
+ const by = this.view.colorBy;
524
+ if (!by || !this.controls) {
525
+ this.legend = null;
526
+ this.colorBarCss = null;
527
+ return;
528
+ }
529
+ const meta = by.kind === 'column'
530
+ ? this.dataset?.columns.find((c) => c.name === by.name)
531
+ : undefined;
532
+ const mine = ++this.keyToken;
533
+ if (meta?.kind === 'categorical') {
534
+ try {
535
+ const colors = await this.controls.categoryColors(by.name);
536
+ // Same race, same cost if it is lost: a slower earlier column would paint
537
+ // its palette into the legend for the column now selected.
538
+ if (mine !== this.keyToken)
539
+ return;
540
+ this.legend = meta.categories.map((label, i) => ({ label, color: colors[i] }));
541
+ this.colorBarCss = null;
542
+ }
543
+ catch {
544
+ if (mine !== this.keyToken)
545
+ return;
546
+ // The column's values may not have loaded yet; leave the key empty
547
+ // rather than showing a legend that might not match the render.
548
+ this.legend = null;
549
+ this.colorBarCss = null;
550
+ }
551
+ return;
552
+ }
553
+ // Continuous (a numeric column or a gene): a colour bar from the same LUT.
554
+ this.legend = null;
555
+ this.colorBarCss = this.buildColorBar();
556
+ }
557
+ /**
558
+ * The continuous colour scale's colormap. Clearing it goes back to following
559
+ * the image's, which is the default.
560
+ */
561
+ onContinuousColormap(node) {
562
+ this.selectedColormapNode = node;
563
+ // The value is a ColormapValue, which is a NAME for the built-in scales and an
564
+ // inline `[stop, colour]` array for the rest — half the library's colormaps
565
+ // are the array kind, so anything that only accepts a string silently drops
566
+ // them. A group row carries no value and clears the setting.
567
+ this.controls?.setViewState({ continuousColormap: node?.data?.value ?? null });
568
+ }
569
+ /** The tree node holding a colormap value, so the picker shows what is in use. */
570
+ colormapNodeFor(value) {
571
+ if (!value)
572
+ return null;
573
+ for (const group of this.colormapOptions) {
574
+ for (const node of group.children ?? []) {
575
+ // Reference equality: the value came out of this same tree, and an inline
576
+ // scale is a 256-entry array not worth comparing element by element.
577
+ if (node.data?.value === value)
578
+ return node;
579
+ }
580
+ if (group.data?.value === value)
581
+ return group;
582
+ }
583
+ return null;
584
+ }
585
+ /**
586
+ * `linear-gradient(...)` sampling the active colormap at 16 stops.
587
+ *
588
+ * Resolved exactly the way the renderer resolves it — the same override, the
589
+ * same grey fallback. Built from `lutFor` alone, this bar showed a black-to-white
590
+ * ramp while the canvas drew Viridis, which makes the key worse than no key.
591
+ */
592
+ buildColorBar() {
593
+ const lut = spatialContinuousLut(this.colormap?.data?.value, this.reverse, this.view.continuousColormap);
594
+ const stops = [];
595
+ const steps = 16;
596
+ for (let i = 0; i <= steps; i++) {
597
+ const [r, g, b] = lut[Math.round((i / steps) * 255)];
598
+ stops.push(`rgb(${r},${g},${b}) ${((i / steps) * 100).toFixed(0)}%`);
599
+ }
600
+ return `linear-gradient(to right, ${stops.join(', ')})`;
601
+ }
602
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: SpatialControlsComponent, deps: [{ token: VISUALIZER }], target: i0.ɵɵFactoryTarget.Component }); }
603
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.9", type: SpatialControlsComponent, selector: "spatial-controls", inputs: { visible: "visible", is3d: "is3d" }, outputs: { visibleChange: "visibleChange" }, viewQueries: [{ propertyName: "charts", first: true, predicate: SpatialChartsComponent, descendants: true }], ngImport: i0, template: "<p-dialog header=\"Spatial omics\"\n [visible]=\"visible\" (visibleChange)=\"onVisibleChange($event)\"\n [modal]=\"false\" [resizable]=\"true\" [draggable]=\"true\"\n position=\"left\"\n (onResizeEnd)=\"onResizeEnd()\"\n [style]=\"{ width: '30rem' }\"\n [breakpoints]=\"{ '960px': '90vw' }\"\n styleClass=\"spatial-controls-dialog\">\n\n <!-- No port bound: say why rather than showing controls that do nothing. -->\n <div class=\"sc-empty\" *ngIf=\"!controls\">\n No spatial-omics data source is configured. A host provides one by binding\n <code>SPATIAL_DATA_PORT</code>.\n </div>\n\n <div class=\"sc-empty\" *ngIf=\"controls && !dataset\">\n No dataset is loaded.\n </div>\n\n <div class=\"sc-body\" *ngIf=\"controls && dataset as ds\">\n\n <div class=\"sc-dataset\">\n <span class=\"sc-dataset-name\">{{ ds.name }}</span>\n <span class=\"sc-dataset-count\">{{ ds.observations.count | number }} observations</span>\n </div>\n\n <!-- Colour source. Column and gene are mutually exclusive: picking one\n clears the other, so what drives the colours is never ambiguous. -->\n <div class=\"sc-section\">\n <label class=\"sc-lbl\" for=\"sc-column\">Colour by column</label>\n <p-dropdown inputId=\"sc-column\" [options]=\"columnOptions\"\n optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"selectedColumn\" (ngModelChange)=\"onColumn($event)\"\n placeholder=\"None (flat colour)\" appendTo=\"body\"\n styleClass=\"sc-full\"></p-dropdown>\n </div>\n\n <div class=\"sc-section\" *ngIf=\"ds.features\">\n <label class=\"sc-lbl\" for=\"sc-gene\">Colour by gene</label>\n <!-- A filterable dropdown, not a free-text box: the genes are visible before\n anything is typed, and each keystroke narrows a list the user can see.\n Virtual-scrolled because a targeted panel runs to a few thousand names. -->\n <p-dropdown inputId=\"sc-gene\" [options]=\"geneOptions\"\n optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"selectedGene\" (ngModelChange)=\"onGene($event)\"\n [filter]=\"true\" filterBy=\"label\" [resetFilterOnHide]=\"true\"\n (onFilter)=\"onGeneFilter($event.filter)\"\n [showClear]=\"true\"\n [virtualScroll]=\"geneOptions.length > geneVirtualScrollFrom\"\n [virtualScrollItemSize]=\"30\"\n [emptyFilterMessage]=\"genesAreRemote ? 'No match \u2014 keep typing' : 'No match'\"\n [emptyMessage]=\"genesAreRemote ? 'Type to search genes' : 'No genes'\"\n appendTo=\"body\"\n placeholder=\"Search {{ ds.features.count | number }} genes\u2026\"\n styleClass=\"sc-full\"></p-dropdown>\n <small class=\"sc-hint\" *ngIf=\"geneSearchFailed\">Gene lookup failed.</small>\n </div>\n\n <!-- Key: categorical legend or a continuous colour bar, built from the same\n functions the renderer uses so it cannot disagree with the canvas. -->\n <div class=\"sc-section sc-key\" *ngIf=\"view.colorBy\">\n <span class=\"sc-lbl\">{{ colorByLabel }}</span>\n <!-- Derived columns say so, so they are not mistaken for measured data. -->\n <small class=\"sc-hint\" *ngIf=\"activeDescription\">{{ activeDescription }}</small>\n\n <!-- Legend rows are buttons: clicking one selects that category, clicking\n it again clears \u2014 so a click is always reversible. -->\n <ul class=\"sc-legend\" *ngIf=\"isCategorical\">\n <li *ngFor=\"let entry of legend; index as i\">\n <button type=\"button\" class=\"sc-legend-btn\"\n [class.selected]=\"selectedCategory === i\"\n (click)=\"selectCategory(i)\"\n [title]=\"'Select ' + entry.label\">\n <span class=\"sc-swatch\" [style.background]=\"entry.color\"></span>\n <span class=\"sc-legend-label\">{{ entry.label }}</span>\n </button>\n </li>\n </ul>\n\n <div *ngIf=\"isContinuous\">\n <div class=\"sc-colorbar\" [style.background]=\"colorBarCss\"></div>\n <div class=\"sc-colorbar-ends\">\n <span>low</span>\n <span class=\"sc-hint\">window from percentiles</span>\n <span>high</span>\n </div>\n <!-- The gradient this bar and the map are drawn with. Sits under the bar\n it controls, with the same swatch previews the image's colormap picker\n uses: which gradient reads best is a visual judgement, so a list of\n names would be the wrong control. Cleared = follow the image's. -->\n <div class=\"sc-row\">\n <span class=\"sc-lbl\">Colormap</span>\n <p-treeSelect [options]=\"colormapOptions\" [ngModel]=\"selectedColormapNode\"\n selectionMode=\"single\" [showClear]=\"true\"\n placeholder=\"Match the image\" [scrollHeight]=\"'260px'\"\n appendTo=\"body\"\n pTooltip=\"Colour scale for gene and numeric values \u2014 the markers, the gene map and this bar. Clear it to follow the image's colormap\"\n tooltipPosition=\"top\"\n (onNodeSelect)=\"onContinuousColormap($event.node)\"\n (onClear)=\"onContinuousColormap(null)\">\n <ng-template let-node pTemplate=\"default\">\n <span class=\"sc-cmap-option\">\n <img *ngIf=\"!node.children && node.data?.src\" [src]=\"node.data.src\"\n class=\"sc-cmap-swatch\" alt=\"\" />\n <span>{{ node.label }}</span>\n </span>\n </ng-template>\n </p-treeSelect>\n <span class=\"sc-val\"></span>\n </div>\n </div>\n </div>\n\n <!-- Display -->\n <div class=\"sc-section\">\n <div class=\"sc-row\">\n <span class=\"sc-lbl\">Point size</span>\n <p-slider [ngModel]=\"view.pointScale\" [min]=\"0.25\" [max]=\"5\" [step]=\"0.25\"\n (onChange)=\"onPointScale($event.value)\"></p-slider>\n <span class=\"sc-val\">{{ view.pointScale }}\u00D7</span>\n </div>\n <div class=\"sc-row\">\n <span class=\"sc-lbl\">Opacity</span>\n <p-slider [ngModel]=\"view.opacity\" [min]=\"0.1\" [max]=\"1\" [step]=\"0.05\"\n (onChange)=\"onOpacity($event.value)\"></p-slider>\n <span class=\"sc-val\">{{ view.opacity }}</span>\n </div>\n </div>\n\n <!-- Only for a gene: the map draws that gene's expression as a field. In 2D\n it goes under the cells of the displayed plane; in 3D it is one sheet per\n imaged section, optionally smoothed along z into a volume. -->\n <div class=\"sc-section\" *ngIf=\"canMapGene\">\n <div class=\"sc-row\">\n <p-checkbox [binary]=\"true\" [ngModel]=\"view.geneMap\"\n (onChange)=\"onGeneMap($event.checked)\"\n label=\"Gene map (expression field)\"\n [pTooltip]=\"is3d\n ? 'Draw this gene\\'s expression as one field per imaged section \u2014 an estimate between the cells, empty where nothing was measured'\n : 'Draw this gene\\'s expression as a smooth field UNDER the cells \u2014 an estimate between them, transparent where nothing was measured'\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n <!-- 3D only: interpolating along z turns the measured sheets into an\n estimate, so it is its own decision and its own label. -->\n <div class=\"sc-row\" *ngIf=\"view.geneMap && is3d\">\n <p-checkbox [binary]=\"true\" [ngModel]=\"view.geneMapVolume\"\n (onChange)=\"onGeneMapVolume($event.checked)\"\n label=\"Volume rendering (interpolate along z)\"\n pTooltip=\"Smooth the per-section fields into a continuous volume \u2014 an ESTIMATE between the imaged sections, not measured tissue\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n <!-- Meaningless while interpolating: a volume built from one section would\n smear that slide through the specimen's whole depth. -->\n <div class=\"sc-row\" *ngIf=\"view.geneMap && is3d && isSectioned && !view.geneMapVolume\">\n <p-checkbox [binary]=\"true\" [ngModel]=\"geneMapOneSection\"\n (onChange)=\"onGeneMapOneSection($event.checked)\"\n label=\"One map section at a time\"\n pTooltip=\"Draw a single imaged section's field instead of the whole stack of sheets\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n <div class=\"sc-row\"\n *ngIf=\"view.geneMap && is3d && isSectioned && !view.geneMapVolume && geneMapOneSection\">\n <span class=\"sc-lbl\">Map section</span>\n <p-slider [ngModel]=\"view.geneMapSection\" [min]=\"0\" [max]=\"lastSection\" [step]=\"1\"\n (onChange)=\"onGeneMapSection($event.value)\"></p-slider>\n <span class=\"sc-val\">{{ geneMapSectionLabel }}</span>\n </div>\n <div class=\"sc-row\" *ngIf=\"view.geneMap\">\n <span class=\"sc-lbl\">Smoothing</span>\n <p-slider [ngModel]=\"view.geneMapSmoothing\" [min]=\"0.5\" [max]=\"4\" [step]=\"0.5\"\n (onChange)=\"onGeneMapSmoothing($event.value)\"></p-slider>\n <span class=\"sc-val\">{{ view.geneMapSmoothing }}\u00D7</span>\n </div>\n <div class=\"sc-row\" *ngIf=\"view.geneMap\">\n <span class=\"sc-lbl\">Map opacity</span>\n <p-slider [ngModel]=\"view.geneMapOpacity\" [min]=\"0.1\" [max]=\"1\" [step]=\"0.05\"\n (onChange)=\"onGeneMapOpacity($event.value)\"></p-slider>\n <span class=\"sc-val\">{{ view.geneMapOpacity }}</span>\n </div>\n <div class=\"sc-hint\" *ngIf=\"view.geneMap && !is3d\">\n Kernel-weighted mean per cell, not a sum \u2014 so a crowded region does not glow\n for being crowded. Transparent where no cell was measured. Turn the cells'\n own Opacity down to read the field under them.\n </div>\n <div class=\"sc-hint\" *ngIf=\"view.geneMap && is3d\">\n {{ geneMapVolumeNote }}\n </div>\n </div>\n\n <!-- 3D only: the volume, the cloud and the density volumes occupy the same\n space, so each hides the others. Independent toggles, because the useful\n views are the combinations \u2014 the field alone, one section's cells over it,\n the anatomy alone. -->\n <div class=\"sc-section\" *ngIf=\"is3d\">\n <span class=\"sc-lbl\">Show</span>\n <div class=\"sc-row\" *ngIf=\"ds.volume\">\n <p-checkbox [binary]=\"true\" [ngModel]=\"view.showVolume\"\n (onChange)=\"onShowVolume($event.checked)\"\n label=\"Reference volume\"\n pTooltip=\"The registered anatomy the observations sit in\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n <!-- The backdrop's own opacity: reading the cloud or a density field\n through the anatomy means turning the anatomy down, not the data. -->\n <div class=\"sc-row\" *ngIf=\"ds.volume && view.showVolume\">\n <span class=\"sc-lbl\">Volume opacity</span>\n <p-slider [ngModel]=\"view.volumeOpacity\" [min]=\"0.05\" [max]=\"1\" [step]=\"0.05\"\n (onChange)=\"onVolumeOpacity($event.value)\"></p-slider>\n <span class=\"sc-val\">{{ view.volumeOpacity }}</span>\n </div>\n <div class=\"sc-row\">\n <p-checkbox [binary]=\"true\" [ngModel]=\"view.showPoints\"\n (onChange)=\"onShowPoints($event.checked)\"\n label=\"Observations\"\n pTooltip=\"The measured cells. Hide them to read the density volumes underneath\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n <!-- Only for data that really is sectioned; a continuous z has no sections\n to step through. -->\n <div class=\"sc-row\" *ngIf=\"view.showPoints && isSectioned\">\n <p-checkbox [binary]=\"true\" [ngModel]=\"oneSection\"\n (onChange)=\"onOneSection($event.checked)\"\n label=\"One section at a time\"\n pTooltip=\"Draw a single imaged section's cells, so the density volumes between the sections stay visible\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n <div class=\"sc-row\" *ngIf=\"view.showPoints && isSectioned && oneSection\">\n <span class=\"sc-lbl\">Section</span>\n <p-slider [ngModel]=\"view.pointSection\" [min]=\"0\" [max]=\"lastSection\" [step]=\"1\"\n (onChange)=\"onPointSection($event.value)\"></p-slider>\n <span class=\"sc-val\">{{ sectionLabel }}</span>\n </div>\n </div>\n\n <div class=\"sc-section\" *ngIf=\"is3d\">\n <div class=\"sc-hint\" *ngIf=\"exceedsCloudPalette\">\n {{ legend?.length }} categories is past the {{ cloudPaletteLimit }} the 3D cloud's\n colour LUT can keep apart, so the points draw flat for this column. The density\n volumes below render it, and so does the 2D Spatial omics view.\n </div>\n <div class=\"sc-row\">\n <p-checkbox [binary]=\"true\" [ngModel]=\"view.densityVolume\"\n (onChange)=\"onDensityVolume($event.checked)\"\n label=\"Cluster density volumes\"\n pTooltip=\"Raymarch each cluster as a smooth density field \u2014 an ESTIMATE between the imaged sections, not measured cells\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n <div class=\"sc-row\" *ngIf=\"view.densityVolume\">\n <span class=\"sc-lbl\">Smoothing</span>\n <p-slider [ngModel]=\"view.densitySmoothing\" [min]=\"0.5\" [max]=\"4\" [step]=\"0.5\"\n (onChange)=\"onDensitySmoothing($event.value)\"></p-slider>\n <span class=\"sc-val\">{{ view.densitySmoothing }}\u00D7</span>\n </div>\n <div class=\"sc-hint\" *ngIf=\"view.densityVolume\">{{ densityNote }}</div>\n </div>\n\n <!-- Continuous-only knobs: meaningless for a categorical palette. -->\n <div class=\"sc-section\" *ngIf=\"isContinuous\">\n <div class=\"sc-row\">\n <p-checkbox [binary]=\"true\" [ngModel]=\"view.logScale\"\n (onChange)=\"onLogScale($event.checked)\"\n label=\"Log scale\"\n pTooltip=\"Counts collapse against their outliers on a linear ramp\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n <div class=\"sc-row\">\n <span class=\"sc-lbl\">Clip outliers</span>\n <p-dropdown [options]=\"clipOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"view.percentileClip\" (ngModelChange)=\"onClip($event)\"\n appendTo=\"body\"\n pTooltip=\"Percentile the contrast window is taken at\"\n tooltipPosition=\"top\"></p-dropdown>\n <!-- Reserves the readout column this row has no readout for, so the\n control's right edge lines up with every slider's. -->\n <span class=\"sc-val\"></span>\n </div>\n </div>\n\n <!-- Selection. Regions come from the existing ROI tools (rectangle, polygon,\n freehand, wand, brush), so there is no separate marquee to learn. -->\n <div class=\"sc-section sc-selection\">\n <div class=\"sc-row\">\n <p-button label=\"Select from ROIs\" icon=\"pi pi-object-group\" size=\"small\"\n [outlined]=\"true\" (onClick)=\"selectFromRegions()\"\n pTooltip=\"Select every observation inside the drawn regions\"\n tooltipPosition=\"top\"></p-button>\n <p-button *ngIf=\"hasSelection\" label=\"Clear\" size=\"small\" [text]=\"true\"\n (onClick)=\"clearSelection()\"></p-button>\n </div>\n <small class=\"sc-hint\" *ngIf=\"hasSelection\">\n {{ selection.count | number }} of {{ ds.observations.count | number }} selected \u2014\n the rest are muted.\n </small>\n <small class=\"sc-hint\" *ngIf=\"selectionMissed\">\n No observations fell inside the drawn regions.\n </small>\n <!-- A lasso on a 2D screen cannot pick a depth, so say what it does pick\n rather than letting the count look like a mistake. -->\n <small class=\"sc-hint\" *ngIf=\"is3d\">\n In 3D a region cuts through the cloud's full depth. Orbit and draw again\n to cut from another angle.\n </small>\n </div>\n\n <!-- Distributions of whatever the map is coloured by. In the SAME dialog as\n the colour controls on purpose: changing the gene and watching the\n distribution move is one action, and two floating windows made that\n harder to see rather than easier. -->\n <div class=\"sc-section sc-charts\">\n <button type=\"button\" class=\"sc-disclosure\" (click)=\"toggleCharts()\"\n [attr.aria-expanded]=\"chartsOpen\" [attr.aria-controls]=\"chartsBodyId\">\n <i class=\"pi\" [class.pi-chevron-right]=\"!chartsOpen\"\n [class.pi-chevron-down]=\"chartsOpen\"></i>\n <span>Distribution</span>\n <small *ngIf=\"!chartsOpen && view.colorBy\">of {{ colorByLabel }}</small>\n </button>\n <!-- Kept MOUNTED when collapsed, only hidden: destroying it would throw\n away the chart kind and grouping the user picked. `active` is what\n stops it drawing while it has no size. -->\n <div [id]=\"chartsBodyId\" [hidden]=\"!chartsOpen\">\n <spatial-charts [active]=\"visible && chartsOpen\"></spatial-charts>\n </div>\n </div>\n\n <div class=\"sc-btns\">\n <p-button label=\"Reset\" size=\"small\" [text]=\"true\" (onClick)=\"reset()\"></p-button>\n </div>\n\n </div>\n</p-dialog>\n", styles: ["@charset \"UTF-8\";:host ::ng-deep .spatial-controls-dialog .p-dialog-content{padding:.75rem 1rem 1rem}.sc-empty{padding:.5rem 0;font-size:.85rem;color:var(--text-color-secondary, #6c757d);line-height:1.4}.sc-empty code{font-size:.8rem}.sc-body{display:flex;flex-direction:column;gap:.85rem}.sc-dataset{display:flex;flex-direction:column;gap:.1rem;padding-bottom:.5rem;border-bottom:1px solid var(--surface-border, #dee2e6)}.sc-dataset-name{font-weight:600;font-size:.9rem}.sc-dataset-count,.sc-hint{font-size:.75rem;color:var(--text-color-secondary, #6c757d)}.sc-section{display:flex;flex-direction:column;gap:.35rem}.sc-lbl{font-size:.78rem;color:var(--text-color-secondary, #6c757d);white-space:nowrap}.sc-row{display:flex;align-items:center;gap:.6rem}.sc-row p-slider,.sc-row p-dropdown,.sc-row p-treeSelect{flex:1 1 0;min-width:0}:host ::ng-deep .sc-row p-slider .p-slider{width:100%}:host ::ng-deep .sc-row p-dropdown .p-dropdown,:host ::ng-deep .sc-row p-treeSelect .p-treeselect{width:100%}.sc-row .sc-lbl{flex:0 0 calc(50% - .6rem);min-width:0;overflow:hidden;text-overflow:ellipsis}.sc-cmap-option{display:inline-flex;align-items:center;gap:.4rem}.sc-cmap-swatch{width:3.5rem;height:.7rem;border-radius:2px;display:block}.sc-val{flex:0 0 4rem;text-align:right;font-size:.78rem;font-variant-numeric:tabular-nums;white-space:nowrap}:host ::ng-deep .sc-full,:host ::ng-deep .sc-full .p-dropdown,:host ::ng-deep .sc-full .p-autocomplete,:host ::ng-deep .sc-full input{width:100%}.sc-key{padding:.5rem 0;border-top:1px solid var(--surface-border, #dee2e6);border-bottom:1px solid var(--surface-border, #dee2e6)}.sc-legend{list-style:none;margin:0;padding:0;max-height:11rem;overflow-y:auto;display:flex;flex-direction:column;gap:.2rem}.sc-legend li{display:flex}.sc-legend-btn{display:flex;align-items:center;gap:.45rem;width:100%;padding:.15rem .3rem;font:inherit;font-size:.8rem;text-align:left;background:none;border:1px solid transparent;border-radius:3px;cursor:pointer;color:inherit}.sc-legend-btn:hover{background:var(--surface-hover, rgba(0, 0, 0, .04))}.sc-legend-btn.selected{border-color:var(--primary-color, #3b82f6);background:var(--surface-hover, rgba(59, 130, 246, .08));font-weight:600}.sc-selection .sc-row{gap:.4rem}.sc-swatch{width:.85rem;height:.85rem;flex:0 0 auto;border-radius:2px;border:1px solid rgba(0,0,0,.2)}.sc-legend-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sc-colorbar{height:.75rem;border-radius:2px;border:1px solid rgba(0,0,0,.2)}.sc-colorbar-ends{display:flex;justify-content:space-between;font-size:.7rem;color:var(--text-color-secondary, #6c757d);margin-top:.2rem}.sc-btns{display:flex;justify-content:flex-end}.sc-charts{padding-top:.5rem;border-top:1px solid var(--surface-border, #dee2e6);gap:0}.sc-disclosure{display:flex;align-items:center;gap:.4rem;width:100%;padding:.2rem 0;font:inherit;font-size:.82rem;font-weight:600;text-align:left;background:none;border:0;cursor:pointer;color:inherit}.sc-disclosure i{font-size:.7rem;color:var(--text-color-secondary, #6c757d)}.sc-disclosure small{font-weight:400;color:var(--text-color-secondary, #6c757d);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sc-disclosure:hover{color:var(--primary-color, #3b82f6)}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "style", "styleClass", "badgeClass", "ariaLabel", "autofocus"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "component", type: i6.Dropdown, selector: "p-dropdown", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "autoShowPanelOnPrintableCharacterKeyDown", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i7.Slider, selector: "p-slider", inputs: ["animate", "disabled", "min", "max", "orientation", "step", "range", "style", "styleClass", "ariaLabel", "ariaLabelledBy", "tabindex", "autofocus"], outputs: ["onChange", "onSlideEnd"] }, { kind: "component", type: i8.TreeSelect, selector: "p-treeSelect", inputs: ["inputId", "scrollHeight", "disabled", "metaKeySelection", "variant", "display", "selectionMode", "tabindex", "ariaLabel", "ariaLabelledBy", "placeholder", "panelClass", "panelStyle", "panelStyleClass", "containerStyle", "containerStyleClass", "labelStyle", "labelStyleClass", "overlayOptions", "emptyMessage", "appendTo", "filter", "filterBy", "filterMode", "filterPlaceholder", "filterLocale", "filterInputAutoFocus", "propagateSelectionDown", "propagateSelectionUp", "showClear", "resetFilterOnHide", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autofocus", "options", "showTransitionOptions", "hideTransitionOptions", "loading"], outputs: ["onNodeExpand", "onNodeCollapse", "onShow", "onHide", "onClear", "onFilter", "onFocus", "onBlur", "onNodeUnselect", "onNodeSelect"] }, { kind: "component", type: i9.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "visible", "style", "position"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "component", type: i10.Checkbox, selector: "p-checkbox", inputs: ["value", "name", "disabled", "binary", "label", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "style", "styleClass", "labelStyleClass", "formControl", "checkboxIcon", "readonly", "required", "autofocus", "trueValue", "falseValue", "variant"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i11.SpatialChartsComponent, selector: "spatial-charts", inputs: ["active"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }] }); }
604
+ }
605
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: SpatialControlsComponent, decorators: [{
606
+ type: Component,
607
+ args: [{ selector: 'spatial-controls', template: "<p-dialog header=\"Spatial omics\"\n [visible]=\"visible\" (visibleChange)=\"onVisibleChange($event)\"\n [modal]=\"false\" [resizable]=\"true\" [draggable]=\"true\"\n position=\"left\"\n (onResizeEnd)=\"onResizeEnd()\"\n [style]=\"{ width: '30rem' }\"\n [breakpoints]=\"{ '960px': '90vw' }\"\n styleClass=\"spatial-controls-dialog\">\n\n <!-- No port bound: say why rather than showing controls that do nothing. -->\n <div class=\"sc-empty\" *ngIf=\"!controls\">\n No spatial-omics data source is configured. A host provides one by binding\n <code>SPATIAL_DATA_PORT</code>.\n </div>\n\n <div class=\"sc-empty\" *ngIf=\"controls && !dataset\">\n No dataset is loaded.\n </div>\n\n <div class=\"sc-body\" *ngIf=\"controls && dataset as ds\">\n\n <div class=\"sc-dataset\">\n <span class=\"sc-dataset-name\">{{ ds.name }}</span>\n <span class=\"sc-dataset-count\">{{ ds.observations.count | number }} observations</span>\n </div>\n\n <!-- Colour source. Column and gene are mutually exclusive: picking one\n clears the other, so what drives the colours is never ambiguous. -->\n <div class=\"sc-section\">\n <label class=\"sc-lbl\" for=\"sc-column\">Colour by column</label>\n <p-dropdown inputId=\"sc-column\" [options]=\"columnOptions\"\n optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"selectedColumn\" (ngModelChange)=\"onColumn($event)\"\n placeholder=\"None (flat colour)\" appendTo=\"body\"\n styleClass=\"sc-full\"></p-dropdown>\n </div>\n\n <div class=\"sc-section\" *ngIf=\"ds.features\">\n <label class=\"sc-lbl\" for=\"sc-gene\">Colour by gene</label>\n <!-- A filterable dropdown, not a free-text box: the genes are visible before\n anything is typed, and each keystroke narrows a list the user can see.\n Virtual-scrolled because a targeted panel runs to a few thousand names. -->\n <p-dropdown inputId=\"sc-gene\" [options]=\"geneOptions\"\n optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"selectedGene\" (ngModelChange)=\"onGene($event)\"\n [filter]=\"true\" filterBy=\"label\" [resetFilterOnHide]=\"true\"\n (onFilter)=\"onGeneFilter($event.filter)\"\n [showClear]=\"true\"\n [virtualScroll]=\"geneOptions.length > geneVirtualScrollFrom\"\n [virtualScrollItemSize]=\"30\"\n [emptyFilterMessage]=\"genesAreRemote ? 'No match \u2014 keep typing' : 'No match'\"\n [emptyMessage]=\"genesAreRemote ? 'Type to search genes' : 'No genes'\"\n appendTo=\"body\"\n placeholder=\"Search {{ ds.features.count | number }} genes\u2026\"\n styleClass=\"sc-full\"></p-dropdown>\n <small class=\"sc-hint\" *ngIf=\"geneSearchFailed\">Gene lookup failed.</small>\n </div>\n\n <!-- Key: categorical legend or a continuous colour bar, built from the same\n functions the renderer uses so it cannot disagree with the canvas. -->\n <div class=\"sc-section sc-key\" *ngIf=\"view.colorBy\">\n <span class=\"sc-lbl\">{{ colorByLabel }}</span>\n <!-- Derived columns say so, so they are not mistaken for measured data. -->\n <small class=\"sc-hint\" *ngIf=\"activeDescription\">{{ activeDescription }}</small>\n\n <!-- Legend rows are buttons: clicking one selects that category, clicking\n it again clears \u2014 so a click is always reversible. -->\n <ul class=\"sc-legend\" *ngIf=\"isCategorical\">\n <li *ngFor=\"let entry of legend; index as i\">\n <button type=\"button\" class=\"sc-legend-btn\"\n [class.selected]=\"selectedCategory === i\"\n (click)=\"selectCategory(i)\"\n [title]=\"'Select ' + entry.label\">\n <span class=\"sc-swatch\" [style.background]=\"entry.color\"></span>\n <span class=\"sc-legend-label\">{{ entry.label }}</span>\n </button>\n </li>\n </ul>\n\n <div *ngIf=\"isContinuous\">\n <div class=\"sc-colorbar\" [style.background]=\"colorBarCss\"></div>\n <div class=\"sc-colorbar-ends\">\n <span>low</span>\n <span class=\"sc-hint\">window from percentiles</span>\n <span>high</span>\n </div>\n <!-- The gradient this bar and the map are drawn with. Sits under the bar\n it controls, with the same swatch previews the image's colormap picker\n uses: which gradient reads best is a visual judgement, so a list of\n names would be the wrong control. Cleared = follow the image's. -->\n <div class=\"sc-row\">\n <span class=\"sc-lbl\">Colormap</span>\n <p-treeSelect [options]=\"colormapOptions\" [ngModel]=\"selectedColormapNode\"\n selectionMode=\"single\" [showClear]=\"true\"\n placeholder=\"Match the image\" [scrollHeight]=\"'260px'\"\n appendTo=\"body\"\n pTooltip=\"Colour scale for gene and numeric values \u2014 the markers, the gene map and this bar. Clear it to follow the image's colormap\"\n tooltipPosition=\"top\"\n (onNodeSelect)=\"onContinuousColormap($event.node)\"\n (onClear)=\"onContinuousColormap(null)\">\n <ng-template let-node pTemplate=\"default\">\n <span class=\"sc-cmap-option\">\n <img *ngIf=\"!node.children && node.data?.src\" [src]=\"node.data.src\"\n class=\"sc-cmap-swatch\" alt=\"\" />\n <span>{{ node.label }}</span>\n </span>\n </ng-template>\n </p-treeSelect>\n <span class=\"sc-val\"></span>\n </div>\n </div>\n </div>\n\n <!-- Display -->\n <div class=\"sc-section\">\n <div class=\"sc-row\">\n <span class=\"sc-lbl\">Point size</span>\n <p-slider [ngModel]=\"view.pointScale\" [min]=\"0.25\" [max]=\"5\" [step]=\"0.25\"\n (onChange)=\"onPointScale($event.value)\"></p-slider>\n <span class=\"sc-val\">{{ view.pointScale }}\u00D7</span>\n </div>\n <div class=\"sc-row\">\n <span class=\"sc-lbl\">Opacity</span>\n <p-slider [ngModel]=\"view.opacity\" [min]=\"0.1\" [max]=\"1\" [step]=\"0.05\"\n (onChange)=\"onOpacity($event.value)\"></p-slider>\n <span class=\"sc-val\">{{ view.opacity }}</span>\n </div>\n </div>\n\n <!-- Only for a gene: the map draws that gene's expression as a field. In 2D\n it goes under the cells of the displayed plane; in 3D it is one sheet per\n imaged section, optionally smoothed along z into a volume. -->\n <div class=\"sc-section\" *ngIf=\"canMapGene\">\n <div class=\"sc-row\">\n <p-checkbox [binary]=\"true\" [ngModel]=\"view.geneMap\"\n (onChange)=\"onGeneMap($event.checked)\"\n label=\"Gene map (expression field)\"\n [pTooltip]=\"is3d\n ? 'Draw this gene\\'s expression as one field per imaged section \u2014 an estimate between the cells, empty where nothing was measured'\n : 'Draw this gene\\'s expression as a smooth field UNDER the cells \u2014 an estimate between them, transparent where nothing was measured'\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n <!-- 3D only: interpolating along z turns the measured sheets into an\n estimate, so it is its own decision and its own label. -->\n <div class=\"sc-row\" *ngIf=\"view.geneMap && is3d\">\n <p-checkbox [binary]=\"true\" [ngModel]=\"view.geneMapVolume\"\n (onChange)=\"onGeneMapVolume($event.checked)\"\n label=\"Volume rendering (interpolate along z)\"\n pTooltip=\"Smooth the per-section fields into a continuous volume \u2014 an ESTIMATE between the imaged sections, not measured tissue\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n <!-- Meaningless while interpolating: a volume built from one section would\n smear that slide through the specimen's whole depth. -->\n <div class=\"sc-row\" *ngIf=\"view.geneMap && is3d && isSectioned && !view.geneMapVolume\">\n <p-checkbox [binary]=\"true\" [ngModel]=\"geneMapOneSection\"\n (onChange)=\"onGeneMapOneSection($event.checked)\"\n label=\"One map section at a time\"\n pTooltip=\"Draw a single imaged section's field instead of the whole stack of sheets\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n <div class=\"sc-row\"\n *ngIf=\"view.geneMap && is3d && isSectioned && !view.geneMapVolume && geneMapOneSection\">\n <span class=\"sc-lbl\">Map section</span>\n <p-slider [ngModel]=\"view.geneMapSection\" [min]=\"0\" [max]=\"lastSection\" [step]=\"1\"\n (onChange)=\"onGeneMapSection($event.value)\"></p-slider>\n <span class=\"sc-val\">{{ geneMapSectionLabel }}</span>\n </div>\n <div class=\"sc-row\" *ngIf=\"view.geneMap\">\n <span class=\"sc-lbl\">Smoothing</span>\n <p-slider [ngModel]=\"view.geneMapSmoothing\" [min]=\"0.5\" [max]=\"4\" [step]=\"0.5\"\n (onChange)=\"onGeneMapSmoothing($event.value)\"></p-slider>\n <span class=\"sc-val\">{{ view.geneMapSmoothing }}\u00D7</span>\n </div>\n <div class=\"sc-row\" *ngIf=\"view.geneMap\">\n <span class=\"sc-lbl\">Map opacity</span>\n <p-slider [ngModel]=\"view.geneMapOpacity\" [min]=\"0.1\" [max]=\"1\" [step]=\"0.05\"\n (onChange)=\"onGeneMapOpacity($event.value)\"></p-slider>\n <span class=\"sc-val\">{{ view.geneMapOpacity }}</span>\n </div>\n <div class=\"sc-hint\" *ngIf=\"view.geneMap && !is3d\">\n Kernel-weighted mean per cell, not a sum \u2014 so a crowded region does not glow\n for being crowded. Transparent where no cell was measured. Turn the cells'\n own Opacity down to read the field under them.\n </div>\n <div class=\"sc-hint\" *ngIf=\"view.geneMap && is3d\">\n {{ geneMapVolumeNote }}\n </div>\n </div>\n\n <!-- 3D only: the volume, the cloud and the density volumes occupy the same\n space, so each hides the others. Independent toggles, because the useful\n views are the combinations \u2014 the field alone, one section's cells over it,\n the anatomy alone. -->\n <div class=\"sc-section\" *ngIf=\"is3d\">\n <span class=\"sc-lbl\">Show</span>\n <div class=\"sc-row\" *ngIf=\"ds.volume\">\n <p-checkbox [binary]=\"true\" [ngModel]=\"view.showVolume\"\n (onChange)=\"onShowVolume($event.checked)\"\n label=\"Reference volume\"\n pTooltip=\"The registered anatomy the observations sit in\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n <!-- The backdrop's own opacity: reading the cloud or a density field\n through the anatomy means turning the anatomy down, not the data. -->\n <div class=\"sc-row\" *ngIf=\"ds.volume && view.showVolume\">\n <span class=\"sc-lbl\">Volume opacity</span>\n <p-slider [ngModel]=\"view.volumeOpacity\" [min]=\"0.05\" [max]=\"1\" [step]=\"0.05\"\n (onChange)=\"onVolumeOpacity($event.value)\"></p-slider>\n <span class=\"sc-val\">{{ view.volumeOpacity }}</span>\n </div>\n <div class=\"sc-row\">\n <p-checkbox [binary]=\"true\" [ngModel]=\"view.showPoints\"\n (onChange)=\"onShowPoints($event.checked)\"\n label=\"Observations\"\n pTooltip=\"The measured cells. Hide them to read the density volumes underneath\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n <!-- Only for data that really is sectioned; a continuous z has no sections\n to step through. -->\n <div class=\"sc-row\" *ngIf=\"view.showPoints && isSectioned\">\n <p-checkbox [binary]=\"true\" [ngModel]=\"oneSection\"\n (onChange)=\"onOneSection($event.checked)\"\n label=\"One section at a time\"\n pTooltip=\"Draw a single imaged section's cells, so the density volumes between the sections stay visible\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n <div class=\"sc-row\" *ngIf=\"view.showPoints && isSectioned && oneSection\">\n <span class=\"sc-lbl\">Section</span>\n <p-slider [ngModel]=\"view.pointSection\" [min]=\"0\" [max]=\"lastSection\" [step]=\"1\"\n (onChange)=\"onPointSection($event.value)\"></p-slider>\n <span class=\"sc-val\">{{ sectionLabel }}</span>\n </div>\n </div>\n\n <div class=\"sc-section\" *ngIf=\"is3d\">\n <div class=\"sc-hint\" *ngIf=\"exceedsCloudPalette\">\n {{ legend?.length }} categories is past the {{ cloudPaletteLimit }} the 3D cloud's\n colour LUT can keep apart, so the points draw flat for this column. The density\n volumes below render it, and so does the 2D Spatial omics view.\n </div>\n <div class=\"sc-row\">\n <p-checkbox [binary]=\"true\" [ngModel]=\"view.densityVolume\"\n (onChange)=\"onDensityVolume($event.checked)\"\n label=\"Cluster density volumes\"\n pTooltip=\"Raymarch each cluster as a smooth density field \u2014 an ESTIMATE between the imaged sections, not measured cells\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n <div class=\"sc-row\" *ngIf=\"view.densityVolume\">\n <span class=\"sc-lbl\">Smoothing</span>\n <p-slider [ngModel]=\"view.densitySmoothing\" [min]=\"0.5\" [max]=\"4\" [step]=\"0.5\"\n (onChange)=\"onDensitySmoothing($event.value)\"></p-slider>\n <span class=\"sc-val\">{{ view.densitySmoothing }}\u00D7</span>\n </div>\n <div class=\"sc-hint\" *ngIf=\"view.densityVolume\">{{ densityNote }}</div>\n </div>\n\n <!-- Continuous-only knobs: meaningless for a categorical palette. -->\n <div class=\"sc-section\" *ngIf=\"isContinuous\">\n <div class=\"sc-row\">\n <p-checkbox [binary]=\"true\" [ngModel]=\"view.logScale\"\n (onChange)=\"onLogScale($event.checked)\"\n label=\"Log scale\"\n pTooltip=\"Counts collapse against their outliers on a linear ramp\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n <div class=\"sc-row\">\n <span class=\"sc-lbl\">Clip outliers</span>\n <p-dropdown [options]=\"clipOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"view.percentileClip\" (ngModelChange)=\"onClip($event)\"\n appendTo=\"body\"\n pTooltip=\"Percentile the contrast window is taken at\"\n tooltipPosition=\"top\"></p-dropdown>\n <!-- Reserves the readout column this row has no readout for, so the\n control's right edge lines up with every slider's. -->\n <span class=\"sc-val\"></span>\n </div>\n </div>\n\n <!-- Selection. Regions come from the existing ROI tools (rectangle, polygon,\n freehand, wand, brush), so there is no separate marquee to learn. -->\n <div class=\"sc-section sc-selection\">\n <div class=\"sc-row\">\n <p-button label=\"Select from ROIs\" icon=\"pi pi-object-group\" size=\"small\"\n [outlined]=\"true\" (onClick)=\"selectFromRegions()\"\n pTooltip=\"Select every observation inside the drawn regions\"\n tooltipPosition=\"top\"></p-button>\n <p-button *ngIf=\"hasSelection\" label=\"Clear\" size=\"small\" [text]=\"true\"\n (onClick)=\"clearSelection()\"></p-button>\n </div>\n <small class=\"sc-hint\" *ngIf=\"hasSelection\">\n {{ selection.count | number }} of {{ ds.observations.count | number }} selected \u2014\n the rest are muted.\n </small>\n <small class=\"sc-hint\" *ngIf=\"selectionMissed\">\n No observations fell inside the drawn regions.\n </small>\n <!-- A lasso on a 2D screen cannot pick a depth, so say what it does pick\n rather than letting the count look like a mistake. -->\n <small class=\"sc-hint\" *ngIf=\"is3d\">\n In 3D a region cuts through the cloud's full depth. Orbit and draw again\n to cut from another angle.\n </small>\n </div>\n\n <!-- Distributions of whatever the map is coloured by. In the SAME dialog as\n the colour controls on purpose: changing the gene and watching the\n distribution move is one action, and two floating windows made that\n harder to see rather than easier. -->\n <div class=\"sc-section sc-charts\">\n <button type=\"button\" class=\"sc-disclosure\" (click)=\"toggleCharts()\"\n [attr.aria-expanded]=\"chartsOpen\" [attr.aria-controls]=\"chartsBodyId\">\n <i class=\"pi\" [class.pi-chevron-right]=\"!chartsOpen\"\n [class.pi-chevron-down]=\"chartsOpen\"></i>\n <span>Distribution</span>\n <small *ngIf=\"!chartsOpen && view.colorBy\">of {{ colorByLabel }}</small>\n </button>\n <!-- Kept MOUNTED when collapsed, only hidden: destroying it would throw\n away the chart kind and grouping the user picked. `active` is what\n stops it drawing while it has no size. -->\n <div [id]=\"chartsBodyId\" [hidden]=\"!chartsOpen\">\n <spatial-charts [active]=\"visible && chartsOpen\"></spatial-charts>\n </div>\n </div>\n\n <div class=\"sc-btns\">\n <p-button label=\"Reset\" size=\"small\" [text]=\"true\" (onClick)=\"reset()\"></p-button>\n </div>\n\n </div>\n</p-dialog>\n", styles: ["@charset \"UTF-8\";:host ::ng-deep .spatial-controls-dialog .p-dialog-content{padding:.75rem 1rem 1rem}.sc-empty{padding:.5rem 0;font-size:.85rem;color:var(--text-color-secondary, #6c757d);line-height:1.4}.sc-empty code{font-size:.8rem}.sc-body{display:flex;flex-direction:column;gap:.85rem}.sc-dataset{display:flex;flex-direction:column;gap:.1rem;padding-bottom:.5rem;border-bottom:1px solid var(--surface-border, #dee2e6)}.sc-dataset-name{font-weight:600;font-size:.9rem}.sc-dataset-count,.sc-hint{font-size:.75rem;color:var(--text-color-secondary, #6c757d)}.sc-section{display:flex;flex-direction:column;gap:.35rem}.sc-lbl{font-size:.78rem;color:var(--text-color-secondary, #6c757d);white-space:nowrap}.sc-row{display:flex;align-items:center;gap:.6rem}.sc-row p-slider,.sc-row p-dropdown,.sc-row p-treeSelect{flex:1 1 0;min-width:0}:host ::ng-deep .sc-row p-slider .p-slider{width:100%}:host ::ng-deep .sc-row p-dropdown .p-dropdown,:host ::ng-deep .sc-row p-treeSelect .p-treeselect{width:100%}.sc-row .sc-lbl{flex:0 0 calc(50% - .6rem);min-width:0;overflow:hidden;text-overflow:ellipsis}.sc-cmap-option{display:inline-flex;align-items:center;gap:.4rem}.sc-cmap-swatch{width:3.5rem;height:.7rem;border-radius:2px;display:block}.sc-val{flex:0 0 4rem;text-align:right;font-size:.78rem;font-variant-numeric:tabular-nums;white-space:nowrap}:host ::ng-deep .sc-full,:host ::ng-deep .sc-full .p-dropdown,:host ::ng-deep .sc-full .p-autocomplete,:host ::ng-deep .sc-full input{width:100%}.sc-key{padding:.5rem 0;border-top:1px solid var(--surface-border, #dee2e6);border-bottom:1px solid var(--surface-border, #dee2e6)}.sc-legend{list-style:none;margin:0;padding:0;max-height:11rem;overflow-y:auto;display:flex;flex-direction:column;gap:.2rem}.sc-legend li{display:flex}.sc-legend-btn{display:flex;align-items:center;gap:.45rem;width:100%;padding:.15rem .3rem;font:inherit;font-size:.8rem;text-align:left;background:none;border:1px solid transparent;border-radius:3px;cursor:pointer;color:inherit}.sc-legend-btn:hover{background:var(--surface-hover, rgba(0, 0, 0, .04))}.sc-legend-btn.selected{border-color:var(--primary-color, #3b82f6);background:var(--surface-hover, rgba(59, 130, 246, .08));font-weight:600}.sc-selection .sc-row{gap:.4rem}.sc-swatch{width:.85rem;height:.85rem;flex:0 0 auto;border-radius:2px;border:1px solid rgba(0,0,0,.2)}.sc-legend-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sc-colorbar{height:.75rem;border-radius:2px;border:1px solid rgba(0,0,0,.2)}.sc-colorbar-ends{display:flex;justify-content:space-between;font-size:.7rem;color:var(--text-color-secondary, #6c757d);margin-top:.2rem}.sc-btns{display:flex;justify-content:flex-end}.sc-charts{padding-top:.5rem;border-top:1px solid var(--surface-border, #dee2e6);gap:0}.sc-disclosure{display:flex;align-items:center;gap:.4rem;width:100%;padding:.2rem 0;font:inherit;font-size:.82rem;font-weight:600;text-align:left;background:none;border:0;cursor:pointer;color:inherit}.sc-disclosure i{font-size:.7rem;color:var(--text-color-secondary, #6c757d)}.sc-disclosure small{font-weight:400;color:var(--text-color-secondary, #6c757d);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sc-disclosure:hover{color:var(--primary-color, #3b82f6)}\n"] }]
608
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
609
+ type: Inject,
610
+ args: [VISUALIZER]
611
+ }] }], propDecorators: { charts: [{
612
+ type: ViewChild,
613
+ args: [SpatialChartsComponent]
614
+ }], visible: [{
615
+ type: Input
616
+ }], is3d: [{
617
+ type: Input
618
+ }], visibleChange: [{
619
+ type: Output
620
+ }] } });
621
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BhdGlhbC1jb250cm9scy5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL3NwYXRpYWwtY29udHJvbHMvc3BhdGlhbC1jb250cm9scy5jb21wb25lbnQudHMiLCIuLi8uLi8uLi8uLi9zcmMvbGliL3NwYXRpYWwtY29udHJvbHMvc3BhdGlhbC1jb250cm9scy5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsU0FBUyxFQUFFLFlBQVksRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFxQixNQUFNLEVBQUUsU0FBUyxHQUM3RSxNQUFNLGVBQWUsQ0FBQztBQUN2QixPQUFPLEVBQUUsWUFBWSxFQUFFLGFBQWEsRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUVuRCxPQUFPLEVBQUUsVUFBVSxFQUFpQyxNQUFNLGtDQUFrQyxDQUFDO0FBQzdGLE9BQU8sRUFBRSxzQkFBc0IsRUFBRSxNQUFNLDJDQUEyQyxDQUFDO0FBSW5GLE9BQU8sRUFDMEMsb0JBQW9CLEdBQ3BFLE1BQU0sNEJBQTRCLENBQUM7QUFDcEMsT0FBTyxFQUNMLHlCQUF5QixFQUFFLG9CQUFvQixHQUNoRCxNQUFNLDZCQUE2QixDQUFDO0FBQ3JDLE9BQU8sRUFDaUIsY0FBYyxHQUNyQyxNQUFNLDhCQUE4QixDQUFDO0FBQ3RDLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQzs7Ozs7Ozs7Ozs7OztBQVF6RDtzRkFDc0Y7QUFDdEYsTUFBTSxVQUFVLEdBQUcsQ0FBQyxJQUFZLEVBQW9DLEVBQUUsQ0FBQyxDQUFDO0lBQ3RFLEtBQUssRUFBRSxJQUFJO0lBQ1gsS0FBSyxFQUFFLElBQUk7Q0FDWixDQUFDLENBQUM7QUFFSCxrRkFBa0Y7QUFDbEYsSUFBSSxtQkFBbUIsR0FBRyxDQUFDLENBQUM7QUFFNUIsb0VBQW9FO0FBQ3BFLE1BQU0sWUFBWSxHQUFpRDtJQUNqRSxFQUFFLEtBQUssRUFB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MVFhLHdCQUF3QjtrQkFMcEMsU0FBUzsrQkFDRSxrQkFBa0I7OzBCQW9IZixNQUFNOzJCQUFDLFVBQVU7eUNBOUdhLE1BQU07c0JBQWhELFNBQVM7dUJBQUMsc0JBQXNCO2dCQWdCeEIsT0FBTztzQkFBZixLQUFLO2dCQUlHLElBQUk7c0JBQVosS0FBSztnQkFDSSxhQUFhO3NCQUF0QixNQUFNIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgQ29tcG9uZW50LCBFdmVudEVtaXR0ZXIsIEluamVjdCwgSW5wdXQsIE9uRGVzdHJveSwgT25Jbml0LCBPdXRwdXQsIFZpZXdDaGlsZCxcbn0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBTdWJzY3JpcHRpb24sIGNvbWJpbmVMYXRlc3QgfSBmcm9tICdyeGpzJztcblxuaW1wb3J0IHsgVklTVUFMSVpFUiwgSVZpc3VhbGl6ZXIsIElTcGF0aWFsQ29udHJvbHMgfSBmcm9tICcuLi9jb250cmFjdHMvdmlzdWFsaXplci5jb250cmFjdCc7XG5pbXBvcnQgeyBTcGF0aWFsQ2hhcnRzQ29tcG9uZW50IH0gZnJvbSAnLi9zcGF0aWFsLWNoYXJ0cy9zcGF0aWFsLWNoYXJ0cy5jb21wb25lbnQnO1xuaW1wb3J0IHtcbiAgQ2F0ZWdvcmljYWxDb2x1bW5NZXRhLCBTcGF0aWFsQ29sdW1uTWV0YSwgU3BhdGlhbERhdGFzZXQsXG59IGZyb20gJy4uL2NvbnRyYWN0cy9zcGF0aWFsLWRhdGFzZXQuY29udHJhY3QnO1xuaW1wb3J0IHtcbiAgQ29sb3JtYXBOb2RlLCBDb2xvcm1hcFZhbHVlLCBTcGF0aWFsVmlld1N0YXRlLCBERUZBVUxUX1NQQVRJQUxfVklFVyxcbn0gZnJvbSAnLi4vY29udHJhY3RzL2Rpc3BsYXktdHlwZXMnO1xuaW1wb3J0IHtcbiAgU1BBVElBTF8zRF9NQVhfQ0FURUdPUklFUywgc3BhdGlhbENvbnRpbnVvdXNMdXQsXG59IGZyb20gJy4uL3NwYXRpYWwvc3BhdGlhbC1lbmNvZGluZyc7XG5pbXBvcnQge1xuICBTcGF0aWFsU2VsZWN0aW9uTWFzaywgZW1wdHlTZWxlY3Rpb24sXG59IGZyb20gJy4uL3NwYXRpYWwvc3BhdGlhbC1zZWxlY3Rpb24nO1xuaW1wb3J0IHsgc2VhcmNoR2VuZU5hbWVzIH0gZnJvbSAnLi4vc3BhdGlhbC9nZW5lLXNlYXJjaCc7XG5cbi8qKiBPbmUgbGVnZW5kIHJvdyBmb3IgYSBjYXRlZ29yaWNhbCBjb2xvdXJpbmcuICovXG5leHBvcnQgaW50ZXJmYWNlIFNwYXRpYWxMZWdlbmRFbnRyeSB7XG4gIGxhYmVsOiBzdHJpbmc7XG4gIGNvbG9yOiBzdHJpbmc7XG59XG5cbi8qKiBBIGdlbmUgbmFtZSBhcyBhIGRyb3Bkb3duIG9wdGlvbi4gT2JqZWN0cyByYXRoZXIgdGhhbiBiYXJlIHN0cmluZ3MgYmVjYXVzZSB0aGVcbiAqICBkcm9wZG93biBmaWx0ZXJzIG9uIGEgbmFtZWQgZmllbGQgKGBmaWx0ZXJCeT1cImxhYmVsXCJgKSwgd2hpY2ggYSBzdHJpbmcgaGFzIG5vdC4gKi9cbmNvbnN0IGdlbmVPcHRpb24gPSAobmFtZTogc3RyaW5nKTogeyBsYWJlbDogc3RyaW5nOyB2YWx1ZTogc3RyaW5nIH0gPT4gKHtcbiAgbGFiZWw6IG5hbWUsXG4gIHZhbHVlOiBuYW1lLFxufSk7XG5cbi8qKiBQZXItaW5zdGFuY2UgaWQgc291cmNlIOKAlCBzZWUge0BsaW5rIFNwYXRpYWxDb250cm9sc0NvbXBvbmVudC5jaGFydHNCb2R5SWR9LiAqL1xubGV0IGNvbnRyb2xzSW5zdGFuY2VTZXEgPSAwO1xuXG4vKiogT3V0bGllciBjbGlwcGluZyBwcmVzZXRzLCBhcyBgW2xvLCBoaV1gIHBlcmNlbnRpbGUgZnJhY3Rpb25zLiAqL1xuY29uc3QgQ0xJUF9PUFRJT05TOiB7IGxhYmVsOiBzdHJpbmc7IHZhbHVlOiBbbnVtYmVyLCBudW1iZXJdIH1bXSA9IFtcbiAgeyBsYWJlbDogJ05vbmUnLCB2YWx1ZTogWzAsIDFdIH0sXG4gIHsgbGFiZWw6ICcxJScsIHZhbHVlOiBbMC4wMSwgMC45OV0gfSxcbiAgeyBsYWJlbDogJzIlJywgdmFsdWU6IFswLjAyLCAwLjk4XSB9LFxuICB7IGxhYmVsOiAnNSUnLCB2YWx1ZTogWzAuMDUsIDAuOTVdIH0sXG5dO1xuXG4vKipcbiAqIFNwYXRpYWwtb21pY3MgY29udHJvbHM6IGEgbm9uLW1vZGFsLCByZXNpemFibGUsIGRyYWdnYWJsZSBkaWFsb2cgZm9yIGNob29zaW5nXG4gKiB3aGF0IHRoZSBvYnNlcnZhdGlvbiBtYXJrZXJzIGFyZSBjb2xvdXJlZCBieSwgYW5kIGhvdyB0aGV5IGFyZSBkcmF3bi5cbiAqXG4gKiBEZXBlbmRzIG9ubHkgb24ge0BsaW5rIElTcGF0aWFsQ29udHJvbHN9LCByZWFjaGVkIHRocm91Z2ggdGhlIGBWSVNVQUxJWkVSYFxuICogY29udHJhY3QgdG9rZW4g4oCUIG5ldmVyIGEgY29uY3JldGUgYmFja2VuZCDigJQgbWlycm9yaW5nIGhvd1xuICogYENoYW5uZWxIaXN0b2dyYW1Db21wb25lbnRgIGRlcGVuZHMgb25seSBvbiBgQ0hBTk5FTF9ISVNUT0dSQU1fQVBJYC4gV2hlbiBub1xuICogaG9zdCBoYXMgYm91bmQgYFNQQVRJQUxfREFUQV9QT1JUYCwgYGdldFNwYXRpYWxDb250cm9scygpYCByZXR1cm5zIG51bGwgYW5kXG4gKiB0aGUgZGlhbG9nIHJlbmRlcnMgYW4gZXhwbGFuYXRvcnkgZW1wdHkgc3RhdGUgcmF0aGVyIHRoYW4gZGVhZCBjb250cm9scy5cbiAqXG4gKiBUaGUgbGVnZW5kIHN3YXRjaGVzIGFuZCB0aGUgY29udGludW91cyBjb2xvdXIgYmFyIGFyZSBidWlsdCB3aXRoIHRoZSBTQU1FXG4gKiBmdW5jdGlvbnMgdGhlIHJlbmRlcmVyIHVzZXMgKGBjYXRlZ29yeUNvbG9yc2Ag4oaSIGByZXNvbHZlQ2F0ZWdvcnlDb2xvcnNgLFxuICogYHNwYXRpYWxDb250aW51b3VzTHV0YCksIHNvIHRoZSBrZXkgY2Fubm90IGRyaWZ0IGZyb20gd2hhdCBpcyBvbiBzY3JlZW4uXG4gKi9cbkBDb21wb25lbnQoe1xuICBzZWxlY3RvcjogJ3NwYXRpYWwtY29udHJvbHMnLFxuICB0ZW1wbGF0ZVVybDogJy4vc3BhdGlhbC1jb250cm9scy5jb21wb25lbnQuaHRtbCcsXG4gIHN0eWxlVXJsczogWycuL3NwYXRpYWwtY29udHJvbHMuY29tcG9uZW50LnNjc3MnXSxcbn0pXG5leHBvcnQgY2xhc3MgU3BhdGlhbENvbnRyb2xzQ29tcG9uZW50IGltcGxlbWVudHMgT25Jbml0LCBPbkRlc3Ryb3kge1xuICAvKiogVGhlIGVtYmVkZGVkIGNoYXJ0LCBzbyBhIGRpYWxvZyByZXNpemUgY2FuIHJlLWZpdCBpdC4gKi9cbiAgQFZpZXdDaGlsZChTcGF0aWFsQ2hhcnRzQ29tcG9uZW50KSBwcml2YXRlIGNoYXJ0cz86IFNwYXRpYWxDaGFydHNDb21wb25lbnQ7XG5cbiAgLyoqXG4gICAqIFJlLWZpdCB0aGUgY2hhcnQgYWZ0ZXIgdGhlIGRpYWxvZyBpcyByZXNpemVkLlxuICAgKlxuICAgKiBQbG90bHkgZG9lcyBub3QgZm9sbG93IGEgY29udGFpbmVyIHJlc2l6ZSBvbiBpdHMgb3duIOKAlCBpdHMgYHJlc3BvbnNpdmVgXG4gICAqIG9wdGlvbiBsaXN0ZW5zIGZvciBXSU5ET1cgcmVzaXplcyBvbmx5LCBzbyBkcmFnZ2luZyB0aGUgZGlhbG9nJ3MgaGFuZGxlXG4gICAqIHJlYWNoZXMgaXQgdGhyb3VnaCBub3RoaW5nLiBQcmltZU5HIHRlbGxzIHVzIHdoZW4gdGhlIGRyYWcgZW5kcywgd2hpY2ggaXMgYVxuICAgKiBmYXIgbW9yZSBkaXJlY3Qgc2lnbmFsIHRoYW4gd2F0Y2hpbmcgYm94ZXMsIGFuZCBmaXJpbmcgb25jZSBvbiByZWxlYXNlIG1lYW5zXG4gICAqIG5vIHJlbGF5b3V0IGNodXJuIGR1cmluZyB0aGUgZHJhZy5cbiAgICovXG4gIG9uUmVzaXplRW5kKCk6IHZvaWQge1xuICAgIHRoaXMuY2hhcnRzPy5yZXNpemUoKTtcbiAgfVxuXG4gIC8qKiBEaWFsb2cgdmlzaWJpbGl0eSwgdHdvLXdheSBib3VuZCBzbyBhIHRvb2xiYXIgYnV0dG9uIGNhbiBvcGVuIGl0LiAqL1xuICBASW5wdXQoKSB2aXNpYmxlID0gZmFsc2U7XG4gIC8qKiBTZXQgd2hpbGUgdGhlIDNEIGNsb3VkIGlzIHRoZSBhY3RpdmUgbW9kZS4gQ2hhbmdlcyB3aGF0IHRoZSBST0kgc2VsZWN0aW9uXG4gICAqICBNRUFOUyDigJQgYSBzY3JlZW4tc3BhY2UgbGFzc28gY3V0dGluZyB0aHJvdWdoIHRoZSBjbG91ZCdzIGZ1bGwgZGVwdGggcmF0aGVyXG4gICAqICB0aGFuIGEgc2hhcGUgaW4gdGlzc3VlIGNvb3JkaW5hdGVzIOKAlCBzbyB0aGUgaGludCBiZWxvdyBpdCBzYXlzIHNvLiAqL1xuICBASW5wdXQoKSBpczNkID0gZmFsc2U7XG4gIEBPdXRwdXQoKSB2aXNpYmxlQ2hhbmdlID0gbmV3IEV2ZW50RW1pdHRlcjxib29sZWFuPigpO1xuXG4gIHJlYWRvbmx5IGNsaXBPcHRpb25zID0gQ0xJUF9PUFRJT05TO1xuXG4gIC8qKiBOdWxsIHdoZW4gdGhlIGhvc3QgYm91bmQgbm8gYFNQQVRJQUxfREFUQV9QT1JUYC4gKi9cbiAgY29udHJvbHM6IElTcGF0aWFsQ29udHJvbHMgfCBudWxsID0gbnVsbDtcbiAgZGF0YXNldDogU3BhdGlhbERhdGFzZXQgfCBudWxsID0gbnVsbDtcbiAgdmlldzogU3BhdGlhbFZpZXdTdGF0ZSA9IHsgLi4uREVGQVVMVF9TUEFUSUFMX1ZJRVcgfTtcblxuICAvKiogQ29sb3VyLWJ5IGNvbHVtbiBjaG9pY2VzIOKAlCBcIk5vbmVcIiBwbHVzIGV2ZXJ5IGNvbHVtbiB0aGUgZGF0YXNldCBkZWNsYXJlcy4gKi9cbiAgY29sdW1uT3B0aW9uczogeyBsYWJlbDogc3RyaW5nOyB2YWx1ZTogc3RyaW5nIHwgbnVsbCB9W10gPSBbXTtcbiAgc2VsZWN0ZWRDb2x1bW46IHN0cmluZyB8IG51bGwgPSBudWxsO1xuXG4gIC8qKiBWaXJ0dWFsLXNjcm9sbGVkIG9ubHkgcGFzdCB0aGlzIG1hbnkgb3B0aW9uczogdGhlIHNjcm9sbGVyIGVhcm5zIGl0c1xuICAgKiAgY29tcGxleGl0eSBmb3IgYSBmZXcgdGhvdXNhbmQgbmFtZXMsIGFuZCBmb3IgZWlnaHQgaXQgYWRkcyBvbmx5IG92ZXJoZWFkIOKAlFxuICAgKiAgYSB2aXJ0dWFsIHZpZXdwb3J0IHRoYXQgc2hvcnQgc3dhbGxvd3MgdGhlIGNsaWNrcyBpdCBpcyBtZWFudCB0byBmb3J3YXJkLiAqL1xuICByZWFkb25seSBnZW5lVmlydHVhbFNjcm9sbEZyb20gPSAyMDA7XG4gIC8qKlxuICAgKiBHZW5lIHBpY2tlcjogYSBmaWx0ZXJhYmxlIGRyb3Bkb3duIHJhdGhlciB0aGFuIGEgZnJlZS10ZXh0IHR5cGVhaGVhZCwgc28gdGhlXG4gICAqIG9wdGlvbnMgYXJlIHZpc2libGUgYmVmb3JlIGFueXRoaW5nIGlzIHR5cGVkIGFuZCBlYWNoIGtleXN0cm9rZSBuYXJyb3dzIGEgbGlzdFxuICAgKiB0aGUgdXNlciBjYW4gc2VlLlxuICAgKlxuICAgKiBgZ2VuZU9wdGlvbnNgIGlzIHRoZSBmdWxsIG5hbWUgbGlzdCB3aGVuIHRoZSBkYXRhc2V0IGlubGluZXMgaXQgKGEgdGFyZ2V0ZWRcbiAgICogcGFuZWw6IDggZm9yIHRoZSBBQkMgZGVtbywgMzAw4oCTNSwwMDAgZm9yIFhlbml1bS9Db3NNeCkuIEEgd2hvbGUtdHJhbnNjcmlwdG9tZVxuICAgKiBkYXRhc2V0IGRvZXMgbm90IHNoaXAgaXRzIH4zMWsgbmFtZXMsIHNvIHRoZXJlIHRoZSBsaXN0IGlzIHdoYXQgdGhlIHBvcnQncyBsYXN0XG4gICAqIHNlYXJjaCByZXR1cm5lZCBhbmQgZmlsdGVyaW5nIGlzIHNlcnZlci1zaWRlIOKAlCBzYW1lIGNvbnRyb2wgZWl0aGVyIHdheS5cbiAgICovXG4gIGdlbmVPcHRpb25zOiB7IGxhYmVsOiBzdHJpbmc7IHZhbHVlOiBzdHJpbmcgfVtdID0gW107XG4gIC8qKlxuICAgKiBFdmVyeSBnZW5lIHRoZSBkYXRhc2V0IGlubGluZWQsIHdoZXRoZXIgb3Igbm90IGl0IGlzIGN1cnJlbnRseSBhbiBvcHRpb24uXG4gICAqXG4gICAqIEhlbGQgYXMgcGxhaW4gc3RyaW5ncyBhbmQgc2VhcmNoZWQgcGVyIGtleXN0cm9rZS4gQSB3aG9sZS10cmFuc2NyaXB0b21lIGRhdGFzZXRcbiAgICogaW5saW5lcyB+MThrIG5hbWVzIOKAlCBjaGVhcCB0byBrZWVwLCBydWlub3VzIHRvIGhhbmQgYSBkcm9wZG93biBhbGwgYXQgb25jZSDigJQgc29cbiAgICoge0BsaW5rIGdlbmVPcHRpb25zfSBpcyBvbmx5IGV2ZXIgdGhlIGJlc3QgZmV3IGh1bmRyZWQgb2YgdGhlc2UuXG4gICAqL1xuICBwcml2YXRlIGdlbmVOYW1lczogc3RyaW5nW10gPSBbXTtcbiAgc2VsZWN0ZWRHZW5lOiBzdHJpbmcgfCBudWxsID0gbnVsbDtcbiAgLyoqIFRydWUgd2hlbiB0aGUgb3B0aW9ucyBjb21lIGZyb20gdGhlIHBvcnQgcGVyIGtleXN0cm9rZSByYXRoZXIgdGhhbiBhIHJlc2lkZW50XG4gICAqICBsaXN0LCB3aGljaCBjaGFuZ2VzIHdoYXQgYW4gZW1wdHkgbGlzdCBtZWFucyAobm90aGluZyBtYXRjaGVkICp5ZXQqKS4gKi9cbiAgZ2VuZXNBcmVSZW1vdGUgPSBmYWxzZTtcbiAgZ2VuZVNlYXJjaEZhaWxlZCA9IGZhbHNlO1xuXG4gIC8qKiBDYXRlZ29yaWNhbCBrZXksIG9yIG51bGwgd2hlbiB0aGUgYWN0aXZlIGNvbG91cmluZyBpcyBjb250aW51b3VzLiAqL1xuICBsZWdlbmQ6IFNwYXRpYWxMZWdlbmRFbnRyeVtdIHwgbnVsbCA9IG51bGw7XG4gIC8qKiBDU1MgZ3JhZGllbnQgZm9yIGEgY29udGludW91cyBjb2xvdXJpbmcsIG9yIG51bGwgd2hlbiBjYXRlZ29yaWNhbC4gKi9cbiAgY29sb3JCYXJDc3M6IHN0cmluZyB8IG51bGwgPSBudWxsO1xuXG4gIC8qKiBDdXJyZW50IHNlbGVjdGlvbiDigJQgZHJpdmVzIHRoZSBjb3VudCwgdGhlIENsZWFyIGJ1dHRvbiBhbmQgdGhlIG11dGluZy4gKi9cbiAgc2VsZWN0aW9uOiBTcGF0aWFsU2VsZWN0aW9uTWFzayA9IGVtcHR5U2VsZWN0aW9uKCk7XG4gIC8qKiBUaGUgbGVnZW5kIHJvdyB3aG9zZSBjYXRlZ29yeSBpcyBjdXJyZW50bHkgc2VsZWN0ZWQsIGZvciBoaWdobGlnaHRpbmcuICovXG4gIHNlbGVjdGVkQ2F0ZWdvcnk6IG51bWJlciB8IG51bGwgPSBudWxsO1xuICAvKiogQW4gUk9JIHNlbGVjdGlvbiB0aGF0IG1hdGNoZWQgbm90aGluZywgc28gdGhlIFVJIGNhbiBzYXkgc28gcmF0aGVyIHRoYW5cbiAgICogIGxvb2tpbmcgbGlrZSB0aGUgYnV0dG9uIGRpZCBub3RoaW5nLiAqL1xuICBzZWxlY3Rpb25NaXNzZWQgPSBmYWxzZTtcblxuICAvKipcbiAgICogV2hldGhlciB0aGUgRGlzdHJpYnV0aW9uIHNlY3Rpb24gaXMgZXhwYW5kZWQuIENvbGxhcHNlZCBieSBkZWZhdWx0OiB0aGVcbiAgICogcGFuZWwncyBwcmltYXJ5IGpvYiBpcyB0aGUgY29sb3VyIGNvbnRyb2xzLCBhbmQgdGhlIGNoYXJ0IHJvdWdobHkgZG91Ymxlc1xuICAgKiBpdHMgaGVpZ2h0LlxuICAgKi9cbiAgY2hhcnRzT3BlbiA9IGZhbHNlO1xuICAvKiogUGVyLWluc3RhbmNlIGlkIGZvciB0aGUgY29sbGFwc2libGUgYm9keSwgc28gYGFyaWEtY29udHJvbHNgIHBvaW50cyBhdCBvbmVcbiAgICogIGVsZW1lbnQgYW5kIGV4cGFuZGluZyB0aGUgc2Vjb25kIHBhbmVsIGNhbm5vdCBzY3JvbGwgdGhlIGZpcnN0IG9uZSdzIGNoYXJ0LiAqL1xuICByZWFkb25seSBjaGFydHNCb2R5SWQgPSBgc2MtY2hhcnRzLWJvZHktJHsrK2NvbnRyb2xzSW5zdGFuY2VTZXF9YDtcblxuICAvKipcbiAgICogVGhlIGRhdGFzZXQncyBpbWFnZWQgc2VjdGlvbiBwb3NpdGlvbnMsIG9yIG51bGwgd2hlbiBpdHMgeiBpcyBjb250aW51b3VzXG4gICAqIHJhdGhlciB0aGFuIHNlY3Rpb25lZC4gUmVhZCBvbmNlIHBlciBkYXRhc2V0IOKAlCB0aGUgc2NhbiB3YWxrcyB0aGUgeiBvZiBldmVyeVxuICAgKiBvYnNlcnZhdGlvbiwgc28gaXQgbXVzdCBub3Qgc2l0IGluIGEgdGVtcGxhdGUgZ2V0dGVyLlxuICAgKi9cbiAgc2VjdGlvbnM6IEZsb2F0MzJBcnJheSB8IG51bGwgPSBudWxsO1xuXG4gIC8qKlxuICAgKiBDb2xvcm1hcCB0cmVlIGZvciB0aGUgY29udGludW91cyBjb2xvdXIgc2NhbGUsIGFuZCB0aGUgbm9kZSBjdXJyZW50bHkgcGlja2VkXG4gICAqIGZyb20gaXQuIFRoZSBsaWJyYXJ5J3Mgb3duIGBDT0xPUk1BUF9PUFRJT05TYCwgc2hvd24gd2l0aCB0aGUgc2FtZSBzd2F0Y2hlc1xuICAgKiB0aGUgaW1hZ2UncyBjb2xvcm1hcCBwaWNrZXIgdXNlcyDigJQgY2hvb3NpbmcgYSBncmFkaWVudCBpcyBhIHZpc3VhbCBkZWNpc2lvbixcbiAgICogc28gYSBsaXN0IG9mIG5hbWVzIHdvdWxkIGJlIHRoZSB3cm9uZyBjb250cm9sLlxuICAgKi9cbiAgY29sb3JtYXBPcHRpb25zOiBDb2xvcm1hcE5vZGVbXSA9IFtdO1xuICBzZWxlY3RlZENvbG9ybWFwTm9kZTogQ29sb3JtYXBOb2RlIHwgbnVsbCA9IG51bGw7XG5cbiAgcHJpdmF0ZSBjb2xvcm1hcDogQ29sb3JtYXBOb2RlIHwgbnVsbCA9IG51bGw7XG4gIHByaXZhdGUgcmV2ZXJzZSA9IGZhbHNlO1xuICAvKiogR3VhcmRzIHRoZSBnZW5lIHR5cGVhaGVhZCBhbmQgdGhlIGxlZ2VuZC9jb2xvdXItYmFyIHJlYnVpbGQ6IGJvdGggYXJlIGFzeW5jXG4gICAqICBhbmQgYm90aCBhcmUgZHJpdmVuIGJ5IGlucHV0IHRoZSB1c2VyIGNoYW5nZXMgZmFzdGVyIHRoYW4gdGhleSByZXNvbHZlLiAqL1xuICBwcml2YXRlIGdlbmVTZWFyY2hUb2tlbiA9IDA7XG4gIHByaXZhdGUga2V5VG9rZW4gPSAwO1xuICBwcml2YXRlIHJlYWRvbmx5IHN1YnMgPSBuZXcgU3Vic2NyaXB0aW9uKCk7XG5cbiAgY29uc3RydWN0b3IoQEluamVjdChWSVNVQUxJWkVSKSBwcml2YXRlIHJlYWRvbmx5IHZpejogSVZpc3VhbGl6ZXIpIHt9XG5cbiAgbmdPbkluaXQoKTogdm9pZCB7XG4gICAgdGhpcy5jb250cm9scyA9IHRoaXMudml6LmdldFNwYXRpYWxDb250cm9scz8uKCkgPz8gbnVsbDtcbiAgICB0aGlzLmNvbG9ybWFwT3B0aW9ucyA9IHRoaXMudml6LmdldENvbG9ybWFwT3B0aW9ucz8uKCkgPz8gW107XG4gICAgaWYgKCF0aGlzLmNvbnRyb2xzKSByZXR1cm47XG5cbiAgICB0aGlzLnN1YnMuYWRkKHRoaXMuY29udHJvbHMuZ2V0RGF0YXNldCQoKS5zdWJzY3JpYmUoKGRhdGFzZXQpID0+IHtcbiAgICAgIHRoaXMuZGF0YXNldCA9IGRhdGFzZXQ7XG4gICAgICB0aGlzLmNvbHVtbk9wdGlvbnMgPSBbXG4gICAgICAgIHsgbGFiZWw6ICdOb25lIChmbGF0IGNvbG91ciknLCB2YWx1ZTogbnVsbCB9LFxuICAgICAgICAuLi4oZGF0YXNldD8uY29sdW1ucyA/PyBbXSkubWFwKChjKSA9PiAoeyBsYWJlbDogdGhpcy5jb2x1bW5MYWJlbChjKSwgdmFsdWU6IGMubmFtZSB9KSksXG4gICAgICBdO1xuICAgICAgLy8gQSBuZXcgZGF0YXNldCBhbG1vc3QgY2VydGFpbmx5IGhhcyBkaWZmZXJlbnQgY29sdW1uczsgZHJvcCBzdGFsZSBVSSBzdGF0ZS5cbiAgICAgIHRoaXMuc2VsZWN0ZWRHZW5lID0gbnVsbDtcbiAgICAgIHRoaXMuZ2VuZVNlYXJjaEZhaWxlZCA9IGZhbHNlO1xuICAgICAgY29uc3QgbmFtZXMgPSBkYXRhc2V0Py5mZWF0dXJlcz8ubmFtZXM7XG4gICAgICB0aGlzLmdlbmVzQXJlUmVtb3RlID0gISFkYXRhc2V0Py5mZWF0dXJlcyAmJiAhbmFtZXM7XG4gICAgICB0aGlzLmdlbmVOYW1lcyA9IG5hbWVzID8gWy4uLm5hbWVzXSA6IFtdO1xuICAgICAgLy8gVGhlIGhlYWQgb2YgdGhlIGxpc3QsIG5vdCBhbGwgb2YgaXQ6IHNlZSBgZ2VuZU5hbWVzYC5cbiAgICAgIHRoaXMuZ2VuZU9wdGlvbnMgPSBzZWFyY2hHZW5lTmFtZXModGhpcy5nZW5lTmFtZXMsICcnKS5tYXAoZ2VuZU9wdGlvbik7XG4gICAgICB0aGlzLnNlY3Rpb25zID0gdGhpcy5jb250cm9scz8uc2FtcGxlZFNlY3Rpb25zKCkgPz8gbnVsbDtcbiAgICAgIHZvaWQgdGhpcy5yZWZyZXNoS2V5KCk7XG4gICAgfSkpO1xuXG4gICAgdGhpcy5zdWJzLmFkZCh0aGlzLmNvbnRyb2xzLmdldFZpZXdTdGF0ZSQoKS5zdWJzY3JpYmUoKHZpZXcpID0+IHtcbiAgICAgIHRoaXMudmlldyA9IHZpZXc7XG4gICAgICB0aGlzLnNlbGVjdGVkQ29sdW1uID0gdmlldy5jb2xvckJ5Py5raW5kID09PSAnY29sdW1uJyA/IHZpZXcuY29sb3JCeS5uYW1lIDogbnVsbDtcbiAgICAgIHRoaXMuc2VsZWN0ZWRHZW5lID0gdmlldy5jb2xvckJ5Py5raW5kID09PSAnZmVhdHVyZScgPyB2aWV3LmNvbG9yQnkubmFtZSA6IG51bGw7XG4gICAgICB0aGlzLnNlbGVjdGVkQ29sb3JtYXBOb2RlID0gdGhpcy5jb2xvcm1hcE5vZGVGb3Iodmlldy5jb250aW51b3VzQ29sb3JtYXApO1xuICAgICAgdm9pZCB0aGlzLnJlZnJlc2hLZXkoKTtcbiAgICB9KSk7XG5cbiAgICB0aGlzLnN1YnMuYWRkKHRoaXMuY29udHJvbHMuZ2V0U2VsZWN0aW9uJCgpLnN1YnNjcmliZSgoc2VsZWN0aW9uKSA9PiB7XG4gICAgICB0aGlzLnNlbGVjdGlvbiA9IHNlbGVjdGlvbjtcbiAgICAgIGlmIChzZWxlY3Rpb24uY291bnQgPT09IDApIHRoaXMuc2VsZWN0ZWRDYXRlZ29yeSA9IG51bGw7XG4gICAgfSkpO1xuXG4gICAgLy8gVGhlIGNvbG91ciBiYXIgbXVzdCB1c2UgdGhlIGNvbG9ybWFwIHRoZSByZW5kZXJlciBpcyB1c2luZy5cbiAgICB0aGlzLnN1YnMuYWRkKFxuICAgICAgY29tYmluZUxhdGVzdChbdGhpcy52aXouZ2V0Q29sb3JtYXAoKSwgdGhpcy52aXouZ2V0UmV2ZXJzZVNjYWxlKCldKVxuICAgICAgICAuc3Vic2NyaWJlKChbY29sb3JtYXAsIHJldmVyc2VdKSA9PiB7XG4gICAgICAgICAgdGhpcy5jb2xvcm1hcCA9IChjb2xvcm1hcCBhcyBDb2xvcm1hcE5vZGUpID8/IG51bGw7XG4gICAgICAgICAgdGhpcy5yZXZlcnNlID0gISFyZXZlcnNlO1xuICAgICAgICAgIHZvaWQgdGhpcy5yZWZyZXNoS2V5KCk7XG4gICAgICAgIH0pLFxuICAgICk7XG4gIH1cblxuICBuZ09uRGVzdHJveSgpOiB2b2lkIHtcbiAgICB0aGlzLnN1YnMudW5zdWJzY3JpYmUoKTtcbiAgfVxuXG4gIG9uVmlzaWJsZUNoYW5nZSh2YWx1ZTogYm9vbGVhbik6IHZvaWQge1xuICAgIHRoaXMudmlzaWJsZSA9IHZhbHVlO1xuICAgIHRoaXMudmlzaWJsZUNoYW5nZS5lbWl0KHZhbHVlKTtcbiAgfVxuXG4gIC8vIOKUgOKUgCBjb2xvdXIgc291cmNlIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG4gIC8qKiBDb2x1bW4gZHJvcGRvd24uIENob29zaW5nIGEgY29sdW1uIHN1cGVyc2VkZXMgYW55IGdlbmUgc2VsZWN0aW9uLiAqL1xuICBvbkNvbHVtbihuYW1lOiBzdHJpbmcgfCBudWxsKTogdm9pZCB7XG4gICAgdGhpcy5zZWxlY3RlZENvbHVtbiA9IG5hbWU7XG4gICAgaWYgKCFuYW1lKSB7XG4gICAgICB0aGlzLmNvbnRyb2xzPy5jbGVhckNvbG9yQnkoKTtcbiAgICAgIHRoaXMuc2VsZWN0ZWRHZW5lID0gbnVsbDtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgdGhpcy5zZWxlY3RlZEdlbmUgPSBudWxsO1xuICAgIHRoaXMuY29udHJvbHM/LmNvbG9yQnlDb2x1bW4obmFtZSk7XG4gIH1cblxuICAvKipcbiAgICogQSBrZXlzdHJva2UgaW4gdGhlIGdlbmUgZHJvcGRvd24ncyBmaWx0ZXIgYm94LlxuICAgKlxuICAgKiBXaXRoIHRoZSBuYW1lcyByZXNpZGVudCB0aGUgZHJvcGRvd24gZmlsdGVycyB0aGVtIGl0c2VsZiBhbmQgdGhpcyBvbmx5IGNsZWFycyBhXG4gICAqIHN0YWxlIGZhaWx1cmUuIFdpdGhvdXQgdGhlbSB0aGVyZSBpcyBub3RoaW5nIHRvIGZpbHRlciwgc28gdGhlIHF1ZXJ5IGdvZXMgdG8gdGhlXG4gICAqIHBvcnQgYW5kIGl0cyBhbnN3ZXIgQkVDT01FUyB0aGUgb3B0aW9uIGxpc3Qg4oCUIHRoZSBzYW1lIGNvbnRyb2wsIGZpbHRlcmluZyBvbmVcbiAgICogaG9wIGZ1cnRoZXIgYXdheS5cbiAgICovXG4gIGFzeW5jIG9uR2VuZUZpbHRlcihxdWVyeTogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgdGhpcy5nZW5lU2VhcmNoRmFpbGVkID0gZmFsc2U7XG4gICAgaWYgKCF0aGlzLmdlbmVzQXJlUmVtb3RlKSB7XG4gICAgICAvLyBSZXNpZGVudCBuYW1lczogc2VhcmNoIHRoZW0gaGVyZSBhbmQgbWF0ZXJpYWxpc2Ugb25seSB0aGUgdG9wIG1hdGNoZXMuIFRoZVxuICAgICAgLy8gZHJvcGRvd24ncyBvd24gZmlsdGVyIHRoZW4gcnVucyBvdmVyIHRob3NlIGFuZCBhZ3JlZXMg4oCUIHRoZXkgd2VyZSBjaG9zZW4gYnkgdGhlXG4gICAgICAvLyBzYW1lIHF1ZXJ5IOKAlCBzbyB0aGUgY29udHJvbCBiZWhhdmVzIGFzIGlmIGl0IHN0aWxsIGhlbGQgdGhlIHdob2xlIGxpc3QuXG4gICAgICB0aGlzLmdlbmVPcHRpb25zID0gc2VhcmNoR2VuZU5hbWVzKHRoaXMuZ2VuZU5hbWVzLCBxdWVyeSkubWFwKGdlbmVPcHRpb24pO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAoIXRoaXMuY29udHJvbHMpIHJldHVybjtcbiAgICAvLyBUeXBpbmcgb3V0cnVucyB0aGUgbG9va3VwLCBzbyBhIHNsb3cgYW5zd2VyIGZvciBhbiBlYXJsaWVyIHF1ZXJ5IHdvdWxkXG4gICAgLy8gcmVwbGFjZSB0aGUgb3B0aW9ucyBmb3IgdGhlIHRleHQgbm93IGluIHRoZSBib3gg4oCUIGluY2x1ZGluZyBhIGZhaWx1cmUsIHdoaWNoXG4gICAgLy8gd291bGQgd3JvbmdseSBtYXJrIHRoZSBjdXJyZW50IHF1ZXJ5IGFzIGZhaWxlZC5cbiAgICBjb25zdCBtaW5lID0gKyt0aGlzLmdlbmVTZWFyY2hUb2tlbjtcbiAgICBpZiAoIXF1ZXJ5KSB7XG4gICAgICB0aGlzLmdlbmVPcHRpb25zID0gW107XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRyeSB7XG4gICAgICBjb25zdCBuYW1lcyA9IGF3YWl0IHRoaXMuY29udHJvbHMuc2VhcmNoRmVhdHVyZXMocXVlcnksIDUwKTtcbiAgICAgIGlmIChtaW5lICE9PSB0aGlzLmdlbmVTZWFyY2hUb2tlbikgcmV0dXJuO1xuICAgICAgdGhpcy5nZW5lT3B0aW9ucyA9IG5hbWVzLm1hcChnZW5lT3B0aW9uKTtcbiAgICB9IGNhdGNoIHtcbiAgICAgIGlmIChtaW5lICE9PSB0aGlzLmdlbmVTZWFyY2hUb2tlbikgcmV0dXJuO1xuICAgICAgLy8gQSBmYWlsZWQgbG9va3VwIG11c3Qgbm90IHdlZGdlIHRoZSBjb250cm9sIOKAlCBzaG93IG5vbmUgYW5kIHNheSBzby5cbiAgICAgIHRoaXMuZ2VuZU9wdGlvbnMgPSBbXTtcbiAgICAgIHRoaXMuZ2VuZVNlYXJjaEZhaWxlZCA9IHRydWU7XG4gICAgfVxuICB9XG5cbiAgLyoqIEEgZ2VuZSB3YXMgcGlja2VkOyBpdCBzdXBlcnNlZGVzIGFueSBjb2x1bW4gc2VsZWN0aW9uLiAqL1xuICBvbkdlbmUobmFtZTogc3RyaW5nIHwgbnVsbCk6IHZvaWQge1xuICAgIHRoaXMuc2VsZWN0ZWRHZW5lID0gbmFtZTtcbiAgICBpZiAoIW5hbWUpIHtcbiAgICAgIHRoaXMuY29udHJvbHM/LmNsZWFyQ29sb3JCeSgpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICB0aGlzLnNlbGVjdGVkQ29sdW1uID0gbnVsbDtcbiAgICB0aGlzLmNvbnRyb2xzPy5jb2xvckJ5RmVhdHVyZShuYW1lKTtcbiAgfVxuXG4gIC8vIOKUgOKUgCBzZWxlY3Rpb24g4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG5cbiAgLyoqIFNlbGVjdCBldmVyeSBvYnNlcnZhdGlvbiBpbnNpZGUgdGhlIGRyYXduIHJlZ2lvbnMgKHRoZWlyIHVuaW9uKS4gKi9cbiAgc2VsZWN0RnJvbVJlZ2lvbnMoKTogdm9pZCB7XG4gICAgY29uc3QgY291bnQgPSB0aGlzLmNvbnRyb2xzPy5zZWxlY3RGcm9tUmVnaW9ucygpID8/IDA7XG4gICAgdGhpcy5zZWxlY3RlZENhdGVnb3J5ID0gbnVsbDtcbiAgICB0aGlzLnNlbGVjdGlvbk1pc3NlZCA9IGNvdW50ID09PSAwO1xuICB9XG5cbiAgLyoqIExlZ2VuZCBjbGljazogc2VsZWN0IG9uZSBjYXRlZ29yeS4gQ2xpY2tpbmcgdGhlIGFjdGl2ZSByb3cgY2xlYXJzIGl0LCBzbyBhXG4gICAqICBzZWNvbmQgY2xpY2sgaXMgYW4gdW5kbyByYXRoZXIgdGhhbiBhIG5vLW9wLiAqL1xuICBhc3luYyBzZWxlY3RDYXRlZ29yeShpbmRleDogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgYnkgPSB0aGlzLnZpZXcuY29sb3JCeTtcbiAgICBpZiAoIXRoaXMuY29udHJvbHMgfHwgYnk/LmtpbmQgIT09ICdjb2x1bW4nKSByZXR1cm47XG4gICAgaWYgKHRoaXMuc2VsZWN0ZWRDYXRlZ29yeSA9PT0gaW5kZXgpIHtcbiAgICAgIHRoaXMuY2xlYXJTZWxlY3Rpb24oKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IHRoaXMuY29udHJvbHMuc2VsZWN0Q2F0ZWdvcnkoYnkubmFtZSwgaW5kZXgpO1xuICAgICAgdGhpcy5zZWxlY3RlZENhdGVnb3J5ID0gaW5kZXg7XG4gICAgICB0aGlzLnNlbGVjdGlvbk1pc3NlZCA9IGZhbHNlO1xuICAgIH0gY2F0Y2gge1xuICAgICAgdGhpcy5zZWxlY3RlZENhdGVnb3J5ID0gbnVsbDtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRXhwYW5kIG9yIGNvbGxhcHNlIHRoZSBkaXN0cmlidXRpb24gc2VjdGlvbi5cbiAgICpcbiAgICogRXhwYW5kaW5nIFNDUk9MTFMgaXQgaW50byB2aWV3LCBiZWNhdXNlIHRoZSBwYW5lbCBpcyB0YWxsZXIgdGhhbiB0aGUgdmlld3BvcnRcbiAgICogb25jZSB0aGlzIHNlY3Rpb24gaXMgb3BlbiBhbmQgdGhlIGNoYXJ0IGlzIGl0cyBsYXN0IHJvdzogZXhwYW5kaW5nIGl0IGRyZXcgYVxuICAgKiBjaGFydCB+MzAwIHB4IGJlbG93IHRoZSBmb2xkLCB3aGljaCByZWFkcyBhcyB0aGUgc2VjdGlvbiBiZWluZyBlbXB0eS4gVGhlXG4gICAqIHNjcm9sbCBpcyBkZWZlcnJlZCBhIHRhc2sgc28gdGhlIGNoYXJ0J3Mgb3duIGRlZmVycmVkIGZpcnN0IGRyYXcgaGFzIGdpdmVuIHRoZVxuICAgKiBib2R5IGl0cyBoZWlnaHQuXG4gICAqL1xuICB0b2dnbGVDaGFydHMoKTogdm9pZCB7XG4gICAgdGhpcy5jaGFydHNPcGVuID0gIXRoaXMuY2hhcnRzT3BlbjtcbiAgICBpZiAoIXRoaXMuY2hhcnRzT3BlbikgcmV0dXJuO1xuICAgIHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQodGhpcy5jaGFydHNCb2R5SWQpXG4gICAgICAgID8uc2Nyb2xsSW50b1ZpZXcoeyBibG9jazogJ2VuZCcsIGJlaGF2aW9yOiAnc21vb3RoJyB9KTtcbiAgICB9LCAwKTtcbiAgfVxuXG4gIGNsZWFyU2VsZWN0aW9uKCk6IHZvaWQge1xuICAgIHRoaXMuY29udHJvbHM/LmNsZWFyU2VsZWN0aW9uKCk7XG4gICAgdGhpcy5zZWxlY3RlZENhdGVnb3J5ID0gbnVsbDtcbiAgICB0aGlzLnNlbGVjdGlvbk1pc3NlZCA9IGZhbHNlO1xuICB9XG5cbiAgLyoqIFRydWUgd2hpbGUgYSBzZWxlY3Rpb24gaXMgYWN0aXZlIOKAlCBldmVyeXRoaW5nIGVsc2UgcmVuZGVycyBtdXRlZC4gKi9cbiAgZ2V0IGhhc1NlbGVjdGlvbigpOiBib29sZWFuIHtcbiAgICByZXR1cm4gdGhpcy5zZWxlY3Rpb24uY291bnQgPiAwO1xuICB9XG5cbiAgLy8g4pSA4pSAIGRpc3BsYXkg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG5cbiAgLy8gUHJpbWVORydzIHNsaWRlciByZXBvcnRzIGBudW1iZXIgfCB1bmRlZmluZWRgOyBpZ25vcmUgdGhlIGVtcHR5IGNhc2UgcmF0aGVyXG4gIC8vIHRoYW4gd3JpdGluZyBgdW5kZWZpbmVkYCBpbnRvIHRoZSBzdG9yZSBhbmQgcmVuZGVyaW5nIE5hTi1zaXplZCBtYXJrZXJzLlxuICBvblBvaW50U2NhbGUodmFsdWU6IG51bWJlciB8IHVuZGVmaW5lZCk6IHZvaWQge1xuICAgIGlmICh2YWx1ZSA9PT0gdW5kZWZpbmVkKSByZXR1cm47XG4gICAgdGhpcy5jb250cm9scz8uc2V0Vmlld1N0YXRlKHsgcG9pbnRTY2FsZTogdmFsdWUgfSk7XG4gIH1cbiAgb25PcGFjaXR5KHZhbHVlOiBudW1iZXIgfCB1bmRlZmluZWQpOiB2b2lkIHtcbiAgICBpZiAodmFsdWUgPT09IHVuZGVmaW5lZCkgcmV0dXJuO1xuICAgIHRoaXMuY29udHJvbHM/LnNldFZpZXdTdGF0ZSh7IG9wYWNpdHk6IHZhbHVlIH0pO1xuICB9XG4gIG9uTG9nU2NhbGUob246IGJvb2xlYW4pOiB2b2lkIHtcbiAgICB0aGlzLmNvbnRyb2xzPy5zZXRWaWV3U3RhdGUoeyBsb2dTY2FsZTogb24gfSk7XG4gIH1cbiAgLyoqXG4gICAqIFNldCB3aGVuIHRoZSBhY3RpdmUgY2F0ZWdvcmljYWwgY29sb3VyaW5nIGhhcyBtb3JlIGNhdGVnb3JpZXMgdGhhbiB0aGUgM0RcbiAgICogY2xvdWQgY2FuIGtlZXAgYXBhcnQsIHNvIHRoZSBwb2ludHMgYXJlIGRyYXdpbmcgRkxBVC5cbiAgICpcbiAgICogU2FpZCBpbiB0aGUgcGFuZWwgcmF0aGVyIHRoYW4gbGVmdCB0byBhIGNvbnNvbGUgd2FybmluZzogYHN1YmNsYXNzYCAoMzM4KSBpc1xuICAgKiBzZXJ2ZWQgcHJlY2lzZWx5IGJlY2F1c2UgdGhlIGRlbnNpdHkgdm9sdW1lcyBhbmQgdGhlIDJEIHZpZXcgY2FuIHJlbmRlciBpdCwgYW5kXG4gICAqIGEgdXNlciB3aG8gcGlja3MgaXQgaW4gdGhlIGNsb3VkIGFuZCBzZWVzIG9uZSBjb2xvdXIgZGVzZXJ2ZXMgdG8ga25vdyBib3RoIHdoeVxuICAgKiBhbmQgd2hhdCB0byBkbyBpbnN0ZWFkLlxuICAgKi9cbiAgZ2V0IGV4Y2VlZHNDbG91ZFBhbGV0dGUoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHRoaXMuaXMzZCAmJiAodGhpcy5sZWdlbmQ/Lmxlbmd0aCA/PyAwKSA+IFNQQVRJQUxfM0RfTUFYX0NBVEVHT1JJRVM7XG4gIH1cbiAgLyoqIFRoZSBjZWlsaW5nIGl0c2VsZiwgZm9yIHRoZSBtZXNzYWdlLiAqL1xuICByZWFkb25seSBjbG91ZFBhbGV0dGVMaW1pdCA9IFNQQVRJQUxfM0RfTUFYX0NBVEVHT1JJRVM7XG5cbiAgb25HZW5lTWFwKG9uOiBib29sZWFuKTogdm9pZCB7XG4gICAgdGhpcy5jb250cm9scz8uc2V0Vmlld1N0YXRlKHsgZ2VuZU1hcDogb24gfSk7XG4gIH1cbiAgLyoqIDNEOiBzbW9vdGggdGhlIHBlci1zZWN0aW9uIHNoZWV0cyBhbG9uZyB6IGludG8gYSBjb250aW51b3VzIHZvbHVtZS4gKi9cbiAgb25HZW5lTWFwVm9sdW1lKG9uOiBib29sZWFuKTogdm9pZCB7XG4gICAgdGhpcy5jb250cm9scz8uc2V0Vmlld1N0YXRlKHsgZ2VuZU1hcFZvbHVtZTogb24gfSk7XG4gIH1cbiAgLyoqIDNEOiByZXN0cmljdCB0aGUgc2hlZXRzIHRvIG9uZSBpbWFnZWQgc2VjdGlvbi4gKi9cbiAgb25HZW5lTWFwT25lU2VjdGlvbihvbjogYm9vbGVhbik6IHZvaWQge1xuICAgIGlmICghb24pIHtcbiAgICAgIHRoaXMuY29udHJvbHM/LnNldFZpZXdTdGF0ZSh7IGdlbmVNYXBTZWN0aW9uOiBudWxsIH0pO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBtaWRkbGUgPSB0aGlzLnNlY3Rpb25zID8gTWF0aC5mbG9vcigodGhpcy5zZWN0aW9ucy5sZW5ndGggLSAxKSAvIDIpIDogMDtcbiAgICB0aGlzLmNvbnRyb2xzPy5zZXRWaWV3U3RhdGUoeyBnZW5lTWFwU2VjdGlvbjogbWlkZGxlIH0pO1xuICB9XG4gIG9uR2VuZU1hcFNlY3Rpb24odmFsdWU6IG51bWJlciB8IHVuZGVmaW5lZCk6IHZvaWQge1xuICAgIGlmICh2YWx1ZSA9PT0gdW5kZWZpbmVkKSByZXR1cm47XG4gICAgdGhpcy5jb250cm9scz8uc2V0Vmlld1N0YXRlKHsgZ2VuZU1hcFNlY3Rpb246IHZhbHVlIH0pO1xuICB9XG4gIGdldCBnZW5lTWFwT25lU2VjdGlvbigpOiBib29sZWFuIHtcbiAgICByZXR1cm4gdGhpcy52aWV3LmdlbmVNYXBTZWN0aW9uICE9IG51bGw7XG4gIH1cbiAgLyoqIFwiMTIgb2YgNTNcIiBmb3IgdGhlIGdlbmUgbWFwJ3Mgb3duIHNlY3Rpb24sIDEtYmFzZWQgbGlrZSB0aGUgY2xvdWQncy4gKi9cbiAgZ2V0IGdlbmVNYXBTZWN0aW9uTGFiZWwoKTogc3RyaW5nIHtcbiAgICBjb25zdCB0b3RhbCA9IHRoaXMuc2VjdGlvbnM/Lmxlbmd0aCA/PyAwO1xuICAgIGlmICghdG90YWwpIHJldHVybiAnJztcbiAgICBjb25zdCBhdCA9IE1hdGgubWF4KDAsIE1hdGgubWluKHRvdGFsIC0gMSwgdGhpcy52aWV3LmdlbmVNYXBTZWN0aW9uID8/IDApKTtcbiAgICByZXR1cm4gYCR7YXQgKyAxfSBvZiAke3RvdGFsfWA7XG4gIH1cbiAgb25HZW5lTWFwU21vb3RoaW5nKHZhbHVlOiBudW1iZXIgfCB1bmRlZmluZWQpOiB2b2lkIHtcbiAgICBpZiAodmFsdWUgPT09IHVuZGVmaW5lZCkgcmV0dXJuO1xuICAgIHRoaXMuY29udHJvbHM/LnNldFZpZXdTdGF0ZSh7IGdlbmVNYXBTbW9vdGhpbmc6IHZhbHVlIH0pO1xuICB9XG4gIG9uR2VuZU1hcE9wYWNpdHkodmFsdWU6IG51bWJlciB8IHVuZGVmaW5lZCk6IHZvaWQge1xuICAgIGlmICh2YWx1ZSA9PT0gdW5kZWZpbmVkKSByZXR1cm47XG4gICAgdGhpcy5jb250cm9scz8uc2V0Vmlld1N0YXRlKHsgZ2VuZU1hcE9wYWNpdHk6IHZhbHVlIH0pO1xuICB9XG4gIC8qKlxuICAgKiBXaGF0IHRoZSAzRCBnZW5lIG1hcCBpcyBjdXJyZW50bHkgc2hvd2luZywgc2FpZCBwbGFpbmx5IOKAlCB0aGUgc2hlZXRzIGFyZSBhXG4gICAqIG1lYXN1cmVtZW50IGFuZCB0aGUgdm9sdW1lIGlzIGFuIGVzdGltYXRlLCBhbmQgdGhlIHBhbmVsIGhhcyB0byBiZSB0aGUgcGxhY2VcbiAgICogdGhhdCBzYXlzIHdoaWNoIG9uZSBpcyBvbiBzY3JlZW4uXG4gICAqL1xuICBnZXQgZ2VuZU1hcFZvbHVtZU5vdGUoKTogc3RyaW5nIHtcbiAgICBjb25zdCB0b3RhbCA9IHRoaXMuc2VjdGlvbnM/Lmxlbmd0aCA/PyAwO1xuICAgIGlmICh0aGlzLnZpZXcuZ2VuZU1hcFZvbHVtZSkge1xuICAgICAgcmV0dXJuICdJbnRlcnBvbGF0ZWQgYWxvbmcgejogdGhlIHBsYW5lcyBiZXR3ZWVuIHRoZSBpbWFnZWQgc2VjdGlvbnMgY2FycnkgYW4gJ1xuICAgICAgICArICdFU1RJTUFURSwgc21vb3RoZWQgZnJvbSB0aGVpciBuZWlnaGJvdXJzXFwnIG1lYW4uIE5vdGhpbmcgaXMgZHJhd24gYmV5b25kIHRoZSAnXG4gICAgICAgICsgJ291dGVybW9zdCBzZWN0aW9uLic7XG4gICAgfVxuICAgIGlmICh0aGlzLmdlbmVNYXBPbmVTZWN0aW9uKSB7XG4gICAgICByZXR1cm4gJ09uZSBpbWFnZWQgc2VjdGlvblxcJ3MgZmllbGQg4oCUIG1lYXN1cmVkLCBub3QgaW50ZXJwb2xhdGVkLiBIaWRlIHRoZSAnXG4gICAgICAgICsgJ29ic2VydmF0aW9ucyB0byByZWFkIGl0LCBvciBsZWF2ZSB0aGVtIG9uIHRvIGNoZWNrIHRoZSBmaWVsZCBhZ2FpbnN0IHRoZW0uJztcbiAgICB9XG4gICAgcmV0dXJuIGBPbmUgZmllbGQgcGVyIGltYWdlZCBzZWN0aW9uJHt0b3RhbCA/IGAgKCR7dG90YWx9KWAgOiAnJ30sIGF0IGl0cyBvd24gZGVwdGgsIGBcbiAgICAgICsgJ3dpdGggdGhlIGdhcHMgYmV0d2VlbiBzZWN0aW9ucyBlbXB0eS4gS2VybmVsLXdlaWdodGVkIG1lYW4gcGVyIGNlbGwsIG5vdCBhIHN1bS4nO1xuICB9XG5cbiAgLyoqIFRydWUgd2hpbGUgYSBnZW5lIGlzIHRoZSBjb2xvdXIgc291cmNlIOKAlCB0aGUgb25seSB0aGluZyBhIGdlbmUgbWFwIGNhbiBtYXAuICovXG4gIGdldCBjYW5NYXBHZW5lKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB0aGlzLnZpZXcuY29sb3JCeT8ua2luZCA9PT0gJ2ZlYXR1cmUnO1xuICB9XG5cbiAgLy8g4pSA4pSAIHdoYXQgdGhlIDNEIHNjZW5lIGRyYXdzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuICAvLyBUaGUgdm9sdW1lLCB0aGUgY2xvdWQgYW5kIHRoZSBkZW5zaXR5IHZvbHVtZXMgc2hhcmUgb25lIHNwYWNlLCBzbyBlYWNoIG9uZVxuICAvLyBoaWRlcyB0aGUgb3RoZXJzIHRvIHNvbWUgZGVncmVlLiBJbmRlcGVuZGVudCB0b2dnbGVzIGJlY2F1c2UgdGhlIHVzZWZ1bCB2aWV3c1xuICAvLyBhcmUgdGhlIGNvbWJpbmF0aW9ucywgbm90IGEgc2luZ2xlIFwiM0QgbW9kZVwiLlxuXG4gIG9uU2hvd1ZvbHVtZShvbjogYm9vbGVhbik6IHZvaWQge1xuICAgIHRoaXMuY29udHJvbHM/LnNldFZpZXdTdGF0ZSh7IHNob3dWb2x1bWU6IG9uIH0pO1xuICB9XG4gIG9uVm9sdW1lT3BhY2l0eSh2YWx1ZTogbnVtYmVyIHwgdW5kZWZpbmVkKTogdm9pZCB7XG4gICAgaWYgKHZhbHVlID09PSB1bmRlZmluZWQpIHJldHVybjtcbiAgICB0aGlzLmNvbnRyb2xzPy5zZXRWaWV3U3RhdGUoeyB2b2x1bWVPcGFjaXR5OiB2YWx1ZSB9KTtcbiAgfVxuICBvblNob3dQb2ludHMob246IGJvb2xlYW4pOiB2b2lkIHtcbiAgICB0aGlzLmNvbnRyb2xzPy5zZXRWaWV3U3RhdGUoeyBzaG93UG9pbnRzOiBvbiB9KTtcbiAgfVxuICAvKiogVGhlIFwib25lIHNlY3Rpb24gYXQgYSB0aW1lXCIgc3dpdGNoOiBudWxsIHJlc3RvcmVzIHRoZSB3aG9sZSBzdGFjay4gKi9cbiAgb25PbmVTZWN0aW9uKG9uOiBib29sZWFuKTogdm9pZCB7XG4gICAgaWYgKCFvbikge1xuICAgICAgdGhpcy5jb250cm9scz8uc2V0Vmlld1N0YXRlKHsgcG9pbnRTZWN0aW9uOiBudWxsIH0pO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICAvLyBPcGVuIGluIHRoZSBtaWRkbGUgb2YgdGhlIHN0YWNrIHJhdGhlciB0aGFuIG9uIHRoZSBmaXJzdCBzZWN0aW9uLCB3aGljaCBmb3JcbiAgICAvLyBhIGJyYWluIGlzIGEgbmVhcmx5IGVtcHR5IG9sZmFjdG9yeS1idWxiIHNsaWRlLlxuICAgIGNvbnN0IG1pZGRsZSA9IHRoaXMuc2VjdGlvbnMgPyBNYXRoLmZsb29yKCh0aGlzLnNlY3Rpb25zLmxlbmd0aCAtIDEpIC8gMikgOiAwO1xuICAgIHRoaXMuY29udHJvbHM/LnNldFZpZXdTdGF0ZSh7IHBvaW50U2VjdGlvbjogbWlkZGxlIH0pO1xuICB9XG4gIG9uUG9pbnRTZWN0aW9uKHZhbHVlOiBudW1iZXIgfCB1bmRlZmluZWQpOiB2b2lkIHtcbiAgICBpZiAodmFsdWUgPT09IHVuZGVmaW5lZCkgcmV0dXJuO1xuICAgIHRoaXMuY29udHJvbHM/LnNldFZpZXdTdGF0ZSh7IHBvaW50U2VjdGlvbjogdmFsdWUgfSk7XG4gIH1cbiAgLyoqIFRydWUgd2hpbGUgdGhlIGNsb3VkIGlzIHJlc3RyaWN0ZWQgdG8gYSBzaW5nbGUgc2VjdGlvbi4gKi9cbiAgZ2V0IG9uZVNlY3Rpb24oKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHRoaXMudmlldy5wb2ludFNlY3Rpb24gIT0gbnVsbDtcbiAgfVxuICAvKiogSGlnaGVzdCBzZWN0aW9uIGluZGV4IHRoZSBzbGlkZXIgY2FuIHJlYWNoLiAqL1xuICBnZXQgbGFzdFNlY3Rpb24oKTogbnVtYmVyIHtcbiAgICByZXR1cm4gTWF0aC5tYXgoMCwgKHRoaXMuc2VjdGlvbnM/Lmxlbmd0aCA/PyAxKSAtIDEpO1xuICB9XG4gIC8qKiBcIjEyIG9mIDUzXCIg4oCUIDEtYmFzZWQsIGJlY2F1c2UgdGhlIHNlY3Rpb25zIGFyZSBzbGlkZXMsIG5vdCBhcnJheSBzbG90cy4gKi9cbiAgZ2V0IHNlY3Rpb25MYWJlbCgpOiBzdHJpbmcge1xuICAgIGNvbnN0IHRvdGFsID0gdGhpcy5zZWN0aW9ucz8ubGVuZ3RoID8/IDA7XG4gICAgaWYgKCF0b3RhbCkgcmV0dXJuICcnO1xuICAgIGNvbnN0IGF0ID0gTWF0aC5tYXgoMCwgTWF0aC5taW4odG90YWwgLSAxLCB0aGlzLnZpZXcucG9pbnRTZWN0aW9uID8/IDApKTtcbiAgICByZXR1cm4gYCR7YXQgKyAxfSBvZiAke3RvdGFsfWA7XG4gIH1cbiAgLyoqIFdoZXRoZXIgdGhpcyBkYXRhc2V0IGhhcyBzZWN0aW9ucyB0byBwaWNrIGZyb20gYXQgYWxsLiAqL1xuICBnZXQgaXNTZWN0aW9uZWQoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuICh0aGlzLnNlY3Rpb25zPy5sZW5ndGggPz8gMCkgPiAxO1xuICB9XG5cbiAgb25EZW5zaXR5Vm9sdW1lKG9uOiBib29sZWFuKTogdm9pZCB7XG4gICAgdGhpcy5jb250cm9scz8uc2V0Vmlld1N0YXRlKHsgZGVuc2l0eVZvbHVtZTogb24gfSk7XG4gIH1cbiAgb25EZW5zaXR5U21vb3RoaW5nKHZhbHVlOiBudW1iZXIgfCB1bmRlZmluZWQpOiB2b2lkIHtcbiAgICBpZiAodmFsdWUgPT09IHVuZGVmaW5lZCkgcmV0dXJuO1xuICAgIHRoaXMuY29udHJvbHM/LnNldFZpZXdTdGF0ZSh7IGRlbnNpdHlTbW9vdGhpbmc6IHZhbHVlIH0pO1xuICB9XG5cbiAgLyoqIFdoYXQgdGhlIGRlbnNpdHkgdm9sdW1lcyBhcmUgYWN0dWFsbHkgc2hvd2luZywgc2FpZCBwbGFpbmx5IOKAlCBhbiBlc3RpbWF0ZSBpc1xuICAgKiAgb25seSBob25lc3QgaWYgdGhlIHJlYWRlciBrbm93cyBpdCBpcyBvbmUsIGFuZCB3aGljaCBjbHVzdGVycyBhcmUgaW4gdmlldy4gKi9cbiAgZ2V0IGRlbnNpdHlOb3RlKCk6IHN0cmluZyB7XG4gICAgY29uc3QgY2FwcGVkID0gYHRoZSAke1NwYXRpYWxDb250cm9sc0NvbXBvbmVudC5ERU5TSVRZX01BWF9DTFVTVEVSU30gbGFyZ2VzdCBjbHVzdGVyc2A7XG4gICAgY29uc3Qgd2hhdCA9IHRoaXMubGVnZW5kID8gY2FwcGVkIDogdGhpcy5oYXNTZWxlY3Rpb24gPyAndGhlIHNlbGVjdGVkIGNlbGxzJyA6ICdhbGwgY2VsbHMnO1xuICAgIHJldHVybiBgRGVuc2l0eSBlc3RpbWF0ZSBvdmVyICR7d2hhdH0g4oCUIHNtb290aGVkIGJldHdlZW4gdGhlIGltYWdlZCBzZWN0aW9ucywgYFxuICAgICAgKyAnbm90IG1lYXN1cmVkIGNlbGxzLiBMb3dlciBPcGFjaXR5IHRvIHJlYWQgdGhlIGZpZWxkcyB1bmRlciB0aGUgY2xvdWQuJztcbiAgfVxuXG4gIC8qKiBNaXJyb3JzIHRoZSByZW5kZXJlcidzIGNhcCwgZm9yIHRoZSBub3RlIG9ubHkuICovXG4gIHByaXZhdGUgc3RhdGljIHJlYWRvbmx5IERFTlNJVFlfTUFYX0NMVVNURVJTID0gNjtcbiAgb25DbGlwKHZhbHVlOiBbbnVtYmVyLCBudW1iZXJdKTogdm9pZCB7XG4gICAgdGhpcy5jb250cm9scz8uc2V0Vmlld1N0YXRlKHsgcGVyY2VudGlsZUNsaXA6IHZhbHVlIH0pO1xuICB9XG4gIHJlc2V0KCk6IHZvaWQge1xuICAgIHRoaXMuY29udHJvbHM/LnNldFZpZXdTdGF0ZSh7IC4uLkRFRkFVTFRfU1BBVElBTF9WSUVXIH0pO1xuICAgIHRoaXMuY2xlYXJTZWxlY3Rpb24oKTtcbiAgICB0aGlzLnNlbGVjdGVkQ29sdW1uID0gbnVsbDtcbiAgICB0aGlzLnNlbGVjdGVkR2VuZSA9IG51bGw7XG4gIH1cblxuICAvKipcbiAgICogRGVzY3JpcHRpb24gb2YgdGhlIGFjdGl2ZSBjb2x1bW4sIHdoZW4gaXQgaGFzIG9uZS4gU3VyZmFjZWQgYmVjYXVzZSBhXG4gICAqIERFUklWRUQgY29sdW1uIChrLW1lYW5zIGNsdXN0ZXJzLCBRQyB0b3RhbHMgY29tcHV0ZWQgYXQgY29udmVyc2lvbikgbXVzdCBub3RcbiAgICogcmVhZCBhcyB0aG91Z2ggaXQgY2FtZSB3aXRoIHRoZSBkYXRhLlxuICAgKi9cbiAgZ2V0IGFjdGl2ZURlc2NyaXB0aW9uKCk6IHN0cmluZyB8IG51bGwge1xuICAgIGNvbnN0IGJ5ID0gdGhpcy52aWV3LmNvbG9yQnk7XG4gICAgaWYgKCFieSB8fCBieS5raW5kICE9PSAnY29sdW1uJykgcmV0dXJuIG51bGw7XG4gICAgcmV0dXJuIHRoaXMuZGF0YXNldD8uY29sdW1ucy5maW5kKChjKSA9PiBjLm5hbWUgPT09IGJ5Lm5hbWUpPy5kZXNjcmlwdGlvbiA/PyBudWxsO1xuICB9XG5cbiAgLyoqIExhYmVsIGZvciB0aGUgY3VycmVudCBjb2xvdXJpbmcsIGZvciB0aGUga2V5J3MgaGVhZGluZy4gKi9cbiAgZ2V0IGNvbG9yQnlMYWJlbCgpOiBzdHJpbmcge1xuICAgIGNvbnN0IGJ5ID0gdGhpcy52aWV3LmNvbG9yQnk7XG4gICAgaWYgKCFieSkgcmV0dXJuICdGbGF0IGNvbG91cic7XG4gICAgcmV0dXJuIGJ5LmtpbmQgPT09ICdmZWF0dXJlJyA/IGBHZW5lIMK3ICR7YnkubmFtZX1gIDogYnkubmFtZTtcbiAgfVxuXG4gIC8qKiBUcnVlIHdoZW4gdGhlIGFjdGl2ZSBjb2xvdXJpbmcgaXMgYSBjYXRlZ29yaWNhbCBjb2x1bW4gKGRyaXZlcyB0aGUga2V5KS4gKi9cbiAgZ2V0IGlzQ2F0ZWdvcmljYWwoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHRoaXMubGVnZW5kICE9PSBudWxsO1xuICB9XG5cbiAgLyoqIFdoZXRoZXIgbG9nL2NsaXAgYXBwbHkg4oCUIHRoZXkgYXJlIGNvbnRpbnVvdXMtb25seSBrbm9icy4gKi9cbiAgZ2V0IGlzQ29udGludW91cygpOiBib29sZWFuIHtcbiAgICByZXR1cm4gISF0aGlzLnZpZXcuY29sb3JCeSAmJiB0aGlzLmxlZ2VuZCA9PT0gbnVsbDtcbiAgfVxuXG4gIC8vIOKUgOKUgCBpbnRlcm5hbHMg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG5cbiAgcHJpdmF0ZSBjb2x1bW5MYWJlbChjOiBTcGF0aWFsQ29sdW1uTWV0YSk6IHN0cmluZyB7XG4gICAgY29uc3Qga2luZCA9IGMua2luZCA9PT0gJ2NhdGVnb3JpY2FsJ1xuICAgICAgPyBgJHsoYyBhcyBDYXRlZ29yaWNhbENvbHVtbk1ldGEpLmNhdGVnb3JpZXMubGVuZ3RofSBjYXRlZ29yaWVzYFxuICAgICAgOiBjLnVuaXQgPz8gJ2NvbnRpbnVvdXMnO1xuICAgIHJldHVybiBgJHtjLm5hbWV9IOKAlCAke2tpbmR9YDtcbiAgfVxuXG4gIC8qKiBSZWJ1aWxkIHRoZSBsZWdlbmQgb3IgY29sb3VyIGJhciBmb3IgdGhlIGN1cnJlbnQgY29sb3VyaW5nLiAqL1xuICBwcml2YXRlIGFzeW5jIHJlZnJlc2hLZXkoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgYnkgPSB0aGlzLnZpZXcuY29sb3JCeTtcbiAgICBpZiAoIWJ5IHx8ICF0aGlzLmNvbnRyb2xzKSB7XG4gICAgICB0aGlzLmxlZ2VuZCA9IG51bGw7XG4gICAgICB0aGlzLmNvbG9yQmFyQ3NzID0gbnVsbDtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgbWV0YSA9IGJ5LmtpbmQgPT09ICdjb2x1bW4nXG4gICAgICA/IHRoaXMuZGF0YXNldD8uY29sdW1ucy5maW5kKChjKSA9PiBjLm5hbWUgPT09IGJ5Lm5hbWUpXG4gICAgICA6IHVuZGVmaW5lZDtcblxuICAgIGNvbnN0IG1pbmUgPSArK3RoaXMua2V5VG9rZW47XG4gICAgaWYgKG1ldGE/LmtpbmQgPT09ICdjYXRlZ29yaWNhbCcpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IGNvbG9ycyA9IGF3YWl0IHRoaXMuY29udHJvbHMuY2F0ZWdvcnlDb2xvcnMoYnkubmFtZSk7XG4gICAgICAgIC8vIFNhbWUgcmFjZSwgc2FtZSBjb3N0IGlmIGl0IGlzIGxvc3Q6IGEgc2xvd2VyIGVhcmxpZXIgY29sdW1uIHdvdWxkIHBhaW50XG4gICAgICAgIC8vIGl0cyBwYWxldHRlIGludG8gdGhlIGxlZ2VuZCBmb3IgdGhlIGNvbHVtbiBub3cgc2VsZWN0ZWQuXG4gICAgICAgIGlmIChtaW5lICE9PSB0aGlzLmtleVRva2VuKSByZXR1cm47XG4gICAgICAgIHRoaXMubGVnZW5kID0gbWV0YS5jYXRlZ29yaWVzLm1hcCgobGFiZWwsIGkpID0+ICh7IGxhYmVsLCBjb2xvcjogY29sb3JzW2ldIH0pKTtcbiAgICAgICAgdGhpcy5jb2xvckJhckNzcyA9IG51bGw7XG4gICAgICB9IGNhdGNoIHtcbiAgICAgICAgaWYgKG1pbmUgIT09IHRoaXMua2V5VG9rZW4pIHJldHVybjtcbiAgICAgICAgLy8gVGhlIGNvbHVtbidzIHZhbHVlcyBtYXkgbm90IGhhdmUgbG9hZGVkIHlldDsgbGVhdmUgdGhlIGtleSBlbXB0eVxuICAgICAgICAvLyByYXRoZXIgdGhhbiBzaG93aW5nIGEgbGVnZW5kIHRoYXQgbWlnaHQgbm90IG1hdGNoIHRoZSByZW5kZXIuXG4gICAgICAgIHRoaXMubGVnZW5kID0gbnVsbDtcbiAgICAgICAgdGhpcy5jb2xvckJhckNzcyA9IG51bGw7XG4gICAgICB9XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gQ29udGludW91cyAoYSBudW1lcmljIGNvbHVtbiBvciBhIGdlbmUpOiBhIGNvbG91ciBiYXIgZnJvbSB0aGUgc2FtZSBMVVQuXG4gICAgdGhpcy5sZWdlbmQgPSBudWxsO1xuICAgIHRoaXMuY29sb3JCYXJDc3MgPSB0aGlzLmJ1aWxkQ29sb3JCYXIoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBUaGUgY29udGludW91cyBjb2xvdXIgc2NhbGUncyBjb2xvcm1hcC4gQ2xlYXJpbmcgaXQgZ29lcyBiYWNrIHRvIGZvbGxvd2luZ1xuICAgKiB0aGUgaW1hZ2Uncywgd2hpY2ggaXMgdGhlIGRlZmF1bHQuXG4gICAqL1xuICBvbkNvbnRpbnVvdXNDb2xvcm1hcChub2RlOiBDb2xvcm1hcE5vZGUgfCBudWxsKTogdm9pZCB7XG4gICAgdGhpcy5zZWxlY3RlZENvbG9ybWFwTm9kZSA9IG5vZGU7XG4gICAgLy8gVGhlIHZhbHVlIGlzIGEgQ29sb3JtYXBWYWx1ZSwgd2hpY2ggaXMgYSBOQU1FIGZvciB0aGUgYnVpbHQtaW4gc2NhbGVzIGFuZCBhblxuICAgIC8vIGlubGluZSBgW3N0b3AsIGNvbG91cl1gIGFycmF5IGZvciB0aGUgcmVzdCDigJQgaGFsZiB0aGUgbGlicmFyeSdzIGNvbG9ybWFwc1xuICAgIC8vIGFyZSB0aGUgYXJyYXkga2luZCwgc28gYW55dGhpbmcgdGhhdCBvbmx5IGFjY2VwdHMgYSBzdHJpbmcgc2lsZW50bHkgZHJvcHNcbiAgICAvLyB0aGVtLiBBIGdyb3VwIHJvdyBjYXJyaWVzIG5vIHZhbHVlIGFuZCBjbGVhcnMgdGhlIHNldHRpbmcuXG4gICAgdGhpcy5jb250cm9scz8uc2V0Vmlld1N0YXRlKHsgY29udGludW91c0NvbG9ybWFwOiBub2RlPy5kYXRhPy52YWx1ZSA/PyBudWxsIH0pO1xuICB9XG5cbiAgLyoqIFRoZSB0cmVlIG5vZGUgaG9sZGluZyBhIGNvbG9ybWFwIHZhbHVlLCBzbyB0aGUgcGlja2VyIHNob3dzIHdoYXQgaXMgaW4gdXNlLiAqL1xuICBwcml2YXRlIGNvbG9ybWFwTm9kZUZvcih2YWx1ZTogQ29sb3JtYXBWYWx1ZSB8IG51bGwpOiBDb2xvcm1hcE5vZGUgfCBudWxsIHtcbiAgICBpZiAoIXZhbHVlKSByZXR1cm4gbnVsbDtcbiAgICBmb3IgKGNvbnN0IGdyb3VwIG9mIHRoaXMuY29sb3JtYXBPcHRpb25zKSB7XG4gICAgICBmb3IgKGNvbnN0IG5vZGUgb2YgZ3JvdXAuY2hpbGRyZW4gPz8gW10pIHtcbiAgICAgICAgLy8gUmVmZXJlbmNlIGVxdWFsaXR5OiB0aGUgdmFsdWUgY2FtZSBvdXQgb2YgdGhpcyBzYW1lIHRyZWUsIGFuZCBhbiBpbmxpbmVcbiAgICAgICAgLy8gc2NhbGUgaXMgYSAyNTYtZW50cnkgYXJyYXkgbm90IHdvcnRoIGNvbXBhcmluZyBlbGVtZW50IGJ5IGVsZW1lbnQuXG4gICAgICAgIGlmIChub2RlLmRhdGE/LnZhbHVlID09PSB2YWx1ZSkgcmV0dXJuIG5vZGU7XG4gICAgICB9XG4gICAgICBpZiAoZ3JvdXAuZGF0YT8udmFsdWUgPT09IHZhbHVlKSByZXR1cm4gZ3JvdXA7XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgLyoqXG4gICAqIGBsaW5lYXItZ3JhZGllbnQoLi4uKWAgc2FtcGxpbmcgdGhlIGFjdGl2ZSBjb2xvcm1hcCBhdCAxNiBzdG9wcy5cbiAgICpcbiAgICogUmVzb2x2ZWQgZXhhY3RseSB0aGUgd2F5IHRoZSByZW5kZXJlciByZXNvbHZlcyBpdCDigJQgdGhlIHNhbWUgb3ZlcnJpZGUsIHRoZVxuICAgKiBzYW1lIGdyZXkgZmFsbGJhY2suIEJ1aWx0IGZyb20gYGx1dEZvcmAgYWxvbmUsIHRoaXMgYmFyIHNob3dlZCBhIGJsYWNrLXRvLXdoaXRlXG4gICAqIHJhbXAgd2hpbGUgdGhlIGNhbnZhcyBkcmV3IFZpcmlkaXMsIHdoaWNoIG1ha2VzIHRoZSBrZXkgd29yc2UgdGhhbiBubyBrZXkuXG4gICAqL1xuICBwcml2YXRlIGJ1aWxkQ29sb3JCYXIoKTogc3RyaW5nIHtcbiAgICBjb25zdCBsdXQgPSBzcGF0aWFsQ29udGludW91c0x1dChcbiAgICAgIHRoaXMuY29sb3JtYXA/LmRhdGE/LnZhbHVlLCB0aGlzLnJldmVyc2UsIHRoaXMudmlldy5jb250aW51b3VzQ29sb3JtYXAsXG4gICAgKTtcbiAgICBjb25zdCBzdG9wczogc3RyaW5nW10gPSBbXTtcbiAgICBjb25zdCBzdGVwcyA9IDE2O1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDw9IHN0ZXBzOyBpKyspIHtcbiAgICAgIGNvbnN0IFtyLCBnLCBiXSA9IGx1dFtNYXRoLnJvdW5kKChpIC8gc3RlcHMpICogMjU1KV07XG4gICAgICBzdG9wcy5wdXNoKGByZ2IoJHtyfSwke2d9LCR7Yn0pICR7KChpIC8gc3RlcHMpICogMTAwKS50b0ZpeGVkKDApfSVgKTtcbiAgICB9XG4gICAgcmV0dXJuIGBsaW5lYXItZ3JhZGllbnQodG8gcmlnaHQsICR7c3RvcHMuam9pbignLCAnKX0pYDtcbiAgfVxufVxuIiwiPHAtZGlhbG9nIGhlYWRlcj1cIlNwYXRpYWwgb21pY3NcIlxuICAgICAgICAgIFt2aXNpYmxlXT1cInZpc2libGVcIiAodmlzaWJsZUNoYW5nZSk9XCJvblZpc2libGVDaGFuZ2UoJGV2ZW50KVwiXG4gICAgICAgICAgW21vZGFsXT1cImZhbHNlXCIgW3Jlc2l6YWJsZV09XCJ0cnVlXCIgW2RyYWdnYWJsZV09XCJ0cnVlXCJcbiAgICAgICAgICBwb3NpdGlvbj1cImxlZnRcIlxuICAgICAgICAgIChvblJlc2l6ZUVuZCk9XCJvblJlc2l6ZUVuZCgpXCJcbiAgICAgICAgICBbc3R5bGVdPVwieyB3aWR0aDogJzMwcmVtJyB9XCJcbiAgICAgICAgICBbYnJlYWtwb2ludHNdPVwieyAnOTYwcHgnOiAnOTB2dycgfVwiXG4gICAgICAgICAgc3R5bGVDbGFzcz1cInNwYXRpYWwtY29udHJvbHMtZGlhbG9nXCI+XG5cbiAgPCEtLSBObyBwb3J0IGJvdW5kOiBzYXkgd2h5IHJhdGhlciB0aGFuIHNob3dpbmcgY29udHJvbHMgdGhhdCBkbyBub3RoaW5nLiAtLT5cbiAgPGRpdiBjbGFzcz1cInNjLWVtcHR5XCIgKm5nSWY9XCIhY29udHJvbHNcIj5cbiAgICBObyBzcGF0aWFsLW9taWNzIGRhdGEgc291cmNlIGlzIGNvbmZpZ3VyZWQuIEEgaG9zdCBwcm92aWRlcyBvbmUgYnkgYmluZGluZ1xuICAgIDxjb2RlPlNQQVRJQUxfREFUQV9QT1JUPC9jb2RlPi5cbiAgPC9kaXY+XG5cbiAgPGRpdiBjbGFzcz1cInNjLWVtcHR5XCIgKm5nSWY9XCJjb250cm9scyAmJiAhZGF0YXNldFwiPlxuICAgIE5vIGRhdGFzZXQgaXMgbG9hZGVkLlxuICA8L2Rpdj5cblxuICA8ZGl2IGNsYXNzPVwic2MtYm9keVwiICpuZ0lmPVwiY29udHJvbHMgJiYgZGF0YXNldCBhcyBkc1wiPlxuXG4gICAgPGRpdiBjbGFzcz1cInNjLWRhdGFzZXRcIj5cbiAgICAgIDxzcGFuIGNsYXNzPVwic2MtZGF0YXNldC1uYW1lXCI+e3sgZHMubmFtZSB9fTwvc3Bhbj5cbiAgICAgIDxzcGFuIGNsYXNzPVwic2MtZGF0YXNldC1jb3VudFwiPnt7IGRzLm9ic2VydmF0aW9ucy5jb3VudCB8IG51bWJlciB9fSBvYnNlcnZhdGlvbnM8L3NwYW4+XG4gICAgPC9kaXY+XG5cbiAgICA8IS0tIENvbG91ciBzb3VyY2UuIENvbHVtbiBhbmQgZ2VuZSBhcmUgbXV0dWFsbHkgZXhjbHVzaXZlOiBwaWNraW5nIG9uZVxuICAgICAgICAgY2xlYXJzIHRoZSBvdGhlciwgc28gd2hhdCBkcml2ZXMgdGhlIGNvbG91cnMgaXMgbmV2ZXIgYW1iaWd1b3VzLiAtLT5cbiAgICA8ZGl2IGNsYXNzPVwic2Mtc2VjdGlvblwiPlxuICAgICAgPGxhYmVsIGNsYXNzPVwic2MtbGJsXCIgZm9yPVwic2MtY29sdW1uXCI+Q29sb3VyIGJ5IGNvbHVtbjwvbGFiZWw+XG4gICAgICA8cC1kcm9wZG93biBpbnB1dElkPVwic2MtY29sdW1uXCIgW29wdGlvbnNdPVwiY29sdW1uT3B0aW9uc1wiXG4gICAgICAgICAgICAgICAgICBvcHRpb25MYWJlbD1cImxhYmVsXCIgb3B0aW9uVmFsdWU9XCJ2YWx1ZVwiXG4gICAgICAgICAgICAgICAgICBbbmdNb2RlbF09XCJzZWxlY3RlZENvbHVtblwiIChuZ01vZGVsQ2hhbmdlKT1cIm9uQ29sdW1uKCRldmVudClcIlxuICAgICAgICAgICAgICAgICAgcGxhY2Vob2xkZXI9XCJOb25lIChmbGF0IGNvbG91cilcIiBhcHBlbmRUbz1cImJvZHlcIlxuICAgICAgICAgICAgICAgICAgc3R5bGVDbGFzcz1cInNjLWZ1bGxcIj48L3AtZHJvcGRvd24+XG4gICAgPC9kaXY+XG5cbiAgICA8ZGl2IGNsYXNzPVwic2Mtc2VjdGlvblwiICpuZ0lmPVwiZHMuZmVhdHVyZXNcIj5cbiAgICAgIDxsYWJlbCBjbGFzcz1cInNjLWxibFwiIGZvcj1cInNjLWdlbmVcIj5Db2xvdXIgYnkgZ2VuZTwvbGFiZWw+XG4gICAgICA8IS0tIEEgZmlsdGVyYWJsZSBkcm9wZG93biwgbm90IGEgZnJlZS10ZXh0IGJveDogdGhlIGdlbmVzIGFyZSB2aXNpYmxlIGJlZm9yZVxuICAgICAgICAgICBhbnl0aGluZyBpcyB0eXBlZCwgYW5kIGVhY2gga2V5c3Ryb2tlIG5hcnJvd3MgYSBsaXN0IHRoZSB1c2VyIGNhbiBzZWUuXG4gICAgICAgICAgIFZpcnR1YWwtc2Nyb2xsZWQgYmVjYXVzZSBhIHRhcmdldGVkIHBhbmVsIHJ1bnMgdG8gYSBmZXcgdGhvdXNhbmQgbmFtZXMuIC0tPlxuICAgICAgPHAtZHJvcGRvd24gaW5wdXRJZD1cInNjLWdlbmVcIiBbb3B0aW9uc109XCJnZW5lT3B0aW9uc1wiXG4gICAgICAgICAgICAgICAgICBvcHRpb25MYWJlbD1cImxhYmVsXCIgb3B0aW9uVmFsdWU9XCJ2YWx1ZVwiXG4gICAgICAgICAgICAgICAgICBbbmdNb2RlbF09XCJzZWxlY3RlZEdlbmVcIiAobmdNb2RlbENoYW5nZSk9XCJvbkdlbmUoJGV2ZW50KVwiXG4gICAgICAgICAgICAgICAgICBbZmlsdGVyXT1cInRydWVcIiBmaWx0ZXJCeT1cImxhYmVsXCIgW3Jlc2V0RmlsdGVyT25IaWRlXT1cInRydWVcIlxuICAgICAgICAgICAgICAgICAgKG9uRmlsdGVyKT1cIm9uR2VuZUZpbHRlcigkZXZlbnQuZmlsdGVyKVwiXG4gICAgICAgICAgICAgICAgICBbc2hvd0NsZWFyXT1cInRydWVcIlxuICAgICAgICAgICAgICAgICAgW3ZpcnR1YWxTY3JvbGxdPVwiZ2VuZU9wdGlvbnMubGVuZ3RoID4gZ2VuZVZpcnR1YWxTY3JvbGxGcm9tXCJcbiAgICAgICAgICAgICAgICAgIFt2aXJ0dWFsU2Nyb2xsSXRlbVNpemVdPVwiMzBcIlxuICAgICAgICAgICAgICAgICAgW2VtcHR5RmlsdGVyTWVzc2FnZV09XCJnZW5lc0FyZVJlbW90ZSA/ICdObyBtYXRjaCDigJQga2VlcCB0eXBpbmcnIDogJ05vIG1hdGNoJ1wiXG4gICAgICAgICAgICAgICAgICBbZW1wdHlNZXNzYWdlXT1cImdlbmVzQXJlUmVtb3RlID8gJ1R5cGUgdG8gc2VhcmNoIGdlbmVzJyA6ICdObyBnZW5lcydcIlxuICAgICAgICAgICAgICAgICAgYXBwZW5kVG89XCJib2R5XCJcbiAgICAgICAgICAgICAgICAgIHBsYWNlaG9sZGVyPVwiU2VhcmNoIHt7IGRzLmZlYXR1cmVzLmNvdW50IHwgbnVtYmVyIH19IGdlbmVz4oCmXCJcbiAgICAgICAgICAgICAgICAgIHN0eWxlQ2xhc3M9XCJzYy1mdWxsXCI+PC9wLWRyb3Bkb3duPlxuICAgICAgPHNtYWxsIGNsYXNzPVwic2MtaGludFwiICpuZ0lmPVwiZ2VuZVNlYXJjaEZhaWxlZFwiPkdlbmUgbG9va3VwIGZhaWxlZC48L3NtYWxsPlxuICAgIDwvZGl2PlxuXG4gICAgPCEtLSBLZXk6IGNhdGVnb3JpY2FsIGxlZ2VuZCBvciBhIGNvbnRpbnVvdXMgY29sb3VyIGJhciwgYnVpbHQgZnJvbSB0aGUgc2FtZVxuICAgICAgICAgZnVuY3Rpb25zIHRoZSByZW5kZXJlciB1c2VzIHNvIGl0IGNhbm5vdCBkaXNhZ3JlZSB3aXRoIHRoZSBjYW52YXMuIC0tPlxuICAgIDxkaXYgY2xhc3M9XCJzYy1zZWN0aW9uIHNjLWtleVwiICpuZ0lmPVwidmlldy5jb2xvckJ5XCI+XG4gICAgICA8c3BhbiBjbGFzcz1cInNjLWxibFwiPnt7IGNvbG9yQnlMYWJlbCB9fTwvc3Bhbj5cbiAgICAgIDwhLS0gRGVyaXZlZCBjb2x1bW5zIHNheSBzbywgc28gdGhleSBhcmUgbm90IG1pc3Rha2VuIGZvciBtZWFzdXJlZCBkYXRhLiAtLT5cbiAgICAgIDxzbWFsbCBjbGFzcz1cInNjLWhpbnRcIiAqbmdJZj1cImFjdGl2ZURlc2NyaXB0aW9uXCI+e3sgYWN0aXZlRGVzY3JpcHRpb24gfX08L3NtYWxsPlxuXG4gICAgICA8IS0tIExlZ2VuZCByb3dzIGFyZSBidXR0b25zOiBjbGlja2luZyBvbmUgc2VsZWN0cyB0aGF0IGNhdGVnb3J5LCBjbGlja2luZ1xuICAgICAgICAgICBpdCBhZ2FpbiBjbGVhcnMg4oCUIHNvIGEgY2xpY2sgaXMgYWx3YXlzIHJldmVyc2libGUuIC0tPlxuICAgICAgPHVsIGNsYXNzPVwic2MtbGVnZW5kXCIgKm5nSWY9XCJpc0NhdGVnb3JpY2FsXCI+XG4gICAgICAgIDxsaSAqbmdGb3I9XCJsZXQgZW50cnkgb2YgbGVnZW5kOyBpbmRleCBhcyBpXCI+XG4gICAgICAgICAgPGJ1dHRvbiB0eXBlPVwiYnV0dG9uXCIgY2xhc3M9XCJzYy1sZWdlbmQtYnRuXCJcbiAgICAgICAgICAgICAgICAgIFtjbGFzcy5zZWxlY3RlZF09XCJzZWxlY3RlZENhdGVnb3J5ID09PSBpXCJcbiAgICAgICAgICAgICAgICAgIChjbGljayk9XCJzZWxlY3RDYXRlZ29yeShpKVwiXG4gICAgICAgICAgICAgICAgICBbdGl0bGVdPVwiJ1NlbGVjdCAnICsgZW50cnkubGFiZWxcIj5cbiAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwic2Mtc3dhdGNoXCIgW3N0eWxlLmJhY2tncm91bmRdPVwiZW50cnkuY29sb3JcIj48L3NwYW4+XG4gICAgICAgICAgICA8c3BhbiBjbGFzcz1cInNjLWxlZ2VuZC1sYWJlbFwiPnt7IGVudHJ5LmxhYmVsIH19PC9zcGFuPlxuICAgICAgICAgIDwvYnV0dG9uPlxuICAgICAgICA8L2xpPlxuICAgICAgPC91bD5cblxuICAgICAgPGRpdiAqbmdJZj1cImlzQ29udGludW91c1wiPlxuICAgICAgICA8ZGl2IGNsYXNzPVwic2MtY29sb3JiYXJcIiBbc3R5bGUuYmFja2dyb3VuZF09XCJjb2xvckJhckNzc1wiPjwvZGl2PlxuICAgICAgICA8ZGl2IGNsYXNzPVwic2MtY29sb3JiYXItZW5kc1wiPlxuICAgICAgICAgIDxzcGFuPmxvdzwvc3Bhbj5cbiAgICAgICAgICA8c3BhbiBjbGFzcz1cInNjLWhpbnRcIj53aW5kb3cgZnJvbSBwZXJjZW50aWxlczwvc3Bhbj5cbiAgICAgICAgICA8c3Bhbj5oaWdoPC9zcGFuPlxuICAgICAgICA8L2Rpdj5cbiAgICAgICAgPCEtLSBUaGUgZ3JhZGllbnQgdGhpcyBiYXIgYW5kIHRoZSBtYXAgYXJlIGRyYXduIHdpdGguIFNpdHMgdW5kZXIgdGhlIGJhclxuICAgICAgICAgICAgIGl0IGNvbnRyb2xzLCB3aXRoIHRoZSBzYW1lIHN3YXRjaCBwcmV2aWV3cyB0aGUgaW1hZ2UncyBjb2xvcm1hcCBwaWNrZXJcbiAgICAgICAgICAgICB1c2VzOiB3aGljaCBncmFkaWVudCByZWFkcyBiZXN0IGlzIGEgdmlzdWFsIGp1ZGdlbWVudCwgc28gYSBsaXN0IG9mXG4gICAgICAgICAgICAgbmFtZXMgd291bGQgYmUgdGhlIHdyb25nIGNvbnRyb2wuIENsZWFyZWQgPSBmb2xsb3cgdGhlIGltYWdlJ3MuIC0tPlxuICAgICAgICA8ZGl2IGNsYXNzPVwic2Mtcm93XCI+XG4gICAgICAgICAgPHNwYW4gY2xhc3M9XCJzYy1sYmxcIj5Db2xvcm1hcDwvc3Bhbj5cbiAgICAgICAgICA8cC10cmVlU2VsZWN0IFtvcHRpb25zXT1cImNvbG9ybWFwT3B0aW9uc1wiIFtuZ01vZGVsXT1cInNlbGVjdGVkQ29sb3JtYXBOb2RlXCJcbiAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdGlvbk1vZGU9XCJzaW5nbGVcIiBbc2hvd0NsZWFyXT1cInRydWVcIlxuICAgICAgICAgICAgICAgICAgICAgICAgcGxhY2Vob2xkZXI9XCJNYXRjaCB0aGUgaW1hZ2VcIiBbc2Nyb2xsSGVpZ2h0XT1cIicyNjBweCdcIlxuICAgICAgICAgICAgICAgICAgICAgICAgYXBwZW5kVG89XCJib2R5XCJcbiAgICAgICAgICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiQ29sb3VyIHNjYWxlIGZvciBnZW5lIGFuZCBudW1lcmljIHZhbHVlcyDigJQgdGhlIG1hcmtlcnMsIHRoZSBnZW5lIG1hcCBhbmQgdGhpcyBiYXIuIENsZWFyIGl0IHRvIGZvbGxvdyB0aGUgaW1hZ2UncyBjb2xvcm1hcFwiXG4gICAgICAgICAgICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJ0b3BcIlxuICAgICAgICAgICAgICAgICAgICAgICAgKG9uTm9kZVNlbGVjdCk9XCJvbkNvbnRpbnVvdXNDb2xvcm1hcCgkZXZlbnQubm9kZSlcIlxuICAgICAgICAgICAgICAgICAgICAgICAgKG9uQ2xlYXIpPVwib25Db250aW51b3VzQ29sb3JtYXAobnVsbClcIj5cbiAgICAgICAgICAgIDxuZy10ZW1wbGF0ZSBsZXQtbm9kZSBwVGVtcGxhdGU9XCJkZWZhdWx0XCI+XG4gICAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwic2MtY21hcC1vcHRpb25cIj5cbiAgICAgICAgICAgICAgICA8aW1nICpuZ0lmPVwiIW5vZGUuY2hpbGRyZW4gJiYgbm9kZS5kYXRhPy5zcmNcIiBbc3JjXT1cIm5vZGUuZGF0YS5zcmNcIlxuICAgICAgICAgICAgICAgICAgICAgY2xhc3M9XCJzYy1jbWFwLXN3YXRjaFwiIGFsdD1cIlwiIC8+XG4gICAgICAgICAgICAgICAgPHNwYW4+e3sgbm9kZS5sYWJlbCB9fTwvc3Bhbj5cbiAgICAgICAgICAgICAgPC9zcGFuPlxuICAgICAgICAgICAgPC9uZy10ZW1wbGF0ZT5cbiAgICAgICAgICA8L3AtdHJlZVNlbGVjdD5cbiAgICAgICAgICA8c3BhbiBjbGFzcz1cInNjLXZhbFwiPjwvc3Bhbj5cbiAgICAgICAgPC9kaXY+XG4gICAgICA8L2Rpdj5cbiAgICA8L2Rpdj5cblxuICAgIDwhLS0gRGlzcGxheSAtLT5cbiAgICA8ZGl2IGNsYXNzPVwic2Mtc2VjdGlvblwiPlxuICAgICAgPGRpdiBjbGFzcz1cInNjLXJvd1wiPlxuICAgICAgICA8c3BhbiBjbGFzcz1cInNjLWxibFwiPlBvaW50IHNpemU8L3NwYW4+XG4gICAgICAgIDxwLXNsaWRlciBbbmdNb2RlbF09XCJ2aWV3LnBvaW50U2NhbGVcIiBbbWluXT1cIjAuMjVcIiBbbWF4XT1cIjVcIiBbc3RlcF09XCIwLjI1XCJcbiAgICAgICAgICAgICAgICAgIChvbkNoYW5nZSk9XCJvblBvaW50U2NhbGUoJGV2ZW50LnZhbHVlKVwiPjwvcC1zbGlkZXI+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwic2MtdmFsXCI+e3sgdmlldy5wb2ludFNjYWxlIH19w5c8L3NwYW4+XG4gICAgICA8L2Rpdj5cbiAgICAgIDxkaXYgY2xhc3M9XCJzYy1yb3dcIj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJzYy1sYmxcIj5PcGFjaXR5PC9zcGFuPlxuICAgICAgICA8cC1zbGlkZXIgW25nTW9kZWxdPVwidmlldy5vcGFjaXR5XCIgW21pbl09XCIwLjFcIiBbbWF4XT1cIjFcIiBbc3RlcF09XCIwLjA1XCJcbiAgICAgICAgICAgICAgICAgIChvbkNoYW5nZSk9XCJvbk9wYWNpdHkoJGV2ZW50LnZhbHVlKVwiPjwvcC1zbGlkZXI+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwic2MtdmFsXCI+e3sgdmlldy5vcGFjaXR5IH19PC9zcGFuPlxuICAgICAgPC9kaXY+XG4gICAgPC9kaXY+XG5cbiAgICA8IS0tIE9ubHkgZm9yIGEgZ2VuZTogdGhlIG1hcCBkcmF3cyB0aGF0IGdlbmUncyBleHByZXNzaW9uIGFzIGEgZmllbGQuIEluIDJEXG4gICAgICAgICBpdCBnb2VzIHVuZGVyIHRoZSBjZWxscyBvZiB0aGUgZGlzcGxheWVkIHBsYW5lOyBpbiAzRCBpdCBpcyBvbmUgc2hlZXQgcGVyXG4gICAgICAgICBpbWFnZWQgc2VjdGlvbiwgb3B0aW9uYWxseSBzbW9vdGhlZCBhbG9uZyB6IGludG8gYSB2b2x1bWUuIC0tPlxuICAgIDxkaXYgY2xhc3M9XCJzYy1zZWN0aW9uXCIgKm5nSWY9XCJjYW5NYXBHZW5lXCI+XG4gICAgICA8ZGl2IGNsYXNzPVwic2Mtcm93XCI+XG4gICAgICAgIDxwLWNoZWNrYm94IFtiaW5hcnldPVwidHJ1ZVwiIFtuZ01vZGVsXT1cInZpZXcuZ2VuZU1hcFwiXG4gICAgICAgICAgICAgICAgICAgIChvbkNoYW5nZSk9XCJvbkdlbmVNYXAoJGV2ZW50LmNoZWNrZWQpXCJcbiAgICAgICAgICAgICAgICAgICAgbGFiZWw9XCJHZW5lIG1hcCAoZXhwcmVzc2lvbiBmaWVsZClcIlxuICAgICAgICAgICAgICAgICAgICBbcFRvb2x0aXBdPVwiaXMzZFxuICAgICAgICAgICAgICAgICAgICAgID8gJ0RyYXcgdGhpcyBnZW5lXFwncyBleHByZXNzaW9uIGFzIG9uZSBmaWVsZCBwZXIgaW1hZ2VkIHNlY3Rpb24g4oCUIGFuIGVzdGltYXRlIGJldHdlZW4gdGhlIGNlbGxzLCBlbXB0eSB3aGVyZSBub3RoaW5nIHdhcyBtZWFzdXJlZCdcbiAgICAgICAgICAgICAgICAgICAgICA6ICdEcmF3IHRoaXMgZ2VuZVxcJ3MgZXhwcmVzc2lvbiBhcyBhIHNtb290aCBmaWVsZCBVTkRFUiB0aGUgY2VsbHMg4oCUIGFuIGVzdGltYXRlIGJldHdlZW4gdGhlbSwgdHJhbnNwYXJlbnQgd2hlcmUgbm90aGluZyB3YXMgbWVhc3VyZWQnXCJcbiAgICAgICAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwidG9wXCI+PC9wLWNoZWNrYm94PlxuICAgICAgPC9kaXY+XG4gICAgICA8IS0tIDNEIG9ubHk6IGludGVycG9sYXRpbmcgYWxvbmcgeiB0dXJucyB0aGUgbWVhc3VyZWQgc2hlZXRzIGludG8gYW5cbiAgICAgICAgICAgZXN0aW1hdGUsIHNvIGl0IGlzIGl0cyBvd24gZGVjaXNpb24gYW5kIGl0cyBvd24gbGFiZWwuIC0tPlxuICAgICAgPGRpdiBjbGFzcz1cInNjLXJvd1wiICpuZ0lmPVwidmlldy5nZW5lTWFwICYmIGlzM2RcIj5cbiAgICAgICAgPHAtY2hlY2tib3ggW2JpbmFyeV09XCJ0cnVlXCIgW25nTW9kZWxdPVwidmlldy5nZW5lTWFwVm9sdW1lXCJcbiAgICAgICAgICAgICAgICAgICAgKG9uQ2hhbmdlKT1cIm9uR2VuZU1hcFZvbHVtZSgkZXZlbnQuY2hlY2tlZClcIlxuICAgICAgICAgICAgICAgICAgICBsYWJlbD1cIlZvbHVtZSByZW5kZXJpbmcgKGludGVycG9sYXRlIGFsb25nIHopXCJcbiAgICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJTbW9vdGggdGhlIHBlci1zZWN0aW9uIGZpZWxkcyBpbnRvIGEgY29udGludW91cyB2b2x1bWUg4oCUIGFuIEVTVElNQVRFIGJldHdlZW4gdGhlIGltYWdlZCBzZWN0aW9ucywgbm90IG1lYXN1cmVkIHRpc3N1ZVwiXG4gICAgICAgICAgICAgICAgICAgIHRvb2x0aXBQb3NpdGlvbj1cInRvcFwiPjwvcC1jaGVja2JveD5cbiAgICAgIDwvZGl2PlxuICAgICAgPCEtLSBNZWFuaW5nbGVzcyB3aGlsZSBpbnRlcnBvbGF0aW5nOiBhIHZvbHVtZSBidWlsdCBmcm9tIG9uZSBzZWN0aW9uIHdvdWxkXG4gICAgICAgICAgIHNtZWFyIHRoYXQgc2xpZGUgdGhyb3VnaCB0aGUgc3BlY2ltZW4ncyB3aG9sZSBkZXB0aC4gLS0+XG4gICAgICA8ZGl2IGNsYXNzPVwic2Mtcm93XCIgKm5nSWY9XCJ2aWV3LmdlbmVNYXAgJiYgaXMzZCAmJiBpc1NlY3Rpb25lZCAmJiAhdmlldy5nZW5lTWFwVm9sdW1lXCI+XG4gICAgICAgIDxwLWNoZWNrYm94IFtiaW5hcnldPVwidHJ1ZVwiIFtuZ01vZGVsXT1cImdlbmVNYXBPbmVTZWN0aW9uXCJcbiAgICAgICAgICAgICAgICAgICAgKG9uQ2hhbmdlKT1cIm9uR2VuZU1hcE9uZVNlY3Rpb24oJGV2ZW50LmNoZWNrZWQpXCJcbiAgICAgICAgICAgICAgICAgICAgbGFiZWw9XCJPbmUgbWFwIHNlY3Rpb24gYXQgYSB0aW1lXCJcbiAgICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJEcmF3IGEgc2luZ2xlIGltYWdlZCBzZWN0aW9uJ3MgZmllbGQgaW5zdGVhZCBvZiB0aGUgd2hvbGUgc3RhY2sgb2Ygc2hlZXRzXCJcbiAgICAgICAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwidG9wXCI+PC9wLWNoZWNrYm94PlxuICAgICAgPC9kaXY+XG4gICAgICA8ZGl2IGNsYXNzPVwic2Mtcm93XCJcbiAgICAgICAgICAgKm5nSWY9XCJ2aWV3LmdlbmVNYXAgJiYgaXMzZCAmJiBpc1NlY3Rpb25lZCAmJiAhdmlldy5nZW5lTWFwVm9sdW1lICYmIGdlbmVNYXBPbmVTZWN0aW9uXCI+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwic2MtbGJsXCI+TWFwIHNlY3Rpb248L3NwYW4+XG4gICAgICAgIDxwLXNsaWRlciBbbmdNb2RlbF09XCJ2aWV3LmdlbmVNYXBTZWN0aW9uXCIgW21pbl09XCIwXCIgW21heF09XCJsYXN0U2VjdGlvblwiIFtzdGVwXT1cIjFcIlxuICAgICAgICAgICAgICAgICAgKG9uQ2hhbmdlKT1cIm9uR2VuZU1hcFNlY3Rpb24oJGV2ZW50LnZhbHVlKVwiPjwvcC1zbGlkZXI+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwic2MtdmFsXCI+e3sgZ2VuZU1hcFNlY3Rpb25MYWJlbCB9fTwvc3Bhbj5cbiAgICAgIDwvZGl2PlxuICAgICAgPGRpdiBjbGFzcz1cInNjLXJvd1wiICpuZ0lmPVwidmlldy5nZW5lTWFwXCI+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwic2MtbGJsXCI+U21vb3RoaW5nPC9zcGFuPlxuICAgICAgICA8cC1zbGlkZXIgW25nTW9kZWxdPVwidmlldy5nZW5lTWFwU21vb3RoaW5nXCIgW21pbl09XCIwLjVcIiBbbWF4XT1cIjRcIiBbc3RlcF09XCIwLjVcIlxuICAgICAgICAgICAgICAgICAgKG9uQ2hhbmdlKT1cIm9uR2VuZU1hcFNtb290aGluZygkZXZlbnQudmFsdWUpXCI+PC9wLXNsaWRlcj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJzYy12YWxcIj57eyB2aWV3LmdlbmVNYXBTbW9vdGhpbmcgfX3Dlzwvc3Bhbj5cbiAgICAgIDwvZGl2PlxuICAgICAgPGRpdiBjbGFzcz1cInNjLXJvd1wiICpuZ0lmPVwidmlldy5nZW5lTWFwXCI+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwic2MtbGJsXCI+TWFwIG9wYWNpdHk8L3NwYW4+XG4gICAgICAgIDxwLXNsaWRlciBbbmdNb2RlbF09XCJ2aWV3LmdlbmVNYXBPcGFjaXR5XCIgW21pbl09XCIwLjFcIiBbbWF4XT1cIjFcIiBbc3RlcF09XCIwLjA1XCJcbiAgICAgICAgICAgICAgICAgIChvbkNoYW5nZSk9XCJvbkdlbmVNYXBPcGFjaXR5KCRldmVudC52YWx1ZSlcIj48L3Atc2xpZGVyPlxuICAgICAgICA8c3BhbiBjbGFzcz1cInNjLXZhbFwiPnt7IHZpZXcuZ2VuZU1hcE9wYWNpdHkgfX08L3NwYW4+XG4gICAgICA8L2Rpdj5cbiAgICAgIDxkaXYgY2xhc3M9XCJzYy1oaW50XCIgKm5nSWY9XCJ2aWV3LmdlbmVNYXAgJiYgIWlzM2RcIj5cbiAgICAgICAgS2VybmVsLXdlaWdodGVkIG1lYW4gcGVyIGNlbGwsIG5vdCBhIHN1bSDigJQgc28gYSBjcm93ZGVkIHJlZ2lvbiBkb2VzIG5vdCBnbG93XG4gICAgICAgIGZvciBiZWluZyBjcm93ZGVkLiBUcmFuc3BhcmVudCB3aGVyZSBubyBjZWxsIHdhcyBtZWFzdXJlZC4gVHVybiB0aGUgY2VsbHMnXG4gICAgICAgIG93biBPcGFjaXR5IGRvd24gdG8gcmVhZCB0aGUgZmllbGQgdW5kZXIgdGhlbS5cbiAgICAgIDwvZGl2PlxuICAgICAgPGRpdiBjbGFzcz1cInNjLWhpbnRcIiAqbmdJZj1cInZpZXcuZ2VuZU1hcCAmJiBpczNkXCI+XG4gICAgICAgIHt7IGdlbmVNYXBWb2x1bWVOb3RlIH19XG4gICAgICA8L2Rpdj5cbiAgICA8L2Rpdj5cblxuICAgIDwhLS0gM0Qgb25seTogdGhlIHZvbHVtZSwgdGhlIGNsb3VkIGFuZCB0aGUgZGVuc2l0eSB2b2x1bWVzIG9jY3VweSB0aGUgc2FtZVxuICAgICAgICAgc3BhY2UsIHNvIGVhY2ggaGlkZXMgdGhlIG90aGVycy4gSW5kZXBlbmRlbnQgdG9nZ2xlcywgYmVjYXVzZSB0aGUgdXNlZnVsXG4gICAgICAgICB2aWV3cyBhcmUgdGhlIGNvbWJpbmF0aW9ucyDigJQgdGhlIGZpZWxkIGFsb25lLCBvbmUgc2VjdGlvbidzIGNlbGxzIG92ZXIgaXQsXG4gICAgICAgICB0aGUgYW5hdG9teSBhbG9uZS4gLS0+XG4gICAgPGRpdiBjbGFzcz1cInNjLXNlY3Rpb25cIiAqbmdJZj1cImlzM2RcIj5cbiAgICAgIDxzcGFuIGNsYXNzPVwic2MtbGJsXCI+U2hvdzwvc3Bhbj5cbiAgICAgIDxkaXYgY2xhc3M9XCJzYy1yb3dcIiAqbmdJZj1cImRzLnZvbHVtZVwiPlxuICAgICAgICA8cC1jaGVja2JveCBbYmluYXJ5XT1cInRydWVcIiBbbmdNb2RlbF09XCJ2aWV3LnNob3dWb2x1bWVcIlxuICAgICAgICAgICAgICAgICAgICAob25DaGFuZ2UpPVwib25TaG93Vm9sdW1lKCRldmVudC5jaGVja2VkKVwiXG4gICAgICAgICAgICAgICAgICAgIGxhYmVsPVwiUmVmZXJlbmNlIHZvbHVtZVwiXG4gICAgICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiVGhlIHJlZ2lzdGVyZWQgYW5hdG9teSB0aGUgb2JzZXJ2YXRpb25zIHNpdCBpblwiXG4gICAgICAgICAgICAgICAgICAgIHRvb2x0aXBQb3NpdGlvbj1cInRvcFwiPjwvcC1jaGVja2JveD5cbiAgICAgIDwvZGl2PlxuICAgICAgPCEtLSBUaGUgYmFja2Ryb3AncyBvd24gb3BhY2l0eTogcmVhZGluZyB0aGUgY2xvdWQgb3IgYSBkZW5zaXR5IGZpZWxkXG4gICAgICAgICAgIHRocm91Z2ggdGhlIGFuYXRvbXkgbWVhbnMgdHVybmluZyB0aGUgYW5hdG9teSBkb3duLCBub3QgdGhlIGRhdGEuIC0tPlxuICAgICAgPGRpdiBjbGFzcz1cInNjLXJvd1wiICpuZ0lmPVwiZHMudm9sdW1lICYmIHZpZXcuc2hvd1ZvbHVtZVwiPlxuICAgICAgICA8c3BhbiBjbGFzcz1cInNjLWxibFwiPlZvbHVtZSBvcGFjaXR5PC9zcGFuPlxuICAgICAgICA8cC1zbGlkZXIgW25nTW9kZWxdPVwidmlldy52b2x1bWVPcGFjaXR5XCIgW21pbl09XCIwLjA1XCIgW21heF09XCIxXCIgW3N0ZXBdPVwiMC4wNVwiXG4gICAgICAgICAgICAgICAgICAob25DaGFuZ2UpPVwib25Wb2x1bWVPcGFjaXR5KCRldmVudC52YWx1ZSlcIj48L3Atc2xpZGVyPlxuICAgICAgICA8c3BhbiBjbGFzcz1cInNjLXZhbFwiPnt7IHZpZXcudm9sdW1lT3BhY2l0eSB9fTwvc3Bhbj5cbiAgICAgIDwvZGl2PlxuICAgICAgPGRpdiBjbGFzcz1cInNjLXJvd1wiPlxuICAgICAgICA8cC1jaGVja2JveCBbYmluYXJ5XT1cInRydWVcIiBbbmdNb2RlbF09XCJ2aWV3LnNob3dQb2ludHNcIlxuICAgICAgICAgICAgICAgICAgICAob25DaGFuZ2UpPVwib25TaG93UG9pbnRzKCRldmVudC5jaGVja2VkKVwiXG4gICAgICAgICAgICAgICAgICAgIGxhYmVsPVwiT2JzZXJ2YXRpb25zXCJcbiAgICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJUaGUgbWVhc3VyZWQgY2VsbHMuIEhpZGUgdGhlbSB0byByZWFkIHRoZSBkZW5zaXR5IHZvbHVtZXMgdW5kZXJuZWF0aFwiXG4gICAgICAgICAgICAgICAgICAgIHRvb2x0aXBQb3NpdGlvbj1cInRvcFwiPjwvcC1jaGVja2JveD5cbiAgICAgIDwvZGl2PlxuICAgICAgPCEtLSBPbmx5IGZvciBkYXRhIHRoYXQgcmVhbGx5IGlzIHNlY3Rpb25lZDsgYSBjb250aW51b3VzIHogaGFzIG5vIHNlY3Rpb25zXG4gICAgICAgICAgIHRvIHN0ZXAgdGhyb3VnaC4gLS0+XG4gICAgICA8ZGl2IGNsYXNzPVwic2Mtcm93XCIgKm5nSWY9XCJ2aWV3LnNob3dQb2ludHMgJiYgaXNTZWN0aW9uZWRcIj5cbiAgICAgICAgPHAtY2hlY2tib3ggW2JpbmFyeV09XCJ0cnVlXCIgW25nTW9kZWxdPVwib25lU2VjdGlvblwiXG4gICAgICAgICAgICAgICAgICAgIChvbkNoYW5nZSk9XCJvbk9uZVNlY3Rpb24oJGV2ZW50LmNoZWNrZWQpXCJcbiAgICAgICAgICAgICAgICAgICAgbGFiZWw9XCJPbmUgc2VjdGlvbiBhdCBhIHRpbWVcIlxuICAgICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIkRyYXcgYSBzaW5nbGUgaW1hZ2VkIHNlY3Rpb24ncyBjZWxscywgc28gdGhlIGRlbnNpdHkgdm9sdW1lcyBiZXR3ZWVuIHRoZSBzZWN0aW9ucyBzdGF5IHZpc2libGVcIlxuICAgICAgICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJ0b3BcIj48L3AtY2hlY2tib3g+XG4gICAgICA8L2Rpdj5cbiAgICAgIDxkaXYgY2xhc3M9XCJzYy1yb3dcIiAqbmdJZj1cInZpZXcuc2hvd1BvaW50cyAmJiBpc1NlY3Rpb25lZCAmJiBvbmVTZWN0aW9uXCI+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwic2MtbGJsXCI+U2VjdGlvbjwvc3Bhbj5cbiAgICAgICAgPHAtc2xpZGVyIFtuZ01vZGVsXT1cInZpZXcucG9pbnRTZWN0aW9uXCIgW21pbl09XCIwXCIgW21heF09XCJsYXN0U2VjdGlvblwiIFtzdGVwXT1cIjFcIlxuICAgICAgICAgICAgICAgICAgKG9uQ2hhbmdlKT1cIm9uUG9pbnRTZWN0aW9uKCRldmVudC52YWx1ZSlcIj48L3Atc2xpZGVyPlxuICAgICAgICA8c3BhbiBjbGFzcz1cInNjLXZhbFwiPnt7IHNlY3Rpb25MYWJlbCB9fTwvc3Bhbj5cbiAgICAgIDwvZGl2PlxuICAgIDwvZGl2PlxuXG4gICAgPGRpdiBjbGFzcz1cInNjLXNlY3Rpb25cIiAqbmdJZj1cImlzM2RcIj5cbiAgICAgIDxkaXYgY2xhc3M9XCJzYy1oaW50XCIgKm5nSWY9XCJleGNlZWRzQ2xvdWRQYWxldHRlXCI+XG4gICAgICAgIHt7IGxlZ2VuZD8ubGVuZ3RoIH19IGNhdGVnb3JpZXMgaXMgcGFzdCB0aGUge3sgY2xvdWRQYWxldHRlTGltaXQgfX0gdGhlIDNEIGNsb3VkJ3NcbiAgICAgICAgY29sb3VyIExVVCBjYW4ga2VlcCBhcGFydCwgc28gdGhlIHBvaW50cyBkcmF3IGZsYXQgZm9yIHRoaXMgY29sdW1uLiBUaGUgZGVuc2l0eVxuICAgICAgICB2b2x1bWVzIGJlbG93IHJlbmRlciBpdCwgYW5kIHNvIGRvZXMgdGhlIDJEIFNwYXRpYWwgb21pY3Mgdmlldy5cbiAgICAgIDwvZGl2PlxuICAgICAgPGRpdiBjbGFzcz1cInNjLXJvd1wiPlxuICAgICAgICA8cC1jaGVja2JveCBbYmluYXJ5XT1cInRydWVcIiBbbmdNb2RlbF09XCJ2aWV3LmRlbnNpdHlWb2x1bWVcIlxuICAgICAgICAgICAgICAgICAgICAob25DaGFuZ2UpPVwib25EZW5zaXR5Vm9sdW1lKCRldmVudC5jaGVja2VkKVwiXG4gICAgICAgICAgICAgICAgICAgIGxhYmVsPVwiQ2x1c3RlciBkZW5zaXR5IHZvbHVtZXNcIlxuICAgICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlJheW1hcmNoIGVhY2ggY2x1c3RlciBhcyBhIHNtb290aCBkZW5zaXR5IGZpZWxkIOKAlCBhbiBFU1RJTUFURSBiZXR3ZWVuIHRoZSBpbWFnZWQgc2VjdGlvbnMsIG5vdCBtZWFzdXJlZCBjZWxsc1wiXG4gICAgICAgICAgICAgICAgICAgIHRvb2x0aXBQb3NpdGlvbj1cInRvcFwiPjwvcC1jaGVja2JveD5cbiAgICAgIDwvZGl2PlxuICAgICAgPGRpdiBjbGFzcz1cInNjLXJvd1wiICpuZ0lmPVwidmlldy5kZW5zaXR5Vm9sdW1lXCI+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwic2MtbGJsXCI+U21vb3RoaW5nPC9zcGFuPlxuICAgICAgICA8cC1zbGlkZXIgW25nTW9kZWxdPVwidmlldy5kZW5zaXR5U21vb3RoaW5nXCIgW21pbl09XCIwLjVcIiBbbWF4XT1cIjRcIiBbc3RlcF09XCIwLjVcIlxuICAgICAgICAgICAgICAgICAgKG9uQ2hhbmdlKT1cIm9uRGVuc2l0eVNtb290aGluZygkZXZlbnQudmFsdWUpXCI+PC9wLXNsaWRlcj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJzYy12YWxcIj57eyB2aWV3LmRlbnNpdHlTbW9vdGhpbmcgfX3Dlzwvc3Bhbj5cbiAgICAgIDwvZGl2PlxuICAgICAgPGRpdiBjbGFzcz1cInNjLWhpbnRcIiAqbmdJZj1cInZpZXcuZGVuc2l0eVZvbHVtZVwiPnt7IGRlbnNpdHlOb3RlIH19PC9kaXY+XG4gICAgPC9kaXY+XG5cbiAgICA8IS0tIENvbnRpbnVvdXMtb25seSBrbm9iczogbWVhbmluZ2xlc3MgZm9yIGEgY2F0ZWdvcmljYWwgcGFsZXR0ZS4gLS0+XG4gICAgPGRpdiBjbGFzcz1cInNjLXNlY3Rpb25cIiAqbmdJZj1cImlzQ29udGludW91c1wiPlxuICAgICAgPGRpdiBjbGFzcz1cInNjLXJvd1wiPlxuICAgICAgICA8cC1jaGVja2JveCBbYmluYXJ5XT1cInRydWVcIiBbbmdNb2RlbF09XCJ2aWV3LmxvZ1NjYWxlXCJcbiAgICAgICAgICAgICAgICAgICAgKG9uQ2hhbmdlKT1cIm9uTG9nU2NhbGUoJGV2ZW50LmNoZWNrZWQpXCJcbiAgICAgICAgICAgICAgICAgICAgbGFiZWw9XCJMb2cgc2NhbGVcIlxuICAgICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIkNvdW50cyBjb2xsYXBzZSBhZ2FpbnN0IHRoZWlyIG91dGxpZXJzIG9uIGEgbGluZWFyIHJhbXBcIlxuICAgICAgICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJ0b3BcIj48L3AtY2hlY2tib3g+XG4gICAgICA8L2Rpdj5cbiAgICAgIDxkaXYgY2xhc3M9XCJzYy1yb3dcIj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJzYy1sYmxcIj5DbGlwIG91dGxpZXJzPC9zcGFuPlxuICAgICAgICA8cC1kcm9wZG93biBbb3B0aW9uc109XCJjbGlwT3B0aW9uc1wiIG9wdGlvbkxhYmVsPVwibGFiZWxcIiBvcHRpb25WYWx1ZT1cInZhbHVlXCJcbiAgICAgICAgICAgICAgICAgICAgW25nTW9kZWxdPVwidmlldy5wZXJjZW50aWxlQ2xpcFwiIChuZ01vZGVsQ2hhbmdlKT1cIm9uQ2xpcCgkZXZlbnQpXCJcbiAgICAgICAgICAgICAgICAgICAgYXBwZW5kVG89XCJib2R5XCJcbiAgICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJQZXJjZW50aWxlIHRoZSBjb250cmFzdCB3aW5kb3cgaXMgdGFrZW4gYXRcIlxuICAgICAgICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJ0b3BcIj48L3AtZHJvcGRvd24+XG4gICAgICAgIDwhLS0gUmVzZXJ2ZXMgdGhlIHJlYWRvdXQgY29sdW1uIHRoaXMgcm93IGhhcyBubyByZWFkb3V0IGZvciwgc28gdGhlXG4gICAgICAgICAgICAgY29udHJvbCdzIHJpZ2h0IGVkZ2UgbGluZXMgdXAgd2l0aCBldmVyeSBzbGlkZXIncy4gLS0+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwic2MtdmFsXCI+PC9zcGFuPlxuICAgICAgPC9kaXY+XG4gICAgPC9kaXY+XG5cbiAgICA8IS0tIFNlbGVjdGlvbi4gUmVnaW9ucyBjb21lIGZyb20gdGhlIGV4aXN0aW5nIFJPSSB0b29scyAocmVjdGFuZ2xlLCBwb2x5Z29uLFxuICAgICAgICAgZnJlZWhhbmQsIHdhbmQsIGJydXNoKSwgc28gdGhlcmUgaXMgbm8gc2VwYXJhdGUgbWFycXVlZSB0byBsZWFybi4gLS0+XG4gICAgPGRpdiBjbGFzcz1cInNjLXNlY3Rpb24gc2Mtc2VsZWN0aW9uXCI+XG4gICAgICA8ZGl2IGNsYXNzPVwic2Mtcm93XCI+XG4gICAgICAgIDxwLWJ1dHRvbiBsYWJlbD1cIlNlbGVjdCBmcm9tIFJPSXNcIiBpY29uPVwicGkgcGktb2JqZWN0LWdyb3VwXCIgc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgICAgIFtvdXRsaW5lZF09XCJ0cnVlXCIgKG9uQ2xpY2spPVwic2VsZWN0RnJvbVJlZ2lvbnMoKVwiXG4gICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlNlbGVjdCBldmVyeSBvYnNlcnZhdGlvbiBpbnNpZGUgdGhlIGRyYXduIHJlZ2lvbnNcIlxuICAgICAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwidG9wXCI+PC9wLWJ1dHRvbj5cbiAgICAgICAgPHAtYnV0dG9uICpuZ0lmPVwiaGFzU2VsZWN0aW9uXCIgbGFiZWw9XCJDbGVhclwiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwiY2xlYXJTZWxlY3Rpb24oKVwiPjwvcC1idXR0b24+XG4gICAgICA8L2Rpdj5cbiAgICAgIDxzbWFsbCBjbGFzcz1cInNjLWhpbnRcIiAqbmdJZj1cImhhc1NlbGVjdGlvblwiPlxuICAgICAgICB7eyBzZWxlY3Rpb24uY291bnQgfCBudW1iZXIgfX0gb2Yge3sgZHMub2JzZXJ2YXRpb25zLmNvdW50IHwgbnVtYmVyIH19IHNlbGVjdGVkIOKAlFxuICAgICAgICB0aGUgcmVzdCBhcmUgbXV0ZWQuXG4gICAgICA8L3NtYWxsPlxuICAgICAgPHNtYWxsIGNsYXNzPVwic2MtaGludFwiICpuZ0lmPVwic2VsZWN0aW9uTWlzc2VkXCI+XG4gICAgICAgIE5vIG9ic2VydmF0aW9ucyBmZWxsIGluc2lkZSB0aGUgZHJhd24gcmVnaW9ucy5cbiAgICAgIDwvc21hbGw+XG4gICAgICA8IS0tIEEgbGFzc28gb24gYSAyRCBzY3JlZW4gY2Fubm90IHBpY2sgYSBkZXB0aCwgc28gc2F5IHdoYXQgaXQgZG9lcyBwaWNrXG4gICAgICAgICAgIHJhdGhlciB0aGFuIGxldHRpbmcgdGhlIGNvdW50IGxvb2sgbGlrZSBhIG1pc3Rha2UuIC0tPlxuICAgICAgPHNtYWxsIGNsYXNzPVwic2MtaGludFwiICpuZ0lmPVwiaXMzZFwiPlxuICAgICAgICBJbiAzRCBhIHJlZ2lvbiBjdXRzIHRocm91Z2ggdGhlIGNsb3VkJ3MgZnVsbCBkZXB0aC4gT3JiaXQgYW5kIGRyYXcgYWdhaW5cbiAgICAgICAgdG8gY3V0IGZyb20gYW5vdGhlciBhbmdsZS5cbiAgICAgIDwvc21hbGw+XG4gICAgPC9kaXY+XG5cbiAgICA8IS0tIERpc3RyaWJ1dGlvbnMgb2Ygd2hhdGV2ZXIgdGhlIG1hcCBpcyBjb2xvdXJlZCBieS4gSW4gdGhlIFNBTUUgZGlhbG9nIGFzXG4gICAgICAgICB0aGUgY29sb3VyIGNvbnRyb2xzIG9uIHB1cnBvc2U6IGNoYW5naW5nIHRoZSBnZW5lIGFuZCB3YXRjaGluZyB0aGVcbiAgICAgICAgIGRpc3RyaWJ1dGlvbiBtb3ZlIGlzIG9uZSBhY3Rpb24sIGFuZCB0d28gZmxvYXRpbmcgd2luZG93cyBtYWRlIHRoYXRcbiAgICAgICAgIGhhcmRlciB0byBzZWUgcmF0aGVyIHRoYW4gZWFzaWVyLiAtLT5cbiAgICA8ZGl2IGNsYXNzPVwic2Mtc2VjdGlvbiBzYy1jaGFydHNcIj5cbiAgICAgIDxidXR0b24gdHlwZT1cImJ1dHRvblwiIGNsYXNzPVwic2MtZGlzY2xvc3VyZVwiIChjbGljayk9XCJ0b2dnbGVDaGFydHMoKVwiXG4gICAgICAgICAgICAgIFthdHRyLmFyaWEtZXhwYW5kZWRdPVwiY2hhcnRzT3BlblwiIFthdHRyLmFyaWEtY29udHJvbHNdPVwiY2hhcnRzQm9keUlkXCI+XG4gICAgICAgIDxpIGNsYXNzPVwicGlcIiBbY2xhc3MucGktY2hldnJvbi1yaWdodF09XCIhY2hhcnRzT3BlblwiXG4gICAgICAgICAgIFtjbGFzcy5waS1jaGV2cm9uLWRvd25dPVwiY2hhcnRzT3BlblwiPjwvaT5cbiAgICAgICAgPHNwYW4+RGlzdHJpYnV0aW9uPC9zcGFuPlxuICAgICAgICA8c21hbGwgKm5nSWY9XCIhY2hhcnRzT3BlbiAmJiB2aWV3LmNvbG9yQnlcIj5vZiB7eyBjb2xvckJ5TGFiZWwgfX08L3NtYWxsPlxuICAgICAgPC9idXR0b24+XG4gICAgICA8IS0tIEtlcHQgTU9VTlRFRCB3aGVuIGNvbGxhcHNlZCwgb25seSBoaWRkZW46IGRlc3Ryb3lpbmcgaXQgd291bGQgdGhyb3dcbiAgICAgICAgICAgYXdheSB0aGUgY2hhcnQga2luZCBhbmQgZ3JvdXBpbmcgdGhlIHVzZXIgcGlja2VkLiBgYWN0aXZlYCBpcyB3aGF0XG4gICAgICAgICAgIHN0b3BzIGl0IGRyYXdpbmcgd2hpbGUgaXQgaGFzIG5vIHNpemUuIC0tPlxuICAgICAgPGRpdiBbaWRdPVwiY2hhcnRzQm9keUlkXCIgW2hpZGRlbl09XCIhY2hhcnRzT3BlblwiPlxuICAgICAgICA8c3BhdGlhbC1jaGFydHMgW2FjdGl2ZV09XCJ2aXNpYmxlICYmIGNoYXJ0c09wZW5cIj48L3NwYXRpYWwtY2hhcnRzPlxuICAgICAgPC9kaXY+XG4gICAgPC9kaXY+XG5cbiAgICA8ZGl2IGNsYXNzPVwic2MtYnRuc1wiPlxuICAgICAgPHAtYnV0dG9uIGxhYmVsPVwiUmVzZXRcIiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCIgKG9uQ2xpY2spPVwicmVzZXQoKVwiPjwvcC1idXR0b24+XG4gICAgPC9kaXY+XG5cbiAgPC9kaXY+XG48L3AtZGlhbG9nPlxuIl19