@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.
- package/README.md +215 -11
- package/esm2022/index.mjs +20 -1
- package/esm2022/lib/contracts/display-types.mjs +20 -2
- package/esm2022/lib/contracts/image.contract.mjs +1 -1
- package/esm2022/lib/contracts/plot-type.mjs +36 -1
- package/esm2022/lib/contracts/ports/spatial-data.port.mjs +3 -0
- package/esm2022/lib/contracts/spatial-dataset.contract.mjs +45 -0
- package/esm2022/lib/contracts/visualizer.contract.mjs +1 -1
- package/esm2022/lib/implementations/napari-js/napari-axes-labels.mjs +10 -19
- package/esm2022/lib/implementations/napari-js/napari-region-overlay.mjs +71 -7
- package/esm2022/lib/implementations/napari-js/napari-scale-bar.mjs +1 -1
- package/esm2022/lib/implementations/napari-js/napari-spatial-tooltip.mjs +96 -0
- package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +1934 -55
- package/esm2022/lib/implementations/napari-js/napari-zoom.mjs +18 -0
- package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +5 -3
- package/esm2022/lib/implementations/osd/osd-region-overlay.mjs +3 -4
- package/esm2022/lib/implementations/osd/osd-zoom.mjs +20 -0
- package/esm2022/lib/implementations/plotly/omics-trace-builders.mjs +555 -0
- package/esm2022/lib/implementations/plotly/plotly.service.mjs +13 -7
- package/esm2022/lib/implementations/spatial-data-http/spatial-data-http.service.mjs +304 -0
- package/esm2022/lib/implementations/spatial-data-http/spatial-wire.mjs +179 -0
- package/esm2022/lib/routing-visualizer.service.mjs +223 -12
- package/esm2022/lib/spatial/embedding-compute.mjs +142 -0
- package/esm2022/lib/spatial/gene-search.mjs +77 -0
- package/esm2022/lib/spatial/spatial-density.mjs +224 -0
- package/esm2022/lib/spatial/spatial-encoding.mjs +232 -0
- package/esm2022/lib/spatial/spatial-expression.mjs +257 -0
- package/esm2022/lib/spatial/spatial-framing.mjs +60 -0
- package/esm2022/lib/spatial/spatial-heatmap.mjs +185 -0
- package/esm2022/lib/spatial/spatial-hover.mjs +79 -0
- package/esm2022/lib/spatial/spatial-sections.mjs +96 -0
- package/esm2022/lib/spatial/spatial-selection.mjs +264 -0
- package/esm2022/lib/spatial/spatial-volume-image.mjs +163 -0
- package/esm2022/lib/spatial/tsne-worker.mjs +10 -0
- package/esm2022/lib/spatial/world-grid.mjs +77 -0
- package/esm2022/lib/spatial-controls/spatial-charts/spatial-charts.component.mjs +1278 -0
- package/esm2022/lib/spatial-controls/spatial-controls.component.mjs +621 -0
- package/esm2022/lib/store/spatial-selection.service.mjs +47 -0
- package/esm2022/lib/store/visualizer-store.service.mjs +23 -1
- package/esm2022/lib/toolbar/segmentation/cellpose-segmenter.service.mjs +2 -2
- package/esm2022/lib/toolbar/segmentation/sam-model-registry.mjs +9 -9
- package/esm2022/lib/toolbar/toolbar.component.mjs +30 -7
- package/esm2022/lib/visualization.module.mjs +23 -5
- package/esm2022/lib/visualizer.component.mjs +283 -22
- 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
- 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
- package/fesm2022/jax-data-science-sci-image-visualizer-tsne-worker-B_nXDGf0.mjs +12 -0
- package/fesm2022/jax-data-science-sci-image-visualizer-tsne-worker-B_nXDGf0.mjs.map +1 -0
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +15282 -7829
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
- package/fesm2022/tsne.worker.js +19091 -0
- package/index.d.ts +15 -0
- package/lib/contracts/display-types.d.ts +131 -0
- package/lib/contracts/image.contract.d.ts +7 -0
- package/lib/contracts/plot-type.d.ts +42 -2
- package/lib/contracts/ports/spatial-data.port.d.ts +90 -0
- package/lib/contracts/spatial-dataset.contract.d.ts +281 -0
- package/lib/contracts/visualizer.contract.d.ts +99 -1
- package/lib/implementations/napari-js/napari-axes-labels.d.ts +9 -7
- package/lib/implementations/napari-js/napari-region-overlay.d.ts +38 -4
- package/lib/implementations/napari-js/napari-scale-bar.d.ts +7 -3
- package/lib/implementations/napari-js/napari-spatial-tooltip.d.ts +32 -0
- package/lib/implementations/napari-js/napari-visualizer.service.d.ts +497 -16
- package/lib/implementations/napari-js/napari-zoom.d.ts +15 -0
- package/lib/implementations/osd/osd-zoom.d.ts +19 -0
- package/lib/implementations/plotly/omics-trace-builders.d.ts +193 -0
- package/lib/implementations/plotly/plotly.service.d.ts +9 -0
- package/lib/implementations/spatial-data-http/spatial-data-http.service.d.ts +123 -0
- package/lib/implementations/spatial-data-http/spatial-wire.d.ts +115 -0
- package/lib/routing-visualizer.service.d.ts +45 -6
- package/lib/spatial/embedding-compute.d.ts +73 -0
- package/lib/spatial/gene-search.d.ts +48 -0
- package/lib/spatial/spatial-density.d.ts +73 -0
- package/lib/spatial/spatial-encoding.d.ts +155 -0
- package/lib/spatial/spatial-expression.d.ts +144 -0
- package/lib/spatial/spatial-framing.d.ts +37 -0
- package/lib/spatial/spatial-heatmap.d.ts +111 -0
- package/lib/spatial/spatial-hover.d.ts +49 -0
- package/lib/spatial/spatial-sections.d.ts +59 -0
- package/lib/spatial/spatial-selection.d.ts +113 -0
- package/lib/spatial/spatial-volume-image.d.ts +67 -0
- package/lib/spatial/tsne-worker.d.ts +7 -0
- package/lib/spatial/world-grid.d.ts +58 -0
- package/lib/spatial-controls/spatial-charts/spatial-charts.component.d.ts +439 -0
- package/lib/spatial-controls/spatial-controls.component.d.ts +257 -0
- package/lib/store/spatial-selection.service.d.ts +27 -0
- package/lib/store/visualizer-store.service.d.ts +13 -0
- package/lib/toolbar/toolbar.component.d.ts +19 -3
- package/lib/visualization.module.d.ts +28 -24
- package/lib/visualizer.component.d.ts +73 -2
- package/package.json +7 -3
- package/src/lib/assets/spatial-omics-3d.svg +31 -0
- package/src/lib/assets/spatial-omics.svg +27 -0
|
@@ -0,0 +1,1278 @@
|
|
|
1
|
+
import { Component, Inject, Input, } from '@angular/core';
|
|
2
|
+
import { Subscription, combineLatest } from 'rxjs';
|
|
3
|
+
import * as Plotly from 'plotly.js-dist-min';
|
|
4
|
+
import { VISUALIZER } from '../../contracts/visualizer.contract';
|
|
5
|
+
import { DEFAULT_SPATIAL_VIEW } from '../../contracts/display-types';
|
|
6
|
+
import { emptySelection, maskToIndices, } from '../../spatial/spatial-selection';
|
|
7
|
+
import { cellsAsGroups, heatmapMatrix } from '../../spatial/spatial-heatmap';
|
|
8
|
+
import { geneOptionsFor } from '../../spatial/gene-search';
|
|
9
|
+
import { EmbeddingComputeRun } from '../../spatial/embedding-compute';
|
|
10
|
+
import { benefitsFromGrouping, buildCountTraces, buildHeatmapTraces, buildOmicsTraces, buildEmbeddingTraces, countsLayout, heatmapLayout, omicsLayout, embeddingLayout, } from '../../implementations/plotly/omics-trace-builders';
|
|
11
|
+
import * as i0 from "@angular/core";
|
|
12
|
+
import * as i1 from "@angular/common";
|
|
13
|
+
import * as i2 from "@angular/forms";
|
|
14
|
+
import * as i3 from "primeng/button";
|
|
15
|
+
import * as i4 from "primeng/tooltip";
|
|
16
|
+
import * as i5 from "primeng/dropdown";
|
|
17
|
+
import * as i6 from "primeng/dialog";
|
|
18
|
+
import * as i7 from "primeng/progressbar";
|
|
19
|
+
import * as i8 from "primeng/checkbox";
|
|
20
|
+
import * as i9 from "primeng/multiselect";
|
|
21
|
+
import * as i10 from "primeng/selectbutton";
|
|
22
|
+
/**
|
|
23
|
+
* Widen the help tooltip, once per document.
|
|
24
|
+
*
|
|
25
|
+
* PrimeNG caps `.p-tooltip` at 12.5rem and appends it to `<body>`, so a component
|
|
26
|
+
* stylesheet cannot reach it — the same reason the detached dialog is sized inline. At
|
|
27
|
+
* 200 px the chart explanations render as a tall thread of two-word lines, which is worse
|
|
28
|
+
* than not showing them. The library ships no global stylesheet to put this in, so one
|
|
29
|
+
* rule is injected instead: scoped to this component's own tooltip class, idempotent, and
|
|
30
|
+
* skipped where there is no document.
|
|
31
|
+
*/
|
|
32
|
+
const HELP_TIP_STYLE_ID = 'sx-help-tip-style';
|
|
33
|
+
function ensureHelpTipStyle() {
|
|
34
|
+
if (typeof document === 'undefined')
|
|
35
|
+
return;
|
|
36
|
+
if (document.getElementById(HELP_TIP_STYLE_ID))
|
|
37
|
+
return;
|
|
38
|
+
const style = document.createElement('style');
|
|
39
|
+
style.id = HELP_TIP_STYLE_ID;
|
|
40
|
+
style.textContent = '.sx-help-tip .p-tooltip-text { max-width: none; width: 24rem; '
|
|
41
|
+
+ 'line-height: 1.45; }\n.sx-help-tip { max-width: none; }';
|
|
42
|
+
document.head.appendChild(style);
|
|
43
|
+
}
|
|
44
|
+
/** Per-instance chart-div id source — see {@link SpatialChartsComponent.chartDiv}. */
|
|
45
|
+
let chartInstanceSeq = 0;
|
|
46
|
+
/** Plotly config: a static-ish analysis chart, not an editable figure. */
|
|
47
|
+
const CHART_CONFIG = {
|
|
48
|
+
displaylogo: false,
|
|
49
|
+
responsive: true,
|
|
50
|
+
modeBarButtonsToRemove: ['lasso2d', 'select2d', 'autoScale2d'],
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Config for the embedding, which unlike the distributions is a plot you SELECT in.
|
|
54
|
+
*
|
|
55
|
+
* The lasso and box-select are kept — drawing round a cluster is how a population gets
|
|
56
|
+
* picked out of a UMAP — and the wheel zooms, because exploring an embedding means
|
|
57
|
+
* zooming into a cluster and reaching for a toolbar button to do it breaks that.
|
|
58
|
+
*/
|
|
59
|
+
const EMBEDDING_CONFIG = {
|
|
60
|
+
displaylogo: false,
|
|
61
|
+
responsive: true,
|
|
62
|
+
scrollZoom: true,
|
|
63
|
+
modeBarButtonsToRemove: ['autoScale2d'],
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Distribution charts over the spatial-omics values — histogram, violin, box —
|
|
67
|
+
* **linked to the map**: they chart whatever the map is coloured by, and narrow
|
|
68
|
+
* to the current selection.
|
|
69
|
+
*
|
|
70
|
+
* Embedded INSIDE `<spatial-controls>` rather than owning a dialog of its own:
|
|
71
|
+
* the two are one workflow (change the colour source, watch the distribution
|
|
72
|
+
* move), and splitting them across two floating windows made the link harder to
|
|
73
|
+
* see, not easier. It stays a separate component so the pure trace builders and
|
|
74
|
+
* its own tests keep their boundary.
|
|
75
|
+
*
|
|
76
|
+
* Charting the active colour source rather than offering its own value picker is
|
|
77
|
+
* deliberate: it keeps one source of truth, so what you see on the map and what
|
|
78
|
+
* you see in the chart cannot disagree, and it makes the link legible without
|
|
79
|
+
* explaining it.
|
|
80
|
+
*
|
|
81
|
+
* Depends only on {@link ISpatialControls} through the `VISUALIZER` contract
|
|
82
|
+
* token, like `SpatialControlsComponent`. Trace building lives in the pure
|
|
83
|
+
* `omics-trace-builders`; this component only moves data and owns the div.
|
|
84
|
+
*/
|
|
85
|
+
export class SpatialChartsComponent {
|
|
86
|
+
/**
|
|
87
|
+
* Whether the host panel is on screen. The chart only draws when it is: the
|
|
88
|
+
* enclosing dialog creates and destroys its content, so without this the
|
|
89
|
+
* component would mount with a div present but no state change to trigger a
|
|
90
|
+
* first draw.
|
|
91
|
+
*/
|
|
92
|
+
set active(on) {
|
|
93
|
+
const was = this.isActive;
|
|
94
|
+
this.isActive = on;
|
|
95
|
+
// Deferred by a task: when a collapsed section expands, the host is still
|
|
96
|
+
// `hidden` at the moment this setter runs, so plotting now would size the
|
|
97
|
+
// chart to a zero-height div.
|
|
98
|
+
if (on && !was)
|
|
99
|
+
setTimeout(() => void this.reload(), 0);
|
|
100
|
+
else if (on)
|
|
101
|
+
void this.reload();
|
|
102
|
+
}
|
|
103
|
+
get active() {
|
|
104
|
+
return this.isActive;
|
|
105
|
+
}
|
|
106
|
+
static { this.CONTINUOUS_KINDS = [
|
|
107
|
+
{ label: 'Histogram', value: 'histogram' },
|
|
108
|
+
{ label: 'Violin', value: 'violin' },
|
|
109
|
+
{ label: 'Box', value: 'box' },
|
|
110
|
+
]; }
|
|
111
|
+
static { this.CATEGORICAL_KINDS = [
|
|
112
|
+
{ label: 'Counts', value: 'counts' },
|
|
113
|
+
]; }
|
|
114
|
+
/** Available whatever the map is coloured by: the heatmap's subject is a GENE
|
|
115
|
+
* LIST crossed with a grouping, not the active colour source. */
|
|
116
|
+
static { this.ALWAYS_KINDS = [
|
|
117
|
+
{ label: 'Heatmap', value: 'heatmap' },
|
|
118
|
+
]; }
|
|
119
|
+
/**
|
|
120
|
+
* A t-SNE the dataset does not publish, offered so it can be computed here.
|
|
121
|
+
*
|
|
122
|
+
* A dropped-in `.h5ad` typically carries one UMAP and a PCA, and no t-SNE — the two
|
|
123
|
+
* views answer different questions, so having only one is a real gap. Computing it
|
|
124
|
+
* needs no expression data: t-SNE runs on the PCA scores, which is why this can happen
|
|
125
|
+
* in the browser at all while PCA cannot.
|
|
126
|
+
*/
|
|
127
|
+
static { this.COMPUTABLE = [
|
|
128
|
+
{ name: 'local:tsne', label: 't-SNE (compute)', dims: 2, derived: true },
|
|
129
|
+
{ name: 'local:tsne3d', label: 't-SNE 3D (compute)', dims: 3, derived: true },
|
|
130
|
+
]; }
|
|
131
|
+
/** The menu suffix that says a click will start work; not part of the name. */
|
|
132
|
+
static { this.COMPUTE_SUFFIX = ' (compute)'; }
|
|
133
|
+
/**
|
|
134
|
+
* Largest dataset this will offer to embed in the browser.
|
|
135
|
+
*
|
|
136
|
+
* t-SNE is O(N²) per iteration — vectorised on the GPU, but not approximated — so the
|
|
137
|
+
* cost is quadratic in observations. Measured end to end in Firefox on WebGPU: 2,688
|
|
138
|
+
* points took 95 s. That scales to roughly five and a half minutes at this threshold,
|
|
139
|
+
* and to about 76 minutes at seqFISH's 19,416, which was also measured offline.
|
|
140
|
+
*
|
|
141
|
+
* Past this the option is WITHDRAWN rather than offered with a warning. An hour-long
|
|
142
|
+
* job started from a button is not a choice a reader can meaningfully consent to in a
|
|
143
|
+
* dialog, and a t-SNE that large belongs in the offline pipeline, where it can run
|
|
144
|
+
* once and be served to everyone.
|
|
145
|
+
*/
|
|
146
|
+
static { this.BROWSER_TSNE_MAX_OBSERVATIONS = 5000; }
|
|
147
|
+
/** Measured anchor for the estimate: seconds for 2,688 points on WebGPU. */
|
|
148
|
+
static { this.TSNE_SECONDS_AT = { seconds: 95, observations: 2688 }; }
|
|
149
|
+
/** Offered only when the dataset publishes an embedding to draw. */
|
|
150
|
+
// Labelled for what it is rather than for one instance of it: this view draws whatever
|
|
151
|
+
// embedding the dataset publishes — a UMAP, a PCA, a t-SNE — and calling the tab "UMAP"
|
|
152
|
+
// while it showed a PCA would be a lie the picker beneath it immediately contradicts.
|
|
153
|
+
static { this.EMBEDDING_KINDS = [
|
|
154
|
+
{ label: 'Embedding', value: 'embedding' },
|
|
155
|
+
]; }
|
|
156
|
+
/** The kinds the ACTIVE subject can be drawn as. A category code is a label,
|
|
157
|
+
* not a magnitude, so a histogram of it would be meaningless — what a
|
|
158
|
+
* categorical column has is a frequency distribution. */
|
|
159
|
+
get kindOptions() {
|
|
160
|
+
return [
|
|
161
|
+
...(this.categorical
|
|
162
|
+
? SpatialChartsComponent.CATEGORICAL_KINDS
|
|
163
|
+
: SpatialChartsComponent.CONTINUOUS_KINDS),
|
|
164
|
+
...SpatialChartsComponent.ALWAYS_KINDS,
|
|
165
|
+
// An embedding is a property of the DATASET, not of the active colour source, so
|
|
166
|
+
// it is offered whenever one is published and never otherwise — a tab that draws
|
|
167
|
+
// nothing is worse than an absent one.
|
|
168
|
+
...(this.embeddings.length > 0 ? SpatialChartsComponent.EMBEDDING_KINDS : []),
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
/** Past this many selected cells the per-cell view is a texture, not a
|
|
172
|
+
* readable panel, so the columns go back to being classes. */
|
|
173
|
+
static { this.HEATMAP_CELL_COLUMNS = 200; }
|
|
174
|
+
/**
|
|
175
|
+
* Column cap for the grouped heatmap.
|
|
176
|
+
*
|
|
177
|
+
* Any categorical column can be the x axis, and they are not the same size:
|
|
178
|
+
* `neurotransmitter` has 9 categories, `class` 34, `subclass` 338. At 338 the
|
|
179
|
+
* panel is a texture — columns a couple of pixels wide, no readable label —
|
|
180
|
+
* so past this many the strongest columns are kept and the note says how many
|
|
181
|
+
* were dropped. See `maxCols` in `spatial-heatmap.ts`.
|
|
182
|
+
*/
|
|
183
|
+
static { this.HEATMAP_MAX_COLUMNS = 40; }
|
|
184
|
+
constructor(viz) {
|
|
185
|
+
this.viz = viz;
|
|
186
|
+
this.isActive = true;
|
|
187
|
+
/** Per-instance, for the same reason the visualizer's plot div is
|
|
188
|
+
* (`visualizer.component.ts`): two mounted charts sharing one DOM id means
|
|
189
|
+
* `getElementById` hands both of them the first element, so one instance draws
|
|
190
|
+
* into — or purges — the other's canvas. */
|
|
191
|
+
/** One increment per INSTANCE — a static initializer would run once per class and
|
|
192
|
+
* hand every instance the same id, which is the bug this id exists to avoid. Declared
|
|
193
|
+
* before the ids because field initializers run in order. */
|
|
194
|
+
this.seq = ++chartInstanceSeq;
|
|
195
|
+
this.chartDiv = `spatial-charts-plot-${this.seq}`;
|
|
196
|
+
/**
|
|
197
|
+
* The div inside the detached window.
|
|
198
|
+
*
|
|
199
|
+
* A separate id rather than moving the existing div: Angular destroys and recreates a
|
|
200
|
+
* div across an `*ngIf`, so Plotly would be left holding a detached node. Two divs and
|
|
201
|
+
* a redraw is both simpler and correct.
|
|
202
|
+
*
|
|
203
|
+
* ONE is enough for every kind, because only the active kind is ever plotted. Detaching
|
|
204
|
+
* is remembered per kind, but at most one window is open at a time.
|
|
205
|
+
*/
|
|
206
|
+
this.detachedDiv = `spatial-charts-detached-${this.seq}`;
|
|
207
|
+
/**
|
|
208
|
+
* Whether the chart is shown in its own window rather than inline.
|
|
209
|
+
*
|
|
210
|
+
* Detached, it sits ALONGSIDE the spatial-omics dialog instead of inside it — which is
|
|
211
|
+
* the point: these are read against the map, not instead of it. The dialog is only so
|
|
212
|
+
* wide.
|
|
213
|
+
*
|
|
214
|
+
* One flag for the PANEL, not one per kind. Where the window is, is a property of the
|
|
215
|
+
* workspace someone has arranged, not of the tab they happen to be on: having put the
|
|
216
|
+
* charts beside the map, switching from the heatmap to the counts should swap what the
|
|
217
|
+
* window shows, not yank it back into the dialog and make them detach it again.
|
|
218
|
+
*/
|
|
219
|
+
this.detached = false;
|
|
220
|
+
/** Coordinates computed in this browser, by name. Not persisted: a reload recomputes. */
|
|
221
|
+
this.computed = new Map();
|
|
222
|
+
/** The live run, when one is going. */
|
|
223
|
+
this.computeRun = null;
|
|
224
|
+
/**
|
|
225
|
+
* Bumped by every dataset change and by teardown, so a computation in progress can tell
|
|
226
|
+
* that what it is computing is no longer what is on screen.
|
|
227
|
+
*
|
|
228
|
+
* A run is several awaits long — the PCA scores arrive over HTTP, then the worker takes
|
|
229
|
+
* a minute or more — and the dataset can change at any of them, including BEFORE
|
|
230
|
+
* `computeRun` exists to be terminated. Without a token, the scores fetched for one
|
|
231
|
+
* dataset get embedded and drawn over another's observations: a plot of two datasets at
|
|
232
|
+
* once, which reads as a strange-looking t-SNE rather than as a bug.
|
|
233
|
+
*/
|
|
234
|
+
this.computeToken = 0;
|
|
235
|
+
/** 0..1 while running, or null before the first report. Drives the progress bar. */
|
|
236
|
+
this.computeFraction = null;
|
|
237
|
+
/** Which backend the worker got — 'webgpu', 'wasm' or 'cpu'. Shown while running. */
|
|
238
|
+
this.computeBackend = null;
|
|
239
|
+
this.computeMessage = null;
|
|
240
|
+
this.computeError = null;
|
|
241
|
+
/** True when a t-SNE is missing but the dataset is too big to embed here. */
|
|
242
|
+
this.tsneTooLarge = false;
|
|
243
|
+
/** Observations in the live dataset, kept for the cost estimate. */
|
|
244
|
+
this.observationCount = 0;
|
|
245
|
+
this.controls = null;
|
|
246
|
+
this.kind = 'histogram';
|
|
247
|
+
/** Embeddings the dataset publishes, and which of them is drawn. */
|
|
248
|
+
this.embeddings = [];
|
|
249
|
+
this.embedding = null;
|
|
250
|
+
this.embeddingCoords = null;
|
|
251
|
+
/** The categorical colouring behind the embedding's colours, kept so a redraw for a
|
|
252
|
+
* selection change does not refetch the column. */
|
|
253
|
+
this.embeddingCodes = null;
|
|
254
|
+
/**
|
|
255
|
+
* Genes the heatmap's rows are, and the vectors behind them.
|
|
256
|
+
*
|
|
257
|
+
* Local rather than in `SpatialViewState`, matching how the chart KIND is
|
|
258
|
+
* held: this is what the panel is charting, not what the map is drawing, and
|
|
259
|
+
* the renderer has no use for it.
|
|
260
|
+
*/
|
|
261
|
+
this.heatmapGenes = [];
|
|
262
|
+
/** Z-score each gene across the groups. On by default: without it one loud
|
|
263
|
+
* gene saturates the scale and the rest of the panel reads as blank. */
|
|
264
|
+
this.heatmapZScore = true;
|
|
265
|
+
/** Columns the cap dropped from the last render, for the note. */
|
|
266
|
+
this.heatmapHidden = 0;
|
|
267
|
+
this.geneOptions = [];
|
|
268
|
+
/** Every gene the dataset inlined. `geneOptions` is only ever the best few hundred of
|
|
269
|
+
* these — see `gene-search.ts` for why a whole-transcriptome list cannot be handed to
|
|
270
|
+
* the control whole. */
|
|
271
|
+
this.geneNames = [];
|
|
272
|
+
/** What is typed in the picker's filter box, so the options can be rebuilt when the
|
|
273
|
+
* selection changes without losing the query. */
|
|
274
|
+
this.geneQuery = '';
|
|
275
|
+
/** Fetched vectors by gene name, so adding a fourth gene does not refetch the
|
|
276
|
+
* first three — each is a full per-observation Float32Array. */
|
|
277
|
+
this.geneCache = new Map();
|
|
278
|
+
/** Categorical column the violin/box splits by; null = one trace for all. */
|
|
279
|
+
this.groupBy = null;
|
|
280
|
+
this.groupOptions = [];
|
|
281
|
+
/** What the map is coloured by — the chart's subject. */
|
|
282
|
+
this.colorBy = null;
|
|
283
|
+
this.selectionCount = 0;
|
|
284
|
+
/** Set when the active colour source cannot be charted, for an inline hint. */
|
|
285
|
+
this.notice = null;
|
|
286
|
+
this.busy = false;
|
|
287
|
+
this.view = { ...DEFAULT_SPATIAL_VIEW };
|
|
288
|
+
this.selection = emptySelection();
|
|
289
|
+
this.values = null;
|
|
290
|
+
/** Set instead of `values` when the colour source is a categorical column: its
|
|
291
|
+
* distribution is counts per category, not a histogram of its codes. */
|
|
292
|
+
this.categorical = null;
|
|
293
|
+
this.grouping = null;
|
|
294
|
+
/** Guards the async value fetch: a fast colour-source change can resolve out
|
|
295
|
+
* of order, and a stale vector would be charted against the new label. */
|
|
296
|
+
this.token = 0;
|
|
297
|
+
/** The same guard for the grouping fetch, which the user can change as fast. */
|
|
298
|
+
this.groupToken = 0;
|
|
299
|
+
/** Whether the first view emission has been handled. */
|
|
300
|
+
this.primed = false;
|
|
301
|
+
this.subs = new Subscription();
|
|
302
|
+
/**
|
|
303
|
+
* The height the drawn layout asked for, or null where it autosizes.
|
|
304
|
+
*
|
|
305
|
+
* {@link resize} needs this: the counts and heatmap layouts size their height
|
|
306
|
+
* to their content — one band per bar, per gene row — while the distribution
|
|
307
|
+
* kinds want to fill whatever height they are given.
|
|
308
|
+
*/
|
|
309
|
+
this.drawnHeight = null;
|
|
310
|
+
}
|
|
311
|
+
ngOnInit() {
|
|
312
|
+
// Before the port check: the help icon is in the template either way, and a panel
|
|
313
|
+
// with no data source is exactly where someone reads it.
|
|
314
|
+
ensureHelpTipStyle();
|
|
315
|
+
this.controls = this.viz.getSpatialControls?.() ?? null;
|
|
316
|
+
if (!this.controls)
|
|
317
|
+
return;
|
|
318
|
+
this.subs.add(combineLatest([
|
|
319
|
+
this.controls.getViewState$(), this.controls.getSelection$(),
|
|
320
|
+
]).subscribe(([view, selection]) => {
|
|
321
|
+
const sourceChanged = view.colorBy?.kind !== this.view.colorBy?.kind
|
|
322
|
+
|| view.colorBy?.name !== this.view.colorBy?.name;
|
|
323
|
+
this.view = view;
|
|
324
|
+
this.colorBy = view.colorBy;
|
|
325
|
+
this.selection = selection;
|
|
326
|
+
this.selectionCount = selection.count;
|
|
327
|
+
// A selection or log change only needs a re-render; a new colour source
|
|
328
|
+
// needs its vector fetched first. The FIRST emission always reloads:
|
|
329
|
+
// otherwise `null -> null` reads as "unchanged" and the component sits
|
|
330
|
+
// with no data and no explanation until something else moves.
|
|
331
|
+
if (!this.primed || sourceChanged) {
|
|
332
|
+
this.primed = true;
|
|
333
|
+
void this.reload();
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
// A selection or log-scale change needs only a redraw of the same vector.
|
|
337
|
+
void this.render();
|
|
338
|
+
}
|
|
339
|
+
}));
|
|
340
|
+
this.subs.add(this.controls.getDataset$().subscribe((dataset) => {
|
|
341
|
+
this.groupOptions = [
|
|
342
|
+
{ label: 'No grouping', value: null },
|
|
343
|
+
...(this.controls?.categoricalColumns() ?? []).map((n) => ({ label: n, value: n })),
|
|
344
|
+
];
|
|
345
|
+
// A new dataset's columns are different; a carried-over group is meaningless.
|
|
346
|
+
if (this.groupBy && !this.groupOptions.some((o) => o.value === this.groupBy)) {
|
|
347
|
+
this.groupBy = null;
|
|
348
|
+
this.grouping = null;
|
|
349
|
+
}
|
|
350
|
+
// The heatmap's rows come from the dataset's gene list. A dataset too wide
|
|
351
|
+
// to inline its names offers none here — the panel's typeahead is the way
|
|
352
|
+
// in for those, and the heatmap needs names it can list.
|
|
353
|
+
this.geneNames = [...(dataset?.features?.names ?? [])];
|
|
354
|
+
this.geneQuery = '';
|
|
355
|
+
// Validated against the WHOLE list, not the visible options: those are capped, and
|
|
356
|
+
// filtering the selection by them would drop genes the dataset still has.
|
|
357
|
+
const known = new Set(this.geneNames);
|
|
358
|
+
this.heatmapGenes = this.heatmapGenes.filter((n) => known.has(n));
|
|
359
|
+
this.refreshGeneOptions();
|
|
360
|
+
this.geneCache.clear();
|
|
361
|
+
// Embeddings belong to the dataset, so they are re-read with it and the loaded
|
|
362
|
+
// coordinates dropped: a UMAP from the previous dataset over these observations
|
|
363
|
+
// would be a plot of two different things at once.
|
|
364
|
+
this.computed.clear();
|
|
365
|
+
this.abandonCompute();
|
|
366
|
+
const published = dataset?.embeddings ? [...dataset.embeddings] : [];
|
|
367
|
+
// Offer to compute a t-SNE only where the dataset has PCA to embed and no t-SNE of
|
|
368
|
+
// its own — otherwise the menu advertises work that cannot start, or duplicates
|
|
369
|
+
// what is already served.
|
|
370
|
+
const hasPca = published.some((e) => /pca/i.test(e.label ?? e.name));
|
|
371
|
+
const hasTsne = published.some((e) => /tsne|t-sne/i.test(e.label ?? e.name));
|
|
372
|
+
this.observationCount = dataset?.observations.count ?? 0;
|
|
373
|
+
this.tsneTooLarge = hasPca && !hasTsne
|
|
374
|
+
&& this.observationCount > SpatialChartsComponent.BROWSER_TSNE_MAX_OBSERVATIONS;
|
|
375
|
+
this.embeddings = hasPca && !hasTsne && !this.tsneTooLarge
|
|
376
|
+
? [...published, ...SpatialChartsComponent.COMPUTABLE]
|
|
377
|
+
: published;
|
|
378
|
+
this.embedding = this.embeddings[0] ?? null;
|
|
379
|
+
this.embeddingCoords = null;
|
|
380
|
+
this.embeddingCodes = null;
|
|
381
|
+
// Nothing to draw for the kind that was selected; fall back rather than sit blank.
|
|
382
|
+
if (this.kind === 'embedding' && this.embeddings.length === 0)
|
|
383
|
+
this.kind = 'histogram';
|
|
384
|
+
}));
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Re-fit the plot to the panel's current width.
|
|
388
|
+
*
|
|
389
|
+
* Called by the host when the dialog finishes resizing. Plotly does not notice
|
|
390
|
+
* a container resize on its own — its `responsive` option listens for WINDOW
|
|
391
|
+
* resizes only, so dragging a dialog edge reaches it through nothing at all.
|
|
392
|
+
*/
|
|
393
|
+
resize() {
|
|
394
|
+
this.refit(this.chartDiv);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Re-fit whatever is plotted in `div` to its container.
|
|
398
|
+
*
|
|
399
|
+
* Shared by the panel and the detached window because the rule is the same and getting
|
|
400
|
+
* it wrong is invisible until it is not: `autosize` takes BOTH dimensions from the
|
|
401
|
+
* container, which is right only where the layout did not fix its own height. Measured:
|
|
402
|
+
* autosizing a layout with an explicit height of 500 replaced it with the container's
|
|
403
|
+
* 400. The counts and heatmap layouts size themselves to their row count, so the
|
|
404
|
+
* window resize has to set the width ALONE for them and let the height stand.
|
|
405
|
+
*/
|
|
406
|
+
refit(div) {
|
|
407
|
+
const el = document.getElementById(div);
|
|
408
|
+
if (!el)
|
|
409
|
+
return;
|
|
410
|
+
const width = Math.round(el.clientWidth);
|
|
411
|
+
// Zero while the section is collapsed or the dialog is closed; relaying out
|
|
412
|
+
// to a zero box makes Plotly compute a layout it does not recover from.
|
|
413
|
+
if (width <= 0)
|
|
414
|
+
return;
|
|
415
|
+
try {
|
|
416
|
+
Plotly.relayout(el, this.drawnHeight === null ? { autosize: true } : { width });
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
// Nothing plotted yet.
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
ngOnDestroy() {
|
|
423
|
+
this.subs.unsubscribe();
|
|
424
|
+
// A worker outlives the component that started it: closing the panel mid-run would
|
|
425
|
+
// otherwise leave a t-SNE saturating a GPU for minutes with nothing left to receive
|
|
426
|
+
// the answer.
|
|
427
|
+
this.abandonCompute();
|
|
428
|
+
// BOTH divs: a chart left detached at teardown holds its WebGL context in the
|
|
429
|
+
// window's div, which the inline id would never reach.
|
|
430
|
+
for (const div of [this.chartDiv, this.detachedDiv]) {
|
|
431
|
+
try {
|
|
432
|
+
Plotly.purge(div);
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
// The div may already be gone with the dialog; nothing to clean up.
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
ngAfterViewInit() {
|
|
440
|
+
// The div exists only now, so this is the earliest a first draw can land.
|
|
441
|
+
void this.reload();
|
|
442
|
+
}
|
|
443
|
+
onKind(kind) {
|
|
444
|
+
this.kind = kind;
|
|
445
|
+
if (kind === 'heatmap') {
|
|
446
|
+
// Seed with the gene already on screen, so the chart says something the
|
|
447
|
+
// moment it opens rather than showing an empty grid and a prompt.
|
|
448
|
+
if (this.heatmapGenes.length === 0 && this.colorBy?.kind === 'feature') {
|
|
449
|
+
this.heatmapGenes = [this.colorBy.name];
|
|
450
|
+
}
|
|
451
|
+
void (async () => {
|
|
452
|
+
await this.ensureHeatmapGrouping();
|
|
453
|
+
await this.loadHeatmapGenes();
|
|
454
|
+
void this.render();
|
|
455
|
+
})();
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
void this.render();
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* The grouping the heatmap's columns come from, defaulting rather than
|
|
462
|
+
* demanding one: a heatmap with no columns is not a chart, and "No grouping"
|
|
463
|
+
* is a sensible answer for a violin but not for this.
|
|
464
|
+
*/
|
|
465
|
+
async ensureHeatmapGrouping() {
|
|
466
|
+
if (this.grouping || !this.controls)
|
|
467
|
+
return;
|
|
468
|
+
const first = this.groupOptions.find((o) => o.value)?.value;
|
|
469
|
+
if (!first)
|
|
470
|
+
return;
|
|
471
|
+
await this.onGroupBy(first);
|
|
472
|
+
}
|
|
473
|
+
onHeatmapZScore(on) {
|
|
474
|
+
this.heatmapZScore = on;
|
|
475
|
+
void this.render();
|
|
476
|
+
}
|
|
477
|
+
/** What the heatmap is currently showing, said plainly. */
|
|
478
|
+
get heatmapNote() {
|
|
479
|
+
if (this.heatmapGenes.length === 0)
|
|
480
|
+
return 'Pick one or more genes for the rows.';
|
|
481
|
+
const perCell = this.selectionCount > 0
|
|
482
|
+
&& this.selectionCount <= SpatialChartsComponent.HEATMAP_CELL_COLUMNS;
|
|
483
|
+
if (perCell) {
|
|
484
|
+
return `One column per selected cell (${this.selectionCount}). `
|
|
485
|
+
+ 'Mean expression per cell, so the columns are cells rather than classes.';
|
|
486
|
+
}
|
|
487
|
+
const scope = this.selectionCount > 0 ? 'the selected cells' : 'all cells';
|
|
488
|
+
const scaled = this.heatmapZScore
|
|
489
|
+
? ' Each gene is z-scored across the columns, so the colour is above or below that gene\'s own average.'
|
|
490
|
+
: ' Raw means, so a highly-expressed gene dominates the scale.';
|
|
491
|
+
// Saying so matters: without it the panel looks like the whole column.
|
|
492
|
+
const capped = this.heatmapHidden > 0
|
|
493
|
+
? ` Showing the ${SpatialChartsComponent.HEATMAP_MAX_COLUMNS} strongest of `
|
|
494
|
+
+ `${SpatialChartsComponent.HEATMAP_MAX_COLUMNS + this.heatmapHidden}, `
|
|
495
|
+
+ 'ranked by the largest value any picked gene reaches.'
|
|
496
|
+
: '';
|
|
497
|
+
return `Mean expression of each gene within each ${this.groupBy ?? 'group'}, over ${scope}.`
|
|
498
|
+
+ `${scaled}${capped}`;
|
|
499
|
+
}
|
|
500
|
+
/** Gene rows for the heatmap. */
|
|
501
|
+
async onHeatmapGenes(names) {
|
|
502
|
+
this.heatmapGenes = names ?? [];
|
|
503
|
+
// Chosen genes must stay in the options or the control cannot label its chips.
|
|
504
|
+
this.refreshGeneOptions();
|
|
505
|
+
await this.loadHeatmapGenes();
|
|
506
|
+
void this.render();
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Fetch any gene the heatmap needs and does not already hold.
|
|
510
|
+
*
|
|
511
|
+
* Sequenced on the same token the colour-source load uses: each vector is a
|
|
512
|
+
* separate request, and a slower one must not paint rows for a gene list the
|
|
513
|
+
* user has already moved on from.
|
|
514
|
+
*/
|
|
515
|
+
async loadHeatmapGenes() {
|
|
516
|
+
const controls = this.controls;
|
|
517
|
+
if (!controls)
|
|
518
|
+
return;
|
|
519
|
+
const missing = this.heatmapGenes.filter((n) => !this.geneCache.has(n));
|
|
520
|
+
if (missing.length === 0)
|
|
521
|
+
return;
|
|
522
|
+
const mine = ++this.token;
|
|
523
|
+
this.busy = true;
|
|
524
|
+
try {
|
|
525
|
+
for (const name of missing) {
|
|
526
|
+
const values = await controls.continuousValues({ kind: 'feature', name });
|
|
527
|
+
if (mine !== this.token)
|
|
528
|
+
return;
|
|
529
|
+
this.geneCache.set(name, values);
|
|
530
|
+
}
|
|
531
|
+
this.notice = null;
|
|
532
|
+
}
|
|
533
|
+
catch (err) {
|
|
534
|
+
if (mine !== this.token)
|
|
535
|
+
return;
|
|
536
|
+
this.notice = `A gene could not be charted: ${err?.message ?? err}`;
|
|
537
|
+
}
|
|
538
|
+
finally {
|
|
539
|
+
if (mine === this.token)
|
|
540
|
+
this.busy = false;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
async onGroupBy(name) {
|
|
544
|
+
this.groupBy = name;
|
|
545
|
+
if (!name || !this.controls) {
|
|
546
|
+
this.grouping = null;
|
|
547
|
+
void this.render();
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
// Sequenced: pick A then B and a slower A would otherwise land last, charting
|
|
551
|
+
// A's categories under a dropdown that says B.
|
|
552
|
+
const mine = ++this.groupToken;
|
|
553
|
+
try {
|
|
554
|
+
const view = await this.controls.categoricalView(name);
|
|
555
|
+
if (mine !== this.groupToken)
|
|
556
|
+
return;
|
|
557
|
+
this.grouping = { codes: view.codes, categories: view.categories, colors: view.colors };
|
|
558
|
+
}
|
|
559
|
+
catch {
|
|
560
|
+
if (mine !== this.groupToken)
|
|
561
|
+
return;
|
|
562
|
+
this.grouping = null;
|
|
563
|
+
this.groupBy = null;
|
|
564
|
+
}
|
|
565
|
+
void this.render();
|
|
566
|
+
}
|
|
567
|
+
/** True when the current kind would read better with a grouping chosen. */
|
|
568
|
+
get suggestsGrouping() {
|
|
569
|
+
return benefitsFromGrouping(this.kind) && !this.groupBy && this.groupOptions.length > 1;
|
|
570
|
+
}
|
|
571
|
+
get subject() {
|
|
572
|
+
if (!this.colorBy)
|
|
573
|
+
return '';
|
|
574
|
+
return this.colorBy.kind === 'feature' ? `gene ${this.colorBy.name}` : this.colorBy.name;
|
|
575
|
+
}
|
|
576
|
+
// ── internals ───────────────────────────────────────────────────────────
|
|
577
|
+
/** Fetch the active colour source's vector, then draw. */
|
|
578
|
+
async reload() {
|
|
579
|
+
const controls = this.controls;
|
|
580
|
+
const source = this.view.colorBy;
|
|
581
|
+
const mine = ++this.token;
|
|
582
|
+
if (!controls || !source) {
|
|
583
|
+
this.values = null;
|
|
584
|
+
this.categorical = null;
|
|
585
|
+
this.notice = controls
|
|
586
|
+
? 'Colour the map by a column or a gene to chart its distribution.'
|
|
587
|
+
: null;
|
|
588
|
+
void this.render();
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
this.busy = true;
|
|
592
|
+
// A categorical column charts as COUNTS per category — asked for by name
|
|
593
|
+
// rather than discovered by catching the continuous fetch's error, so a
|
|
594
|
+
// genuine failure still reads as a failure.
|
|
595
|
+
const isCategorical = source.kind === 'column'
|
|
596
|
+
&& (controls.categoricalColumns() ?? []).includes(source.name);
|
|
597
|
+
try {
|
|
598
|
+
if (isCategorical) {
|
|
599
|
+
const view = await controls.categoricalView(source.name);
|
|
600
|
+
if (mine !== this.token)
|
|
601
|
+
return; // superseded
|
|
602
|
+
this.categorical = { codes: view.codes, categories: view.categories, colors: view.colors };
|
|
603
|
+
this.values = null;
|
|
604
|
+
this.kind = 'counts';
|
|
605
|
+
this.notice = null;
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
const values = await controls.continuousValues(source);
|
|
609
|
+
if (mine !== this.token)
|
|
610
|
+
return;
|
|
611
|
+
this.values = values;
|
|
612
|
+
this.categorical = null;
|
|
613
|
+
if (this.kind === 'counts')
|
|
614
|
+
this.kind = 'histogram';
|
|
615
|
+
this.notice = null;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
catch (err) {
|
|
619
|
+
if (mine !== this.token)
|
|
620
|
+
return;
|
|
621
|
+
this.values = null;
|
|
622
|
+
this.categorical = null;
|
|
623
|
+
this.notice = `"${source.name}" could not be charted: ${err?.message ?? err}`;
|
|
624
|
+
}
|
|
625
|
+
finally {
|
|
626
|
+
if (mine === this.token)
|
|
627
|
+
this.busy = false;
|
|
628
|
+
}
|
|
629
|
+
void this.render();
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* The embedding scatter — a UMAP over the same observations.
|
|
633
|
+
*
|
|
634
|
+
* Answers what the map cannot: the map says WHERE a population sits, this says which
|
|
635
|
+
* populations there are and how close they are in expression.
|
|
636
|
+
*
|
|
637
|
+
* Linked to the map without asking for anything: it reuses `this.categorical`, the same
|
|
638
|
+
* loaded colouring the counts chart draws from, so the two cannot disagree about what a
|
|
639
|
+
* colour means and no second fetch happens. A selection dims the rest rather than
|
|
640
|
+
* dropping it — the embedding's shape is the context that makes a selection legible.
|
|
641
|
+
*/
|
|
642
|
+
async renderEmbedding() {
|
|
643
|
+
const controls = this.controls;
|
|
644
|
+
const meta = this.embedding;
|
|
645
|
+
if (!controls || !meta) {
|
|
646
|
+
this.purgePlot();
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
if (!controls.getEmbedding) {
|
|
650
|
+
this.notice = 'This data source does not serve embeddings.';
|
|
651
|
+
this.purgePlot();
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
// Sequenced on the shared token: the coordinates are a round-trip, and a slower one
|
|
655
|
+
// must not paint over a kind the user has already moved on from.
|
|
656
|
+
const mine = ++this.token;
|
|
657
|
+
// Computed here, not served: a local result is the same shape as a fetched one, so
|
|
658
|
+
// everything downstream — colouring, lasso selection, the camera — is unchanged.
|
|
659
|
+
const local = this.computed.get(meta.name);
|
|
660
|
+
if (local) {
|
|
661
|
+
this.embeddingCoords = local;
|
|
662
|
+
}
|
|
663
|
+
else if (this.isComputable) {
|
|
664
|
+
// Offered but not yet computed. The panel shows the Compute button; drawing nothing
|
|
665
|
+
// is right, and an error would be wrong — there is no failure here.
|
|
666
|
+
this.purgePlot();
|
|
667
|
+
this.notice = null;
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
else if (!this.embeddingCoords || this.embeddingCoords.meta.name !== meta.name) {
|
|
671
|
+
this.busy = true;
|
|
672
|
+
try {
|
|
673
|
+
const loaded = await controls.getEmbedding(meta.name);
|
|
674
|
+
if (mine !== this.token)
|
|
675
|
+
return;
|
|
676
|
+
this.embeddingCoords = loaded;
|
|
677
|
+
}
|
|
678
|
+
catch (err) {
|
|
679
|
+
if (mine !== this.token)
|
|
680
|
+
return;
|
|
681
|
+
this.notice = `Could not load ${meta.label ?? meta.name}: `
|
|
682
|
+
+ `${err?.message ?? err}`;
|
|
683
|
+
this.purgePlot();
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
finally {
|
|
687
|
+
if (mine === this.token)
|
|
688
|
+
this.busy = false;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
const coords = this.embeddingCoords;
|
|
692
|
+
if (!coords)
|
|
693
|
+
return;
|
|
694
|
+
const target = this.plotTarget;
|
|
695
|
+
const el = document.getElementById(target);
|
|
696
|
+
if (!el)
|
|
697
|
+
return; // the window is closed, or not rendered yet
|
|
698
|
+
const view = this.liveView(el);
|
|
699
|
+
this.notice = null;
|
|
700
|
+
const input = {
|
|
701
|
+
x: coords.x,
|
|
702
|
+
y: coords.y,
|
|
703
|
+
// The third dimension when the embedding has one — what switches the plot to a
|
|
704
|
+
// rotatable 3D scatter. Decoded only for a 3-dim embedding, so its presence here
|
|
705
|
+
// is the same question as `meta.dims === 3`.
|
|
706
|
+
...(coords.z ? { z: coords.z } : {}),
|
|
707
|
+
label: meta.label ?? meta.name,
|
|
708
|
+
derived: meta.derived,
|
|
709
|
+
// Present for a PCA, absent for a UMAP — see `varianceRatio` on the meta.
|
|
710
|
+
...(meta.varianceRatio ? { varianceRatio: meta.varianceRatio } : {}),
|
|
711
|
+
...(this.categorical
|
|
712
|
+
? {
|
|
713
|
+
categories: {
|
|
714
|
+
codes: this.categorical.codes,
|
|
715
|
+
names: this.categorical.categories,
|
|
716
|
+
colors: this.categorical.colors,
|
|
717
|
+
},
|
|
718
|
+
}
|
|
719
|
+
: {}),
|
|
720
|
+
...(this.selection.count > 0 ? { selection: this.selection.mask } : {}),
|
|
721
|
+
// Whatever view the user has set up, read from the LIVE plot — which is where
|
|
722
|
+
// rotating and zooming leave it.
|
|
723
|
+
...(view ? { view } : {}),
|
|
724
|
+
};
|
|
725
|
+
await this.draw(buildEmbeddingTraces(input), embeddingLayout(input), EMBEDDING_CONFIG, target);
|
|
726
|
+
this.bindEmbeddingSelection(target);
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Turn a lasso in the embedding into a selection of observations.
|
|
730
|
+
*
|
|
731
|
+
* This is what makes two views of one dataset worth having side by side: draw round a
|
|
732
|
+
* cluster here and those cells light up on the tissue, because every view reads the
|
|
733
|
+
* same mask.
|
|
734
|
+
*
|
|
735
|
+
* Bound after each draw, and the previous listener removed first: `Plotly.react`
|
|
736
|
+
* preserves handlers, so re-binding without removing would fire the selection once per
|
|
737
|
+
* redraw the plot had ever had.
|
|
738
|
+
*/
|
|
739
|
+
bindEmbeddingSelection(div) {
|
|
740
|
+
const el = document.getElementById(div);
|
|
741
|
+
if (!el?.on)
|
|
742
|
+
return;
|
|
743
|
+
el.removeAllListeners?.('plotly_selected');
|
|
744
|
+
el.removeAllListeners?.('plotly_deselect');
|
|
745
|
+
el.on('plotly_selected', (ev) => {
|
|
746
|
+
// A lasso that selects nothing arrives as an event with no points; treat it as a
|
|
747
|
+
// clear, which is what dragging an empty patch looks like it should do.
|
|
748
|
+
const points = ev?.points ?? [];
|
|
749
|
+
const indices = [];
|
|
750
|
+
for (const p of points) {
|
|
751
|
+
// The observation index rides on `customdata` — see `buildEmbeddingTraces`. A point's
|
|
752
|
+
// index within its trace is not the observation once the points are split by
|
|
753
|
+
// category, so this is the only correct source.
|
|
754
|
+
const id = p.customdata;
|
|
755
|
+
if (typeof id === 'number')
|
|
756
|
+
indices.push(id);
|
|
757
|
+
}
|
|
758
|
+
if (indices.length === 0) {
|
|
759
|
+
this.controls?.clearSelection();
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
this.controls?.selectIndices(indices);
|
|
763
|
+
});
|
|
764
|
+
// The modebar's deselect, and a plain click on empty space.
|
|
765
|
+
el.on('plotly_deselect', () => this.controls?.clearSelection());
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Drop the embedding's selection handlers from a div another kind is about to use.
|
|
769
|
+
*
|
|
770
|
+
* The detached window outlives any one kind — switching tabs swaps its content and
|
|
771
|
+
* keeps the div — so without this the lasso handlers bound for a UMAP stay live under
|
|
772
|
+
* the counts plot that replaces it. `plotly_deselect` fires on a plain click in ANY
|
|
773
|
+
* Plotly chart, so the first click on a bar would silently clear the map's selection.
|
|
774
|
+
*/
|
|
775
|
+
unbindEmbeddingSelection(div) {
|
|
776
|
+
const el = document.getElementById(div);
|
|
777
|
+
if (!el?.removeAllListeners)
|
|
778
|
+
return;
|
|
779
|
+
el.removeAllListeners('plotly_selected');
|
|
780
|
+
el.removeAllListeners('plotly_deselect');
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* The view the user has set up on the live plot, to carry across a redraw.
|
|
784
|
+
*
|
|
785
|
+
* `Plotly.react` resets a 3D scene camera and any zoomed 2D range unless the layout
|
|
786
|
+
* carries them, so selecting a category or recolouring would throw away an orientation
|
|
787
|
+
* that took work to find. Rotating a cloud to see a structure and losing it on the next
|
|
788
|
+
* click makes the plot useless for what it is for.
|
|
789
|
+
*/
|
|
790
|
+
liveView(el) {
|
|
791
|
+
const full = el._fullLayout;
|
|
792
|
+
if (!full)
|
|
793
|
+
return null; // nothing drawn here yet
|
|
794
|
+
if (full.scene?.camera)
|
|
795
|
+
return { camera: full.scene.camera };
|
|
796
|
+
// 2D: only once the user has actually zoomed. Passing an autoranged range back would
|
|
797
|
+
// freeze the axes and stop the plot re-fitting when the data changes.
|
|
798
|
+
const zoomed = full.xaxis?.autorange === false && full.yaxis?.autorange === false;
|
|
799
|
+
if (zoomed && full.xaxis?.range && full.yaxis?.range) {
|
|
800
|
+
return { ranges: { x: full.xaxis.range, y: full.yaxis.range } };
|
|
801
|
+
}
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Whether the plot on screen fixed its own height.
|
|
806
|
+
*
|
|
807
|
+
* The counts and heatmap layouts size themselves to their row count, so in the detached
|
|
808
|
+
* window they can be TALLER than the window — which has to scroll rather than clip. An
|
|
809
|
+
* embedding fixes no height and fills the window instead. The template needs to know
|
|
810
|
+
* which, because the two want opposite `flex` and `overflow`.
|
|
811
|
+
*/
|
|
812
|
+
get hasFixedHeight() {
|
|
813
|
+
return this.drawnHeight !== null;
|
|
814
|
+
}
|
|
815
|
+
/** Where the active kind draws: its own window, or the shared chart div. */
|
|
816
|
+
get plotTarget() {
|
|
817
|
+
return this.detached ? this.detachedDiv : this.chartDiv;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Header for the detached window — what it is showing, named as its tab is.
|
|
821
|
+
*
|
|
822
|
+
* The embedding names the embedding rather than the tab, because "Embedding" beside a
|
|
823
|
+
* window drawing a t-SNE says less than "t-SNE" does.
|
|
824
|
+
*/
|
|
825
|
+
get detachedTitle() {
|
|
826
|
+
if (this.kind === 'embedding') {
|
|
827
|
+
return this.embedding?.label ?? this.embedding?.name ?? 'Embedding';
|
|
828
|
+
}
|
|
829
|
+
const label = this.kindOptions.find((k) => k.value === this.kind)?.label ?? 'Chart';
|
|
830
|
+
return this.subject ? `${label} · ${this.subject}` : label;
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Move the active kind between its own window and the panel.
|
|
834
|
+
*
|
|
835
|
+
* Purges the div it is LEAVING first. Plotly keeps per-div state, and a graph left
|
|
836
|
+
* behind in a div that Angular then removes leaks its WebGL context — and if the div
|
|
837
|
+
* comes back, react would resize a plot whose data belongs to the other place.
|
|
838
|
+
*/
|
|
839
|
+
toggleDetached() {
|
|
840
|
+
const leaving = this.plotTarget;
|
|
841
|
+
this.detached = !this.detached;
|
|
842
|
+
try {
|
|
843
|
+
Plotly.purge(leaving);
|
|
844
|
+
}
|
|
845
|
+
catch {
|
|
846
|
+
// Nothing was plotted there.
|
|
847
|
+
}
|
|
848
|
+
// Going back INLINE, the div appears with the next change detection, so a task is
|
|
849
|
+
// enough. Going OUT, the window's div does not exist yet — PrimeNG mounts the dialog
|
|
850
|
+
// with a transition — so the dialog's own `onShow` drives that draw instead. A single
|
|
851
|
+
// deferred attempt drew nothing and never retried.
|
|
852
|
+
if (!this.detached)
|
|
853
|
+
setTimeout(() => void this.render(), 0);
|
|
854
|
+
}
|
|
855
|
+
/** The detached window is up and its div exists — now the plot can be drawn into it. */
|
|
856
|
+
onDetachedWindowShown() {
|
|
857
|
+
void this.render();
|
|
858
|
+
}
|
|
859
|
+
/** Re-fit the detached window's plot after it is resized. */
|
|
860
|
+
onDetachedResizeEnd() {
|
|
861
|
+
this.refit(this.detachedDiv);
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Rebuild the visible options: the best matches for what is typed, plus what is chosen.
|
|
865
|
+
*
|
|
866
|
+
* A capped multi-select that drops its own selections cannot resolve their labels, and
|
|
867
|
+
* the model loses them on the next change — the chips vanish for no reason the user
|
|
868
|
+
* can see.
|
|
869
|
+
*/
|
|
870
|
+
refreshGeneOptions() {
|
|
871
|
+
this.geneOptions = geneOptionsFor(this.geneNames, this.geneQuery, this.heatmapGenes)
|
|
872
|
+
.map((n) => ({ label: n, value: n }));
|
|
873
|
+
}
|
|
874
|
+
/** The gene picker's filter box changed: search the resident names, show the best. */
|
|
875
|
+
onGeneFilter(query) {
|
|
876
|
+
this.geneQuery = query ?? '';
|
|
877
|
+
this.refreshGeneOptions();
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Roughly how long a run will take, in seconds, from the measured anchor.
|
|
881
|
+
*
|
|
882
|
+
* Shown before the click. A progress bar tells you a job is going; only an estimate
|
|
883
|
+
* tells you whether to start it.
|
|
884
|
+
*/
|
|
885
|
+
get computeEstimateSeconds() {
|
|
886
|
+
const n = this.observationCount;
|
|
887
|
+
const { seconds, observations } = SpatialChartsComponent.TSNE_SECONDS_AT;
|
|
888
|
+
return Math.max(1, Math.round(seconds * (n / observations) ** 2));
|
|
889
|
+
}
|
|
890
|
+
/** That estimate as something to put on a button. */
|
|
891
|
+
get computeEstimateLabel() {
|
|
892
|
+
const secs = this.computeEstimateSeconds;
|
|
893
|
+
if (secs < 90)
|
|
894
|
+
return `~${secs}s`;
|
|
895
|
+
return `~${Math.round(secs / 60)} min`;
|
|
896
|
+
}
|
|
897
|
+
/** Why the option is absent, when a dataset is past the threshold. */
|
|
898
|
+
get tsneTooLargeNote() {
|
|
899
|
+
const n = this.observationCount;
|
|
900
|
+
return `t-SNE is not offered here for ${n.toLocaleString()} observations — it is `
|
|
901
|
+
+ `quadratic, so it would take roughly ${this.computeEstimateLabel} in the browser. `
|
|
902
|
+
+ 'Compute it offline and serve it with the dataset.';
|
|
903
|
+
}
|
|
904
|
+
/** Whether the selected embedding is one this browser would have to compute. */
|
|
905
|
+
get isComputable() {
|
|
906
|
+
return !!this.embedding && this.embedding.name.startsWith('local:');
|
|
907
|
+
}
|
|
908
|
+
/** Whether it has already been computed in this session. */
|
|
909
|
+
get isComputed() {
|
|
910
|
+
return !!this.embedding && this.computed.has(this.embedding.name);
|
|
911
|
+
}
|
|
912
|
+
get isComputing() {
|
|
913
|
+
return !!this.computeRun?.running;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Compute the selected embedding here, in a worker.
|
|
917
|
+
*
|
|
918
|
+
* The PCA scores come from the port as an ordinary embedding — the server derives them,
|
|
919
|
+
* because PCA needs the whole expression matrix (185 MB for a Visium dataset) while
|
|
920
|
+
* t-SNE needs only the scores (0.51 MB). Sending the matrix to the browser to save a
|
|
921
|
+
* few seconds of arithmetic would be a far slower answer.
|
|
922
|
+
*/
|
|
923
|
+
async computeEmbedding() {
|
|
924
|
+
const meta = this.embedding;
|
|
925
|
+
const controls = this.controls;
|
|
926
|
+
if (!meta || !controls?.getEmbedding || this.isComputing)
|
|
927
|
+
return;
|
|
928
|
+
this.computeError = null;
|
|
929
|
+
this.computeMessage = null;
|
|
930
|
+
this.computeFraction = null;
|
|
931
|
+
this.computeBackend = null;
|
|
932
|
+
// Every early return and every await below is checked against this. `run` is held
|
|
933
|
+
// locally as well as on the instance: after an await `this.computeRun` may already be
|
|
934
|
+
// a newer run's, and clearing or terminating that one is how a fresh computation gets
|
|
935
|
+
// killed by the tail of the one it replaced.
|
|
936
|
+
const mine = ++this.computeToken;
|
|
937
|
+
const superseded = () => mine !== this.computeToken;
|
|
938
|
+
let run = null;
|
|
939
|
+
try {
|
|
940
|
+
// Prefer the 3-D scores: they carry a third component for free, and t-SNE on more
|
|
941
|
+
// components than it needs is not better — the PCA basis is the input either way.
|
|
942
|
+
const source = await this.loadPcaScores(controls);
|
|
943
|
+
if (superseded())
|
|
944
|
+
return;
|
|
945
|
+
if (!source) {
|
|
946
|
+
this.computeError = 'This dataset serves no PCA to embed.';
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
run = new EmbeddingComputeRun();
|
|
950
|
+
this.computeRun = run;
|
|
951
|
+
const result = await run.run({
|
|
952
|
+
scores: source.scores,
|
|
953
|
+
nObs: source.nObs,
|
|
954
|
+
nDims: source.nDims,
|
|
955
|
+
dims: meta.dims,
|
|
956
|
+
}, {
|
|
957
|
+
...meta,
|
|
958
|
+
label: (meta.label ?? meta.name)
|
|
959
|
+
.replace(SpatialChartsComponent.COMPUTE_SUFFIX, ''),
|
|
960
|
+
}, (progress) => {
|
|
961
|
+
// Progress from a superseded run must not drive the bar a newer one is using.
|
|
962
|
+
if (superseded())
|
|
963
|
+
return;
|
|
964
|
+
this.computeFraction = progress.fraction;
|
|
965
|
+
this.computeBackend = progress.backend ?? this.computeBackend;
|
|
966
|
+
if (progress.message)
|
|
967
|
+
this.computeMessage = progress.message;
|
|
968
|
+
});
|
|
969
|
+
// A terminated run resolves null, so this covers abandonment as well as Cancel —
|
|
970
|
+
// but the token is still checked, because the dataset can change between the
|
|
971
|
+
// worker's `done` and this line.
|
|
972
|
+
if (result && !superseded()) {
|
|
973
|
+
this.computed.set(meta.name, result);
|
|
974
|
+
this.embeddingCoords = result;
|
|
975
|
+
await this.render();
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
catch (err) {
|
|
979
|
+
if (!superseded())
|
|
980
|
+
this.computeError = err?.message ?? String(err);
|
|
981
|
+
}
|
|
982
|
+
finally {
|
|
983
|
+
run?.terminate();
|
|
984
|
+
// Only if it is still ours: a dataset switch has already terminated this run and
|
|
985
|
+
// may have started another, and `this.computeRun = null` here would orphan it —
|
|
986
|
+
// leaving a worker nothing can cancel and a Cancel button that does nothing.
|
|
987
|
+
if (this.computeRun === run) {
|
|
988
|
+
this.computeRun = null;
|
|
989
|
+
this.computeFraction = null;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
/** Stop a run. It settles shortly after, leaving no embedding. */
|
|
994
|
+
cancelCompute() {
|
|
995
|
+
this.computeRun?.cancel();
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Drop any computation in progress, because what it is computing no longer applies.
|
|
999
|
+
*
|
|
1000
|
+
* Distinct from {@link cancelCompute}, which is the user's Cancel and asks the worker to
|
|
1001
|
+
* stop politely. This one does not wait: the run is settled and the worker killed, so a
|
|
1002
|
+
* dataset switch or a teardown cannot leave a t-SNE grinding on in the background over
|
|
1003
|
+
* observations nothing is showing any more.
|
|
1004
|
+
*/
|
|
1005
|
+
abandonCompute() {
|
|
1006
|
+
this.computeToken++;
|
|
1007
|
+
this.computeRun?.terminate();
|
|
1008
|
+
this.computeRun = null;
|
|
1009
|
+
this.computeFraction = null;
|
|
1010
|
+
this.computeBackend = null;
|
|
1011
|
+
this.computeMessage = null;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* The dataset's PCA, as row-major scores.
|
|
1015
|
+
*
|
|
1016
|
+
* Assembled from whichever PCA the source offers, widest first — a 3-D one gives t-SNE
|
|
1017
|
+
* three components instead of two for the same request.
|
|
1018
|
+
*/
|
|
1019
|
+
async loadPcaScores(controls) {
|
|
1020
|
+
const candidates = this.embeddings
|
|
1021
|
+
.filter((e) => /pca/i.test(e.label ?? e.name) && !e.name.startsWith('local:'))
|
|
1022
|
+
.sort((a, b) => b.dims - a.dims);
|
|
1023
|
+
for (const candidate of candidates) {
|
|
1024
|
+
try {
|
|
1025
|
+
const pca = await controls.getEmbedding(candidate.name);
|
|
1026
|
+
const planes = [pca.x, pca.y, ...(pca.z ? [pca.z] : [])];
|
|
1027
|
+
const nObs = pca.x.length;
|
|
1028
|
+
const nDims = planes.length;
|
|
1029
|
+
const scores = new Float32Array(nObs * nDims);
|
|
1030
|
+
for (let i = 0; i < nObs; i++) {
|
|
1031
|
+
for (let d = 0; d < nDims; d++)
|
|
1032
|
+
scores[i * nDims + d] = planes[d][i];
|
|
1033
|
+
}
|
|
1034
|
+
return { scores, nObs, nDims };
|
|
1035
|
+
}
|
|
1036
|
+
catch {
|
|
1037
|
+
// Try the next; a source may advertise one it cannot actually serve.
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
1042
|
+
/** Which embedding to draw, when the dataset publishes more than one. */
|
|
1043
|
+
onEmbedding(name) {
|
|
1044
|
+
const next = this.embeddings.find((e) => e.name === name);
|
|
1045
|
+
if (!next || next.name === this.embedding?.name)
|
|
1046
|
+
return;
|
|
1047
|
+
this.embedding = next;
|
|
1048
|
+
this.embeddingCoords = null;
|
|
1049
|
+
void this.render();
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* What the chart on screen actually shows, and what it cannot be read for.
|
|
1053
|
+
*
|
|
1054
|
+
* Shown on hover from a `?` beside the tabs. Each of these plots answers a different
|
|
1055
|
+
* question and two of them are routinely over-read — a UMAP's distances and a heatmap's
|
|
1056
|
+
* unscaled colours — so the caveat is part of the explanation rather than a footnote.
|
|
1057
|
+
*
|
|
1058
|
+
* HTML, because the tooltip renders with `[escape]="false"`: a paragraph and a caveat
|
|
1059
|
+
* read as two thoughts, and a single run-on line is skipped rather than read.
|
|
1060
|
+
*/
|
|
1061
|
+
get kindHelp() {
|
|
1062
|
+
switch (this.kind) {
|
|
1063
|
+
case 'counts':
|
|
1064
|
+
return '<b>Counts</b> — how many observations fall in each category of the column '
|
|
1065
|
+
+ 'the map is coloured by, largest first.<br><br>A category code is a label, not '
|
|
1066
|
+
+ 'a magnitude, so a frequency is the only distribution it has: there is no '
|
|
1067
|
+
+ 'histogram of a cell type.';
|
|
1068
|
+
case 'histogram':
|
|
1069
|
+
return '<b>Histogram</b> — how the active value is distributed over all '
|
|
1070
|
+
+ 'observations.<br><br>With a selection, it is overlaid on the full '
|
|
1071
|
+
+ 'distribution rather than replacing it, so you can see where the selected '
|
|
1072
|
+
+ 'cells sit within the whole.';
|
|
1073
|
+
case 'violin':
|
|
1074
|
+
return '<b>Violin</b> — the active value\'s distribution within each category of '
|
|
1075
|
+
+ 'the grouping column, drawn as a smoothed density.<br><br>Shows shape a box '
|
|
1076
|
+
+ 'plot hides: two groups with the same median can be one peak or two.';
|
|
1077
|
+
case 'box':
|
|
1078
|
+
return '<b>Box</b> — median, quartiles and range of the active value within each '
|
|
1079
|
+
+ 'category of the grouping column.<br><br>Compact and comparable across many '
|
|
1080
|
+
+ 'groups, at the cost of hiding whether a group is bimodal.';
|
|
1081
|
+
case 'heatmap':
|
|
1082
|
+
return '<b>Heatmap</b> — mean expression of each picked gene within each group: '
|
|
1083
|
+
+ 'genes down, groups across.<br><br>Each gene is <b>z-scored across the '
|
|
1084
|
+
+ 'groups</b> by default, so a colour says "above or below this gene\'s own '
|
|
1085
|
+
+ 'average", not "highly expressed". Without that one loud gene saturates the '
|
|
1086
|
+
+ 'scale and the rest of the panel reads as blank. Turning it off compares '
|
|
1087
|
+
+ 'genes on their raw scale instead.';
|
|
1088
|
+
case 'embedding':
|
|
1089
|
+
return this.embeddingHelp;
|
|
1090
|
+
default:
|
|
1091
|
+
return '';
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* What the embedding on screen is, and how far its geometry can be trusted.
|
|
1096
|
+
*
|
|
1097
|
+
* Per METHOD, because that is the part people get wrong: a PCA's axes are ordered and
|
|
1098
|
+
* measurable while a UMAP's are neither, and the same picture read the two ways supports
|
|
1099
|
+
* opposite conclusions.
|
|
1100
|
+
*/
|
|
1101
|
+
get embeddingHelp() {
|
|
1102
|
+
const shared = '<br><br>Every cell is in all views at once: lasso a group here and '
|
|
1103
|
+
+ 'those cells light up on the tissue, because both read the same selection.';
|
|
1104
|
+
const name = (this.embedding?.label ?? this.embedding?.name ?? '').toLowerCase();
|
|
1105
|
+
if (name.includes('pca')) {
|
|
1106
|
+
return '<b>PCA</b> — a <b>linear</b> projection onto the directions of greatest '
|
|
1107
|
+
+ 'variance, in order.<br><br>Alone among these, its axes mean something '
|
|
1108
|
+
+ 'measurable: each reports the share of total variance it explains, which is why '
|
|
1109
|
+
+ 'the labels carry a percentage. Distances are real, and a low percentage tells '
|
|
1110
|
+
+ 'you the picture is a thin slice of the variation.' + shared;
|
|
1111
|
+
}
|
|
1112
|
+
if (name.includes('t-sne') || name.includes('tsne')) {
|
|
1113
|
+
return '<b>t-SNE</b> — cells placed so that close neighbours in expression stay '
|
|
1114
|
+
+ 'close.<br><br>Stricter about local neighbourhoods than UMAP and less '
|
|
1115
|
+
+ 'trustworthy about anything global: it tends to spread clusters into '
|
|
1116
|
+
+ 'evenly-sized islands whose sizes and separations mean little. Read which cells '
|
|
1117
|
+
+ 'group together, not how far apart the groups are.' + shared;
|
|
1118
|
+
}
|
|
1119
|
+
return '<b>UMAP</b> — cells placed so that close neighbours in expression stay '
|
|
1120
|
+
+ 'close.<br><br>The axes are arbitrary: unordered, unitless, and reproducible only '
|
|
1121
|
+
+ 'up to a rotation, which is why they carry no percentage. Read which cells group '
|
|
1122
|
+
+ 'together and which groups touch; do not read the distance between distant '
|
|
1123
|
+
+ 'clusters, or the direction of an axis.' + shared;
|
|
1124
|
+
}
|
|
1125
|
+
/** What the embedding view is showing, said plainly. */
|
|
1126
|
+
get embeddingNote() {
|
|
1127
|
+
const meta = this.embeddingCoords?.meta ?? this.embedding;
|
|
1128
|
+
if (!meta)
|
|
1129
|
+
return 'This dataset publishes no embedding.';
|
|
1130
|
+
// A derived embedding says so, and says HOW when the parameters are known: a t-SNE or
|
|
1131
|
+
// UMAP at different settings is a different picture of the same cells, so "computed
|
|
1132
|
+
// here" alone leaves a reader unable to reproduce or compare it.
|
|
1133
|
+
const derived = meta.derived
|
|
1134
|
+
? ` Computed here${meta.params ? ` (${meta.params})` : ''}, `
|
|
1135
|
+
+ 'not published with the dataset.'
|
|
1136
|
+
: '';
|
|
1137
|
+
const coloured = this.categorical
|
|
1138
|
+
? ' Coloured to match the map.'
|
|
1139
|
+
: ' Colour the map by a categorical column to colour these points.';
|
|
1140
|
+
const sel = this.selection.count > 0
|
|
1141
|
+
? ` ${this.selection.count.toLocaleString()} selected are highlighted.`
|
|
1142
|
+
: '';
|
|
1143
|
+
return `${meta.label ?? meta.name}.${coloured}${sel}${derived}`;
|
|
1144
|
+
}
|
|
1145
|
+
purgePlot() {
|
|
1146
|
+
try {
|
|
1147
|
+
// The ACTIVE target: purging the inline div while detached would leave the
|
|
1148
|
+
// window showing a plot the component believes it has cleared.
|
|
1149
|
+
Plotly.purge(this.plotTarget);
|
|
1150
|
+
}
|
|
1151
|
+
catch {
|
|
1152
|
+
// Nothing plotted.
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Draw, remembering whether this layout fixed its own height.
|
|
1157
|
+
*
|
|
1158
|
+
* The layout builders return `unknown` on purpose — they keep Plotly's layout
|
|
1159
|
+
* types out of the pure module — so the height is read back by narrowing
|
|
1160
|
+
* rather than declared. `height` is Plotly's own key, not ours.
|
|
1161
|
+
*/
|
|
1162
|
+
async draw(traces, layout, config = CHART_CONFIG, div = this.plotTarget) {
|
|
1163
|
+
const height = layout?.height;
|
|
1164
|
+
this.drawnHeight = typeof height === 'number' ? height : null;
|
|
1165
|
+
await Plotly.react(div, traces, layout, config);
|
|
1166
|
+
}
|
|
1167
|
+
async render() {
|
|
1168
|
+
if (!this.isActive)
|
|
1169
|
+
return;
|
|
1170
|
+
// Guard on the div the ACTIVE kind will draw into, not always the shared one. While a
|
|
1171
|
+
// kind is detached the inline div is removed by its `*ngIf`, so checking that one
|
|
1172
|
+
// bailed here and the detached window never got a plot.
|
|
1173
|
+
const target = this.plotTarget;
|
|
1174
|
+
if (!document.getElementById(target))
|
|
1175
|
+
return;
|
|
1176
|
+
// Anything but the embedding inherits a div the embedding may have bound handlers on.
|
|
1177
|
+
if (this.kind !== 'embedding')
|
|
1178
|
+
this.unbindEmbeddingSelection(target);
|
|
1179
|
+
// The heatmap answers a different question from the other kinds — which
|
|
1180
|
+
// genes distinguish which groups — so it is driven by its own gene list and
|
|
1181
|
+
// grouping rather than by whatever the map is coloured by.
|
|
1182
|
+
if (this.kind === 'heatmap') {
|
|
1183
|
+
await this.renderHeatmap();
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
// Also independent of the active colour source: the coordinates are the dataset's,
|
|
1187
|
+
// and the colouring merely follows whatever the map is using.
|
|
1188
|
+
if (this.kind === 'embedding') {
|
|
1189
|
+
await this.renderEmbedding();
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
if (!this.colorBy || (!this.values && !this.categorical)) {
|
|
1193
|
+
this.purgePlot();
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
if (this.categorical) {
|
|
1197
|
+
const counts = {
|
|
1198
|
+
group: this.categorical,
|
|
1199
|
+
selection: this.selection.count > 0 ? this.selection.mask : null,
|
|
1200
|
+
name: this.colorBy.name,
|
|
1201
|
+
};
|
|
1202
|
+
await this.draw(buildCountTraces(counts), countsLayout(counts));
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
if (!this.values)
|
|
1206
|
+
return;
|
|
1207
|
+
const input = {
|
|
1208
|
+
values: this.values,
|
|
1209
|
+
name: this.colorBy.kind === 'feature' ? this.colorBy.name : this.colorBy.name,
|
|
1210
|
+
group: benefitsFromGrouping(this.kind) ? this.grouping : null,
|
|
1211
|
+
selection: this.selection.count > 0 ? this.selection.mask : null,
|
|
1212
|
+
log: this.view.logScale,
|
|
1213
|
+
};
|
|
1214
|
+
const traces = buildOmicsTraces(this.kind, input);
|
|
1215
|
+
await this.draw(traces, omicsLayout(this.kind, input));
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Genes × groups mean expression.
|
|
1219
|
+
*
|
|
1220
|
+
* Columns are the grouping column's categories — or, inside a SMALL
|
|
1221
|
+
* selection, the selected cells themselves. A two-column class matrix is a
|
|
1222
|
+
* poor answer for an ROI holding forty cells; "what is in front of me" is the
|
|
1223
|
+
* question there, and each cell earns a column.
|
|
1224
|
+
*/
|
|
1225
|
+
async renderHeatmap() {
|
|
1226
|
+
const controls = this.controls;
|
|
1227
|
+
const genes = this.heatmapGenes
|
|
1228
|
+
.map((name) => ({ name, values: this.geneCache.get(name) }))
|
|
1229
|
+
.filter((g) => !!g.values);
|
|
1230
|
+
if (!controls || genes.length === 0 || !this.grouping) {
|
|
1231
|
+
this.purgePlot();
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
const selected = this.selection.count > 0 ? maskToIndices(this.selection.mask) : null;
|
|
1235
|
+
const perCell = !!selected && selected.length <= SpatialChartsComponent.HEATMAP_CELL_COLUMNS;
|
|
1236
|
+
const cells = perCell && selected
|
|
1237
|
+
? cellsAsGroups(selected, this.grouping.codes.length, SpatialChartsComponent.HEATMAP_CELL_COLUMNS)
|
|
1238
|
+
: null;
|
|
1239
|
+
const matrix = heatmapMatrix(genes, cells ? cells.groups : { codes: this.grouping.codes, categories: this.grouping.categories }, {
|
|
1240
|
+
...(cells ? { indices: cells.indices, minCells: 1 } : {}),
|
|
1241
|
+
// Outside the per-cell view a selection still narrows the means, the way
|
|
1242
|
+
// the violin and box narrow rather than overlay.
|
|
1243
|
+
...(!cells && selected ? { indices: selected } : {}),
|
|
1244
|
+
// Only the grouped view: `cellsAsGroups` already caps by even thinning,
|
|
1245
|
+
// and re-ranking those columns would lose the selection's spread.
|
|
1246
|
+
...(cells ? {} : { maxCols: SpatialChartsComponent.HEATMAP_MAX_COLUMNS }),
|
|
1247
|
+
zScore: this.heatmapZScore,
|
|
1248
|
+
});
|
|
1249
|
+
if (!matrix) {
|
|
1250
|
+
this.purgePlot();
|
|
1251
|
+
this.notice = 'No group has enough measured cells for a mean.';
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
this.notice = null;
|
|
1255
|
+
this.heatmapHidden = matrix.hiddenCols;
|
|
1256
|
+
const input = {
|
|
1257
|
+
rows: matrix.rows,
|
|
1258
|
+
cols: matrix.cols,
|
|
1259
|
+
values: matrix.values,
|
|
1260
|
+
counts: matrix.counts,
|
|
1261
|
+
zScored: this.heatmapZScore,
|
|
1262
|
+
groupLabel: cells ? 'selected cells' : (this.groupBy ?? ''),
|
|
1263
|
+
};
|
|
1264
|
+
await this.draw(buildHeatmapTraces(input), heatmapLayout(input));
|
|
1265
|
+
}
|
|
1266
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: SpatialChartsComponent, deps: [{ token: VISUALIZER }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1267
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.9", type: SpatialChartsComponent, selector: "spatial-charts", inputs: { active: "active" }, ngImport: i0, template: "<div class=\"sx-body\" *ngIf=\"controls\">\n\n <!-- The chart follows whatever the map is coloured by, so the two can never\n disagree about what is being shown. -->\n <div class=\"sx-head\">\n <span class=\"sx-subject\" *ngIf=\"subject\">{{ subject }}</span>\n <span class=\"sx-subject sx-muted\" *ngIf=\"!subject\">nothing selected to chart</span>\n <span class=\"sx-badge\" *ngIf=\"selectionCount\">\n {{ selectionCount | number }} selected\n </span>\n <i class=\"pi pi-spin pi-spinner sx-busy\" *ngIf=\"busy\" aria-label=\"loading\"></i>\n </div>\n\n <div class=\"sx-controls\">\n <p-selectButton [options]=\"kindOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"kind\" (ngModelChange)=\"onKind($event)\"\n styleClass=\"sx-kind\"></p-selectButton>\n <p-dropdown [options]=\"groupOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"groupBy\" (ngModelChange)=\"onGroupBy($event)\"\n placeholder=\"No grouping\" appendTo=\"body\"\n [disabled]=\"kind === 'histogram'\"\n [pTooltip]=\"kind === 'heatmap'\n ? 'The categorical column the heatmap\\'s columns come from'\n : 'Split a violin or box by a categorical column'\"\n tooltipPosition=\"top\"></p-dropdown>\n <!-- What the chart on screen shows, and what it must NOT be read for. Each of these\n answers a different question and two are routinely over-read, so the caveat\n travels with the explanation. -->\n <i class=\"pi pi-question-circle sx-help\"\n [pTooltip]=\"kindHelp\" [escape]=\"false\" tooltipPosition=\"left\"\n tooltipStyleClass=\"sx-help-tip\"\n [attr.aria-label]=\"'About the ' + kind + ' chart'\" tabindex=\"0\"></i>\n </div>\n\n <!-- The heatmap's rows are a gene LIST, so it needs its own picker: the other\n kinds chart whatever single source the map is coloured by. -->\n <div class=\"sx-controls\" *ngIf=\"kind === 'heatmap'\">\n <p-multiSelect [options]=\"geneOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"heatmapGenes\" (ngModelChange)=\"onHeatmapGenes($event)\"\n [filter]=\"true\" filterBy=\"label\" (onFilter)=\"onGeneFilter($event.filter)\"\n [virtualScroll]=\"true\" [virtualScrollItemSize]=\"30\" appendTo=\"body\"\n [showToggleAll]=\"false\" [maxSelectedLabels]=\"3\"\n selectedItemsLabel=\"{0} genes\"\n placeholder=\"Pick genes for the rows\"\n pTooltip=\"One row per gene, averaged within each group\"\n tooltipPosition=\"top\" styleClass=\"sx-genes\"></p-multiSelect>\n <p-checkbox [binary]=\"true\" [ngModel]=\"heatmapZScore\"\n (onChange)=\"onHeatmapZScore($event.checked)\"\n label=\"z-score\"\n pTooltip=\"Scale each gene across the groups. Without it one highly-expressed gene saturates the colour scale and the rest reads as blank\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n\n <!-- Detach applies to EVERY kind: the dialog is only so wide, and a counts plot read\n against a heatmap is the same argument as a UMAP read against the map. -->\n <div class=\"sx-controls\">\n <!-- A picker only when the dataset publishes more than one embedding. -->\n <p-dropdown *ngIf=\"kind === 'embedding' && embeddings.length > 1\"\n [options]=\"embeddings\" optionLabel=\"label\" optionValue=\"name\"\n [ngModel]=\"embedding?.name\" (ngModelChange)=\"onEmbedding($event)\"\n appendTo=\"body\"\n pTooltip=\"Which embedding to draw\" tooltipPosition=\"top\"></p-dropdown>\n <!-- Offered only for an embedding this dataset does not publish. It runs in a Web\n Worker on the GPU, so the panel stays usable while it goes. -->\n <p-button *ngIf=\"kind === 'embedding' && isComputable && !isComputed && !isComputing\"\n icon=\"pi pi-play\" size=\"small\" [text]=\"true\"\n [label]=\"'Compute (' + computeEstimateLabel + ')'\"\n pTooltip=\"Run t-SNE here, in a background worker on the GPU, from this dataset's PCA. The panel stays usable, and it can be cancelled.\"\n tooltipPosition=\"top\"\n (onClick)=\"computeEmbedding()\"></p-button>\n <p-button *ngIf=\"isComputing\"\n icon=\"pi pi-times\" size=\"small\" [text]=\"true\" severity=\"secondary\"\n label=\"Cancel\" (onClick)=\"cancelCompute()\"></p-button>\n <p-button [icon]=\"detached ? 'pi pi-window-minimize' : 'pi pi-external-link'\"\n size=\"small\" [text]=\"true\"\n [label]=\"detached ? 'Put back' : 'Detach'\"\n [pTooltip]=\"detached\n ? 'Show this chart in the panel again'\n : 'Open this chart in its own window, alongside this panel'\"\n tooltipPosition=\"top\"\n (onClick)=\"toggleDetached()\"></p-button>\n </div>\n\n <!-- Progress while a computation runs. `mode=\"indeterminate\"` until the first report,\n because the run starts with a neighbour graph and there is nothing honest to show\n as a percentage yet. -->\n <div class=\"sx-compute\" *ngIf=\"isComputing\">\n <p-progressBar\n [mode]=\"computeFraction === null ? 'indeterminate' : 'determinate'\"\n [value]=\"computeFraction === null ? 0 : (computeFraction * 100)\"\n [showValue]=\"computeFraction !== null\"\n [style]=\"{ height: '0.6rem' }\"></p-progressBar>\n <small class=\"sx-hint\">\n {{ computeFraction === null ? 'Building the neighbour graph\u2026' : 'Optimising\u2026' }}\n <ng-container *ngIf=\"computeBackend\"> on {{ computeBackend }}</ng-container>\n </small>\n <small class=\"sx-hint sx-warn\" *ngIf=\"computeMessage\">{{ computeMessage }}</small>\n </div>\n <small class=\"sx-hint sx-warn\" *ngIf=\"computeError\">{{ computeError }}</small>\n <!-- Withheld rather than offered with a warning: an hour-long job is not a choice a\n reader can meaningfully consent to from a dialog. -->\n <small class=\"sx-hint\" *ngIf=\"kind === 'embedding' && tsneTooLarge\">{{ tsneTooLargeNote }}</small>\n\n <div [id]=\"chartDiv\" class=\"sx-plot\" *ngIf=\"!detached\"></div>\n\n <!-- Detached: the chart keeps drawing, in its own window. Says so rather than\n leaving a blank panel that looks broken. -->\n <div class=\"sx-detached\" *ngIf=\"detached\">\n Shown in its own window.\n </div>\n\n <ng-container *ngTemplateOutlet=\"hints\"></ng-container>\n\n</div>\n\n<!-- Declared once and rendered in both the panel and the detached window: the ladder is\n deliberately not mutually exclusive (a heatmap still needs its grouping prompt), so\n collapsing it to a single expression would drop a hint rather than move it. -->\n<ng-template #hints>\n <small class=\"sx-hint\" *ngIf=\"notice\">{{ notice }}</small>\n <small class=\"sx-hint\" *ngIf=\"!notice && suggestsGrouping\">\n Pick a grouping to compare categories.\n </small>\n <small class=\"sx-hint\" *ngIf=\"!notice && selectionCount && kind === 'histogram'\">\n The selection is overlaid on the full distribution.\n </small>\n <small class=\"sx-hint\" *ngIf=\"!notice && selectionCount && kind !== 'histogram' && kind !== 'heatmap'\">\n Showing the selection only.\n </small>\n <small class=\"sx-hint\" *ngIf=\"!notice && kind === 'heatmap'\">{{ heatmapNote }}</small>\n <small class=\"sx-hint\" *ngIf=\"!notice && kind === 'embedding'\">{{ embeddingNote }}</small>\n</ng-template>\n\n<!-- The detached chart. Its own non-modal, draggable, resizable window so it can be put\n beside the spatial-omics dialog. `appendTo=\"body\"` keeps it out of the dialog's\n stacking context, or it would be clipped by the panel it is meant to sit next to. -->\n<p-dialog *ngIf=\"detached\"\n [header]=\"detachedTitle\"\n [visible]=\"true\" (visibleChange)=\"toggleDetached()\"\n [modal]=\"false\" [resizable]=\"true\" [draggable]=\"true\"\n position=\"right\" appendTo=\"body\"\n (onShow)=\"onDetachedWindowShown()\"\n (onResizeEnd)=\"onDetachedResizeEnd()\"\n [style]=\"{ width: '34rem', height: '30rem' }\"\n [contentStyle]=\"{\n display: 'flex', flexDirection: 'column', gap: '0.25rem',\n padding: '0.4rem 0.6rem 0.5rem', flex: '1 1 auto', minHeight: '0',\n overflow: 'auto'\n }\"\n styleClass=\"spatial-detached-dialog\">\n <div [id]=\"detachedDiv\" class=\"sx-detached-plot\"\n [class.sx-plot-fixed]=\"hasFixedHeight\"></div>\n <ng-container *ngTemplateOutlet=\"hints\"></ng-container>\n</p-dialog>\n", styles: [":host ::ng-deep .spatial-charts-dialog .p-dialog-content{padding:.75rem 1rem 1rem}.sx-empty{padding:.5rem 0;font-size:.85rem;color:var(--text-color-secondary, #6c757d);line-height:1.4}.sx-body{display:flex;flex-direction:column;gap:.6rem}.sx-head{display:flex;align-items:center;gap:.5rem;padding-bottom:.4rem;border-bottom:1px solid var(--surface-border, #dee2e6)}.sx-subject{font-weight:600;font-size:.9rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sx-muted{font-weight:400;color:var(--text-color-secondary, #6c757d)}.sx-badge{margin-left:auto;padding:.1rem .4rem;border-radius:10px;font-size:.7rem;font-variant-numeric:tabular-nums;background:var(--surface-hover, rgba(213, 94, 0, .12));color:#a8480a;white-space:nowrap}.sx-busy{font-size:.8rem;color:var(--text-color-secondary, #6c757d)}.sx-controls{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.sx-controls p-dropdown{flex:1 1 8rem;min-width:0}:host ::ng-deep .sx-controls p-dropdown .p-dropdown{width:100%}:host ::ng-deep .sx-kind .p-button{padding:.25rem .6rem;font-size:.78rem}.sx-plot{width:100%;min-height:15rem;flex:1}.sx-hint{font-size:.75rem;color:var(--text-color-secondary, #6c757d);line-height:1.35}.sx-detached{flex:1;min-height:6rem;display:flex;align-items:center;justify-content:center;font-size:.78rem;color:var(--text-color-secondary, #6c757d)}.sx-detached-plot{flex:1;width:100%;min-height:0;overflow:hidden}.sx-detached-plot.sx-plot-fixed{flex:0 0 auto;min-height:auto;overflow:visible}.sx-help{margin-left:auto;align-self:center;font-size:.95rem;color:var(--text-color-secondary, #6c757d);cursor:help}.sx-help:hover,.sx-help:focus-visible{color:var(--primary-color, #3b82f6)}.sx-compute{display:flex;flex-direction:column;gap:.3rem;padding:.5rem 0}.sx-warn{color:var(--orange-600, #c2410c)}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { 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: "component", type: i3.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: i4.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: i5.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: i6.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: i7.ProgressBar, selector: "p-progressBar", inputs: ["value", "showValue", "styleClass", "style", "unit", "mode", "color"] }, { kind: "component", type: i8.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: i9.MultiSelect, selector: "p-multiSelect", inputs: ["id", "ariaLabel", "style", "styleClass", "panelStyle", "panelStyleClass", "inputId", "disabled", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "variant", "appendTo", "dataKey", "name", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "showClear", "autofocus", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "defaultLabel", "placeholder", "options", "filterValue", "itemSize", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "component", type: i10.SelectButton, selector: "p-selectButton", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "unselectable", "tabindex", "multiple", "allowEmpty", "style", "styleClass", "ariaLabelledBy", "disabled", "dataKey", "autofocus"], outputs: ["onOptionClick", "onChange"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }] }); }
|
|
1268
|
+
}
|
|
1269
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: SpatialChartsComponent, decorators: [{
|
|
1270
|
+
type: Component,
|
|
1271
|
+
args: [{ selector: 'spatial-charts', template: "<div class=\"sx-body\" *ngIf=\"controls\">\n\n <!-- The chart follows whatever the map is coloured by, so the two can never\n disagree about what is being shown. -->\n <div class=\"sx-head\">\n <span class=\"sx-subject\" *ngIf=\"subject\">{{ subject }}</span>\n <span class=\"sx-subject sx-muted\" *ngIf=\"!subject\">nothing selected to chart</span>\n <span class=\"sx-badge\" *ngIf=\"selectionCount\">\n {{ selectionCount | number }} selected\n </span>\n <i class=\"pi pi-spin pi-spinner sx-busy\" *ngIf=\"busy\" aria-label=\"loading\"></i>\n </div>\n\n <div class=\"sx-controls\">\n <p-selectButton [options]=\"kindOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"kind\" (ngModelChange)=\"onKind($event)\"\n styleClass=\"sx-kind\"></p-selectButton>\n <p-dropdown [options]=\"groupOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"groupBy\" (ngModelChange)=\"onGroupBy($event)\"\n placeholder=\"No grouping\" appendTo=\"body\"\n [disabled]=\"kind === 'histogram'\"\n [pTooltip]=\"kind === 'heatmap'\n ? 'The categorical column the heatmap\\'s columns come from'\n : 'Split a violin or box by a categorical column'\"\n tooltipPosition=\"top\"></p-dropdown>\n <!-- What the chart on screen shows, and what it must NOT be read for. Each of these\n answers a different question and two are routinely over-read, so the caveat\n travels with the explanation. -->\n <i class=\"pi pi-question-circle sx-help\"\n [pTooltip]=\"kindHelp\" [escape]=\"false\" tooltipPosition=\"left\"\n tooltipStyleClass=\"sx-help-tip\"\n [attr.aria-label]=\"'About the ' + kind + ' chart'\" tabindex=\"0\"></i>\n </div>\n\n <!-- The heatmap's rows are a gene LIST, so it needs its own picker: the other\n kinds chart whatever single source the map is coloured by. -->\n <div class=\"sx-controls\" *ngIf=\"kind === 'heatmap'\">\n <p-multiSelect [options]=\"geneOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"heatmapGenes\" (ngModelChange)=\"onHeatmapGenes($event)\"\n [filter]=\"true\" filterBy=\"label\" (onFilter)=\"onGeneFilter($event.filter)\"\n [virtualScroll]=\"true\" [virtualScrollItemSize]=\"30\" appendTo=\"body\"\n [showToggleAll]=\"false\" [maxSelectedLabels]=\"3\"\n selectedItemsLabel=\"{0} genes\"\n placeholder=\"Pick genes for the rows\"\n pTooltip=\"One row per gene, averaged within each group\"\n tooltipPosition=\"top\" styleClass=\"sx-genes\"></p-multiSelect>\n <p-checkbox [binary]=\"true\" [ngModel]=\"heatmapZScore\"\n (onChange)=\"onHeatmapZScore($event.checked)\"\n label=\"z-score\"\n pTooltip=\"Scale each gene across the groups. Without it one highly-expressed gene saturates the colour scale and the rest reads as blank\"\n tooltipPosition=\"top\"></p-checkbox>\n </div>\n\n <!-- Detach applies to EVERY kind: the dialog is only so wide, and a counts plot read\n against a heatmap is the same argument as a UMAP read against the map. -->\n <div class=\"sx-controls\">\n <!-- A picker only when the dataset publishes more than one embedding. -->\n <p-dropdown *ngIf=\"kind === 'embedding' && embeddings.length > 1\"\n [options]=\"embeddings\" optionLabel=\"label\" optionValue=\"name\"\n [ngModel]=\"embedding?.name\" (ngModelChange)=\"onEmbedding($event)\"\n appendTo=\"body\"\n pTooltip=\"Which embedding to draw\" tooltipPosition=\"top\"></p-dropdown>\n <!-- Offered only for an embedding this dataset does not publish. It runs in a Web\n Worker on the GPU, so the panel stays usable while it goes. -->\n <p-button *ngIf=\"kind === 'embedding' && isComputable && !isComputed && !isComputing\"\n icon=\"pi pi-play\" size=\"small\" [text]=\"true\"\n [label]=\"'Compute (' + computeEstimateLabel + ')'\"\n pTooltip=\"Run t-SNE here, in a background worker on the GPU, from this dataset's PCA. The panel stays usable, and it can be cancelled.\"\n tooltipPosition=\"top\"\n (onClick)=\"computeEmbedding()\"></p-button>\n <p-button *ngIf=\"isComputing\"\n icon=\"pi pi-times\" size=\"small\" [text]=\"true\" severity=\"secondary\"\n label=\"Cancel\" (onClick)=\"cancelCompute()\"></p-button>\n <p-button [icon]=\"detached ? 'pi pi-window-minimize' : 'pi pi-external-link'\"\n size=\"small\" [text]=\"true\"\n [label]=\"detached ? 'Put back' : 'Detach'\"\n [pTooltip]=\"detached\n ? 'Show this chart in the panel again'\n : 'Open this chart in its own window, alongside this panel'\"\n tooltipPosition=\"top\"\n (onClick)=\"toggleDetached()\"></p-button>\n </div>\n\n <!-- Progress while a computation runs. `mode=\"indeterminate\"` until the first report,\n because the run starts with a neighbour graph and there is nothing honest to show\n as a percentage yet. -->\n <div class=\"sx-compute\" *ngIf=\"isComputing\">\n <p-progressBar\n [mode]=\"computeFraction === null ? 'indeterminate' : 'determinate'\"\n [value]=\"computeFraction === null ? 0 : (computeFraction * 100)\"\n [showValue]=\"computeFraction !== null\"\n [style]=\"{ height: '0.6rem' }\"></p-progressBar>\n <small class=\"sx-hint\">\n {{ computeFraction === null ? 'Building the neighbour graph\u2026' : 'Optimising\u2026' }}\n <ng-container *ngIf=\"computeBackend\"> on {{ computeBackend }}</ng-container>\n </small>\n <small class=\"sx-hint sx-warn\" *ngIf=\"computeMessage\">{{ computeMessage }}</small>\n </div>\n <small class=\"sx-hint sx-warn\" *ngIf=\"computeError\">{{ computeError }}</small>\n <!-- Withheld rather than offered with a warning: an hour-long job is not a choice a\n reader can meaningfully consent to from a dialog. -->\n <small class=\"sx-hint\" *ngIf=\"kind === 'embedding' && tsneTooLarge\">{{ tsneTooLargeNote }}</small>\n\n <div [id]=\"chartDiv\" class=\"sx-plot\" *ngIf=\"!detached\"></div>\n\n <!-- Detached: the chart keeps drawing, in its own window. Says so rather than\n leaving a blank panel that looks broken. -->\n <div class=\"sx-detached\" *ngIf=\"detached\">\n Shown in its own window.\n </div>\n\n <ng-container *ngTemplateOutlet=\"hints\"></ng-container>\n\n</div>\n\n<!-- Declared once and rendered in both the panel and the detached window: the ladder is\n deliberately not mutually exclusive (a heatmap still needs its grouping prompt), so\n collapsing it to a single expression would drop a hint rather than move it. -->\n<ng-template #hints>\n <small class=\"sx-hint\" *ngIf=\"notice\">{{ notice }}</small>\n <small class=\"sx-hint\" *ngIf=\"!notice && suggestsGrouping\">\n Pick a grouping to compare categories.\n </small>\n <small class=\"sx-hint\" *ngIf=\"!notice && selectionCount && kind === 'histogram'\">\n The selection is overlaid on the full distribution.\n </small>\n <small class=\"sx-hint\" *ngIf=\"!notice && selectionCount && kind !== 'histogram' && kind !== 'heatmap'\">\n Showing the selection only.\n </small>\n <small class=\"sx-hint\" *ngIf=\"!notice && kind === 'heatmap'\">{{ heatmapNote }}</small>\n <small class=\"sx-hint\" *ngIf=\"!notice && kind === 'embedding'\">{{ embeddingNote }}</small>\n</ng-template>\n\n<!-- The detached chart. Its own non-modal, draggable, resizable window so it can be put\n beside the spatial-omics dialog. `appendTo=\"body\"` keeps it out of the dialog's\n stacking context, or it would be clipped by the panel it is meant to sit next to. -->\n<p-dialog *ngIf=\"detached\"\n [header]=\"detachedTitle\"\n [visible]=\"true\" (visibleChange)=\"toggleDetached()\"\n [modal]=\"false\" [resizable]=\"true\" [draggable]=\"true\"\n position=\"right\" appendTo=\"body\"\n (onShow)=\"onDetachedWindowShown()\"\n (onResizeEnd)=\"onDetachedResizeEnd()\"\n [style]=\"{ width: '34rem', height: '30rem' }\"\n [contentStyle]=\"{\n display: 'flex', flexDirection: 'column', gap: '0.25rem',\n padding: '0.4rem 0.6rem 0.5rem', flex: '1 1 auto', minHeight: '0',\n overflow: 'auto'\n }\"\n styleClass=\"spatial-detached-dialog\">\n <div [id]=\"detachedDiv\" class=\"sx-detached-plot\"\n [class.sx-plot-fixed]=\"hasFixedHeight\"></div>\n <ng-container *ngTemplateOutlet=\"hints\"></ng-container>\n</p-dialog>\n", styles: [":host ::ng-deep .spatial-charts-dialog .p-dialog-content{padding:.75rem 1rem 1rem}.sx-empty{padding:.5rem 0;font-size:.85rem;color:var(--text-color-secondary, #6c757d);line-height:1.4}.sx-body{display:flex;flex-direction:column;gap:.6rem}.sx-head{display:flex;align-items:center;gap:.5rem;padding-bottom:.4rem;border-bottom:1px solid var(--surface-border, #dee2e6)}.sx-subject{font-weight:600;font-size:.9rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sx-muted{font-weight:400;color:var(--text-color-secondary, #6c757d)}.sx-badge{margin-left:auto;padding:.1rem .4rem;border-radius:10px;font-size:.7rem;font-variant-numeric:tabular-nums;background:var(--surface-hover, rgba(213, 94, 0, .12));color:#a8480a;white-space:nowrap}.sx-busy{font-size:.8rem;color:var(--text-color-secondary, #6c757d)}.sx-controls{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.sx-controls p-dropdown{flex:1 1 8rem;min-width:0}:host ::ng-deep .sx-controls p-dropdown .p-dropdown{width:100%}:host ::ng-deep .sx-kind .p-button{padding:.25rem .6rem;font-size:.78rem}.sx-plot{width:100%;min-height:15rem;flex:1}.sx-hint{font-size:.75rem;color:var(--text-color-secondary, #6c757d);line-height:1.35}.sx-detached{flex:1;min-height:6rem;display:flex;align-items:center;justify-content:center;font-size:.78rem;color:var(--text-color-secondary, #6c757d)}.sx-detached-plot{flex:1;width:100%;min-height:0;overflow:hidden}.sx-detached-plot.sx-plot-fixed{flex:0 0 auto;min-height:auto;overflow:visible}.sx-help{margin-left:auto;align-self:center;font-size:.95rem;color:var(--text-color-secondary, #6c757d);cursor:help}.sx-help:hover,.sx-help:focus-visible{color:var(--primary-color, #3b82f6)}.sx-compute{display:flex;flex-direction:column;gap:.3rem;padding:.5rem 0}.sx-warn{color:var(--orange-600, #c2410c)}\n"] }]
|
|
1272
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
1273
|
+
type: Inject,
|
|
1274
|
+
args: [VISUALIZER]
|
|
1275
|
+
}] }], propDecorators: { active: [{
|
|
1276
|
+
type: Input
|
|
1277
|
+
}] } });
|
|
1278
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BhdGlhbC1jaGFydHMuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vc3JjL2xpYi9zcGF0aWFsLWNvbnRyb2xzL3NwYXRpYWwtY2hhcnRzL3NwYXRpYWwtY2hhcnRzLmNvbXBvbmVudC50cyIsIi4uLy4uLy4uLy4uLy4uL3NyYy9saWIvc3BhdGlhbC1jb250cm9scy9zcGF0aWFsLWNoYXJ0cy9zcGF0aWFsLWNoYXJ0cy5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ1UsU0FBUyxFQUFFLE1BQU0sRUFBRSxLQUFLLEdBQ3hDLE1BQU0sZUFBZSxDQUFDO0FBQ3ZCLE9BQU8sRUFBRSxZQUFZLEVBQUUsYUFBYSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQ25ELE9BQU8sS0FBSyxNQUFNLE1BQU0sb0JBQW9CLENBQUM7QUFFN0MsT0FBTyxFQUFFLFVBQVUsRUFBaUMsTUFBTSxxQ0FBcUMsQ0FBQztBQUtoRyxPQUFPLEVBQW9DLG9CQUFvQixFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDdkcsT0FBTyxFQUNpQixjQUFjLEVBQUUsYUFBYSxHQUNwRCxNQUFNLGlDQUFpQyxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxhQUFhLEVBQUUsYUFBYSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDN0UsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQzNELE9BQU8sRUFBbUIsbUJBQW1CLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUN2RixPQUFPLEVBQzBCLG9CQUFvQixFQUFFLGdCQUFnQixFQUFFLGtCQUFrQixFQUN6RixnQkFBZ0IsRUFBRSxvQkFBb0IsRUFBRSxZQUFZLEVBQUUsYUFBYSxFQUFFLFdBQVcsRUFBRSxlQUFlLEdBQ2xHLE1BQU0sbURBQW1ELENBQUM7Ozs7Ozs7Ozs7OztBQUUzRDs7Ozs7Ozs7O0dBU0c7QUFDSCxNQUFNLGlCQUFpQixHQUFHLG1C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SSxDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQ2pCLElBQUksQ0FBQyxNQUFNLEdBQUcsZ0RBQWdELENBQUM7WUFDL0QsT0FBTztRQUNULENBQUM7UUFDRCxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQztRQUNuQixJQUFJLENBQUMsYUFBYSxHQUFHLE1BQU0sQ0FBQyxVQUFVLENBQUM7UUFDdkMsTUFBTSxLQUFLLEdBQUc7WUFDWixJQUFJLEVBQUUsTUFBTSxDQUFDLElBQUk7WUFDakIsSUFBSSxFQUFFLE1BQU0sQ0FBQyxJQUFJO1lBQ2pCLE1BQU0sRUFBRSxNQUFNLENBQUMsTUFBTTtZQUNyQixNQUFNLEVBQUUsTUFBTSxDQUFDLE1BQU07WUFDckIsT0FBTyxFQUFFLElBQUksQ0FBQyxhQUFhO1lBQzNCLFVBQVUsRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxPQUFPLElBQUksRUFBRSxDQUFDO1NBQzVELENBQUM7UUFDRixNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsa0JBQWtCLENBQUMsS0FBSyxDQUFDLEVBQUUsYUFBYSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDbkUsQ0FBQzs4R0E5ckNVLHNCQUFzQixrQkEyT2IsVUFBVTtrR0EzT25CLHNCQUFzQixvRkM5Rm5DLHNyUkEwSkE7OzJGRDVEYSxzQkFBc0I7a0JBTGxDLFNBQVM7K0JBQ0UsZ0JBQWdCOzswQkErT2IsTUFBTTsyQkFBQyxVQUFVO3lDQXBPakIsTUFBTTtzQkFBbEIsS0FBSyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIEFmdGVyVmlld0luaXQsIENvbXBvbmVudCwgSW5qZWN0LCBJbnB1dCwgT25EZXN0cm95LCBPbkluaXQsXG59IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgU3Vic2NyaXB0aW9uLCBjb21iaW5lTGF0ZXN0IH0gZnJvbSAncnhqcyc7XG5pbXBvcnQgKiBhcyBQbG90bHkgZnJvbSAncGxvdGx5LmpzLWRpc3QtbWluJztcblxuaW1wb3J0IHsgVklTVUFMSVpFUiwgSVZpc3VhbGl6ZXIsIElTcGF0aWFsQ29udHJvbHMgfSBmcm9tICcuLi8uLi9jb250cmFjdHMvdmlzdWFsaXplci5jb250cmFjdCc7XG5pbXBvcnQge1xuICBTcGF0aWFsRW1iZWRkaW5nLFxuICBTcGF0aWFsRW1iZWRkaW5nTWV0YSxcbn0gZnJvbSAnLi4vLi4vY29udHJhY3RzL3NwYXRpYWwtZGF0YXNldC5jb250cmFjdCc7XG5pbXBvcnQgeyBTcGF0aWFsQ29sb3JCeSwgU3BhdGlhbFZpZXdTdGF0ZSwgREVGQVVMVF9TUEFUSUFMX1ZJRVcgfSBmcm9tICcuLi8uLi9jb250cmFjdHMvZGlzcGxheS10eXBlcyc7XG5pbXBvcnQge1xuICBTcGF0aWFsU2VsZWN0aW9uTWFzaywgZW1wdHlTZWxlY3Rpb24sIG1hc2tUb0luZGljZXMsXG59IGZyb20gJy4uLy4uL3NwYXRpYWwvc3BhdGlhbC1zZWxlY3Rpb24nO1xuaW1wb3J0IHsgY2VsbHNBc0dyb3VwcywgaGVhdG1hcE1hdHJpeCB9IGZyb20gJy4uLy4uL3NwYXRpYWwvc3BhdGlhbC1oZWF0bWFwJztcbmltcG9ydCB7IGdlbmVPcHRpb25zRm9yIH0gZnJvbSAnLi4vLi4vc3BhdGlhbC9nZW5lLXNlYXJjaCc7XG5pbXBvcnQgeyBDb21wdXRlUHJvZ3Jlc3MsIEVtYmVkZGluZ0NvbXB1dGVSdW4gfSBmcm9tICcuLi8uLi9zcGF0aWFsL2VtYmVkZGluZy1jb21wdXRlJztcbmltcG9ydCB7XG4gIE9taWNzQ2hhcnRLaW5kLCBPbWljc0dyb3VwaW5nLCBiZW5lZml0c0Zyb21Hcm91cGluZywgYnVpbGRDb3VudFRyYWNlcywgYnVpbGRIZWF0bWFwVHJhY2VzLFxuICBidWlsZE9taWNzVHJhY2VzLCBidWlsZEVtYmVkZGluZ1RyYWNlcywgY291bnRzTGF5b3V0LCBoZWF0bWFwTGF5b3V0LCBvbWljc0xheW91dCwgZW1iZWRkaW5nTGF5b3V0LFxufSBmcm9tICcuLi8uLi9pbXBsZW1lbnRhdGlvbnMvcGxvdGx5L29taWNzLXRyYWNlLWJ1aWxkZXJzJztcblxuLyoqXG4gKiBXaWRlbiB0aGUgaGVscCB0b29sdGlwLCBvbmNlIHBlciBkb2N1bWVudC5cbiAqXG4gKiBQcmltZU5HIGNhcHMgYC5wLXRvb2x0aXBgIGF0IDEyLjVyZW0gYW5kIGFwcGVuZHMgaXQgdG8gYDxib2R5PmAsIHNvIGEgY29tcG9uZW50XG4gKiBzdHlsZXNoZWV0IGNhbm5vdCByZWFjaCBpdCDigJQgdGhlIHNhbWUgcmVhc29uIHRoZSBkZXRhY2hlZCBkaWFsb2cgaXMgc2l6ZWQgaW5saW5lLiBBdFxuICogMjAwIHB4IHRoZSBjaGFydCBleHBsYW5hdGlvbnMgcmVuZGVyIGFzIGEgdGFsbCB0aHJlYWQgb2YgdHdvLXdvcmQgbGluZXMsIHdoaWNoIGlzIHdvcnNlXG4gKiB0aGFuIG5vdCBzaG93aW5nIHRoZW0uIFRoZSBsaWJyYXJ5IHNoaXBzIG5vIGdsb2JhbCBzdHlsZXNoZWV0IHRvIHB1dCB0aGlzIGluLCBzbyBvbmVcbiAqIHJ1bGUgaXMgaW5qZWN0ZWQgaW5zdGVhZDogc2NvcGVkIHRvIHRoaXMgY29tcG9uZW50J3Mgb3duIHRvb2x0aXAgY2xhc3MsIGlkZW1wb3RlbnQsIGFuZFxuICogc2tpcHBlZCB3aGVyZSB0aGVyZSBpcyBubyBkb2N1bWVudC5cbiAqL1xuY29uc3QgSEVMUF9USVBfU1RZTEVfSUQgPSAnc3gtaGVscC10aXAtc3R5bGUnO1xuZnVuY3Rpb24gZW5zdXJlSGVscFRpcFN0eWxlKCk6IHZvaWQge1xuICBpZiAodHlwZW9mIGRvY3VtZW50ID09PSAndW5kZWZpbmVkJykgcmV0dXJuO1xuICBpZiAoZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoSEVMUF9USVBfU1RZTEVfSUQpKSByZXR1cm47XG4gIGNvbnN0IHN0eWxlID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3R5bGUnKTtcbiAgc3R5bGUuaWQgPSBIRUxQX1RJUF9TVFlMRV9JRDtcbiAgc3R5bGUudGV4dENvbnRlbnQgPSAnLnN4LWhlbHAtdGlwIC5wLXRvb2x0aXAtdGV4dCB7IG1heC13aWR0aDogbm9uZTsgd2lkdGg6IDI0cmVtOyAnXG4gICAgKyAnbGluZS1oZWlnaHQ6IDEuNDU7IH1cXG4uc3gtaGVscC10aXAgeyBtYXgtd2lkdGg6IG5vbmU7IH0nO1xuICBkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKHN0eWxlKTtcbn1cblxuLyoqIFBlci1pbnN0YW5jZSBjaGFydC1kaXYgaWQgc291cmNlIOKAlCBzZWUge0BsaW5rIFNwYXRpYWxDaGFydHNDb21wb25lbnQuY2hhcnREaXZ9LiAqL1xubGV0IGNoYXJ0SW5zdGFuY2VTZXEgPSAwO1xuXG5cbi8qKiBQbG90bHkgY29uZmlnOiBhIHN0YXRpYy1pc2ggYW5hbHlzaXMgY2hhcnQsIG5vdCBhbiBlZGl0YWJsZSBmaWd1cmUuICovXG5jb25zdCBDSEFSVF9DT05GSUcgPSB7XG4gIGRpc3BsYXlsb2dvOiBmYWxzZSxcbiAgcmVzcG9uc2l2ZTogdHJ1ZSxcbiAgbW9kZUJhckJ1dHRvbnNUb1JlbW92ZTogWydsYXNzbzJkJywgJ3NlbGVjdDJkJywgJ2F1dG9TY2FsZTJkJ10sXG59O1xuXG4vKipcbiAqIENvbmZpZyBmb3IgdGhlIGVtYmVkZGluZywgd2hpY2ggdW5saWtlIHRoZSBkaXN0cmlidXRpb25zIGlzIGEgcGxvdCB5b3UgU0VMRUNUIGluLlxuICpcbiAqIFRoZSBsYXNzbyBhbmQgYm94LXNlbGVjdCBhcmUga2VwdCDigJQgZHJhd2luZyByb3VuZCBhIGNsdXN0ZXIgaXMgaG93IGEgcG9wdWxhdGlvbiBnZXRzXG4gKiBwaWNrZWQgb3V0IG9mIGEgVU1BUCDigJQgYW5kIHRoZSB3aGVlbCB6b29tcywgYmVjYXVzZSBleHBsb3JpbmcgYW4gZW1iZWRkaW5nIG1lYW5zXG4gKiB6b29taW5nIGludG8gYSBjbHVzdGVyIGFuZCByZWFjaGluZyBmb3IgYSB0b29sYmFyIGJ1dHRvbiB0byBkbyBpdCBicmVha3MgdGhhdC5cbiAqL1xuY29uc3QgRU1CRURESU5HX0NPTkZJRyA9IHtcbiAgZGlzcGxheWxvZ286IGZhbHNlLFxuICByZXNwb25zaXZlOiB0cnVlLFxuICBzY3JvbGxab29tOiB0cnVlLFxuICBtb2RlQmFyQnV0dG9uc1RvUmVtb3ZlOiBbJ2F1dG9TY2FsZTJkJ10sXG59O1xuXG4vKipcbiAqIERpc3RyaWJ1dGlvbiBjaGFydHMgb3ZlciB0aGUgc3BhdGlhbC1vbWljcyB2YWx1ZXMg4oCUIGhpc3RvZ3JhbSwgdmlvbGluLCBib3gg4oCUXG4gKiAqKmxpbmtlZCB0byB0aGUgbWFwKio6IHRoZXkgY2hhcnQgd2hhdGV2ZXIgdGhlIG1hcCBpcyBjb2xvdXJlZCBieSwgYW5kIG5hcnJvd1xuICogdG8gdGhlIGN1cnJlbnQgc2VsZWN0aW9uLlxuICpcbiAqIEVtYmVkZGVkIElOU0lERSBgPHNwYXRpYWwtY29udHJvbHM+YCByYXRoZXIgdGhhbiBvd25pbmcgYSBkaWFsb2cgb2YgaXRzIG93bjpcbiAqIHRoZSB0d28gYXJlIG9uZSB3b3JrZmxvdyAoY2hhbmdlIHRoZSBjb2xvdXIgc291cmNlLCB3YXRjaCB0aGUgZGlzdHJpYnV0aW9uXG4gKiBtb3ZlKSwgYW5kIHNwbGl0dGluZyB0aGVtIGFjcm9zcyB0d28gZmxvYXRpbmcgd2luZG93cyBtYWRlIHRoZSBsaW5rIGhhcmRlciB0b1xuICogc2VlLCBub3QgZWFzaWVyLiBJdCBzdGF5cyBhIHNlcGFyYXRlIGNvbXBvbmVudCBzbyB0aGUgcHVyZSB0cmFjZSBidWlsZGVycyBhbmRcbiAqIGl0cyBvd24gdGVzdHMga2VlcCB0aGVpciBib3VuZGFyeS5cbiAqXG4gKiBDaGFydGluZyB0aGUgYWN0aXZlIGNvbG91ciBzb3VyY2UgcmF0aGVyIHRoYW4gb2ZmZXJpbmcgaXRzIG93biB2YWx1ZSBwaWNrZXIgaXNcbiAqIGRlbGliZXJhdGU6IGl0IGtlZXBzIG9uZSBzb3VyY2Ugb2YgdHJ1dGgsIHNvIHdoYXQgeW91IHNlZSBvbiB0aGUgbWFwIGFuZCB3aGF0XG4gKiB5b3Ugc2VlIGluIHRoZSBjaGFydCBjYW5ub3QgZGlzYWdyZWUsIGFuZCBpdCBtYWtlcyB0aGUgbGluayBsZWdpYmxlIHdpdGhvdXRcbiAqIGV4cGxhaW5pbmcgaXQuXG4gKlxuICogRGVwZW5kcyBvbmx5IG9uIHtAbGluayBJU3BhdGlhbENvbnRyb2xzfSB0aHJvdWdoIHRoZSBgVklTVUFMSVpFUmAgY29udHJhY3RcbiAqIHRva2VuLCBsaWtlIGBTcGF0aWFsQ29udHJvbHNDb21wb25lbnRgLiBUcmFjZSBidWlsZGluZyBsaXZlcyBpbiB0aGUgcHVyZVxuICogYG9taWNzLXRyYWNlLWJ1aWxkZXJzYDsgdGhpcyBjb21wb25lbnQgb25seSBtb3ZlcyBkYXRhIGFuZCBvd25zIHRoZSBkaXYuXG4gKi9cbkBDb21wb25lbnQoe1xuICBzZWxlY3RvcjogJ3NwYXRpYWwtY2hhcnRzJyxcbiAgdGVtcGxhdGVVcmw6ICcuL3NwYXRpYWwtY2hhcnRzLmNvbXBvbmVudC5odG1sJyxcbiAgc3R5bGVVcmxzOiBbJy4vc3BhdGlhbC1jaGFydHMuY29tcG9uZW50LnNjc3MnXSxcbn0pXG5leHBvcnQgY2xhc3MgU3BhdGlhbENoYXJ0c0NvbXBvbmVudCBpbXBsZW1lbnRzIE9uSW5pdCwgQWZ0ZXJWaWV3SW5pdCwgT25EZXN0cm95IHtcbiAgLyoqXG4gICAqIFdoZXRoZXIgdGhlIGhvc3QgcGFuZWwgaXMgb24gc2NyZWVuLiBUaGUgY2hhcnQgb25seSBkcmF3cyB3aGVuIGl0IGlzOiB0aGVcbiAgICogZW5jbG9zaW5nIGRpYWxvZyBjcmVhdGVzIGFuZCBkZXN0cm95cyBpdHMgY29udGVudCwgc28gd2l0aG91dCB0aGlzIHRoZVxuICAgKiBjb21wb25lbnQgd291bGQgbW91bnQgd2l0aCBhIGRpdiBwcmVzZW50IGJ1dCBubyBzdGF0ZSBjaGFuZ2UgdG8gdHJpZ2dlciBhXG4gICAqIGZpcnN0IGRyYXcuXG4gICAqL1xuICBASW5wdXQoKSBzZXQgYWN0aXZlKG9uOiBib29sZWFuKSB7XG4gICAgY29uc3Qgd2FzID0gdGhpcy5pc0FjdGl2ZTtcbiAgICB0aGlzLmlzQWN0aXZlID0gb247XG4gICAgLy8gRGVmZXJyZWQgYnkgYSB0YXNrOiB3aGVuIGEgY29sbGFwc2VkIHNlY3Rpb24gZXhwYW5kcywgdGhlIGhvc3QgaXMgc3RpbGxcbiAgICAvLyBgaGlkZGVuYCBhdCB0aGUgbW9tZW50IHRoaXMgc2V0dGVyIHJ1bnMsIHNvIHBsb3R0aW5nIG5vdyB3b3VsZCBzaXplIHRoZVxuICAgIC8vIGNoYXJ0IHRvIGEgemVyby1oZWlnaHQgZGl2LlxuICAgIGlmIChvbiAmJiAhd2FzKSBzZXRUaW1lb3V0KCgpID0+IHZvaWQgdGhpcy5yZWxvYWQoKSwgMCk7XG4gICAgZWxzZSBpZiAob24pIHZvaWQgdGhpcy5yZWxvYWQoKTtcbiAgfVxuICBnZXQgYWN0aXZlKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB0aGlzLmlzQWN0aXZlO1xuICB9XG4gIHByaXZhdGUgaXNBY3RpdmUgPSB0cnVlO1xuXG4gIC8qKiBQZXItaW5zdGFuY2UsIGZvciB0aGUgc2FtZSByZWFzb24gdGhlIHZpc3VhbGl6ZXIncyBwbG90IGRpdiBpc1xuICAgKiAgKGB2aXN1YWxpemVyLmNvbXBvbmVudC50c2ApOiB0d28gbW91bnRlZCBjaGFydHMgc2hhcmluZyBvbmUgRE9NIGlkIG1lYW5zXG4gICAqICBgZ2V0RWxlbWVudEJ5SWRgIGhhbmRzIGJvdGggb2YgdGhlbSB0aGUgZmlyc3QgZWxlbWVudCwgc28gb25lIGluc3RhbmNlIGRyYXdzXG4gICAqICBpbnRvIOKAlCBvciBwdXJnZXMg4oCUIHRoZSBvdGhlcidzIGNhbnZhcy4gKi9cbiAgLyoqIE9uZSBpbmNyZW1lbnQgcGVyIElOU1RBTkNFIOKAlCBhIHN0YXRpYyBpbml0aWFsaXplciB3b3VsZCBydW4gb25jZSBwZXIgY2xhc3MgYW5kXG4gICAqICBoYW5kIGV2ZXJ5IGluc3RhbmNlIHRoZSBzYW1lIGlkLCB3aGljaCBpcyB0aGUgYnVnIHRoaXMgaWQgZXhpc3RzIHRvIGF2b2lkLiBEZWNsYXJlZFxuICAgKiAgYmVmb3JlIHRoZSBpZHMgYmVjYXVzZSBmaWVsZCBpbml0aWFsaXplcnMgcnVuIGluIG9yZGVyLiAqL1xuICBwcml2YXRlIHJlYWRvbmx5IHNlcSA9ICsrY2hhcnRJbnN0YW5jZVNlcTtcbiAgcmVhZG9ubHkgY2hhcnREaXYgPSBgc3BhdGlhbC1jaGFydHMtcGxvdC0ke3RoaXMuc2VxfWA7XG4gIC8qKlxuICAgKiBUaGUgZGl2IGluc2lkZSB0aGUgZGV0YWNoZWQgd2luZG93LlxuICAgKlxuICAgKiBBIHNlcGFyYXRlIGlkIHJhdGhlciB0aGFuIG1vdmluZyB0aGUgZXhpc3RpbmcgZGl2OiBBbmd1bGFyIGRlc3Ryb3lzIGFuZCByZWNyZWF0ZXMgYVxuICAgKiBkaXYgYWNyb3NzIGFuIGAqbmdJZmAsIHNvIFBsb3RseSB3b3VsZCBiZSBsZWZ0IGhvbGRpbmcgYSBkZXRhY2hlZCBub2RlLiBUd28gZGl2cyBhbmRcbiAgICogYSByZWRyYXcgaXMgYm90aCBzaW1wbGVyIGFuZCBjb3JyZWN0LlxuICAgKlxuICAgKiBPTkUgaXMgZW5vdWdoIGZvciBldmVyeSBraW5kLCBiZWNhdXNlIG9ubHkgdGhlIGFjdGl2ZSBraW5kIGlzIGV2ZXIgcGxvdHRlZC4gRGV0YWNoaW5nXG4gICAqIGlzIHJlbWVtYmVyZWQgcGVyIGtpbmQsIGJ1dCBhdCBtb3N0IG9uZSB3aW5kb3cgaXMgb3BlbiBhdCBhIHRpbWUuXG4gICAqL1xuICByZWFkb25seSBkZXRhY2hlZERpdiA9IGBzcGF0aWFsLWNoYXJ0cy1kZXRhY2hlZC0ke3RoaXMuc2VxfWA7XG5cbiAgLyoqXG4gICAqIFdoZXRoZXIgdGhlIGNoYXJ0IGlzIHNob3duIGluIGl0cyBvd24gd2luZG93IHJhdGhlciB0aGFuIGlubGluZS5cbiAgICpcbiAgICogRGV0YWNoZWQsIGl0IHNpdHMgQUxPTkdTSURFIHRoZSBzcGF0aWFsLW9taWNzIGRpYWxvZyBpbnN0ZWFkIG9mIGluc2lkZSBpdCDigJQgd2hpY2ggaXNcbiAgICogdGhlIHBvaW50OiB0aGVzZSBhcmUgcmVhZCBhZ2FpbnN0IHRoZSBtYXAsIG5vdCBpbnN0ZWFkIG9mIGl0LiBUaGUgZGlhbG9nIGlzIG9ubHkgc29cbiAgICogd2lkZS5cbiAgICpcbiAgICogT25lIGZsYWcgZm9yIHRoZSBQQU5FTCwgbm90IG9uZSBwZXIga2luZC4gV2hlcmUgdGhlIHdpbmRvdyBpcywgaXMgYSBwcm9wZXJ0eSBvZiB0aGVcbiAgICogd29ya3NwYWNlIHNvbWVvbmUgaGFzIGFycmFuZ2VkLCBub3Qgb2YgdGhlIHRhYiB0aGV5IGhhcHBlbiB0byBiZSBvbjogaGF2aW5nIHB1dCB0aGVcbiAgICogY2hhcnRzIGJlc2lkZSB0aGUgbWFwLCBzd2l0Y2hpbmcgZnJvbSB0aGUgaGVhdG1hcCB0byB0aGUgY291bnRzIHNob3VsZCBzd2FwIHdoYXQgdGhlXG4gICAqIHdpbmRvdyBzaG93cywgbm90IHlhbmsgaXQgYmFjayBpbnRvIHRoZSBkaWFsb2cgYW5kIG1ha2UgdGhlbSBkZXRhY2ggaXQgYWdhaW4uXG4gICAqL1xuICBkZXRhY2hlZCA9IGZhbHNlO1xuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBDT05USU5VT1VTX0tJTkRTOiB7IGxhYmVsOiBzdHJpbmc7IHZhbHVlOiBPbWljc0NoYXJ0S2luZCB9W10gPSBbXG4gICAgeyBsYWJlbDogJ0hpc3RvZ3JhbScsIHZhbHVlOiAnaGlzdG9ncmFtJyB9LFxuICAgIHsgbGFiZWw6ICdWaW9saW4nLCB2YWx1ZTogJ3Zpb2xpbicgfSxcbiAgICB7IGxhYmVsOiAnQm94JywgdmFsdWU6ICdib3gnIH0sXG4gIF07XG4gIHByaXZhdGUgc3RhdGljIHJlYWRvbmx5IENBVEVHT1JJQ0FMX0tJTkRTOiB7IGxhYmVsOiBzdHJpbmc7IHZhbHVlOiBPbWljc0NoYXJ0S2luZCB9W10gPSBbXG4gICAgeyBsYWJlbDogJ0NvdW50cycsIHZhbHVlOiAnY291bnRzJyB9LFxuICBdO1xuXG4gIC8qKiBBdmFpbGFibGUgd2hhdGV2ZXIgdGhlIG1hcCBpcyBjb2xvdXJlZCBieTogdGhlIGhlYXRtYXAncyBzdWJqZWN0IGlzIGEgR0VORVxuICAgKiAgTElTVCBjcm9zc2VkIHdpdGggYSBncm91cGluZywgbm90IHRoZSBhY3RpdmUgY29sb3VyIHNvdXJjZS4gKi9cbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgQUxXQVlTX0tJTkRTOiB7IGxhYmVsOiBzdHJpbmc7IHZhbHVlOiBPbWljc0NoYXJ0S2luZCB9W10gPSBbXG4gICAgeyBsYWJlbDogJ0hlYXRtYXAnLCB2YWx1ZTogJ2hlYXRtYXAnIH0sXG4gIF07XG5cbiAgLyoqXG4gICAqIEEgdC1TTkUgdGhlIGRhdGFzZXQgZG9lcyBub3QgcHVibGlzaCwgb2ZmZXJlZCBzbyBpdCBjYW4gYmUgY29tcHV0ZWQgaGVyZS5cbiAgICpcbiAgICogQSBkcm9wcGVkLWluIGAuaDVhZGAgdHlwaWNhbGx5IGNhcnJpZXMgb25lIFVNQVAgYW5kIGEgUENBLCBhbmQgbm8gdC1TTkUg4oCUIHRoZSB0d29cbiAgICogdmlld3MgYW5zd2VyIGRpZmZlcmVudCBxdWVzdGlvbnMsIHNvIGhhdmluZyBvbmx5IG9uZSBpcyBhIHJlYWwgZ2FwLiBDb21wdXRpbmcgaXRcbiAgICogbmVlZHMgbm8gZXhwcmVzc2lvbiBkYXRhOiB0LVNORSBydW5zIG9uIHRoZSBQQ0Egc2NvcmVzLCB3aGljaCBpcyB3aHkgdGhpcyBjYW4gaGFwcGVuXG4gICAqIGluIHRoZSBicm93c2VyIGF0IGFsbCB3aGlsZSBQQ0EgY2Fubm90LlxuICAgKi9cbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgQ09NUFVUQUJMRTogU3BhdGlhbEVtYmVkZGluZ01ldGFbXSA9IFtcbiAgICB7IG5hbWU6ICdsb2NhbDp0c25lJywgbGFiZWw6ICd0LVNORSAoY29tcHV0ZSknLCBkaW1zOiAyLCBkZXJpdmVkOiB0cnVlIH0sXG4gICAgeyBuYW1lOiAnbG9jYWw6dHNuZTNkJywgbGFiZWw6ICd0LVNORSAzRCAoY29tcHV0ZSknLCBkaW1zOiAzLCBkZXJpdmVkOiB0cnVlIH0sXG4gIF07XG5cbiAgLyoqIFRoZSBtZW51IHN1ZmZpeCB0aGF0IHNheXMgYSBjbGljayB3aWxsIHN0YXJ0IHdvcms7IG5vdCBwYXJ0IG9mIHRoZSBuYW1lLiAqL1xuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBDT01QVVRFX1NVRkZJWCA9ICcgKGNvbXB1dGUpJztcblxuICAvKipcbiAgICogTGFyZ2VzdCBkYXRhc2V0IHRoaXMgd2lsbCBvZmZlciB0byBlbWJlZCBpbiB0aGUgYnJvd3Nlci5cbiAgICpcbiAgICogdC1TTkUgaXMgTyhOwrIpIHBlciBpdGVyYXRpb24g4oCUIHZlY3RvcmlzZWQgb24gdGhlIEdQVSwgYnV0IG5vdCBhcHByb3hpbWF0ZWQg4oCUIHNvIHRoZVxuICAgKiBjb3N0IGlzIHF1YWRyYXRpYyBpbiBvYnNlcnZhdGlvbnMuIE1lYXN1cmVkIGVuZCB0byBlbmQgaW4gRmlyZWZveCBvbiBXZWJHUFU6IDIsNjg4XG4gICAqIHBvaW50cyB0b29rIDk1IHMuIFRoYXQgc2NhbGVzIHRvIHJvdWdobHkgZml2ZSBhbmQgYSBoYWxmIG1pbnV0ZXMgYXQgdGhpcyB0aHJlc2hvbGQsXG4gICAqIGFuZCB0byBhYm91dCA3NiBtaW51dGVzIGF0IHNlcUZJU0gncyAxOSw0MTYsIHdoaWNoIHdhcyBhbHNvIG1lYXN1cmVkIG9mZmxpbmUuXG4gICAqXG4gICAqIFBhc3QgdGhpcyB0aGUgb3B0aW9uIGlzIFdJVEhEUkFXTiByYXRoZXIgdGhhbiBvZmZlcmVkIHdpdGggYSB3YXJuaW5nLiBBbiBob3VyLWxvbmdcbiAgICogam9iIHN0YXJ0ZWQgZnJvbSBhIGJ1dHRvbiBpcyBub3QgYSBjaG9pY2UgYSByZWFkZXIgY2FuIG1lYW5pbmdmdWxseSBjb25zZW50IHRvIGluIGFcbiAgICogZGlhbG9nLCBhbmQgYSB0LVNORSB0aGF0IGxhcmdlIGJlbG9uZ3MgaW4gdGhlIG9mZmxpbmUgcGlwZWxpbmUsIHdoZXJlIGl0IGNhbiBydW5cbiAgICogb25jZSBhbmQgYmUgc2VydmVkIHRvIGV2ZXJ5b25lLlxuICAgKi9cbiAgc3RhdGljIHJlYWRvbmx5IEJST1dTRVJfVFNORV9NQVhfT0JTRVJWQVRJT05TID0gNTAwMDtcblxuICAvKiogTWVhc3VyZWQgYW5jaG9yIGZvciB0aGUgZXN0aW1hdGU6IHNlY29uZHMgZm9yIDIsNjg4IHBvaW50cyBvbiBXZWJHUFUuICovXG4gIHByaXZhdGUgc3RhdGljIHJlYWRvbmx5IFRTTkVfU0VDT05EU19BVCA9IHsgc2Vjb25kczogOTUsIG9ic2VydmF0aW9uczogMjY4OCB9O1xuXG4gIC8qKiBDb29yZGluYXRlcyBjb21wdXRlZCBpbiB0aGlzIGJyb3dzZXIsIGJ5IG5hbWUuIE5vdCBwZXJzaXN0ZWQ6IGEgcmVsb2FkIHJlY29tcHV0ZXMuICovXG4gIHByaXZhdGUgcmVhZG9ubHkgY29tcHV0ZWQgPSBuZXcgTWFwPHN0cmluZywgU3BhdGlhbEVtYmVkZGluZz4oKTtcblxuICAvKiogVGhlIGxpdmUgcnVuLCB3aGVuIG9uZSBpcyBnb2luZy4gKi9cbiAgcHJpdmF0ZSBjb21wdXRlUnVuOiBFbWJlZGRpbmdDb21wdXRlUnVuIHwgbnVsbCA9IG51bGw7XG5cbiAgLyoqXG4gICAqIEJ1bXBlZCBieSBldmVyeSBkYXRhc2V0IGNoYW5nZSBhbmQgYnkgdGVhcmRvd24sIHNvIGEgY29tcHV0YXRpb24gaW4gcHJvZ3Jlc3MgY2FuIHRlbGxcbiAgICogdGhhdCB3aGF0IGl0IGlzIGNvbXB1dGluZyBpcyBubyBsb25nZXIgd2hhdCBpcyBvbiBzY3JlZW4uXG4gICAqXG4gICAqIEEgcnVuIGlzIHNldmVyYWwgYXdhaXRzIGxvbmcg4oCUIHRoZSBQQ0Egc2NvcmVzIGFycml2ZSBvdmVyIEhUVFAsIHRoZW4gdGhlIHdvcmtlciB0YWtlc1xuICAgKiBhIG1pbnV0ZSBvciBtb3JlIOKAlCBhbmQgdGhlIGRhdGFzZXQgY2FuIGNoYW5nZSBhdCBhbnkgb2YgdGhlbSwgaW5jbHVkaW5nIEJFRk9SRVxuICAgKiBgY29tcHV0ZVJ1bmAgZXhpc3RzIHRvIGJlIHRlcm1pbmF0ZWQuIFdpdGhvdXQgYSB0b2tlbiwgdGhlIHNjb3JlcyBmZXRjaGVkIGZvciBvbmVcbiAgICogZGF0YXNldCBnZXQgZW1iZWRkZWQgYW5kIGRyYXduIG92ZXIgYW5vdGhlcidzIG9ic2VydmF0aW9uczogYSBwbG90IG9mIHR3byBkYXRhc2V0cyBhdFxuICAgKiBvbmNlLCB3aGljaCByZWFkcyBhcyBhIHN0cmFuZ2UtbG9va2luZyB0LVNORSByYXRoZXIgdGhhbiBhcyBhIGJ1Zy5cbiAgICovXG4gIHByaXZhdGUgY29tcHV0ZVRva2VuID0gMDtcblxuICAvKiogMC4uMSB3aGlsZSBydW5uaW5nLCBvciBudWxsIGJlZm9yZSB0aGUgZmlyc3QgcmVwb3J0LiBEcml2ZXMgdGhlIHByb2dyZXNzIGJhci4gKi9cbiAgY29tcHV0ZUZyYWN0aW9uOiBudW1iZXIgfCBudWxsID0gbnVsbDtcblxuICAvKiogV2hpY2ggYmFja2VuZCB0aGUgd29ya2VyIGdvdCDigJQgJ3dlYmdwdScsICd3YXNtJyBvciAnY3B1Jy4gU2hvd24gd2hpbGUgcnVubmluZy4gKi9cbiAgY29tcHV0ZUJhY2tlbmQ6IHN0cmluZyB8IG51bGwgPSBudWxsO1xuXG4gIGNvbXB1dGVNZXNzYWdlOiBzdHJpbmcgfCBudWxsID0gbnVsbDtcblxuICBjb21wdXRlRXJyb3I6IHN0cmluZyB8IG51bGwgPSBudWxsO1xuXG4gIC8qKiBUcnVlIHdoZW4gYSB0LVNORSBpcyBtaXNzaW5nIGJ1dCB0aGUgZGF0YXNldCBpcyB0b28gYmlnIHRvIGVtYmVkIGhlcmUuICovXG4gIHRzbmVUb29MYXJnZSA9IGZhbHNlO1xuXG4gIC8qKiBPYnNlcnZhdGlvbnMgaW4gdGhlIGxpdmUgZGF0YXNldCwga2VwdCBmb3IgdGhlIGNvc3QgZXN0aW1hdGUuICovXG4gIHByaXZhdGUgb2JzZXJ2YXRpb25Db3VudCA9IDA7XG5cbiAgLyoqIE9mZmVyZWQgb25seSB3aGVuIHRoZSBkYXRhc2V0IHB1Ymxpc2hlcyBhbiBlbWJlZGRpbmcgdG8gZHJhdy4gKi9cbiAgLy8gTGFiZWxsZWQgZm9yIHdoYXQgaXQgaXMgcmF0aGVyIHRoYW4gZm9yIG9uZSBpbnN0YW5jZSBvZiBpdDogdGhpcyB2aWV3IGRyYXdzIHdoYXRldmVyXG4gIC8vIGVtYmVkZGluZyB0aGUgZGF0YXNldCBwdWJsaXNoZXMg4oCUIGEgVU1BUCwgYSBQQ0EsIGEgdC1TTkUg4oCUIGFuZCBjYWxsaW5nIHRoZSB0YWIgXCJVTUFQXCJcbiAgLy8gd2hpbGUgaXQgc2hvd2VkIGEgUENBIHdvdWxkIGJlIGEgbGllIHRoZSBwaWNrZXIgYmVuZWF0aCBpdCBpbW1lZGlhdGVseSBjb250cmFkaWN0cy5cbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgRU1CRURESU5HX0tJTkRTOiB7IGxhYmVsOiBzdHJpbmc7IHZhbHVlOiBPbWljc0NoYXJ0S2luZCB9W10gPSBbXG4gICAgeyBsYWJlbDogJ0VtYmVkZGluZycsIHZhbHVlOiAnZW1iZWRkaW5nJyB9LFxuICBdO1xuXG4gIC8qKiBUaGUga2luZHMgdGhlIEFDVElWRSBzdWJqZWN0IGNhbiBiZSBkcmF3biBhcy4gQSBjYXRlZ29yeSBjb2RlIGlzIGEgbGFiZWwsXG4gICAqICBub3QgYSBtYWduaXR1ZGUsIHNvIGEgaGlzdG9ncmFtIG9mIGl0IHdvdWxkIGJlIG1lYW5pbmdsZXNzIOKAlCB3aGF0IGFcbiAgICogIGNhdGVnb3JpY2FsIGNvbHVtbiBoYXMgaXMgYSBmcmVxdWVuY3kgZGlzdHJpYnV0aW9uLiAqL1xuICBnZXQga2luZE9wdGlvbnMoKTogeyBsYWJlbDogc3RyaW5nOyB2YWx1ZTogT21pY3NDaGFydEtpbmQgfVtdIHtcbiAgICByZXR1cm4gW1xuICAgICAgLi4uKHRoaXMuY2F0ZWdvcmljYWxcbiAgICAgICAgPyBTcGF0aWFsQ2hhcnRzQ29tcG9uZW50LkNBVEVHT1JJQ0FMX0tJTkRTXG4gICAgICAgIDogU3BhdGlhbENoYXJ0c0NvbXBvbmVudC5DT05USU5VT1VTX0tJTkRTKSxcbiAgICAgIC4uLlNwYXRpYWxDaGFydHNDb21wb25lbnQuQUxXQVlTX0tJTkRTLFxuICAgICAgLy8gQW4gZW1iZWRkaW5nIGlzIGEgcHJvcGVydHkgb2YgdGhlIERBVEFTRVQsIG5vdCBvZiB0aGUgYWN0aXZlIGNvbG91ciBzb3VyY2UsIHNvXG4gICAgICAvLyBpdCBpcyBvZmZlcmVkIHdoZW5ldmVyIG9uZSBpcyBwdWJsaXNoZWQgYW5kIG5ldmVyIG90aGVyd2lzZSDigJQgYSB0YWIgdGhhdCBkcmF3c1xuICAgICAgLy8gbm90aGluZyBpcyB3b3JzZSB0aGFuIGFuIGFic2VudCBvbmUuXG4gICAgICAuLi4odGhpcy5lbWJlZGRpbmdzLmxlbmd0aCA+IDAgPyBTcGF0aWFsQ2hhcnRzQ29tcG9uZW50LkVNQkVERElOR19LSU5EUyA6IFtdKSxcbiAgICBdO1xuICB9XG5cbiAgY29udHJvbHM6IElTcGF0aWFsQ29udHJvbHMgfCBudWxsID0gbnVsbDtcbiAga2luZDogT21pY3NDaGFydEtpbmQgPSAnaGlzdG9ncmFtJztcbiAgLyoqIEVtYmVkZGluZ3MgdGhlIGRhdGFzZXQgcHVibGlzaGVzLCBhbmQgd2hpY2ggb2YgdGhlbSBpcyBkcmF3bi4gKi9cbiAgZW1iZWRkaW5nczogU3BhdGlhbEVtYmVkZGluZ01ldGFbXSA9IFtdO1xuICBlbWJlZGRpbmc6IFNwYXRpYWxFbWJlZGRpbmdNZXRhIHwgbnVsbCA9IG51bGw7XG4gIHByaXZhdGUgZW1iZWRkaW5nQ29vcmRzOiBTcGF0aWFsRW1iZWRkaW5nIHwgbnVsbCA9IG51bGw7XG4gIC8qKiBUaGUgY2F0ZWdvcmljYWwgY29sb3VyaW5nIGJlaGluZCB0aGUgZW1iZWRkaW5nJ3MgY29sb3Vycywga2VwdCBzbyBhIHJlZHJhdyBmb3IgYVxuICAgKiAgc2VsZWN0aW9uIGNoYW5nZSBkb2VzIG5vdCByZWZldGNoIHRoZSBjb2x1bW4uICovXG4gIHByaXZhdGUgZW1iZWRkaW5nQ29kZXM6IHsgY29kZXM6IFVpbnQxNkFycmF5OyBuYW1lczogc3RyaW5nW107IGNvbG9yczogc3RyaW5nW10gfSB8IG51bGwgPSBudWxsO1xuICAvKipcbiAgICogR2VuZXMgdGhlIGhlYXRtYXAncyByb3dzIGFyZSwgYW5kIHRoZSB2ZWN0b3JzIGJlaGluZCB0aGVtLlxuICAgKlxuICAgKiBMb2NhbCByYXRoZXIgdGhhbiBpbiBgU3BhdGlhbFZpZXdTdGF0ZWAsIG1hdGNoaW5nIGhvdyB0aGUgY2hhcnQgS0lORCBpc1xuICAgKiBoZWxkOiB0aGlzIGlzIHdoYXQgdGhlIHBhbmVsIGlzIGNoYXJ0aW5nLCBub3Qgd2hhdCB0aGUgbWFwIGlzIGRyYXdpbmcsIGFuZFxuICAgKiB0aGUgcmVuZGVyZXIgaGFzIG5vIHVzZSBmb3IgaXQuXG4gICAqL1xuICBoZWF0bWFwR2VuZXM6IHN0cmluZ1tdID0gW107XG4gIC8qKiBaLXNjb3JlIGVhY2ggZ2VuZSBhY3Jvc3MgdGhlIGdyb3Vwcy4gT24gYnkgZGVmYXVsdDogd2l0aG91dCBpdCBvbmUgbG91ZFxuICAgKiAgZ2VuZSBzYXR1cmF0ZXMgdGhlIHNjYWxlIGFuZCB0aGUgcmVzdCBvZiB0aGUgcGFuZWwgcmVhZHMgYXMgYmxhbmsuICovXG4gIGhlYXRtYXBaU2NvcmUgPSB0cnVlO1xuICAvKiogUGFzdCB0aGlzIG1hbnkgc2VsZWN0ZWQgY2VsbHMgdGhlIHBlci1jZWxsIHZpZXcgaXMgYSB0ZXh0dXJlLCBub3QgYVxuICAgKiAgcmVhZGFibGUgcGFuZWwsIHNvIHRoZSBjb2x1bW5zIGdvIGJhY2sgdG8gYmVpbmcgY2xhc3Nlcy4gKi9cbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgSEVBVE1BUF9DRUxMX0NPTFVNTlMgPSAyMDA7XG4gIC8qKlxuICAgKiBDb2x1bW4gY2FwIGZvciB0aGUgZ3JvdXBlZCBoZWF0bWFwLlxuICAgKlxuICAgKiBBbnkgY2F0ZWdvcmljYWwgY29sdW1uIGNhbiBiZSB0aGUgeCBheGlzLCBhbmQgdGhleSBhcmUgbm90IHRoZSBzYW1lIHNpemU6XG4gICAqIGBuZXVyb3RyYW5zbWl0dGVyYCBoYXMgOSBjYXRlZ29yaWVzLCBgY2xhc3NgIDM0LCBgc3ViY2xhc3NgIDMzOC4gQXQgMzM4IHRoZVxuICAgKiBwYW5lbCBpcyBhIHRleHR1cmUg4oCUIGNvbHVtbnMgYSBjb3VwbGUgb2YgcGl4ZWxzIHdpZGUsIG5vIHJlYWRhYmxlIGxhYmVsIOKAlFxuICAgKiBzbyBwYXN0IHRoaXMgbWFueSB0aGUgc3Ryb25nZXN0IGNvbHVtbnMgYXJlIGtlcHQgYW5kIHRoZSBub3RlIHNheXMgaG93IG1hbnlcbiAgICogd2VyZSBkcm9wcGVkLiBTZWUgYG1heENvbHNgIGluIGBzcGF0aWFsLWhlYXRtYXAudHNgLlxuICAgKi9cbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgSEVBVE1BUF9NQVhfQ09MVU1OUyA9IDQwO1xuICAvKiogQ29sdW1ucyB0aGUgY2FwIGRyb3BwZWQgZnJvbSB0aGUgbGFzdCByZW5kZXIsIGZvciB0aGUgbm90ZS4gKi9cbiAgcHJpdmF0ZSBoZWF0bWFwSGlkZGVuID0gMDtcbiAgZ2VuZU9wdGlvbnM6IHsgbGFiZWw6IHN0cmluZzsgdmFsdWU6IHN0cmluZyB9W10gPSBbXTtcbiAgLyoqIEV2ZXJ5IGdlbmUgdGhlIGRhdGFzZXQgaW5saW5lZC4gYGdlbmVPcHRpb25zYCBpcyBvbmx5IGV2ZXIgdGhlIGJlc3QgZmV3IGh1bmRyZWQgb2ZcbiAgICogIHRoZXNlIOKAlCBzZWUgYGdlbmUtc2VhcmNoLnRzYCBmb3Igd2h5IGEgd2hvbGUtdHJhbnNjcmlwdG9tZSBsaXN0IGNhbm5vdCBiZSBoYW5kZWQgdG9cbiAgICogIHRoZSBjb250cm9sIHdob2xlLiAqL1xuICBwcml2YXRlIGdlbmVOYW1lczogc3RyaW5nW10gPSBbXTtcbiAgLyoqIFdoYXQgaXMgdHlwZWQgaW4gdGhlIHBpY2tlcidzIGZpbHRlciBib3gsIHNvIHRoZSBvcHRpb25zIGNhbiBiZSByZWJ1aWx0IHdoZW4gdGhlXG4gICAqICBzZWxlY3Rpb24gY2hhbmdlcyB3aXRob3V0IGxvc2luZyB0aGUgcXVlcnkuICovXG4gIHByaXZhdGUgZ2VuZVF1ZXJ5ID0gJyc7XG4gIC8qKiBGZXRjaGVkIHZlY3RvcnMgYnkgZ2VuZSBuYW1lLCBzbyBhZGRpbmcgYSBmb3VydGggZ2VuZSBkb2VzIG5vdCByZWZldGNoIHRoZVxuICAgKiAgZmlyc3QgdGhyZWUg4oCUIGVhY2ggaXMgYSBmdWxsIHBlci1vYnNlcnZhdGlvbiBGbG9hdDMyQXJyYXkuICovXG4gIHByaXZhdGUgcmVhZG9ubHkgZ2VuZUNhY2hlID0gbmV3IE1hcDxzdHJpbmcsIEZsb2F0MzJBcnJheT4oKTtcbiAgLyoqIENhdGVnb3JpY2FsIGNvbHVtbiB0aGUgdmlvbGluL2JveCBzcGxpdHMgYnk7IG51bGwgPSBvbmUgdHJhY2UgZm9yIGFsbC4gKi9cbiAgZ3JvdXBCeTogc3RyaW5nIHwgbnVsbCA9IG51bGw7XG4gIGdyb3VwT3B0aW9uczogeyBsYWJlbDogc3RyaW5nOyB2YWx1ZTogc3RyaW5nIHwgbnVsbCB9W10gPSBbXTtcblxuICAvKiogV2hhdCB0aGUgbWFwIGlzIGNvbG91cmVkIGJ5IOKAlCB0aGUgY2hhcnQncyBzdWJqZWN0LiAqL1xuICBjb2xvckJ5OiBTcGF0aWFsQ29sb3JCeSB8IG51bGwgPSBudWxsO1xuICBzZWxlY3Rpb25Db3VudCA9IDA7XG4gIC8qKiBTZXQgd2hlbiB0aGUgYWN0aXZlIGNvbG91ciBzb3VyY2UgY2Fubm90IGJlIGNoYXJ0ZWQsIGZvciBhbiBpbmxpbmUgaGludC4gKi9cbiAgbm90aWNlOiBzdHJpbmcgfCBudWxsID0gbnVsbDtcbiAgYnVzeSA9IGZhbHNlO1xuXG4gIHByaXZhdGUgdmlldzogU3BhdGlhbFZpZXdTdGF0ZSA9IHsgLi4uREVGQVVMVF9TUEFUSUFMX1ZJRVcgfTtcbiAgcHJpdmF0ZSBzZWxlY3Rpb246IFNwYXRpYWxTZWxlY3Rpb25NYXNrID0gZW1wdHlTZWxlY3Rpb24oKTtcbiAgcHJpdmF0ZSB2YWx1ZXM6IEZsb2F0MzJBcnJheSB8IG51bGwgPSBudWxsO1xuICAvKiogU2V0IGluc3RlYWQgb2YgYHZhbHVlc2Agd2hlbiB0aGUgY29sb3VyIHNvdXJjZSBpcyBhIGNhdGVnb3JpY2FsIGNvbHVtbjogaXRzXG4gICAqICBkaXN0cmlidXRpb24gaXMgY291bnRzIHBlciBjYXRlZ29yeSwgbm90IGEgaGlzdG9ncmFtIG9mIGl0cyBjb2Rlcy4gKi9cbiAgcHJpdmF0ZSBjYXRlZ29yaWNhbDogT21pY3NHcm91cGluZyB8IG51bGwgPSBudWxsO1xuICBwcml2YXRlIGdyb3VwaW5nOiBPbWljc0dyb3VwaW5nIHwgbnVsbCA9IG51bGw7XG4gIC8qKiBHdWFyZHMgdGhlIGFzeW5jIHZhbHVlIGZldGNoOiBhIGZhc3QgY29sb3VyLXNvdXJjZSBjaGFuZ2UgY2FuIHJlc29sdmUgb3V0XG4gICAqICBvZiBvcmRlciwgYW5kIGEgc3RhbGUgdmVjdG9yIHdvdWxkIGJlIGNoYXJ0ZWQgYWdhaW5zdCB0aGUgbmV3IGxhYmVsLiAqL1xuICBwcml2YXRlIHRva2VuID0gMDtcbiAgLyoqIFRoZSBzYW1lIGd1YXJkIGZvciB0aGUgZ3JvdXBpbmcgZmV0Y2gsIHdoaWNoIHRoZSB1c2VyIGNhbiBjaGFuZ2UgYXMgZmFzdC4gKi9cbiAgcHJpdmF0ZSBncm91cFRva2VuID0gMDtcbiAgLyoqIFdoZXRoZXIgdGhlIGZpcnN0IHZpZXcgZW1pc3Npb24gaGFzIGJlZW4gaGFuZGxlZC4gKi9cbiAgcHJpdmF0ZSBwcmltZWQgPSBmYWxzZTtcbiAgcHJpdmF0ZSByZWFkb25seSBzdWJzID0gbmV3IFN1YnNjcmlwdGlvbigpO1xuXG4gIGNvbnN0cnVjdG9yKEBJbmplY3QoVklTVUFMSVpFUikgcHJpdmF0ZSByZWFkb25seSB2aXo6IElWaXN1YWxpemVyKSB7fVxuXG4gIG5nT25Jbml0KCk6IHZvaWQge1xuICAgIC8vIEJlZm9yZSB0aGUgcG9ydCBjaGVjazogdGhlIGhlbHAgaWNvbiBpcyBpbiB0aGUgdGVtcGxhdGUgZWl0aGVyIHdheSwgYW5kIGEgcGFuZWxcbiAgICAvLyB3aXRoIG5vIGRhdGEgc291cmNlIGlzIGV4YWN0bHkgd2hlcmUgc29tZW9uZSByZWFkcyBpdC5cbiAgICBlbnN1cmVIZWxwVGlwU3R5bGUoKTtcbiAgICB0aGlzLmNvbnRyb2xzID0gdGhpcy52aXouZ2V0U3BhdGlhbENvbnRyb2xzPy4oKSA/PyBudWxsO1xuICAgIGlmICghdGhpcy5jb250cm9scykgcmV0dXJuO1xuXG4gICAgdGhpcy5zdWJzLmFkZChjb21iaW5lTGF0ZXN0KFtcbiAgICAgIHRoaXMuY29udHJvbHMuZ2V0Vmlld1N0YXRlJCgpLCB0aGlzLmNvbnRyb2xzLmdldFNlbGVjdGlvbiQoKSxcbiAgICBdKS5zdWJzY3JpYmUoKFt2aWV3LCBzZWxlY3Rpb25dKSA9PiB7XG4gICAgICBjb25zdCBzb3VyY2VDaGFuZ2VkID0gdmlldy5jb2xvckJ5Py5raW5kICE9PSB0aGlzLnZpZXcuY29sb3JCeT8ua2luZFxuICAgICAgICB8fCB2aWV3LmNvbG9yQnk/Lm5hbWUgIT09IHRoaXMudmlldy5jb2xvckJ5Py5uYW1lO1xuICAgICAgdGhpcy52aWV3ID0gdmlldztcbiAgICAgIHRoaXMuY29sb3JCeSA9IHZpZXcuY29sb3JCeTtcbiAgICAgIHRoaXMuc2VsZWN0aW9uID0gc2VsZWN0aW9uO1xuICAgICAgdGhpcy5zZWxlY3Rpb25Db3VudCA9IHNlbGVjdGlvbi5jb3VudDtcbiAgICAgIC8vIEEgc2VsZWN0aW9uIG9yIGxvZyBjaGFuZ2Ugb25seSBuZWVkcyBhIHJlLXJlbmRlcjsgYSBuZXcgY29sb3VyIHNvdXJjZVxuICAgICAgLy8gbmVlZHMgaXRzIHZlY3RvciBmZXRjaGVkIGZpcnN0LiBUaGUgRklSU1QgZW1pc3Npb24gYWx3YXlzIHJlbG9hZHM6XG4gICAgICAvLyBvdGhlcndpc2UgYG51bGwgLT4gbnVsbGAgcmVhZHMgYXMgXCJ1bmNoYW5nZWRcIiBhbmQgdGhlIGNvbXBvbmVudCBzaXRzXG4gICAgICAvLyB3aXRoIG5vIGRhdGEgYW5kIG5vIGV4cGxhbmF0aW9uIHVudGlsIHNvbWV0aGluZyBlbHNlIG1vdmVzLlxuICAgICAgaWYgKCF0aGlzLnByaW1lZCB8fCBzb3VyY2VDaGFuZ2VkKSB7XG4gICAgICAgIHRoaXMucHJpbWVkID0gdHJ1ZTtcbiAgICAgICAgdm9pZCB0aGlzLnJlbG9hZCgpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gQSBzZWxlY3Rpb24gb3IgbG9nLXNjYWxlIGNoYW5nZSBuZWVkcyBvbmx5IGEgcmVkcmF3IG9mIHRoZSBzYW1lIHZlY3Rvci5cbiAgICAgICAgdm9pZCB0aGlzLnJlbmRlcigpO1xuICAgICAgfVxuICAgIH0pKTtcblxuICAgIHRoaXMuc3Vicy5hZGQodGhpcy5jb250cm9scy5nZXREYXRhc2V0JCgpLnN1YnNjcmliZSgoZGF0YXNldCkgPT4ge1xuICAgICAgdGhpcy5ncm91cE9wdGlvbnMgPSBbXG4gICAgICAgIHsgbGFiZWw6ICdObyBncm91cGluZycsIHZhbHVlOiBudWxsIH0sXG4gICAgICAgIC4uLih0aGlzLmNvbnRyb2xzPy5jYXRlZ29yaWNhbENvbHVtbnMoKSA/PyBbXSkubWFwKChuKSA9PiAoeyBsYWJlbDogbiwgdmFsdWU6IG4gfSkpLFxuICAgICAgXTtcbiAgICAgIC8vIEEgbmV3IGRhdGFzZXQncyBjb2x1bW5zIGFyZSBkaWZmZXJlbnQ7IGEgY2FycmllZC1vdmVyIGdyb3VwIGlzIG1lYW5pbmdsZXNzLlxuICAgICAgaWYgKHRoaXMuZ3JvdXBCeSAmJiAhdGhpcy5ncm91cE9wdGlvbnMuc29tZSgobykgPT4gby52YWx1ZSA9PT0gdGhpcy5ncm91cEJ5KSkge1xuICAgICAgICB0aGlzLmdyb3VwQnkgPSBudWxsO1xuICAgICAgICB0aGlzLmdyb3VwaW5nID0gbnVsbDtcbiAgICAgIH1cbiAgICAgIC8vIFRoZSBoZWF0bWFwJ3Mgcm93cyBjb21lIGZyb20gdGhlIGRhdGFzZXQncyBnZW5lIGxpc3QuIEEgZGF0YXNldCB0b28gd2lkZVxuICAgICAgLy8gdG8gaW5saW5lIGl0cyBuYW1lcyBvZmZlcnMgbm9uZSBoZXJlIOKAlCB0aGUgcGFuZWwncyB0eXBlYWhlYWQgaXMgdGhlIHdheVxuICAgICAgLy8gaW4gZm9yIHRob3NlLCBhbmQgdGhlIGhlYXRtYXAgbmVlZHMgbmFtZXMgaXQgY2FuIGxpc3QuXG4gICAgICB0aGlzLmdlbmVOYW1lcyA9IFsuLi4oZGF0YXNldD8uZmVhdHVyZXM/Lm5hbWVzID8/IFtdKV07XG4gICAgICB0aGlzLmdlbmVRdWVyeSA9ICcnO1xuICAgICAgLy8gVmFsaWRhdGVkIGFnYWluc3QgdGhlIFdIT0xFIGxpc3QsIG5vdCB0aGUgdmlzaWJsZSBvcHRpb25zOiB0aG9zZSBhcmUgY2FwcGVkLCBhbmRcbiAgICAgIC8vIGZpbHRlcmluZyB0aGUgc2VsZWN0aW9uIGJ5IHRoZW0gd291bGQgZHJvcCBnZW5lcyB0aGUgZGF0YXNldCBzdGlsbCBoYXMuXG4gICAgICBjb25zdCBrbm93biA9IG5ldyBTZXQodGhpcy5nZW5lTmFtZXMpO1xuICAgICAgdGhpcy5oZWF0bWFwR2VuZXMgPSB0aGlzLmhlYXRtYXBHZW5lcy5maWx0ZXIoKG4pID0+IGtub3duLmhhcyhuKSk7XG4gICAgICB0aGlzLnJlZnJlc2hHZW5lT3B0aW9ucygpO1xuICAgICAgdGhpcy5nZW5lQ2FjaGUuY2xlYXIoKTtcblxuICAgICAgLy8gRW1iZWRkaW5ncyBiZWxvbmcgdG8gdGhlIGRhdGFzZXQsIHNvIHRoZXkgYXJlIHJlLXJlYWQgd2l0aCBpdCBhbmQgdGhlIGxvYWRlZFxuICAgICAgLy8gY29vcmRpbmF0ZXMgZHJvcHBlZDogYSBVTUFQIGZyb20gdGhlIHByZXZpb3VzIGRhdGFzZXQgb3ZlciB0aGVzZSBvYnNlcnZhdGlvbnNcbiAgICAgIC8vIHdvdWxkIGJlIGEgcGxvdCBvZiB0d28gZGlmZmVyZW50IHRoaW5ncyBhdCBvbmNlLlxuICAgICAgdGhpcy5jb21wdXRlZC5jbGVhcigpO1xuICAgICAgdGhpcy5hYmFuZG9uQ29tcHV0ZSgpO1xuICAgICAgY29uc3QgcHVibGlzaGVkID0gZGF0YXNldD8uZW1iZWRkaW5ncyA/IFsuLi5kYXRhc2V0LmVtYmVkZGluZ3NdIDogW107XG4gICAgICAvLyBPZmZlciB0byBjb21wdXRlIGEgdC1TTkUgb25seSB3aGVyZSB0aGUgZGF0YXNldCBoYXMgUENBIHRvIGVtYmVkIGFuZCBubyB0LVNORSBvZlxuICAgICAgLy8gaXRzIG93biDigJQgb3RoZXJ3aXNlIHRoZSBtZW51IGFkdmVydGlzZXMgd29yayB0aGF0IGNhbm5vdCBzdGFydCwgb3IgZHVwbGljYXRlc1xuICAgICAgLy8gd2hhdCBpcyBhbHJlYWR5IHNlcnZlZC5cbiAgICAgIGNvbnN0IGhhc1BjYSA9IHB1Ymxpc2hlZC5zb21lKChlKSA9PiAvcGNhL2kudGVzdChlLmxhYmVsID8/IGUubmFtZSkpO1xuICAgICAgY29uc3QgaGFzVHNuZSA9IHB1Ymxpc2hlZC5zb21lKChlKSA9PiAvdHNuZXx0LXNuZS9pLnRlc3QoZS5sYWJlbCA/PyBlLm5hbWUpKTtcbiAgICAgIHRoaXMub2JzZXJ2YXRpb25Db3VudCA9IGRhdGFzZXQ/Lm9ic2VydmF0aW9ucy5jb3VudCA/PyAwO1xuICAgICAgdGhpcy50c25lVG9vTGFyZ2UgPSBoYXNQY2EgJiYgIWhhc1RzbmVcbiAgICAgICAgJiYgdGhpcy5vYnNlcnZhdGlvbkNvdW50ID4gU3BhdGlhbENoYXJ0c0NvbXBvbmVudC5CUk9XU0VSX1RTTkVfTUFYX09CU0VSVkFUSU9OUztcbiAgICAgIHRoaXMuZW1iZWRkaW5ncyA9IGhhc1BjYSAmJiAhaGFzVHNuZSAmJiAhdGhpcy50c25lVG9vTGFyZ2VcbiAgICAgICAgPyBbLi4ucHVibGlzaGVkLCAuLi5TcGF0aWFsQ2hhcnRzQ29tcG9uZW50LkNPTVBVVEFCTEVdXG4gICAgICAgIDogcHVibGlzaGVkO1xuICAgICAgdGhpcy5lbWJlZGRpbmcgPSB0aGlzLmVtYmVkZGluZ3NbMF0gPz8gbnVsbDtcbiAgICAgIHRoaXMuZW1iZWRkaW5nQ29vcmRzID0gbnVsbDtcbiAgICAgIHRoaXMuZW1iZWRkaW5nQ29kZXMgPSBudWxsO1xuICAgICAgLy8gTm90aGluZyB0byBkcmF3IGZvciB0aGUga2luZCB0aGF0IHdhcyBzZWxlY3RlZDsgZmFsbCBiYWNrIHJhdGhlciB0aGFuIHNpdCBibGFuay5cbiAgICAgIGlmICh0aGlzLmtpbmQgPT09ICdlbWJlZGRpbmcnICYmIHRoaXMuZW1iZWRkaW5ncy5sZW5ndGggPT09IDApIHRoaXMua2luZCA9ICdoaXN0b2dyYW0nO1xuICAgIH0pKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBUaGUgaGVpZ2h0IHRoZSBkcmF3biBsYXlvdXQgYXNrZWQgZm9yLCBvciBudWxsIHdoZXJlIGl0IGF1dG9zaXplcy5cbiAgICpcbiAgICoge0BsaW5rIHJlc2l6ZX0gbmVlZHMgdGhpczogdGhlIGNvdW50cyBhbmQgaGVhdG1hcCBsYXlvdXRzIHNpemUgdGhlaXIgaGVpZ2h0XG4gICAqIHRvIHRoZWlyIGNvbnRlbnQg4oCUIG9uZSBiYW5kIHBlciBiYXIsIHBlciBnZW5lIHJvdyDigJQgd2hpbGUgdGhlIGRpc3RyaWJ1dGlvblxuICAgKiBraW5kcyB3YW50IHRvIGZpbGwgd2hhdGV2ZXIgaGVpZ2h0IHRoZXkgYXJlIGdpdmVuLlxuICAgKi9cbiAgcHJpdmF0ZSBkcmF3bkhlaWdodDogbnVtYmVyIHwgbnVsbCA9IG51bGw7XG5cbiAgLyoqXG4gICAqIFJlLWZpdCB0aGUgcGxvdCB0byB0aGUgcGFuZWwncyBjdXJyZW50IHdpZHRoLlxuICAgKlxuICAgKiBDYWxsZWQgYnkgdGhlIGhvc3Qgd2hlbiB0aGUgZGlhbG9nIGZpbmlzaGVzIHJlc2l6aW5nLiBQbG90bHkgZG9lcyBub3Qgbm90aWNlXG4gICAqIGEgY29udGFpbmVyIHJlc2l6ZSBvbiBpdHMgb3duIOKAlCBpdHMgYHJlc3BvbnNpdmVgIG9wdGlvbiBsaXN0ZW5zIGZvciBXSU5ET1dcbiAgICogcmVzaXplcyBvbmx5LCBzbyBkcmFnZ2luZyBhIGRpYWxvZyBlZGdlIHJlYWNoZXMgaXQgdGhyb3VnaCBub3RoaW5nIGF0IGFsbC5cbiAgICovXG4gIHJlc2l6ZSgpOiB2b2lkIHtcbiAgICB0aGlzLnJlZml0KHRoaXMuY2hhcnREaXYpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlLWZpdCB3aGF0ZXZlciBpcyBwbG90dGVkIGluIGBkaXZgIHRvIGl0cyBjb250YWluZXIuXG4gICAqXG4gICAqIFNoYXJlZCBieSB0aGUgcGFuZWwgYW5kIHRoZSBkZXRhY2hlZCB3aW5kb3cgYmVjYXVzZSB0aGUgcnVsZSBpcyB0aGUgc2FtZSBhbmQgZ2V0dGluZ1xuICAgKiBpdCB3cm9uZyBpcyBpbnZpc2libGUgdW50aWwgaXQgaXMgbm90OiBgYXV0b3NpemVgIHRha2VzIEJPVEggZGltZW5zaW9ucyBmcm9tIHRoZVxuICAgKiBjb250YWluZXIsIHdoaWNoIGlzIHJpZ2h0IG9ubHkgd2hlcmUgdGhlIGxheW91dCBkaWQgbm90IGZpeCBpdHMgb3duIGhlaWdodC4gTWVhc3VyZWQ6XG4gICAqIGF1dG9zaXppbmcgYSBsYXlvdXQgd2l0aCBhbiBleHBsaWNpdCBoZWlnaHQgb2YgNTAwIHJlcGxhY2VkIGl0IHdpdGggdGhlIGNvbnRhaW5lcidzXG4gICAqIDQwMC4gVGhlIGNvdW50cyBhbmQgaGVhdG1hcCBsYXlvdXRzIHNpemUgdGhlbXNlbHZlcyB0byB0aGVpciByb3cgY291bnQsIHNvIHRoZVxuICAgKiB3aW5kb3cgcmVzaXplIGhhcyB0byBzZXQgdGhlIHdpZHRoIEFMT05FIGZvciB0aGVtIGFuZCBsZXQgdGhlIGhlaWdodCBzdGFuZC5cbiAgICovXG4gIHByaXZhdGUgcmVmaXQoZGl2OiBzdHJpbmcpOiB2b2lkIHtcbiAgICBjb25zdCBlbCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGRpdik7XG4gICAgaWYgKCFlbCkgcmV0dXJuO1xuICAgIGNvbnN0IHdpZHRoID0gTWF0aC5yb3VuZChlbC5jbGllbnRXaWR0aCk7XG4gICAgLy8gWmVybyB3aGlsZSB0aGUgc2VjdGlvbiBpcyBjb2xsYXBzZWQgb3IgdGhlIGRpYWxvZyBpcyBjbG9zZWQ7IHJlbGF5aW5nIG91dFxuICAgIC8vIHRvIGEgemVybyBib3ggbWFrZXMgUGxvdGx5IGNvbXB1dGUgYSBsYXlvdXQgaXQgZG9lcyBub3QgcmVjb3ZlciBmcm9tLlxuICAgIGlmICh3aWR0aCA8PSAwKSByZXR1cm47XG4gICAgdHJ5IHtcbiAgICAgIFBsb3RseS5yZWxheW91dChlbCwgdGhpcy5kcmF3bkhlaWdodCA9PT0gbnVsbCA/IHsgYXV0b3NpemU6IHRydWUgfSA6IHsgd2lkdGggfSk7XG4gICAgfSBjYXRjaCB7XG4gICAgICAvLyBOb3RoaW5nIHBsb3R0ZWQgeWV0LlxuICAgIH1cbiAgfVxuXG4gIG5nT25EZXN0cm95KCk6IHZvaWQge1xuICAgIHRoaXMuc3Vicy51bnN1YnNjcmliZSgpO1xuICAgIC8vIEEgd29ya2VyIG91dGxpdmVzIHRoZSBjb21wb25lbnQgdGhhdCBzdGFydGVkIGl0OiBjbG9zaW5nIHRoZSBwYW5lbCBtaWQtcnVuIHdvdWxkXG4gICAgLy8gb3RoZXJ3aXNlIGxlYXZlIGEgdC1TTkUgc2F0dXJhdGluZyBhIEdQVSBmb3IgbWludXRlcyB3aXRoIG5vdGhpbmcgbGVmdCB0byByZWNlaXZlXG4gICAgLy8gdGhlIGFuc3dlci5cbiAgICB0aGlzLmFiYW5kb25Db21wdXRlKCk7XG4gICAgLy8gQk9USCBkaXZzOiBhIGNoYXJ0IGxlZnQgZGV0YWNoZWQgYXQgdGVhcmRvd24gaG9sZHMgaXRzIFdlYkdMIGNvbnRleHQgaW4gdGhlXG4gICAgLy8gd2luZG93J3MgZGl2LCB3aGljaCB0aGUgaW5saW5lIGlkIHdvdWxkIG5ldmVyIHJlYWNoLlxuICAgIGZvciAoY29uc3QgZGl2IG9mIFt0aGlzLmNoYXJ0RGl2LCB0aGlzLmRldGFjaGVkRGl2XSkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgUGxvdGx5LnB1cmdlKGRpdik7XG4gICAgICB9IGNhdGNoIHtcbiAgICAgICAgLy8gVGhlIGRpdiBtYXkgYWxyZWFkeSBiZSBnb25lIHdpdGggdGhlIGRpYWxvZzsgbm90aGluZyB0byBjbGVhbiB1cC5cbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICBuZ0FmdGVyVmlld0luaXQoKTogdm9pZCB7XG4gICAgLy8gVGhlIGRpdiBleGlzdHMgb25seSBub3csIHNvIHRoaXMgaXMgdGhlIGVhcmxpZXN0IGEgZmlyc3QgZHJhdyBjYW4gbGFuZC5cbiAgICB2b2lkIHRoaXMucmVsb2FkKCk7XG4gIH1cblxuICBvbktpbmQoa2luZDogT21pY3NDaGFydEtpbmQpOiB2b2lkIHtcbiAgICB0aGlzLmtpbmQgPSBraW5kO1xuICAgIGlmIChraW5kID09PSAnaGVhdG1hcCcpIHtcbiAgICAgIC8vIFNlZWQgd2l0aCB0aGUgZ2VuZSBhbHJlYWR5IG9uIHNjcmVlbiwgc28gdGhlIGNoYXJ0IHNheXMgc29tZXRoaW5nIHRoZVxuICAgICAgLy8gbW9tZW50IGl0IG9wZW5zIHJhdGhlciB0aGFuIHNob3dpbmcgYW4gZW1wdHkgZ3JpZCBhbmQgYSBwcm9tcHQuXG4gICAgICBpZiAodGhpcy5oZWF0bWFwR2VuZXMubGVuZ3RoID09PSAwICYmIHRoaXMuY29sb3JCeT8ua2luZCA9PT0gJ2ZlYXR1cmUnKSB7XG4gICAgICAgIHRoaXMuaGVhdG1hcEdlbmVzID0gW3RoaXMuY29sb3JCeS5uYW1lXTtcbiAgICAgIH1cbiAgICAgIHZvaWQgKGFzeW5jICgpID0+IHtcbiAgICAgICAgYXdhaXQgdGhpcy5lbnN1cmVIZWF0bWFwR3JvdXBpbmcoKTtcbiAgICAgICAgYXdhaXQgdGhpcy5sb2FkSGVhdG1hcEdlbmVzKCk7XG4gICAgICAgIHZvaWQgdGhpcy5yZW5kZXIoKTtcbiAgICAgIH0pKCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHZvaWQgdGhpcy5yZW5kZXIoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBUaGUgZ3JvdXBpbmcgdGhlIGhlYXRtYXAncyBjb2x1bW5zIGNvbWUgZnJvbSwgZGVmYXVsdGluZyByYXRoZXIgdGhhblxuICAgKiBkZW1hbmRpbmcgb25lOiBhIGhlYXRtYXAgd2l0aCBubyBjb2x1bW5zIGlzIG5vdCBhIGNoYXJ0LCBhbmQgXCJObyBncm91cGluZ1wiXG4gICAqIGlzIGEgc2Vuc2libGUgYW5zd2VyIGZvciBhIHZpb2xpbiBidXQgbm90IGZvciB0aGlzLlxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBlbnN1cmVIZWF0bWFwR3JvdXBpbmcoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgaWYgKHRoaXMuZ3JvdXBpbmcgfHwgIXRoaXMuY29udHJvbHMpIHJldHVybjtcbiAgICBjb25zdCBmaXJzdCA9IHRoaXMuZ3JvdXBPcHRpb25zLmZpbmQoKG8pID0+IG8udmFsdWUpPy52YWx1ZTtcbiAgICBpZiAoIWZpcnN0KSByZXR1cm47XG4gICAgYXdhaXQgdGhpcy5vbkdyb3VwQnkoZmlyc3QpO1xuICB9XG5cbiAgb25IZWF0bWFwWlNjb3JlKG9uOiBib29sZWFuKTogdm9pZCB7XG4gICAgdGhpcy5oZWF0bWFwWlNjb3JlID0gb247XG4gICAgdm9pZCB0aGlzLnJlbmRlcigpO1xuICB9XG5cbiAgLyoqIFdoYXQgdGhlIGhlYXRtYXAgaXMgY3VycmVudGx5IHNob3dpbmcsIHNhaWQgcGxhaW5seS4gKi9cbiAgZ2V0IGhlYXRtYXBOb3RlKCk6IHN0cmluZyB7XG4gICAgaWYgKHRoaXMuaGVhdG1hcEdlbmVzLmxlbmd0aCA9PT0gMCkgcmV0dXJuICdQaWNrIG9uZSBvciBtb3JlIGdlbmVzIGZvciB0aGUgcm93cy4nO1xuICAgIGNvbnN0IHBlckNlbGwgPSB0aGlzLnNlbGVjdGlvbkNvdW50ID4gMFxuICAgICAgJiYgdGhpcy5zZWxlY3Rpb25Db3VudCA8PSBTcGF0aWFsQ2hhcnRzQ29tcG9uZW50LkhFQVRNQVBfQ0VMTF9DT0xVTU5TO1xuICAgIGlmIChwZXJDZWxsKSB7XG4gICAgICByZXR1cm4gYE9uZSBjb2x1bW4gcGVyIHNlbGVjdGVkIGNlbGwgKCR7dGhpcy5zZWxlY3Rpb25Db3VudH0pLiBgXG4gICAgICAgICsgJ01lYW4gZXhwcmVzc2lvbiBwZXIgY2VsbCwgc28gdGhlIGNvbHVtbnMgYXJlIGNlbGxzIHJhdGhlciB0aGFuIGNsYXNzZXMuJztcbiAgICB9XG4gICAgY29uc3Qgc2NvcGUgPSB0aGlzLnNlbGVjdGlvbkNvdW50ID4gMCA/ICd0aGUgc2VsZWN0ZWQgY2VsbHMnIDogJ2FsbCBjZWxscyc7XG4gICAgY29uc3Qgc2NhbGVkID0gdGhpcy5oZWF0bWFwWlNjb3JlXG4gICAgICA/ICcgRWFjaCBnZW5lIGlzIHotc2NvcmVkIGFjcm9zcyB0aGUgY29sdW1ucywgc28gdGhlIGNvbG91ciBpcyBhYm92ZSBvciBiZWxvdyB0aGF0IGdlbmVcXCdzIG93biBhdmVyYWdlLidcbiAgICAgIDogJyBSYXcgbWVhbnMsIHNvIGEgaGlnaGx5LWV4cHJlc3NlZCBnZW5lIGRvbWluYXRlcyB0aGUgc2NhbGUuJztcbiAgICAvLyBTYXlpbmcgc28gbWF0dGVyczogd2l0aG91dCBpdCB0aGUgcGFuZWwgbG9va3MgbGlrZSB0aGUgd2hvbGUgY29sdW1uLlxuICAgIGNvbnN0IGNhcHBlZCA9IHRoaXMuaGVhdG1hcEhpZGRlbiA+IDBcbiAgICAgID8gYCBTaG93aW5nIHRoZSAke1NwYXRpYWxDaGFydHNDb21wb25lbnQuSEVBVE1BUF9NQVhfQ09MVU1OU30gc3Ryb25nZXN0IG9mIGBcbiAgICAgICAgKyBgJHtTcGF0aWFsQ2hhcnRzQ29tcG9uZW50LkhFQVRNQVBfTUFYX0NPTFVNTlMgKyB0aGlzLmhlYXRtYXBIaWRkZW59LCBgXG4gICAgICAgICsgJ3JhbmtlZCBieSB0aGUgbGFyZ2VzdCB2YWx1ZSBhbnkgcGlja2VkIGdlbmUgcmVhY2hlcy4nXG4gICAgICA6ICcnO1xuICAgIHJldHVybiBgTWVhbiBleHByZXNzaW9uIG9mIGVhY2ggZ2VuZSB3aXRoaW4gZWFjaCAke3RoaXMuZ3JvdXBCeSA/PyAnZ3JvdXAnfSwgb3ZlciAke3Njb3BlfS5gXG4gICAgICArIGAke3NjYWxlZH0ke2NhcHBlZH1gO1xuICB9XG5cbiAgLyoqIEdlbmUgcm93cyBmb3IgdGhlIGhlYXRtYXAuICovXG4gIGFzeW5jIG9uSGVhdG1hcEdlbmVzKG5hbWVzOiBzdHJpbmdbXSk6IFByb21pc2U8dm9pZD4ge1xuICAgIHRoaXMuaGVhdG1hcEdlbmVzID0gbmFtZXMgPz8gW107XG4gICAgLy8gQ2hvc2VuIGdlbmVzIG11c3Qgc3RheSBpbiB0aGUgb3B0aW9ucyBvciB0aGUgY29udHJvbCBjYW5ub3QgbGFiZWwgaXRzIGNoaXBzLlxuICAgIHRoaXMucmVmcmVzaEdlbmVPcHRpb25zKCk7XG4gICAgYXdhaXQgdGhpcy5sb2FkSGVhdG1hcEdlbmVzKCk7XG4gICAgdm9pZCB0aGlzLnJlbmRlcigpO1xuICB9XG5cbiAgLyoqXG4gICAqIEZldGNoIGFueSBnZW5lIHRoZSBoZWF0bWFwIG5lZWRzIGFuZCBkb2VzIG5vdCBhbHJlYWR5IGhvbGQuXG4gICAqXG4gICAqIFNlcXVlbmNlZCBvbiB0aGUgc2FtZSB0b2tlbiB0aGUgY29sb3VyLXNvdXJjZSBsb2FkIHVzZXM6IGVhY2ggdmVjdG9yIGlzIGFcbiAgICogc2VwYXJhdGUgcmVxdWVzdCwgYW5kIGEgc2xvd2VyIG9uZSBtdXN0IG5vdCBwYWludCByb3dzIGZvciBhIGdlbmUgbGlzdCB0aGVcbiAgICogdXNlciBoYXMgYWxyZWFkeSBtb3ZlZCBvbiBmcm9tLlxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBsb2FkSGVhdG1hcEdlbmVzKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IGNvbnRyb2xzID0gdGhpcy5jb250cm9scztcbiAgICBpZiAoIWNvbnRyb2xzKSByZXR1cm47XG4gICAgY29uc3QgbWlzc2luZyA9IHRoaXMuaGVhdG1hcEdlbmVzLmZpbHRlcigobikgPT4gIXRoaXMuZ2VuZUNhY2hlLmhhcyhuKSk7XG4gICAgaWYgKG1pc3NpbmcubGVuZ3RoID09PSAwKSByZXR1cm47XG4gICAgY29uc3QgbWluZSA9ICsrdGhpcy50b2tlbjtcbiAgICB0aGlzLmJ1c3kgPSB0cnVlO1xuICAgIHRyeSB7XG4gICAgICBmb3IgKGNvbnN0IG5hbWUgb2YgbWlzc2luZykge1xuICAgICAgICBjb25zdCB2YWx1ZXMgPSBhd2FpdCBjb250cm9scy5jb250aW51b3VzVmFsdWVzKHsga2luZDogJ2ZlYXR1cmUnLCBuYW1lIH0pO1xuICAgICAgICBpZiAobWluZSAhPT0gdGhpcy50b2tlbikgcmV0dXJuO1xuICAgICAgICB0aGlzLmdlbmVDYWNoZS5zZXQobmFtZSwgdmFsdWVzKTtcbiAgICAgIH1cbiAgICAgIHRoaXMubm90aWNlID0gbnVsbDtcbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIGlmIChtaW5lICE9PSB0aGlzLnRva2VuKSByZXR1cm47XG4gICAgICB0aGlzLm5vdGljZSA9IGBBIGdlbmUgY291bGQgbm90IGJlIGNoYXJ0ZWQ6ICR7KGVyciBhcyBFcnJvcik/Lm1lc3NhZ2UgPz8gZXJyfWA7XG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIGlmIChtaW5lID09PSB0aGlzLnRva2VuKSB0aGlzLmJ1c3kgPSBmYWxzZTtcbiAgICB9XG4gIH1cblxuICBhc3luYyBvbkdyb3VwQnkobmFtZTogc3RyaW5nIHwgbnVsbCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHRoaXMuZ3JvdXBCeSA9IG5hbWU7XG4gICAgaWYgKCFuYW1lIHx8ICF0aGlzLmNvbnRyb2xzKSB7XG4gICAgICB0aGlzLmdyb3VwaW5nID0gbnVsbDtcbiAgICAgIHZvaWQgdGhpcy5yZW5kZXIoKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgLy8gU2VxdWVuY2VkOiBwaWNrIEEgdGhlbiBCIGFuZCBhIHNsb3dlciBBIHdvdWxkIG90aGVyd2lzZSBsYW5kIGxhc3QsIGNoYXJ0aW5nXG4gICAgLy8gQSdzIGNhdGVnb3JpZXMgdW5kZXIgYSBkcm9wZG93biB0aGF0IHNheXMgQi5cbiAgICBjb25zdCBtaW5lID0gKyt0aGlzLmdyb3VwVG9rZW47XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHZpZXcgPSBhd2FpdCB0aGlzLmNvbnRyb2xzLmNhdGVnb3JpY2FsVmlldyhuYW1lKTtcbiAgICAgIGlmIChtaW5lICE9PSB0aGlzLmdyb3VwVG9rZW4pIHJldHVybjtcbiAgICAgIHRoaXMuZ3JvdXBpbmcgPSB7IGNvZGVzOiB2aWV3LmNvZGVzLCBjYXRlZ29yaWVzOiB2aWV3LmNhdGVnb3JpZXMsIGNvbG9yczogdmlldy5jb2xvcnMgfTtcbiAgICB9IGNhdGNoIHtcbiAgICAgIGlmIChtaW5lICE9PSB0aGlzLmdyb3VwVG9rZW4pIHJldHVybjtcbiAgICAgIHRoaXMuZ3JvdXBpbmcgPSBudWxsO1xuICAgICAgdGhpcy5ncm91cEJ5ID0gbnVsbDtcbiAgICB9XG4gICAgdm9pZCB0aGlzLnJlbmRlcigpO1xuICB9XG5cbiAgLyoqIFRydWUgd2hlbiB0aGUgY3VycmVudCBraW5kIHdvdWxkIHJlYWQgYmV0dGVyIHdpdGggYSBncm91cGluZyBjaG9zZW4uICovXG4gIGdldCBzdWdnZXN0c0dyb3VwaW5nKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiBiZW5lZml0c0Zyb21Hcm91cGluZyh0aGlzLmtpbmQpICYmICF0aGlzLmdyb3VwQnkgJiYgdGhpcy5ncm91cE9wdGlvbnMubGVuZ3RoID4gMTtcbiAgfVxuXG4gIGdldCBzdWJqZWN0KCk6IHN0cmluZyB7XG4gICAgaWYgKCF0aGlzLmNvbG9yQnkpIHJldHVybiAnJztcbiAgICByZXR1cm4gdGhpcy5jb2xvckJ5LmtpbmQgPT09ICdmZWF0dXJlJyA/IGBnZW5lICR7dGhpcy5jb2xvckJ5Lm5hbWV9YCA6IHRoaXMuY29sb3JCeS5uYW1lO1xuICB9XG5cbiAgLy8g4pSA4pSAIGludGVybmFscyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcblxuICAvKiogRmV0Y2ggdGhlIGFjdGl2ZSBjb2xvdXIgc291cmNlJ3MgdmVjdG9yLCB0aGVuIGRyYXcuICovXG4gIHByaXZhdGUgYXN5bmMgcmVsb2FkKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IGNvbnRyb2xzID0gdGhpcy5jb250cm9scztcbiAgICBjb25zdCBzb3VyY2UgPSB0aGlzLnZpZXcuY29sb3JCeTtcbiAgICBjb25zdCBtaW5lID0gKyt0aGlzLnRva2VuO1xuICAgIGlmICghY29udHJvbHMgfHwgIXNvdXJjZSkge1xuICAgICAgdGhpcy52YWx1ZXMgPSBudWxsO1xuICAgICAgdGhpcy5jYXRlZ29yaWNhbCA9IG51bGw7XG4gICAgICB0aGlzLm5vdGljZSA9IGNvbnRyb2xzXG4gICAgICAgID8gJ0NvbG91ciB0aGUgbWFwIGJ5IGEgY29sdW1uIG9yIGEgZ2VuZSB0byBjaGFydCBpdHMgZGlzdHJpYnV0aW9uLidcbiAgICAgICAgOiBudWxsO1xuICAgICAgdm9pZCB0aGlzLnJlbmRlcigpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICB0aGlzLmJ1c3kgPSB0cnVlO1xuICAgIC8vIEEgY2F0ZWdvcmljYWwgY29sdW1uIGNoYXJ0cyBhcyBDT1VOVFMgcGVyIGNhdGVnb3J5IOKAlCBhc2tlZCBmb3IgYnkgbmFtZVxuICAgIC8vIHJhdGhlciB0aGFuIGRpc2NvdmVyZWQgYnkgY2F0Y2hpbmcgdGhlIGNvbnRpbnVvdXMgZmV0Y2gncyBlcnJvciwgc28gYVxuICAgIC8vIGdlbnVpbmUgZmFpbHVyZSBzdGlsbCByZWFkcyBhcyBhIGZhaWx1cmUuXG4gICAgY29uc3QgaXNDYXRlZ29yaWNhbCA9IHNvdXJjZS5raW5kID09PSAnY29sdW1uJ1xuICAgICAgJiYgKGNvbnRyb2xzLmNhdGVnb3JpY2FsQ29sdW1ucygpID8/IFtdKS5pbmNsdWRlcyhzb3VyY2UubmFtZSk7XG4gICAgdHJ5IHtcbiAgICAgIGlmIChpc0NhdGVnb3JpY2FsKSB7XG4gICAgICAgIGNvbnN0IHZpZXcgPSBhd2FpdCBjb250cm9scy5jYXRlZ29yaWNhbFZpZXcoc291cmNlLm5hbWUpO1xuICAgICAgICBpZiAobWluZSAhPT0gdGhpcy50b2tlbikgcmV0dXJuOyAvLyBzdXBlcnNlZGVkXG4gICAgICAgIHRoaXMuY2F0ZWdvcmljYWwgPSB7IGNvZGVzOiB2aWV3LmNvZGVzLCBjYXRlZ29yaWVzOiB2aWV3LmNhdGVnb3JpZXMsIGNvbG9yczogdmlldy5jb2xvcnMgfTtcbiAgICAgICAgdGhpcy52YWx1ZXMgPSBudWxsO1xuICAgICAgICB0aGlzLmtpbmQgPSAnY291bnRzJztcbiAgICAgICAgdGhpcy5ub3RpY2UgPSBudWxsO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgY29uc3QgdmFsdWVzID0gYXdhaXQgY29udHJvbHMuY29udGludW91c1ZhbHVlcyhzb3VyY2UpO1xuICAgICAgICBpZiAobWluZSAhPT0gdGhpcy50b2tlbikgcmV0dXJuO1xuICAgICAgICB0aGlzLnZhbHVlcyA9IHZhbHVlcztcbiAgICAgICAgdGhpcy5jYXRlZ29yaWNhbCA9IG51bGw7XG4gICAgICAgIGlmICh0aGlzLmtpbmQgPT09ICdjb3VudHMnKSB0aGlzLmtpbmQgPSAnaGlzdG9ncmFtJztcbiAgICAgICAgdGhpcy5ub3RpY2UgPSBudWxsO1xuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgaWYgKG1pbmUgIT09IHRoaXMudG9rZW4pIHJldHVybjtcbiAgICAgIHRoaXMudmFsdWVzID0gbnVsbDtcbiAgICAgIHRoaXMuY2F0ZWdvcmljYWwgPSBudWxsO1xuICAgICAgdGhpcy5ub3RpY2UgPSBgXCIke3NvdXJjZS5uYW1lfVwiIGNvdWxkIG5vdCBiZSBjaGFydGVkOiAkeyhlcnIgYXMgRXJyb3IpPy5tZXNzYWdlID8/IGVycn1gO1xuICAgIH0gZmluYWxseSB7XG4gICAgICBpZiAobWluZSA9PT0gdGhpcy50b2tlbikgdGhpcy5idXN5ID0gZmFsc2U7XG4gICAgfVxuICAgIHZvaWQgdGhpcy5yZW5kZXIoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBUaGUgZW1iZWRkaW5nIHNjYXR0ZXIg4oCUIGEgVU1BUCBvdmVyIHRoZSBzYW1lIG9ic2VydmF0aW9ucy5cbiAgICpcbiAgICogQW5zd2VycyB3aGF0IHRoZSBtYXAgY2Fubm90OiB0aGUgbWFwIHNheXMgV0hFUkUgYSBwb3B1bGF0aW9uIHNpdHMsIHRoaXMgc2F5cyB3aGljaFxuICAgKiBwb3B1bGF0aW9ucyB0aGVyZSBhcmUgYW5kIGhvdyBjbG9zZSB0aGV5IGFyZSBpbiBleHByZXNzaW9uLlxuICAgKlxuICAgKiBMaW5rZWQgdG8gdGhlIG1hcCB3aXRob3V0IGFza2luZyBmb3IgYW55dGhpbmc6IGl0IHJldXNlcyBgdGhpcy5jYXRlZ29yaWNhbGAsIHRoZSBzYW1lXG4gICAqIGxvYWRlZCBjb2xvdXJpbmcgdGhlIGNvdW50cyBjaGFydCBkcmF3cyBmcm9tLCBzbyB0aGUgdHdvIGNhbm5vdCBkaXNhZ3JlZSBhYm91dCB3aGF0IGFcbiAgICogY29sb3VyIG1lYW5zIGFuZCBubyBzZWNvbmQgZmV0Y2ggaGFwcGVucy4gQSBzZWxlY3Rpb24gZGltcyB0aGUgcmVzdCByYXRoZXIgdGhhblxuICAgKiBkcm9wcGluZyBpdCDigJQgdGhlIGVtYmVkZGluZydzIHNoYXBlIGlzIHRoZSBjb250ZXh0IHRoYXQgbWFrZXMgYSBzZWxlY3Rpb24gbGVnaWJsZS5cbiAgICovXG4gIHByaXZhdGUgYXN5bmMgcmVuZGVyRW1iZWRkaW5nKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IGNvbnRyb2xzID0gdGhpcy5jb250cm9scztcbiAgICBjb25zdCBtZXRhID0gdGhpcy5lbWJlZGRpbmc7XG4gICAgaWYgKCFjb250cm9scyB8fCAhbWV0YSkge1xuICAgICAgdGhpcy5wdXJnZVBsb3QoKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKCFjb250cm9scy5nZXRFbWJlZGRpbmcpIHtcbiAgICAgIHRoaXMubm90aWNlID0gJ1RoaXMgZGF0YSBzb3VyY2UgZG9lcyBub3Qgc2VydmUgZW1iZWRkaW5ncy4nO1xuICAgICAgdGhpcy5wdXJnZVBsb3QoKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBTZXF1ZW5jZWQgb24gdGhlIHNoYXJlZCB0b2tlbjogdGhlIGNvb3JkaW5hdGVzIGFyZSBhIHJvdW5kLXRyaXAsIGFuZCBhIHNsb3dlciBvbmVcbiAgICAvLyBtdXN0IG5vdCBwYWludCBvdmVyIGEga2luZCB0aGUgdXNlciBoYXMgYWxyZWFkeSBtb3ZlZCBvbiBmcm9tLlxuICAgIGNvbnN0IG1pbmUgPSArK3RoaXMudG9rZW47XG4gICAgLy8gQ29tcHV0ZWQgaGVyZSwgbm90IHNlcnZlZDogYSBsb2NhbCByZXN1bHQgaXMgdGhlIHNhbWUgc2hhcGUgYXMgYSBmZXRjaGVkIG9uZSwgc29cbiAgICAvLyBldmVyeXRoaW5nIGRvd25zdHJlYW0g4oCUIGNvbG91cmluZywgbGFzc28gc2VsZWN0aW9uLCB0aGUgY2FtZXJhIOKAlCBpcyB1bmNoYW5nZWQuXG4gICAgY29uc3QgbG9jYWwgPSB0aGlzLmNvbXB1dGVkLmdldChtZXRhLm5hbWUpO1xuICAgIGlmIChsb2NhbCkge1xuICAgICAgdGhpcy5lbWJlZGRpbmdDb29yZHMgPSBsb2NhbDtcbiAgICB9IGVsc2UgaWYgKHRoaXMuaXNDb21wdXRhYmxlKSB7XG4gICAgICAvLyBPZmZlcmVkIGJ1dCBub3QgeWV0IGNvbXB1dGVkLiBUaGUgcGFuZWwgc2hvd3MgdGhlIENvbXB1dGUgYnV0dG9uOyBkcmF3aW5nIG5vdGhpbmdcbiAgICAgIC8vIGlzIHJpZ2h0LCBhbmQgYW4gZXJyb3Igd291bGQgYmUgd3Jvbmcg4oCUIHRoZXJlIGlzIG5vIGZhaWx1cmUgaGVyZS5cbiAgICAgIHRoaXMucHVyZ2VQbG90KCk7XG4gICAgICB0aGlzLm5vdGljZSA9IG51bGw7XG4gICAgICByZXR1cm47XG4gICAgfSBlbHNlIGlmICghdGhpcy5lbWJlZGRpbmdDb29yZHMgfHwgdGhpcy5lbWJlZGRpbmdDb29yZHMubWV0YS5uYW1lICE9PSBtZXRhLm5hbWUpIHtcbiAgICAgIHRoaXMuYnVzeSA9IHRydWU7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCBsb2FkZWQgPSBhd2FpdCBjb250cm9scy5nZXRFbWJlZGRpbmcobWV0YS5uYW1lKTtcbiAgICAgICAgaWYgKG1pbmUgIT09IHRoaXMudG9rZW4pIHJldHVybjtcbiAgICAgICAgdGhpcy5lbWJlZGRpbmdDb29yZHMgPSBsb2FkZWQ7XG4gICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgaWYgKG1pbmUgIT09IHRoaXMudG9rZW4pIHJldHVybjtcbiAgICAgICAgdGhpcy5ub3RpY2UgPSBgQ291bGQgbm90IGxvYWQgJHttZXRhLmxhYmVsID8/IG1ldGEubmFtZX06IGBcbiAgICAgICAgICArIGAkeyhlcnIgYXMgRXJyb3IpPy5tZXNzYWdlID8/IGVycn1gO1xuICAgICAgICB0aGlzLnB1cmdlUGxvdCgpO1xuICAgICAgICByZXR1cm47XG4gICAgICB9IGZpbmFsbHkge1xuICAgICAgICBpZiAobWluZSA9PT0gdGhpcy50b2tlbikgdGhpcy5idXN5ID0gZmFsc2U7XG4gICAgICB9XG4gICAgfVxuICAgIGNvbnN0IGNvb3JkcyA9IHRoaXMuZW1iZWRkaW5nQ29vcmRzO1xuICAgIGlmICghY29vcmRzKSByZXR1cm47XG5cbiAgICBjb25zdCB0YXJnZXQgPSB0aGlzLnBsb3RUYXJnZXQ7XG4gICAgY29uc3QgZWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCh0YXJnZXQpO1xuICAgIGlmICghZWwpIHJldHVybjsgLy8gdGhlIHdpbmRvdyBpcyBjbG9zZWQsIG9yIG5vdCByZW5kZXJlZCB5ZXRcbiAgICBjb25zdCB2aWV3ID0gdGhpcy5saXZlVmlldyhlbCk7XG5cbiAgICB0aGlzLm5vdGljZSA9IG51bGw7XG4gICAgY29uc3QgaW5wdXQgPSB7XG4gICAgICB4OiBjb29yZHMueCxcbiAgICAgIHk6IGNvb3Jkcy55LFxuICAgICAgLy8gVGhlIHRoaXJkIGRpbWVuc2lvbiB3aGVuIHRoZSBlbWJlZGRpbmcgaGFzIG9uZSDigJQgd2hhdCBzd2l0Y2hlcyB0aGUgcGxvdCB0byBhXG4gICAgICAvLyByb3RhdGFibGUgM0Qgc2NhdHRlci4gRGVjb2RlZCBvbmx5IGZvciBhIDMtZGltIGVtYmVkZGluZywgc28gaXRzIHByZXNlbmNlIGhlcmVcbiAgICAgIC8vIGlzIHRoZSBzYW1lIHF1ZXN0aW9uIGFzIGBtZXRhLmRpbXMgPT09IDNgLlxuICAgICAgLi4uKGNvb3Jkcy56ID8geyB6OiBjb29yZHMueiB9IDoge30pLFxuICAgICAgbGFiZWw6IG1ldGEubGFiZWwgPz8gbWV0YS5uYW1lLFxuICAgICAgZGVyaXZlZDogbWV0YS5kZXJpdmVkLFxuICAgICAgLy8gUHJlc2VudCBmb3IgYSBQQ0EsIGFic2VudCBmb3IgYSBVTUFQIOKAlCBzZWUgYHZhcmlhbmNlUmF0aW9gIG9uIHRoZSBtZXRhLlxuICAgICAgLi4uKG1ldGEudmFyaWFuY2VSYXRpbyA/IHsgdmFyaWFuY2VSYXRpbzogbWV0YS52YXJpYW5jZVJhdGlvIH0gOiB7fSksXG4gICAgICAuLi4odGhpcy5jYXRlZ29yaWNhbFxuICAgICAgICA/IHtcbiAgICAgICAgICBjYXRlZ29yaWVzOiB7XG4gICAgICAgICAgICBjb2RlczogdGhpcy5jYXRlZ29yaWNhbC5jb2RlcyxcbiAgICAgICAgICAgIG5hbWVzOiB0aGlzLmNhdGVnb3JpY2FsLmNhdGVnb3JpZXMsXG4gICAgICAgICAgICBjb2xvcnM6IHRoaXMuY2F0ZWdvcmljYWwuY29sb3JzLFxuICAgICAgICAgIH0sXG4gICAgICAgIH1cbiAgICAgICAgOiB7fSksXG4gICAgICAuLi4odGhpcy5zZWxlY3Rpb24uY291bnQgPiAwID8geyBzZWxlY3Rpb246IHRoaXMuc2VsZWN0aW9uLm1hc2sgfSA6IHt9KSxcbiAgICAgIC8vIFdoYXRldmVyIHZpZXcgdGhlIHVzZXIgaGFzIHNldCB1cCwgcmVhZCBmcm9tIHRoZSBMSVZFIHBsb3Qg4oCUIHdoaWNoIGlzIHdoZXJlXG4gICAgICAvLyByb3RhdGluZyBhbmQgem9vbWluZyBsZWF2ZSBpdC5cbiAgICAgIC4uLih2aWV3ID8geyB2aWV3IH0gOiB7fSksXG4gICAgfTtcbiAgICBhd2FpdCB0aGlzLmRyYXcoYnVpbGRFbWJlZGRpbmdUcmFjZXMoaW5wdXQpLCBlbWJlZGRpbmdMYXlvdXQoaW5wdXQpLCBFTUJFRERJTkdfQ09ORklHLCB0YXJnZXQpO1xuICAgIHRoaXMuYmluZEVtYmVkZGluZ1NlbGVjdGlvbih0YXJnZXQpO1xuICB9XG5cbiAgLyoqXG4gICAqIFR1cm4gYSBsYXNzbyBpbiB0aGUgZW1iZWRkaW5nIGludG8gYSBzZWxlY3Rpb24gb2Ygb2JzZXJ2YXRpb25zLlxuICAgKlxuICAgKiBUaGlzIGlzIHdoYXQgbWFrZXMgdHdvIHZpZXdzIG9mIG9uZSBkYXRhc2V0IHdvcnRoIGhhdmluZyBzaWRlIGJ5IHNpZGU6IGRyYXcgcm91bmQgYVxuICAgKiBjbHVzdGVyIGhlcmUgYW5kIHRob3NlIGNlbGxzIGxpZ2h0IHVwIG9uIHRoZSB0aXNzdWUsIGJlY2F1c2UgZXZlcnkgdmlldyByZWFkcyB0aGVcbiAgICogc2FtZSBtYXNrLlxuICAgKlxuICAgKiBCb3VuZCBhZnRlciBlYWNoIGRyYXcsIGFuZCB0aGUgcHJldmlvdXMgbGlzdGVuZXIgcmVtb3ZlZCBmaXJzdDogYFBsb3RseS5yZWFjdGBcbiAgICogcHJlc2VydmVzIGhhbmRsZXJzLCBzbyByZS1iaW5kaW5nIHdpdGhvdXQgcmVtb3Zpbmcgd291bGQgZmlyZSB0aGUgc2VsZWN0aW9uIG9uY2UgcGVyXG4gICAqIHJlZHJhdyB0aGUgcGxvdCBoYWQgZXZlciBoYWQuXG4gICAqL1xuICBwcml2YXRlIGJpbmRFbWJlZGRpbmdTZWxlY3Rpb24oZGl2OiBzdHJpbmcpOiB2b2lkIHtcbiAgICBjb25zdCBlbCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGRpdikgYXMgKFBsb3RseS5QbG90bHlIVE1MRWxlbWVudCB8IG51bGwpO1xuICAgIGlmICghZWw/Lm9uKSByZXR1cm47XG4gICAgZWwucmVtb3ZlQWxsTGlzdGVuZXJzPy4oJ3Bsb3RseV9zZWxlY3RlZCcpO1xuICAgIGVsLnJlbW92ZUFsbExpc3RlbmVycz8uKCdwbG90bHlfZGVzZWxlY3QnKTtcbiAgICBlbC5vbigncGxvdGx5X3NlbGVjdGVkJywgKGV2KSA9PiB7XG4gICAgICAvLyBBIGxhc3NvIHRoYXQgc2VsZWN0cyBub3RoaW5nIGFycml2ZXMgYXMgYW4gZXZlbnQgd2l0aCBubyBwb2ludHM7IHRyZWF0IGl0IGFzIGFcbiAgICAgIC8vIGNsZWFyLCB3aGljaCBpcyB3aGF0IGRyYWdnaW5nIGFuIGVtcHR5IHBhdGNoIGxvb2tzIGxpa2UgaXQgc2hvdWxkIGRvLlxuICAgICAgY29uc3QgcG9pbnRzID0gKGV2IGFzIHsgcG9pbnRzPzogcmVhZG9ubHkgdW5rbm93bltdIH0gfCB1bmRlZmluZWQpPy5wb2ludHMgPz8gW107XG4gICAgICBjb25zdCBpbmRpY2VzOiBudW1iZXJbXSA9IFtdO1xuICAgICAgZm9yIChjb25zdCBwIG9mIHBvaW50cykge1xuICAgICAgICAvLyBUaGUgb2JzZXJ2YXRpb24gaW5kZXggcmlkZXMgb24gYGN1c3RvbWRhdGFgIOKAlCBzZWUgYGJ1aWxkRW1iZWRkaW5nVHJhY2VzYC4gQSBwb2ludCdzXG4gICAgICAgIC8vIGluZGV4IHdpdGhpbiBpdHMgdHJhY2UgaXMgbm90IHRoZSBvYnNlcnZhdGlvbiBvbmNlIHRoZSBwb2ludHMgYXJlIHNwbGl0IGJ5XG4gICAgICAgIC8vIGNhdGVnb3J5LCBzbyB0aGlzIGlzIHRoZSBvbmx5IGNvcnJlY3Qgc291cmNlLlxuICAgICAgICBjb25zdCBpZCA9IChwIGFzIHsgY3VzdG9tZGF0YT86IHVua25vd24gfSkuY3VzdG9tZGF0YTtcbiAgICAgICAgaWYgKHR5cGVvZiBpZCA9PT0gJ251bWJlcicpIGluZGljZXMucHVzaChpZCk7XG4gICAgICB9XG4gICAgICBpZiAoaW5kaWNlcy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgdGhpcy5jb250cm9scz8uY2xlYXJTZWxlY3Rpb24oKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgdGhpcy5jb250cm9scz8uc2VsZWN0SW5kaWNlcyhpbmRpY2VzKTtcbiAgICB9KTtcbiAgICAvLyBUaGUgbW9kZWJhcidzIGRlc2VsZWN0LCBhbmQgYSBwbGFpbiBjbGljayBvbiBlbXB0eSBzcGFjZS5cbiAgICBlbC5vbigncGxvdGx5X2Rlc2VsZWN0JywgKCkgPT4gdGhpcy5jb250cm9scz8uY2xlYXJTZWxlY3Rpb24oKSk7XG4gIH1cblxuICAvKipcbiAgICogRHJvcCB0aGUgZW1iZWRkaW5nJ3Mgc2VsZWN0aW9uIGhhbmRsZXJzIGZyb20gYSBkaXYgYW5vdGhlciBraW5kIGlzIGFib3V0IHRvIHVzZS5cbiAgICpcbiAgICogVGhlIGRldGFjaGVkIHdpbmRvdyBvdXRsaXZlcyBhbnkgb25lIGtpbmQg4oCUIHN3aXRjaGluZyB0YWJzIHN3YXBzIGl0cyBjb250ZW50IGFuZFxuICAgKiBrZWVwcyB0aGUgZGl2IOKAlCBzbyB3aXRob3V0IHRoaXMgdGhlIGxhc3NvIGhhbmRsZXJzIGJvdW5kIGZvciBhIFVNQVAgc3RheSBsaXZlIHVuZGVyXG4gICAqIHRoZSBjb3VudHMgcGxvdCB0aGF0IHJlcGxhY2VzIGl0LiBgcGxvdGx5X2Rlc2VsZWN0YCBmaXJlcyBvbiBhIHBsYWluIGNsaWNrIGluIEFOWVxuICAgKiBQbG90bHkgY2hhcnQsIHNvIHRoZSBmaXJzdCBjbGljayBvbiBhIGJhciB3b3VsZCBzaWxlbnRseSBjbGVhciB0aGUgbWFwJ3Mgc2VsZWN0aW9uLlxuICAgKi9cbiAgcHJpdmF0ZSB1bmJpbmRFbWJlZGRpbmdTZWxlY3Rpb24oZGl2OiBzdHJpbmcpOiB2b2lkIHtcbiAgICBjb25zdCBlbCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGRpdikgYXMgKFBsb3RseS5QbG90bHlIVE1MRWxlbWVudCB8IG51bGwpO1xuICAgIGlmICghZWw/LnJlbW92ZUFsbExpc3RlbmVycykgcmV0dXJuO1xuICAgIGVsLnJlbW92ZUFsbExpc3RlbmVycygncGxvdGx5X3NlbGVjdGVkJyk7XG4gICAgZWwucmVtb3ZlQWxsTGlzdGVuZXJzKCdwbG90bHlfZGVzZWxlY3QnKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBUaGUgdmlldyB0aGUgdXNlciBoYXMgc2V0IHVwIG9uIHRoZSBsaXZlIHBsb3QsIHRvIGNhcnJ5IGFjcm9zcyBhIHJlZHJhdy5cbiAgICpcbiAgICogYFBsb3RseS5yZWFjdGAgcmVzZXRzIGEgM0Qgc2NlbmUgY2FtZXJhIGFuZCBhbnkgem9vbWVkIDJEIHJhbmdlIHVubGVzcyB0aGUgbGF5b3V0XG4gICAqIGNhcnJpZXMgdGhlbSwgc28gc2VsZWN0aW5nIGEgY2F0ZWdvcnkgb3IgcmVjb2xvdXJpbmcgd291bGQgdGhyb3cgYXdheSBhbiBvcmllbnRhdGlvblxuICAgKiB0aGF0IHRvb2sgd29yayB0byBmaW5kLiBSb3RhdGluZyBhIGNsb3VkIHRvIHNlZSBhIHN0cnVjdHVyZSBhbmQgbG9zaW5nIGl0IG9uIHRoZSBuZXh0XG4gICAqIGNsaWNrIG1ha2VzIHRoZSBwbG90IHVzZWxlc3MgZm9yIHdoYXQgaXQgaXMgZm9yLlxuICAgKi9cbiAgcHJpdmF0ZSBsaXZlVmlldyhcbiAgICBlbDogSFRNTEVsZW1lbnQsXG4gICk6IHsgY2FtZXJhPzogdW5rbm93bjsgcmFuZ2VzPzogeyB4OiB1bmtub3duOyB5OiB1bmtub3duIH0gfSB8IG51bGwge1xuICAgIGNvbnN0IGZ1bGwgPSAoZWwgYXMge1xuICAgICAgX2Z1bGxMYXlvdXQ/OiB7XG4gICAgICAgIHNjZW5lPzogeyBjYW1lcmE/OiB1bmtub3duIH07XG4gICAgICAgIHhheGlzPzogeyByYW5nZT86IHVua25vd247IGF1dG9yYW5nZT86IGJvb2xlYW4gfTtcbiAgICAgICAgeWF4aXM/OiB7IHJhbmdlPzogdW5rbm93bjsgYXV0b3JhbmdlPzogYm9vbGVhbiB9O1xuICAgICAgfTtcbiAgICB9KS5fZnVsbExheW91dDtcbiAgICBpZiAoIWZ1bGwpIHJldHVybiBudWxsOyAvLyBub3RoaW5nIGRyYXduIGhlcmUgeWV0XG4gICAgaWYgKGZ1bGwuc2NlbmU/LmNhbWVyYSkgcmV0dXJuIHsgY2FtZXJhOiBmdWxsLnNjZW5lLmNhbWVyYSB9O1xuICAgIC8vIDJEOiBvbmx5IG9uY2UgdGhlIHVzZXIgaGFzIGFjdHVhbGx5IHpvb21lZC4gUGFzc2luZyBhbiBhdXRvcmFuZ2VkIHJhbmdlIGJhY2sgd291bGRcbiAgICAvLyBmcmVlemUgdGhlIGF4ZXMgYW5kIHN0b3AgdGhlIHBsb3QgcmUtZml0dGluZyB3aGVuIHRoZSBkYXRhIGNoYW5nZXMuXG4gICAgY29uc3Qgem9vbWVkID0gZnVsbC54YXhpcz8uYXV0b3JhbmdlID09PSBmYWxzZSAmJiBmdWxsLnlheGlzPy5hdXRvcmFuZ2UgPT09IGZhbHNlO1xuICAgIGlmICh6b29tZWQgJiYgZnVsbC54YXhpcz8ucmFuZ2UgJiYgZnVsbC55YXhpcz8ucmFuZ2UpIHtcbiAgICAgIHJldHVybiB7IHJhbmdlczogeyB4OiBmdWxsLnhheGlzLnJhbmdlLCB5OiBmdWxsLnlheGlzLnJhbmdlIH0gfTtcbiAgICB9XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICAvKipcbiAgICogV2hldGhlciB0aGUgcGxvdCBvbiBzY3JlZW4gZml4ZWQgaXRzIG93biBoZWlnaHQuXG4gICAqXG4gICAqIFRoZSBjb3VudHMgYW5kIGhlYXRtYXAgbGF5b3V0cyBzaXplIHRoZW1zZWx2ZXMgdG8gdGhlaXIgcm93IGNvdW50LCBzbyBpbiB0aGUgZGV0YWNoZWRcbiAgICogd2luZG93IHRoZXkgY2FuIGJlIFRBTExFUiB0aGFuIHRoZSB3aW5kb3cg4oCUIHdoaWNoIGhhcyB0byBzY3JvbGwgcmF0aGVyIHRoYW4gY2xpcC4gQW5cbiAgICogZW1iZWRkaW5nIGZpeGVzIG5vIGhlaWdodCBhbmQgZmlsbHMgdGhlIHdpbmRvdyBpbnN0ZWFkLiBUaGUgdGVtcGxhdGUgbmVlZHMgdG8ga25vd1xuICAgKiB3aGljaCwgYmVjYXVzZSB0aGUgdHdvIHdhbnQgb3Bwb3NpdGUgYGZsZXhgIGFuZCBgb3ZlcmZsb3dgLlxuICAgKi9cbiAgZ2V0IGhhc0ZpeGVkSGVpZ2h0KCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB0aGlzLmRyYXduSGVpZ2h0ICE9PSBudWxsO1xuICB9XG5cbiAgLyoqIFdoZXJlIHRoZSBhY3RpdmUga2luZCBkcmF3czogaXRzIG93biB3aW5kb3csIG9yIHRoZSBzaGFyZWQgY2hhcnQgZGl2LiAqL1xuICBnZXQgcGxvdFRhcmdldCgpOiBzdHJpbmcge1xuICAgIHJldHVybiB0aGlzLmRldGFjaGVkID8gdGhpcy5kZXRhY2hlZERpdiA6IHRoaXMuY2hhcnREaXY7XG4gIH1cblxuICAvKipcbiAgICogSGVhZGVyIGZvciB0aGUgZGV0YWNoZWQgd2luZG93IOKAlCB3aGF0IGl0IGlzIHNob3dpbmcsIG5hbWVkIGFzIGl0cyB0YWIgaXMuXG4gICAqXG4gICAqIFRoZSBlbWJlZGRpbmcgbmFtZXMgdGhlIGVtYmVkZGluZyByYXRoZXIgdGhhbiB0aGUgdGFiLCBiZWNhdXNlIFwiRW1iZWRkaW5nXCIgYmVzaWRlIGFcbiAgICogd2luZG93IGRyYXdpbmcgYSB0LVNORSBzYXlzIGxlc3MgdGhhbiBcInQtU05FXCIgZG9lcy5cbiAgICovXG4gIGdldCBkZXRhY2hlZFRpdGxlKCk6IHN0cmluZyB7XG4gICAgaWYgKHRoaXMua2luZCA9PT0gJ2VtYmVkZGluZycpIHtcbiAgICAgIHJldHVybiB0aGlzLmVtYmVkZGluZz8ubGFiZWwgPz8gdGhpcy5lbWJlZGRpbmc/Lm5hbWUgPz8gJ0VtYmVkZGluZyc7XG4gICAgfVxuICAgIGNvbnN0IGxhYmVsID0gdGhpcy5raW5kT3B0aW9ucy5maW5kKChrKSA9PiBrLnZhbHVlID09PSB0aGlzLmtpbmQpPy5sYWJlbCA/PyAnQ2hhcnQnO1xuICAgIHJldHVybiB0aGlzLnN1YmplY3QgPyBgJHtsYWJlbH0gwrcgJHt0aGlzLnN1YmplY3R9YCA6IGxhYmVsO1xuICB9XG5cbiAgLyoqXG4gICAqIE1vdmUgdGhlIGFjdGl2ZSBraW5kIGJldHdlZW4gaXRzIG93biB3aW5kb3cgYW5kIHRoZSBwYW5lbC5cbiAgICpcbiAgICogUHVyZ2VzIHRoZSBkaXYgaXQgaXMgTEVBVklORyBmaXJzdC4gUGxvdGx5IGtlZXBzIHBlci1kaXYgc3RhdGUsIGFuZCBhIGdyYXBoIGxlZnRcbiAgICogYmVoaW5kIGluIGEgZGl2IHRoYXQgQW5ndWxhciB0aGVuIHJlbW92ZXMgbGVha3MgaXRzIFdlYkdMIGNvbnRleHQg4oCUIGFuZCBpZiB0aGUgZGl2XG4gICAqIGNvbWVzIGJhY2ssIHJlYWN0IHdvdWxkIHJlc2l6ZSBhIHBsb3Qgd2hvc2UgZGF0YSBiZWxvbmdzIHRvIHRoZSBvdGhlciBwbGFjZS5cbiAgICovXG4gIHRvZ2dsZURldGFjaGVkKCk6IHZvaWQge1xuICAgIGNvbnN0IGxlYXZpbmcgPSB0aGlzLnBsb3RUYXJnZXQ7XG4gICAgdGhpcy5kZXRhY2hlZCA9ICF0aGlzLmRldGFjaGVkO1xuICAgIHRyeSB7XG4gICAgICBQbG90bHkucHVyZ2UobGVhdmluZyk7XG4gICAgfSBjYXRjaCB7XG4gICAgICAvLyBOb3RoaW5nIHdhcyBwbG90dGVkIHRoZXJlLlxuICAgIH1cbiAgICAvLyBHb2luZyBiYWNrIElOTElORSwgdGhlIGRpdiBhcHBlYXJzIHdpdGggdGhlIG5leHQgY2hhbmdlIGRldGVjdGlvbiwgc28gYSB0YXNrIGlzXG4gICAgLy8gZW5vdWdoLiBHb2luZyBPVVQsIHRoZSB3aW5kb3cncyBkaXYgZG9lcyBub3QgZXhpc3QgeWV0IOKAlCBQcmltZU5HIG1vdW50cyB0aGUgZGlhbG9nXG4gICAgLy8gd2l0aCBhIHRyYW5zaXRpb24g4oCUIHNvIHRoZSBkaWFsb2cncyBvd24gYG9uU2hvd2AgZHJpdmVzIHRoYXQgZHJhdyBpbnN0ZWFkLiBBIHNpbmdsZVxuICAgIC8vIGRlZmVycmVkIGF0dGVtcHQgZHJldyBub3RoaW5nIGFuZCBuZXZlciByZXRyaWVkLlxuICAgIGlmICghdGhpcy5kZXRhY2hlZCkgc2V0VGltZW91dCgoKSA9PiB2b2lkIHRoaXMucmVuZGVyKCksIDApO1xuICB9XG5cbiAgLyoqIFRoZSBkZXRhY2hlZCB3aW5kb3cgaXMgdXAgYW5kIGl0cyBkaXYgZXhpc3RzIOKAlCBub3cgdGhlIHBsb3QgY2FuIGJlIGRyYXduIGludG8gaXQuICovXG4gIG9uRGV0YWNoZWRXaW5kb3dTaG93bigpOiB2b2lkIHtcbiAgICB2b2lkIHRoaXMucmVuZGVyKCk7XG4gIH1cblxuICAvKiogUmUtZml0IHRoZSBkZXRhY2hlZCB3aW5kb3cncyBwbG90IGFmdGVyIGl0IGlzIHJlc2l6ZWQuICovXG4gIG9uRGV0YWNoZWRSZXNpemVFbmQoKTogdm9pZCB7XG4gICAgdGhpcy5yZWZpdCh0aGlzLmRldGFjaGVkRGl2KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZWJ1aWxkIHRoZSB2aXNpYmxlIG9wdGlvbnM6IHRoZSBiZXN0IG1hdGNoZXMgZm9yIHdoYXQgaXMgdHlwZWQsIHBsdXMgd2hhdCBpcyBjaG9zZW4uXG4gICAqXG4gICAqIEEgY2FwcGVkIG11bHRpLXNlbGVjdCB0aGF0IGRyb3BzIGl0cyBvd24gc2VsZWN0aW9ucyBjYW5ub3QgcmVzb2x2ZSB0aGVpciBsYWJlbHMsIGFuZFxuICAgKiB0aGUgbW9kZWwgbG9zZXMgdGhlbSBvbiB0aGUgbmV4dCBjaGFuZ2Ug4oCUIHRoZSBjaGlwcyB2YW5pc2ggZm9yIG5vIHJlYXNvbiB0aGUgdXNlclxuICAgKiBjYW4gc2VlLlxuICAgKi9cbiAgcHJpdmF0ZSByZWZyZXNoR2VuZU9wdGlvbnMoKTogdm9pZCB7XG4gICAgdGhpcy5nZW5lT3B0aW9ucyA9IGdlbmVPcHRpb25zRm9yKHRoaXMuZ2VuZU5hbWVzLCB0aGlzLmdlbmVRdWVyeSwgdGhpcy5oZWF0bWFwR2VuZXMpXG4gICAgICAubWFwKChuKSA9PiAoeyBsYWJlbDogbiwgdmFsdWU6IG4gfSkpO1xuICB9XG5cbiAgLyoqIFRoZSBnZW5lIHBpY2tlcidzIGZpbHRlciBib3ggY2hhbmdlZDogc2VhcmNoIHRoZSByZXNpZGVudCBuYW1lcywgc2hvdyB0aGUgYmVzdC4gKi9cbiAgb25HZW5lRmlsdGVyKHF1ZXJ5OiBzdHJpbmcpOiB2b2lkIHtcbiAgICB0aGlzLmdlbmVRdWVyeSA9IHF1ZXJ5ID8/ICcnO1xuICAgIHRoaXMucmVmcmVzaEdlbmVPcHRpb25zKCk7XG4gIH1cblxuICAvKipcbiAgICogUm91Z2hseSBob3cgbG9uZyBhIHJ1biB3aWxsIHRha2UsIGluIHNlY29uZHMsIGZyb20gdGhlIG1lYXN1cmVkIGFuY2hvci5cbiAgICpcbiAgICogU2hvd24gYmVmb3JlIHRoZSBjbGljay4gQSBwcm9ncmVzcyBiYXIgdGVsbHMgeW91IGEgam9iIGlzIGdvaW5nOyBvbmx5IGFuIGVzdGltYXRlXG4gICAqIHRlbGxzIHlvdSB3aGV0aGVyIHRvIHN0YXJ0IGl0LlxuICAgKi9cbiAgZ2V0IGNvbXB1dGVFc3RpbWF0ZVNlY29uZHMoKTogbnVtYmVyIHtcbiAgICBjb25zdCBuID0gdGhpcy5vYnNlcnZhdGlvbkNvdW50O1xuICAgIGNvbnN0IHsgc2Vjb25kcywgb2JzZXJ2YXRpb25zIH0gPSBTcGF0aWFsQ2hhcnRzQ29tcG9uZW50LlRTTkVfU0VDT05EU19BVDtcbiAgICByZXR1cm4gTWF0aC5tYXgoMSwgTWF0aC5yb3VuZChzZWNvbmRzICogKG4gLyBvYnNlcnZhdGlvbnMpICoqIDIpKTtcbiAgfVxuXG4gIC8qKiBUaGF0IGVzdGltYXRlIGFzIHNvbWV0aGluZyB0byBwdXQgb24gYSBidXR0b24uICovXG4gIGdldCBjb21wdXRlRXN0aW1hdGVMYWJlbCgpOiBzdHJpbmcge1xuICAgIGNvbnN0IHNlY3MgPSB0aGlzLmNvbXB1dGVFc3RpbWF0ZVNlY29uZHM7XG4gICAgaWYgKHNlY3MgPCA5MCkgcmV0dXJuIGB+JHtzZWNzfXNgO1xuICAgIHJldHVybiBgfiR7TWF0aC5yb3VuZChzZWNzIC8gNjApfSBtaW5gO1xuICB9XG5cbiAgLyoqIFdoeSB0aGUgb3B0aW9uIGlzIGFic2VudCwgd2hlbiBhIGRhdGFzZXQgaXMgcGFzdCB0aGUgdGhyZXNob2xkLiAqL1xuICBnZXQgdHNuZVRvb0xhcmdlTm90ZSgpOiBzdHJpbmcge1xuICAgIGNvbnN0IG4gPSB0aGlzLm9ic2VydmF0aW9uQ291bnQ7XG4gICAgcmV0dXJuIGB0LVNORSBpcyBub3Qgb2ZmZXJlZCBoZXJlIGZvciAke24udG9Mb2NhbGVTdHJpbmcoKX0gb2JzZXJ2YXRpb25zIOKAlCBpdCBpcyBgXG4gICAgICArIGBxdWFkcmF0aWMsIHNvIGl0IHdvdWxkIHRha2Ugcm91Z2hseSAke3RoaXMuY29tcHV0ZUVzdGltYXRlTGFiZWx9IGluIHRoZSBicm93c2VyLiBgXG4gICAgICArICdDb21wdXRlIGl0IG9mZmxpbmUgYW5kIHNlcnZlIGl0IHdpdGggdGhlIGRhdGFzZXQuJztcbiAgfVxuXG4gIC8qKiBXaGV0aGVyIHRoZSBzZWxlY3RlZCBlbWJlZGRpbmcgaXMgb25lIHRoaXMgYnJvd3NlciB3b3VsZCBoYXZlIHRvIGNvbXB1dGUuICovXG4gIGdldCBpc0NvbXB1dGFibGUoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuICEhdGhpcy5lbWJlZGRpbmcgJiYgdGhpcy5lbWJlZGRpbmcubmFtZS5zdGFydHNXaXRoKCdsb2NhbDonKTtcbiAgfVxuXG4gIC8qKiBXaGV0aGVyIGl0IGhhcyBhbHJlYWR5IGJlZW4gY29tcHV0ZWQgaW4gdGhpcyBzZXNzaW9uLiAqL1xuICBnZXQgaXNDb21wdXRlZCgpOiBib29sZWFuIHtcbiAgICByZXR1cm4gISF0aGlzLmVtYmVkZGluZyAmJiB0aGlzLmNvbXB1dGVkLmhhcyh0aGlzLmVtYmVkZGluZy5uYW1lKTtcbiAgfVxuXG4gIGdldCBpc0NvbXB1dGluZygpOiBib29sZWFuIHtcbiAgICByZXR1cm4gISF0aGlzLmNvbXB1dGVSdW4/LnJ1bm5pbmc7XG4gIH1cblxuICAvKipcbiAgICogQ29tcHV0ZSB0aGUgc2VsZWN0ZWQgZW1iZWRkaW5nIGhlcmUsIGluIGEgd29ya2VyLlxuICAgKlxuICAgKiBUaGUgUENBIHNjb3JlcyBjb21lIGZyb20gdGhlIHBvcnQgYXMgYW4gb3JkaW5hcnkgZW1iZWRkaW5nIOKAlCB0aGUgc2VydmVyIGRlcml2ZXMgdGhlbSxcbiAgICogYmVjYXVzZSBQQ0EgbmVlZHMgdGhlIHdob2xlIGV4cHJlc3Npb24gbWF0cml4ICgxODUgTUIgZm9yIGEgVmlzaXVtIGRhdGFzZXQpIHdoaWxlXG4gICAqIHQtU05FIG5lZWRzIG9ubHkgdGhlIHNjb3JlcyAoMC41MSBNQikuIFNlbmRpbmcgdGhlIG1hdHJpeCB0byB0aGUgYnJvd3NlciB0byBzYXZlIGFcbiAgICogZmV3IHNlY29uZHMgb2YgYXJpdGhtZXRpYyB3b3VsZCBiZSBhIGZhciBzbG93ZXIgYW5zd2VyLlxuICAgKi9cbiAgYXN5bmMgY29tcHV0ZUVtYmVkZGluZygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCBtZXRhID0gdGhpcy5lbWJlZGRpbmc7XG4gICAgY29uc3QgY29udHJvbHMgPSB0aGlzLmNvbnRyb2xzO1xuICAgIGlmICghbWV0YSB8fCAhY29udHJvbHM/LmdldEVtYmVkZGluZyB8fCB0aGlzLmlzQ29tcHV0aW5nKSByZXR1cm47XG4gICAgdGhpcy5jb21wdXRlRXJyb3IgPSBudWxsO1xuICAgIHRoaXMuY29tcHV0ZU1lc3NhZ2UgPSBudWxsO1xuICAgIHRoaXMuY29tcHV0ZUZyYWN0aW9uID0gbnVsbDtcbiAgICB0aGlzLmNvbXB1dGVCYWNrZW5kID0gbnVsbDtcblxuICAgIC8vIEV2ZXJ5IGVhcmx5IHJldHVybiBhbmQgZXZlcnkgYXdhaXQgYmVsb3cgaXMgY2hlY2tlZCBhZ2FpbnN0IHRoaXMuIGBydW5gIGlzIGhlbGRcbiAgICAvLyBsb2NhbGx5IGFzIHdlbGwgYXMgb24gdGhlIGluc3RhbmNlOiBhZnRlciBhbiBhd2FpdCBgdGhpcy5jb21wdXRlUnVuYCBtYXkgYWxyZWFkeSBiZVxuICAgIC8vIGEgbmV3ZXIgcnVuJ3MsIGFuZCBjbGVhcmluZyBvciB0ZXJtaW5hdGluZyB0aGF0IG9uZSBpcyBob3cgYSBmcmVzaCBjb21wdXRhdGlvbiBnZXRzXG4gICAgLy8ga2lsbGVkIGJ5IHRoZSB0YWlsIG9mIHRoZSBvbmUgaXQgcmVwbGFjZWQuXG4gICAgY29uc3QgbWluZSA9ICsrdGhpcy5jb21wdXRlVG9rZW47XG4gICAgY29uc3Qgc3VwZXJzZWRlZCA9ICgpID0+IG1pbmUgIT09IHRoaXMuY29tcHV0ZVRva2VuO1xuICAgIGxldCBydW46IEVtYmVkZGluZ0NvbXB1dGVSdW4gfCBudWxsID0gbnVsbDtcblxuICAgIHRyeSB7XG4gICAgICAvLyBQcmVmZXIgdGhlIDMtRCBzY29yZXM6IHRoZXkgY2FycnkgYSB0aGlyZCBjb21wb25lbnQgZm9yIGZyZWUsIGFuZCB0LVNORSBvbiBtb3JlXG4gICAgICAvLyBjb21wb25lbnRzIHRoYW4gaXQgbmVlZHMgaXMgbm90IGJldHRlciDigJQgdGhlIFBDQSBiYXNpcyBpcyB0aGUgaW5wdXQgZWl0aGVyIHdheS5cbiAgICAgIGNvbnN0IHNvdXJjZSA9IGF3YWl0IHRoaXMubG9hZFBjYVNjb3Jlcyhjb250cm9scyk7XG4gICAgICBpZiAoc3VwZXJzZWRlZCgpKSByZXR1cm47XG4gICAgICBpZiAoIXNvdXJjZSkge1xuICAgICAgICB0aGlzLmNvbXB1dGVFcnJvciA9ICdUaGlzIGRhdGFzZXQgc2VydmVzIG5vIFBDQSB0byBlbWJlZC4nO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBydW4gPSBuZXcgRW1iZWRkaW5nQ29tcHV0ZVJ1bigpO1xuICAgICAgdGhpcy5jb21wdXRlUnVuID0gcnVuO1xuICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgcnVuLnJ1bihcbiAgICAgICAge1xuICAgICAgICAgIHNjb3Jlczogc291cmNlLnNjb3JlcyxcbiAgICAgICAgICBuT2JzOiBzb3VyY2Uubk9icyxcbiAgICAgICAgICBuRGltczogc291cmNlLm5EaW1zLFxuICAgICAgICAgIGRpbXM6IG1ldGEuZGltcyxcbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIC4uLm1ldGEsXG4gICAgICAgICAgbGFiZWw6IChtZXRhLmxhYmVsID8/IG1ldGEubmFtZSlcbiAgICAgICAgICAgIC5yZXBsYWNlKFNwYXRpYWxDaGFydHNDb21wb25lbnQuQ09NUFVURV9TVUZGSVgsICcnKSxcbiAgICAgICAgfSxcbiAgICAgICAgKHByb2dyZXNzOiBDb21wdXRlUHJvZ3Jlc3MpID0+IHtcbiAgICAgICAgICAvLyBQcm9ncmVzcyBmcm9tIGEgc3VwZXJzZWRlZCBydW4gbXVzdCBub3QgZHJpdmUgdGhlIGJhciBhIG5ld2VyIG9uZSBpcyB1c2luZy5cbiAgICAgICAgICBpZiAoc3VwZXJzZWRlZCgpKSByZXR1cm47XG4gICAgICAgICAgdGhpcy5jb21wdXRlRnJhY3Rpb24gPSBwcm9ncmVzcy5mcmFjdGlvbjtcbiAgICAgICAgICB0aGlzLmNvbXB1dGVCYWNrZW5kID0gcHJvZ3Jlc3MuYmFja2VuZCA/PyB0aGlzLmNvbXB1dGVCYWNrZW5kO1xuICAgICAgICAgIGlmIChwcm9ncmVzcy5tZXNzYWdlKSB0aGlzLmNvbXB1dGVNZXNzYWdlID0gcHJvZ3Jlc3MubWVzc2FnZTtcbiAgICAgICAgfSxcbiAgICAgICk7XG4gICAgICAvLyBBIHRlcm1pbmF0ZWQgcnVuIHJlc29sdmVzIG51bGwsIHNvIHRoaXMgY292ZXJzIGFiYW5kb25tZW50IGFzIHdlbGwgYXMgQ2FuY2VsIOKAlFxuICAgICAgLy8gYnV0IHRoZSB0b2tlbiBpcyBzdGlsbCBjaGVja2VkLCBiZWNhdXNlIHRoZSBkYXRhc2V0IGNhbiBjaGFuZ2UgYmV0d2VlbiB0aGVcbiAgICAgIC8vIHdvcmtlcidzIGBkb25lYCBhbmQgdGhpcyBsaW5lLlxuICAgICAgaWYgKHJlc3VsdCAmJiAhc3VwZXJzZWRlZCgpKSB7XG4gICAgICAgIHRoaXMuY29tcHV0ZWQuc2V0KG1ldGEubmFtZSwgcmVzdWx0KTtcbiAgICAgICAgdGhpcy5lbWJlZGRpbmdDb29yZHMgPSByZXN1bHQ7XG4gICAgICAgIGF3YWl0IHRoaXMucmVuZGVyKCk7XG4gICAgICB9XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBpZiAoIXN1cGVyc2VkZWQoKSkgdGhpcy5jb21wdXRlRXJyb3IgPSAoZXJyIGFzIEVycm9yKT8ubWVzc2FnZSA/PyBTdHJpbmcoZXJyKTtcbiAgICB9IGZpbmFsbHkge1xuICAgICAgcnVuPy50ZXJtaW5hdGUoKTtcbiAgICAgIC8vIE9ubHkgaWYgaXQgaXMgc3RpbGwgb3VyczogYSBkYXRhc2V0IHN3aXRjaCBoYXMgYWxyZWFkeSB0ZXJtaW5hdGVkIHRoaXMgcnVuIGFuZFxuICAgICAgLy8gbWF5IGhhdmUgc3RhcnRlZCBhbm90aGVyLCBhbmQgYHRoaXMuY29tcHV0ZVJ1biA9IG51bGxgIGhlcmUgd291bGQgb3JwaGFuIGl0IOKAlFxuICAgICAgLy8gbGVhdmluZyBhIHdvcmtlciBub3RoaW5nIGNhbiBjYW5jZWwgYW5kIGEgQ2FuY2VsIGJ1dHRvbiB0aGF0IGRvZXMgbm90aGluZy5cbiAgICAgIGlmICh0aGlzLmNvbXB1dGVSdW4gPT09IHJ1bikge1xuICAgICAgICB0aGlzLmNvbXB1dGVSdW4gPSBudWxsO1xuICAgICAgICB0aGlzLmNvbXB1dGVGcmFjdGlvbiA9IG51bGw7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqIFN0b3AgYSBydW4uIEl0IHNldHRsZXMgc2hvcnRseSBhZnRlciwgbGVhdmluZyBubyBlbWJlZGRpbmcuICovXG4gIGNhbmNlbENvbXB1dGUoKTogdm9pZCB7XG4gICAgdGhpcy5jb21wdXRlUnVuPy5jYW5jZWwoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBEcm9wIGFueSBjb21wdXRhdGlvbiBpbiBwcm9ncmVzcywgYmVjYXVzZSB3aGF0IGl0IGlzIGNvbXB1dGluZyBubyBsb25nZXIgYXBwbGllcy5cbiAgICpcbiAgICogRGlzdGluY3QgZnJvbSB7QGxpbmsgY2FuY2VsQ29tcHV0ZX0sIHdoaWNoIGlzIHRoZSB1c2VyJ3MgQ2FuY2VsIGFuZCBhc2tzIHRoZSB3b3JrZXIgdG9cbiAgICogc3RvcCBwb2xpdGVseS4gVGhpcyBvbmUgZG9lcyBub3Qgd2FpdDogdGhlIHJ1biBpcyBzZXR0bGVkIGFuZCB0aGUgd29ya2VyIGtpbGxlZCwgc28gYVxuICAgKiBkYXRhc2V0IHN3aXRjaCBvciBhIHRlYXJkb3duIGNhbm5vdCBsZWF2ZSBhIHQtU05FIGdyaW5kaW5nIG9uIGluIHRoZSBiYWNrZ3JvdW5kIG92ZXJcbiAgICogb2JzZXJ2YXRpb25zIG5vdGhpbmcgaXMgc2hvd2luZyBhbnkgbW9yZS5cbiAgICovXG4gIHByaXZhdGUgYWJhbmRvbkNvbXB1dGUoKTogdm9pZCB7XG4gICAgdGhpcy5jb21wdXRlVG9rZW4rKztcbiAgICB0aGlzLmNvbXB1dGVSdW4/LnRlcm1pbmF0ZSgpO1xuICAgIHRoaXMuY29tcHV0ZVJ1biA9IG51bGw7XG4gICAgdGhpcy5jb21wdXRlRnJhY3Rpb24gPSBudWxsO1xuICAgIHRoaXMuY29tcHV0ZUJhY2tlbmQgPSBudWxsO1xuICAgIHRoaXMuY29tcHV0ZU1lc3NhZ2UgPSBudWxsO1xuICB9XG5cbiAgLyoqXG4gICAqIFRoZSBkYXRhc2V0J3MgUENBLCBhcyByb3ctbWFqb3Igc2NvcmVzLlxuICAgKlxuICAgKiBBc3NlbWJsZWQgZnJvbSB3aGljaGV2ZXIgUENBIHRoZSBzb3VyY2Ugb2ZmZXJzLCB3aWRlc3QgZmlyc3Qg4oCUIGEgMy1EIG9uZSBnaXZlcyB0LVNORVxuICAgKiB0aHJlZSBjb21wb25lbnRzIGluc3RlYWQgb2YgdHdvIGZvciB0aGUgc2FtZSByZXF1ZXN0LlxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBsb2FkUGNhU2NvcmVzKFxuICAgIGNvbnRyb2xzOiBJU3BhdGlhbENvbnRyb2xzLFxuICApOiBQcm9taXNlPHsgc2NvcmVzOiBGbG9hdDMyQXJyYXk7IG5PYnM6IG51bWJlcjsgbkRpbXM6IG51bWJlciB9IHwgbnVsbD4ge1xuICAgIGNvbnN0IGNhbmRpZGF0ZXMgPSB0aGlzLmVtYmVkZGluZ3NcbiAgICAgIC5maWx0ZXIoKGUpID0+IC9wY2EvaS50ZXN0KGUubGFiZWwgPz8gZS5uYW1lKSAmJiAhZS5uYW1lLnN0YXJ0c1dpdGgoJ2xvY2FsOicpKVxuICAgICAgLnNvcnQoKGEsIGIpID0+IGIuZGltcyAtIGEuZGltcyk7XG4gICAgZm9yIChjb25zdCBjYW5kaWRhdGUgb2YgY2FuZGlkYXRlcykge1xuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgcGNhID0gYXdhaXQgY29udHJvbHMuZ2V0RW1iZWRkaW5nIShjYW5kaWRhdGUubmFtZSk7XG4gICAgICAgIGNvbnN0IHBsYW5lcyA9IFtwY2EueCwgcGNhLnksIC4uLihwY2EueiA/IFtwY2Euel0gOiBbXSldO1xuICAgICAgICBjb25zdCBuT2JzID0gcGNhLngubGVuZ3RoO1xuICAgICAgICBjb25zdCBuRGltcyA9IHBsYW5lcy5sZW5ndGg7XG4gICAgICAgIGNvbnN0IHNjb3JlcyA9IG5ldyBGbG9hdDMyQXJyYXkobk9icyAqIG5EaW1zKTtcbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBuT2JzOyBpKyspIHtcbiAgICAgICAgICBmb3IgKGxldCBkID0gMDsgZCA8IG5EaW1zOyBkKyspIHNjb3Jlc1tpICogbkRpbXMgKyBkXSA9IHBsYW5lc1tkXVtpXTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4geyBzY29yZXMsIG5PYnMsIG5EaW1zIH07XG4gICAgICB9IGNhdGNoIHtcbiAgICAgICAgLy8gVHJ5IHRoZSBuZXh0OyBhIHNvdXJjZSBtYXkgYWR2ZXJ0aXNlIG9uZSBpdCBjYW5ub3QgYWN0dWFsbHkgc2VydmUuXG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgLyoqIFdoaWNoIGVtYmVkZGluZyB0byBkcmF3LCB3aGVuIHRoZSBkYXRhc2V0IHB1Ymxpc2hlcyBtb3JlIHRoYW4gb25lLiAqL1xuICBvbkVtYmVkZGluZyhuYW1lOiBzdHJpbmcpOiB2b2lkIHtcbiAgICBjb25zdCBuZXh0ID0gdGhpcy5lbWJlZGRpbmdzLmZpbmQoKGUpID0+IGUubmFtZSA9PT0gbmFtZSk7XG4gICAgaWYgKCFuZXh0IHx8IG5leHQubmFtZSA9PT0gdGhpcy5lbWJlZGRpbmc/Lm5hbWUpIHJldHVybjtcbiAgICB0aGlzLmVtYmVkZGluZyA9IG5leHQ7XG4gICAgdGhpcy5lbWJlZGRpbmdDb29yZHMgPSBudWxsO1xuICAgIHZvaWQgdGhpcy5yZW5kZXIoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBXaGF0IHRoZSBjaGFydCBvbiBzY3JlZW4gYWN0dWFsbHkgc2hvd3MsIGFuZCB3aGF0IGl0IGNhbm5vdCBiZSByZWFkIGZvci5cbiAgICpcbiAgICogU2hvd24gb24gaG92ZXIgZnJvbSBhIGA/YCBiZXNpZGUgdGhlIHRhYnMuIEVhY2ggb2YgdGhlc2UgcGxvdHMgYW5zd2VycyBhIGRpZmZlcmVudFxuICAgKiBxdWVzdGlvbiBhbmQgdHdvIG9mIHRoZW0gYXJlIHJvdXRpbmVseSBvdmVyLXJlYWQg4oCUIGEgVU1BUCdzIGRpc3RhbmNlcyBhbmQgYSBoZWF0bWFwJ3NcbiAgICogdW5zY2FsZWQgY29sb3VycyDigJQgc28gdGhlIGNhdmVhdCBpcyBwYXJ0IG9mIHRoZSBleHBsYW5hdGlvbiByYXRoZXIgdGhhbiBhIGZvb3Rub3RlLlxuICAgKlxuICAgKiBIVE1MLCBiZWNhdXNlIHRoZSB0b29sdGlwIHJlbmRlcnMgd2l0aCBgW2VzY2FwZV09XCJmYWxzZVwiYDogYSBwYXJhZ3JhcGggYW5kIGEgY2F2ZWF0XG4gICAqIHJlYWQgYXMgdHdvIHRob3VnaHRzLCBhbmQgYSBzaW5nbGUgcnVuLW9uIGxpbmUgaXMgc2tpcHBlZCByYXRoZXIgdGhhbiByZWFkLlxuICAgKi9cbiAgZ2V0IGtpbmRIZWxwKCk6IHN0cmluZyB7XG4gICAgc3dpdGNoICh0aGlzLmtpbmQpIHtcbiAgICAgIGNhc2UgJ2NvdW50cyc6XG4gICAgICAgIHJldHVybiAnPGI+Q291bnRzPC9iPiDigJQgaG93IG1hbnkgb2JzZXJ2YXRpb25zIGZhbGwgaW4gZWFjaCBjYXRlZ29yeSBvZiB0aGUgY29sdW1uICdcbiAgICAgICAgICArICd0aGUgbWFwIGlzIGNvbG91cmVkIGJ5LCBsYXJnZXN0IGZpcnN0Ljxicj48YnI+QSBjYXRlZ29yeSBjb2RlIGlzIGEgbGFiZWwsIG5vdCAnXG4gICAgICAgICAgKyAnYSBtYWduaXR1ZGUsIHNvIGEgZnJlcXVlbmN5IGlzIHRoZSBvbmx5IGRpc3RyaWJ1dGlvbiBpdCBoYXM6IHRoZXJlIGlzIG5vICdcbiAgICAgICAgICArICdoaXN0b2dyYW0gb2YgYSBjZWxsIHR5cGUuJztcbiAgICAgIGNhc2UgJ2hpc3RvZ3JhbSc6XG4gICAgICAgIHJldHVybiAnPGI+SGlzdG9ncmFtPC9iPiDigJQgaG93IHRoZSBhY3RpdmUgdmFsdWUgaXMgZGlzdHJpYnV0ZWQgb3ZlciBhbGwgJ1xuICAgICAgICAgICsgJ29ic2VydmF0aW9ucy48YnI+PGJyPldpdGggYSBzZWxlY3Rpb24sIGl0IGlzIG92ZXJsYWlkIG9uIHRoZSBmdWxsICdcbiAgICAgICAgICArICdkaXN0cmlidXRpb24gcmF0aGVyIHRoYW4gcmVwbGFjaW5nIGl0LCBzbyB5b3UgY2FuIHNlZSB3aGVyZSB0aGUgc2VsZWN0ZWQgJ1xuICAgICAgICAgICsgJ2NlbGxzIHNpdCB3aXRoaW4gdGhlIHdob2xlLic7XG4gICAgICBjYXNlICd2aW9saW4nOlxuICAgICAgICByZXR1cm4gJzxiPlZpb2xpbjwvYj4g4oCUIHRoZSBhY3RpdmUgdmFsdWVcXCdzIGRpc3RyaWJ1dGlvbiB3aXRoaW4gZWFjaCBjYXRlZ29yeSBvZiAnXG4gICAgICAgICAgKyAndGhlIGdyb3VwaW5nIGNvbHVtbiwgZHJhd24gYXMgYSBzbW9vdGhlZCBkZW5zaXR5Ljxicj48YnI+U2hvd3Mgc2hhcGUgYSBib3ggJ1xuICAgICAgICAgICsgJ3Bsb3QgaGlkZXM6IHR3byBncm91cHMgd2l0aCB0aGUgc2FtZSBtZWRpYW4gY2FuIGJlIG9uZSBwZWFrIG9yIHR3by4nO1xuICAgICAgY2FzZSAnYm94JzpcbiAgICAgICAgcmV0dXJuICc8Yj5Cb3g8L2I+IOKAlCBtZWRpYW4sIHF1YXJ0aWxlcyBhbmQgcmFuZ2Ugb2YgdGhlIGFjdGl2ZSB2YWx1ZSB3aXRoaW4gZWFjaCAnXG4gICAgICAgICAgKyAnY2F0ZWdvcnkgb2YgdGhlIGdyb3VwaW5nIGNvbHVtbi48YnI+PGJyPkNvbXBhY3QgYW5kIGNvbXBhcmFibGUgYWNyb3NzIG1hbnkgJ1xuICAgICAgICAgICsgJ2dyb3VwcywgYXQgdGhlIGNvc3Qgb2YgaGlkaW5nIHdoZXRoZXIgYSBncm91cCBpcyBiaW1vZGFsLic7XG4gICAgICBjYXNlICdoZWF0bWFwJzpcbiAgICAgICAgcmV0dXJuICc8Yj5IZWF0bWFwPC9iPiDigJQgbWVhbiBleHByZXNzaW9uIG9mIGVhY2ggcGlja2VkIGdlbmUgd2l0aGluIGVhY2ggZ3JvdXA6ICdcbiAgICAgICAgICArICdnZW5lcyBkb3duLCBncm91cHMgYWNyb3NzLjxicj48YnI+RWFjaCBnZW5lIGlzIDxiPnotc2NvcmVkIGFjcm9zcyB0aGUgJ1xuICAgICAgICAgICsgJ2dyb3VwczwvYj4gYnkgZGVmYXVsdCwgc28gYSBjb2xvdXIgc2F5cyBcImFib3ZlIG9yIGJlbG93IHRoaXMgZ2VuZVxcJ3Mgb3duICdcbiAgICAgICAgICArICdhdmVyYWdlXCIsIG5vdCBcImhpZ2hseSBleHByZXNzZWRcIi4gV2l0aG91dCB0aGF0IG9uZSBsb3VkIGdlbmUgc2F0dXJhdGVzIHRoZSAnXG4gICAgICAgICAgKyAnc2NhbGUgYW5kIHRoZSByZXN0IG9mIHRoZSBwYW5lbCByZWFkcyBhcyBibGFuay4gVHVybmluZyBpdCBvZmYgY29tcGFyZXMgJ1xuICAgICAgICAgICsgJ2dlbmVzIG9uIHRoZWlyIHJhdyBzY2FsZSBpbnN0ZWFkLic7XG4gICAgICBjYXNlICdlbWJlZGRpbmcnOlxuICAgICAgICByZXR1cm4gdGhpcy5lbWJlZGRpbmdIZWxwO1xuICAgICAgZGVmYXVsdDpcbiAgICAgICAgcmV0dXJuICcnO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBXaGF0IHRoZSBlbWJlZGRpbmcgb24gc2NyZWVuIGlzLCBhbmQgaG93IGZhciBpdHMgZ2VvbWV0cnkgY2FuIGJlIHRydXN0ZWQuXG4gICAqXG4gICAqIFBlciBNRVRIT0QsIGJlY2F1c2UgdGhhdCBpcyB0aGUgcGFydCBwZW9wbGUgZ2V0IHdyb25nOiBhIFBDQSdzIGF4ZXMgYXJlIG9yZGVyZWQgYW5kXG4gICAqIG1lYXN1cmFibGUgd2hpbGUgYSBVTUFQJ3MgYXJlIG5laXRoZXIsIGFuZCB0aGUgc2FtZSBwaWN0dXJlIHJlYWQgdGhlIHR3byB3YXlzIHN1cHBvcnRzXG4gICAqIG9wcG9zaXRlIGNvbmNsdXNpb25zLlxuICAgKi9cbiAgcHJpdmF0ZSBnZXQgZW1iZWRkaW5nSGVscCgpOiBzdHJpbmcge1xuICAgIGNvbnN0IHNoYXJlZCA9ICc8YnI+PGJyPkV2ZXJ5IGNlbGwgaXMgaW4gYWxsIHZpZXdzIGF0IG9uY2U6IGxhc3NvIGEgZ3JvdXAgaGVyZSBhbmQgJ1xuICAgICAgKyAndGhvc2UgY2VsbHMgbGlnaHQgdXAgb24gdGhlIHRpc3N1ZSwgYmVjYXVzZSBib3RoIHJlYWQgdGhlIHNhbWUgc2VsZWN0aW9uLic7XG4gICAgY29uc3QgbmFtZSA9ICh0aGlzLmVtYmVkZGluZz8ubGFiZWwgPz8gdGhpcy5lbWJlZGRpbmc/Lm5hbWUgPz8gJycpLnRvTG93ZXJDYXNlKCk7XG4gICAgaWYgKG5hbWUuaW5jbHVkZXMoJ3BjYScpKSB7XG4gICAgICByZXR1cm4gJzxiPlBDQTwvYj4g4oCUIGEgPGI+bGluZWFyPC9iPiBwcm9qZWN0aW9uIG9udG8gdGhlIGRpcmVjdGlvbnMgb2YgZ3JlYXRlc3QgJ1xuICAgICAgICArICd2YXJpYW5jZSwgaW4gb3JkZXIuPGJyPjxicj5BbG9uZSBhbW9uZyB0aGVzZSwgaXRzIGF4ZXMgbWVhbiBzb21ldGhpbmcgJ1xuICAgICAgICArICdtZWFzdXJhYmxlOiBlYWNoIHJlcG9ydHMgdGhlIHNoYXJlIG9mIHRvdGFsIHZhcmlhbmNlIGl0IGV4cGxhaW5zLCB3aGljaCBpcyB3aHkgJ1xuICAgICAgICArICd0aGUgbGFiZWxzIGNhcnJ5IGEgcGVyY2VudGFnZS4gRGlzdGFuY2VzIGFyZSByZWFsLCBhbmQgYSBsb3cgcGVyY2VudGFnZSB0ZWxscyAnXG4gICAgICAgICsgJ3lvdSB0aGUgcGljdHVyZSBpcyBhIHRoaW4gc2xpY2Ugb2YgdGhlIHZhcmlhdGlvbi4nICsgc2hhcmVkO1xuICAgIH1cbiAgICBpZiAobmFtZS5pbmNsdWRlcygndC1zbmUnKSB8fCBuYW1lLmluY2x1ZGVzKCd0c25lJykpIHtcbiAgICAgIHJldHVybiAnPGI+dC1TTkU8L2I+IOKAlCBjZWxscyBwbGFjZWQgc28gdGhhdCBjbG9zZSBuZWlnaGJvdXJzIGluIGV4cHJlc3Npb24gc3RheSAnXG4gICAgICAgICsgJ2Nsb3NlLjxicj48YnI+U3RyaWN0ZXIgYWJvdXQgbG9jYWwgbmVpZ2hib3VyaG9vZHMgdGhhbiBVTUFQIGFuZCBsZXNzICdcbiAgICAgICAgKyAndHJ1c3R3b3J0aHkgYWJvdXQgYW55dGhpbmcgZ2xvYmFsOiBpdCB0ZW5kcyB0byBzcHJlYWQgY2x1c3RlcnMgaW50byAnXG4gICAgICAgICsgJ2V2ZW5seS1zaXplZCBpc2xhbmRzIHdob3NlIHNpemVzIGFuZCBzZXBhcmF0aW9ucyBtZWFuIGxpdHRsZS4gUmVhZCB3aGljaCBjZWxscyAnXG4gICAgICAgICsgJ2dyb3VwIHRvZ2V0aGVyLCBub3QgaG93IGZhciBhcGFydCB0aGUgZ3JvdXBzIGFyZS4nICsgc2hhcmVkO1xuICAgIH1cbiAgICByZXR1cm4gJzxiPlVNQVA8L2I+IOKAlCBjZWxscyBwbGFjZWQgc28gdGhhdCBjbG9zZSBuZWlnaGJvdXJzIGluIGV4cHJlc3Npb24gc3RheSAnXG4gICAgICArICdjbG9zZS48YnI+PGJyPlRoZSBheGVzIGFyZSBhcmJpdHJhcnk6IHVub3JkZXJlZCwgdW5pdGxlc3MsIGFuZCByZXByb2R1Y2libGUgb25seSAnXG4gICAgICArICd1cCB0byBhIHJvdGF0aW9uLCB3aGljaCBpcyB3aHkgdGhleSBjYXJyeSBubyBwZXJjZW50YWdlLiBSZWFkIHdoaWNoIGNlbGxzIGdyb3VwICdcbiAgICAgICsgJ3RvZ2V0aGVyIGFuZCB3aGljaCBncm91cHMgdG91Y2g7IGRvIG5vdCByZWFkIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIGRpc3RhbnQgJ1xuICAgICAgKyAnY2x1c3RlcnMsIG9yIHRoZSBkaXJlY3Rpb24gb2YgYW4gYXhpcy4nICsgc2hhcmVkO1xuICB9XG5cbiAgLyoqIFdoYXQgdGhlIGVtYmVkZGluZyB2aWV3IGlzIHNob3dpbmcsIHNhaWQgcGxhaW5seS4gKi9cbiAgZ2V0IGVtYmVkZGluZ05vdGUoKTogc3RyaW5nIHtcbiAgICBjb25zdCBtZXRhID0gdGhpcy5lbWJlZGRpbmdDb29yZHM/Lm1ldGEgPz8gdGhpcy5lbWJlZGRpbmc7XG4gICAgaWYgKCFtZXRhKSByZXR1cm4gJ1RoaXMgZGF0YXNldCBwdWJsaXNoZXMgbm8gZW1iZWRkaW5nLic7XG4gICAgLy8gQSBkZXJpdmVkIGVtYmVkZGluZyBzYXlzIHNvLCBhbmQgc2F5cyBIT1cgd2hlbiB0aGUgcGFyYW1ldGVycyBhcmUga25vd246IGEgdC1TTkUgb3JcbiAgICAvLyBVTUFQIGF0IGRpZmZlcmVudCBzZXR0aW5ncyBpcyBhIGRpZmZlcmVudCBwaWN0dXJlIG9mIHRoZSBzYW1lIGNlbGxzLCBzbyBcImNvbXB1dGVkXG4gICAgLy8gaGVyZVwiIGFsb25lIGxlYXZlcyBhIHJlYWRlciB1bmFibGUgdG8gcmVwcm9kdWNlIG9yIGNvbXBhcmUgaXQuXG4gICAgY29uc3QgZGVyaXZlZCA9IG1ldGEuZGVyaXZlZFxuICAgICAgPyBgIENvbXB1dGVkIGhlcmUke21ldGEucGFyYW1zID8gYCAoJHttZXRhLnBhcmFtc30pYCA6ICcnfSwgYFxuICAgICAgICArICdub3QgcHVibGlzaGVkIHdpdGggdGhlIGRhdGFzZXQuJ1xuICAgICAgOiAnJztcbiAgICBjb25zdCBjb2xvdXJlZCA9IHRoaXMuY2F0ZWdvcmljYWxcbiAgICAgID8gJyBDb2xvdXJlZCB0byBtYXRjaCB0aGUgbWFwLidcbiAgICAgIDogJyBDb2xvdXIgdGhlIG1hcCBieSBhIGNhdGVnb3JpY2FsIGNvbHVtbiB0byBjb2xvdXIgdGhlc2UgcG9pbnRzLic7XG4gICAgY29uc3Qgc2VsID0gdGhpcy5zZWxlY3Rpb24uY291bnQgPiAwXG4gICAgICA/IGAgJHt0aGlzLnNlbGVjdGlvbi5jb3VudC50b0xvY2FsZVN0cmluZygpfSBzZWxlY3RlZCBhcmUgaGlnaGxpZ2h0ZWQuYFxuICAgICAgOiAnJztcbiAgICByZXR1cm4gYCR7bWV0YS5sYWJlbCA/PyBtZXRhLm5hbWV9LiR7Y29sb3VyZWR9JHtzZWx9JHtkZXJpdmVkfWA7XG4gIH1cblxuICBwcml2YXRlIHB1cmdlUGxvdCgpOiB2b2lkIHtcbiAgICB0cnkge1xuICAgICAgLy8gVGhlIEFDVElWRSB0YXJnZXQ6IHB1cmdpbmcgdGhlIGlubGluZSBkaXYgd2hpbGUgZGV0YWNoZWQgd291bGQgbGVhdmUgdGhlXG4gICAgICAvLyB3aW5kb3cgc2hvd2luZyBhIHBsb3QgdGhlIGNvbXBvbmVudCBiZWxpZXZlcyBpdCBoYXMgY2xlYXJlZC5cbiAgICAgIFBsb3RseS5wdXJnZSh0aGlzLnBsb3RUYXJnZXQpO1xuICAgIH0gY2F0Y2gge1xuICAgICAgLy8gTm90aGluZyBwbG90dGVkLlxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBEcmF3LCByZW1lbWJlcmluZyB3aGV0aGVyIHRoaXMgbGF5b3V0IGZpeGVkIGl0cyBvd24gaGVpZ2h0LlxuICAgKlxuICAgKiBUaGUgbGF5b3V0IGJ1aWxkZXJzIHJldHVybiBgdW5rbm93bmAgb24gcHVycG9zZSDigJQgdGhleSBrZWVwIFBsb3RseSdzIGxheW91dFxuICAgKiB0eXBlcyBvdXQgb2YgdGhlIHB1cmUgbW9kdWxlIOKAlCBzbyB0aGUgaGVpZ2h0IGlzIHJlYWQgYmFjayBieSBuYXJyb3dpbmdcbiAgICogcmF0aGVyIHRoYW4gZGVjbGFyZWQuIGBoZWlnaHRgIGlzIFBsb3RseSdzIG93biBrZXksIG5vdCBvdXJzLlxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBkcmF3KFxuICAgIHRyYWNlczogdW5rbm93biwgbGF5b3V0OiB1bmtub3duLCBjb25maWc6IHVua25vd24gPSBDSEFSVF9DT05GSUcsIGRpdiA9IHRoaXMucGxvdFRhcmdldCxcbiAgKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgaGVpZ2h0ID0gKGxheW91dCBhcyB7IGhlaWdodD86IHVua25vd24gfSB8IG51bGwpPy5oZWlnaHQ7XG4gICAgdGhpcy5kcmF3bkhlaWdodCA9IHR5cGVvZiBoZWlnaHQgPT09ICdudW1iZXInID8gaGVpZ2h0IDogbnVsbDtcbiAgICBhd2FpdCBQbG90bHkucmVhY3QoZGl2LCB0cmFjZXMgYXMgbmV2ZXIsIGxheW91dCBhcyBuZXZlciwgY29uZmlnIGFzIG5ldmVyKTtcbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgcmVuZGVyKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGlmICghdGhpcy5pc0FjdGl2ZSkgcmV0dXJuO1xuICAgIC8vIEd1YXJkIG9uIHRoZSBkaXYgdGhlIEFDVElWRSBraW5kIHdpbGwgZHJhdyBpbnRvLCBub3QgYWx3YXlzIHRoZSBzaGFyZWQgb25lLiBXaGlsZSBhXG4gICAgLy8ga2luZCBpcyBkZXRhY2hlZCB0aGUgaW5saW5lIGRpdiBpcyByZW1vdmVkIGJ5IGl0cyBgKm5nSWZgLCBzbyBjaGVja2luZyB0aGF0IG9uZVxuICAgIC8vIGJhaWxlZCBoZXJlIGFuZCB0aGUgZGV0YWNoZWQgd2luZG93IG5ldmVyIGdvdCBhIHBsb3QuXG4gICAgY29uc3QgdGFyZ2V0ID0gdGhpcy5wbG90VGFyZ2V0O1xuICAgIGlmICghZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQodGFyZ2V0KSkgcmV0dXJuO1xuICAgIC8vIEFueXRoaW5nIGJ1dCB0aGUgZW1iZWRkaW5nIGluaGVyaXRzIGEgZGl2IHRoZSBlbWJlZGRpbmcgbWF5IGhhdmUgYm91bmQgaGFuZGxlcnMgb24uXG4gICAgaWYgKHRoaXMua2luZCAhPT0gJ2VtYmVkZGluZycpIHRoaXMudW5iaW5kRW1iZWRkaW5nU2VsZWN0aW9uKHRhcmdldCk7XG4gICAgLy8gVGhlIGhlYXRtYXAgYW5zd2VycyBhIGRpZmZlcmVudCBxdWVzdGlvbiBmcm9tIHRoZSBvdGhlciBraW5kcyDigJQgd2hpY2hcbiAgICAvLyBnZW5lcyBkaXN0aW5ndWlzaCB3aGljaCBncm91cHMg4oCUIHNvIGl0IGlzIGRyaXZlbiBieSBpdHMgb3duIGdlbmUgbGlzdCBhbmRcbiAgICAvLyBncm91cGluZyByYXRoZXIgdGhhbiBieSB3aGF0ZXZlciB0aGUgbWFwIGlzIGNvbG91cmVkIGJ5LlxuICAgIGlmICh0aGlzLmtpbmQgPT09ICdoZWF0bWFwJykge1xuICAgICAgYXdhaXQgdGhpcy5yZW5kZXJIZWF0bWFwKCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIC8vIEFsc28gaW5kZXBlbmRlbnQgb2YgdGhlIGFjdGl2ZSBjb2xvdXIgc291cmNlOiB0aGUgY29vcmRpbmF0ZXMgYXJlIHRoZSBkYXRhc2V0J3MsXG4gICAgLy8gYW5kIHRoZSBjb2xvdXJpbmcgbWVyZWx5IGZvbGxvd3Mgd2hhdGV2ZXIgdGhlIG1hcCBpcyB1c2luZy5cbiAgICBpZiAodGhpcy5raW5kID09PSAnZW1iZWRkaW5nJykge1xuICAgICAgYXdhaXQgdGhpcy5yZW5kZXJFbWJlZGRpbmcoKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKCF0aGlzLmNvbG9yQnkgfHwgKCF0aGlzLnZhbHVlcyAmJiAhdGhpcy5jYXRlZ29yaWNhbCkpIHtcbiAgICAgIHRoaXMucHVyZ2VQbG90KCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmICh0aGlzLmNhdGVnb3JpY2FsKSB7XG4gICAgICBjb25zdCBjb3VudHMgPSB7XG4gICAgICAgIGdyb3VwOiB0aGlzLmNhdGVnb3JpY2FsLFxuICAgICAgICBzZWxlY3Rpb246IHRoaXMuc2VsZWN0aW9uLmNvdW50ID4gMCA/IHRoaXMuc2VsZWN0aW9uLm1hc2sgOiBudWxsLFxuICAgICAgICBuYW1lOiB0aGlzLmNvbG9yQnkubmFtZSxcbiAgICAgIH07XG4gICAgICBhd2FpdCB0aGlzLmRyYXcoYnVpbGRDb3VudFRyYWNlcyhjb3VudHMpLCBjb3VudHNMYXlvdXQoY291bnRzKSk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmICghdGhpcy52YWx1ZXMpIHJldHVybjtcbiAgICBjb25zdCBpbnB1dCA9IHtcbiAgICAgIHZhbHVlczogdGhpcy52YWx1ZXMsXG4gICAgICBuYW1lOiB0aGlzLmNvbG9yQnkua2luZCA9PT0gJ2ZlYXR1cmUnID8gdGhpcy5jb2xvckJ5Lm5hbWUgOiB0aGlzLmNvbG9yQnkubmFtZSxcbiAgICAgIGdyb3VwOiBiZW5lZml0c0Zyb21Hcm91cGluZyh0aGlzLmtpbmQpID8gdGhpcy5ncm91cGluZyA6IG51bGwsXG4gICAgICBzZWxlY3Rpb246IHRoaXMuc2VsZWN0aW9uLmNvdW50ID4gMCA/IHRoaXMuc2VsZWN0aW9uLm1hc2sgOiBudWxsLFxuICAgICAgbG9nOiB0aGlzLnZpZXcubG9nU2NhbGUsXG4gICAgfTtcbiAgICBjb25zdCB0cmFjZXMgPSBidWlsZE9taWNzVHJhY2VzKHRoaXMua2luZCwgaW5wdXQpO1xuICAgIGF3YWl0IHRoaXMuZHJhdyh0cmFjZXMsIG9taWNzTGF5b3V0KHRoaXMua2luZCwgaW5wdXQpKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZW5lcyDDlyBncm91cHMgbWVhbiBleHByZXNzaW9uLlxuICAgKlxuICAgKiBDb2x1bW5zIGFyZSB0aGUgZ3JvdXBpbmcgY29sdW1uJ3MgY2F0ZWdvcmllcyDigJQgb3IsIGluc2lkZSBhIFNNQUxMXG4gICAqIHNlbGVjdGlvbiwgdGhlIHNlbGVjdGVkIGNlbGxzIHRoZW1zZWx2ZXMuIEEgdHdvLWNvbHVtbiBjbGFzcyBtYXRyaXggaXMgYVxuICAgKiBwb29yIGFuc3dlciBmb3IgYW4gUk9JIGhvbGRpbmcgZm9ydHkgY2VsbHM7IFwid2hhdCBpcyBpbiBmcm9udCBvZiBtZVwiIGlzIHRoZVxuICAgKiBxdWVzdGlvbiB0aGVyZSwgYW5kIGVhY2ggY2VsbCBlYXJucyBhIGNvbHVtbi5cbiAgICovXG4gIHByaXZhdGUgYXN5bmMgcmVuZGVySGVhdG1hcCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCBjb250cm9scyA9IHRoaXMuY29udHJvbHM7XG4gICAgY29uc3QgZ2VuZXMgPSB0aGlzLmhlYXRtYXBHZW5lc1xuICAgICAgLm1hcCgobmFtZSkgPT4gKHsgbmFtZSwgdmFsdWVzOiB0aGlzLmdlbmVDYWNoZS5nZXQobmFtZSkgfSkpXG4gICAgICAuZmlsdGVyKChnKTogZyBpcyB7IG5hbWU6IHN0cmluZzsgdmFsdWVzOiBGbG9hdDMyQXJyYXkgfSA9PiAhIWcudmFsdWVzKTtcbiAgICBpZiAoIWNvbnRyb2xzIHx8IGdlbmVzLmxlbmd0aCA9PT0gMCB8fCAhdGhpcy5ncm91cGluZykge1xuICAgICAgdGhpcy5wdXJnZVBsb3QoKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBjb25zdCBzZWxlY3RlZCA9IHRoaXMuc2VsZWN0aW9uLmNvdW50ID4gMCA/IG1hc2tUb0luZGljZXModGhpcy5zZWxlY3Rpb24ubWFzaykgOiBudWxsO1xuICAgIGNvbnN0IHBlckNlbGwgPSAhIXNlbGVjdGVkICYmIHNlbGVjdGVkLmxlbmd0aCA8PSBTcGF0aWFsQ2hhcnRzQ29tcG9uZW50LkhFQVRNQVBfQ0VMTF9DT0xVTU5TO1xuICAgIGNvbnN0IGNlbGxzID0gcGVyQ2VsbCAmJiBzZWxlY3RlZFxuICAgICAgPyBjZWxsc0FzR3JvdXBzKHNlbGVjdGVkLCB0aGlzLmdyb3VwaW5nLmNvZGVzLmxlbmd0aCxcbiAgICAgICAgU3BhdGlhbENoYXJ0c0NvbXBvbmVudC5IRUFUTUFQX0NFTExfQ09MVU1OUylcbiAgICAgIDogbnVsbDtcblxuICAgIGNvbnN0IG1hdHJpeCA9IGhlYXRtYXBNYXRyaXgoXG4gICAgICBnZW5lcyxcbiAgICAgIGNlbGxzID8gY2VsbHMuZ3JvdXBzIDogeyBjb2RlczogdGhpcy5ncm91cGluZy5jb2RlcywgY2F0ZWdvcmllczogdGhpcy5ncm91cGluZy5jYXRlZ29yaWVzIH0sXG4gICAgICB7XG4gICAgICAgIC4uLihjZWxscyA/IHsgaW5kaWNlczogY2VsbHMuaW5kaWNlcywgbWluQ2VsbHM6IDEgfSA6IHt9KSxcbiAgICAgICAgLy8gT3V0c2lkZSB0aGUgcGVyLWNlbGwgdmlldyBhIHNlbGVjdGlvbiBzdGlsbCBuYXJyb3dzIHRoZSBtZWFucywgdGhlIHdheVxuICAgICAgICAvLyB0aGUgdmlvbGluIGFuZCBib3ggbmFycm93IHJhdGhlciB0aGFuIG92ZXJsYXkuXG4gICAgICAgIC4uLighY2VsbHMgJiYgc2VsZWN0ZWQgPyB7IGluZGljZXM6IHNlbGVjdGVkIH0gOiB7fSksXG4gICAgICAgIC8vIE9ubHkgdGhlIGdyb3VwZWQgdmlldzogYGNlbGxzQXNHcm91cHNgIGFscmVhZHkgY2FwcyBieSBldmVuIHRoaW5uaW5nLFxuICAgICAgICAvLyBhbmQgcmUtcmFua2luZyB0aG9zZSBjb2x1bW5zIHdvdWxkIGxvc2UgdGhlIHNlbGVjdGlvbidzIHNwcmVhZC5cbiAgICAgICAgLi4uKGNlbGxzID8ge30gOiB7IG1heENvbHM6IFNwYXRpYWxDaGFydHNDb21wb25lbnQuSEVBVE1BUF9NQVhfQ09MVU1OUyB9KSxcbiAgICAgICAgelNjb3JlOiB0aGlzLmhlYXRtYXBaU2NvcmUsXG4gICAgICB9LFxuICAgICk7XG4gICAgaWYgKCFtYXRyaXgpIHtcbiAgICAgIHRoaXMucHVyZ2VQbG90KCk7XG4gICAgICB0aGlzLm5vdGljZSA9ICdObyBncm91cCBoYXMgZW5vdWdoIG1lYXN1cmVkIGNlbGxzIGZvciBhIG1lYW4uJztcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgdGhpcy5ub3RpY2UgPSBudWxsO1xuICAgIHRoaXMuaGVhdG1hcEhpZGRlbiA9IG1hdHJpeC5oaWRkZW5Db2xzO1xuICAgIGNvbnN0IGlucHV0ID0ge1xuICAgICAgcm93czogbWF0cml4LnJvd3MsXG4gICAgICBjb2xzOiBtYXRyaXguY29scyxcbiAgICAgIHZhbHVlczogbWF0cml4LnZhbHVlcyxcbiAgICAgIGNvdW50czogbWF0cml4LmNvdW50cyxcbiAgICAgIHpTY29yZWQ6IHRoaXMuaGVhdG1hcFpTY29yZSxcbiAgICAgIGdyb3VwTGFiZWw6IGNlbGxzID8gJ3NlbGVjdGVkIGNlbGxzJyA6ICh0aGlzLmdyb3VwQnkgPz8gJycpLFxuICAgIH07XG4gICAgYXdhaXQgdGhpcy5kcmF3KGJ1aWxkSGVhdG1hcFRyYWNlcyhpbnB1dCksIGhlYXRtYXBMYXlvdXQoaW5wdXQpKTtcbiAgfVxufVxuIiwiPGRpdiBjbGFzcz1cInN4LWJvZHlcIiAqbmdJZj1cImNvbnRyb2xzXCI+XG5cbiAgPCEtLSBUaGUgY2hhcnQgZm9sbG93cyB3aGF0ZXZlciB0aGUgbWFwIGlzIGNvbG91cmVkIGJ5LCBzbyB0aGUgdHdvIGNhbiBuZXZlclxuICAgICAgIGRpc2FncmVlIGFib3V0IHdoYXQgaXMgYmVpbmcgc2hvd24uIC0tPlxuICA8ZGl2IGNsYXNzPVwic3gtaGVhZFwiPlxuICAgIDxzcGFuIGNsYXNzPVwic3gtc3ViamVjdFwiICpuZ0lmPVwic3ViamVjdFwiPnt7IHN1YmplY3QgfX08L3NwYW4+XG4gICAgPHNwYW4gY2xhc3M9XCJzeC1zdWJqZWN0IHN4LW11dGVkXCIgKm5nSWY9XCIhc3ViamVjdFwiPm5vdGhpbmcgc2VsZWN0ZWQgdG8gY2hhcnQ8L3NwYW4+XG4gICAgPHNwYW4gY2xhc3M9XCJzeC1iYWRnZVwiICpuZ0lmPVwic2VsZWN0aW9uQ291bnRcIj5cbiAgICAgIHt7IHNlbGVjdGlvbkNvdW50IHwgbnVtYmVyIH19IHNlbGVjdGVkXG4gICAgPC9zcGFuPlxuICAgIDxpIGNsYXNzPVwicGkgcGktc3BpbiBwaS1zcGlubmVyIHN4LWJ1c3lcIiAqbmdJZj1cImJ1c3lcIiBhcmlhLWxhYmVsPVwibG9hZGluZ1wiPjwvaT5cbiAgPC9kaXY+XG5cbiAgPGRpdiBjbGFzcz1cInN4LWNvbnRyb2xzXCI+XG4gICAgPHAtc2VsZWN0QnV0dG9uIFtvcHRpb25zXT1cImtpbmRPcHRpb25zXCIgb3B0aW9uTGFiZWw9XCJsYWJlbFwiIG9wdGlvblZhbHVlPVwidmFsdWVcIlxuICAgICAgICAgICAgICAgICAgICBbbmdNb2RlbF09XCJraW5kXCIgKG5nTW9kZWxDaGFuZ2UpPVwib25LaW5kKCRldmVudClcIlxuICAgICAgICAgICAgICAgICAgICBzdHlsZUNsYXNzPVwic3gta2luZFwiPjwvcC1zZWxlY3RCdXR0b24+XG4gICAgPHAtZHJvcGRvd24gW29wdGlvbnNdPVwiZ3JvdXBPcHRpb25zXCIgb3B0aW9uTGFiZWw9XCJsYWJlbFwiIG9wdGlvblZhbHVlPVwidmFsdWVcIlxuICAgICAgICAgICAgICAgIFtuZ01vZGVsXT1cImdyb3VwQnlcIiAobmdNb2RlbENoYW5nZSk9XCJvbkdyb3VwQnkoJGV2ZW50KVwiXG4gICAgICAgICAgICAgICAgcGxhY2Vob2xkZXI9XCJObyBncm91cGluZ1wiIGFwcGVuZFRvPVwiYm9keVwiXG4gICAgICAgICAgICAgICAgW2Rpc2FibGVkXT1cImtpbmQgPT09ICdoaXN0b2dyYW0nXCJcbiAgICAgICAgICAgICAgICBbcFRvb2x0aXBdPVwia2luZCA9PT0gJ2hlYXRtYXAnXG4gICAgICAgICAgICAgICAgICA/ICdUaGUgY2F0ZWdvcmljYWwgY29sdW1uIHRoZSBoZWF0bWFwXFwncyBjb2x1bW5zIGNvbWUgZnJvbSdcbiAgICAgICAgICAgICAgICAgIDogJ1NwbGl0IGEgdmlvbGluIG9yIGJveCBieSBhIGNhdGVnb3JpY2FsIGNvbHVtbidcIlxuICAgICAgICAgICAgICAgIHRvb2x0aXBQb3NpdGlvbj1cInRvcFwiPjwvcC1kcm9wZG93bj5cbiAgICA8IS0tIFdoYXQgdGhlIGNoYXJ0IG9uIHNjcmVlbiBzaG93cywgYW5kIHdoYXQgaXQgbXVzdCBOT1QgYmUgcmVhZCBmb3IuIEVhY2ggb2YgdGhlc2VcbiAgICAgICAgIGFuc3dlcnMgYSBkaWZmZXJlbnQgcXVlc3Rpb24gYW5kIHR3byBhcmUgcm91dGluZWx5IG92ZXItcmVhZCwgc28gdGhlIGNhdmVhdFxuICAgICAgICAgdHJhdmVscyB3aXRoIHRoZSBleHBsYW5hdGlvbi4gLS0+XG4gICAgPGkgY2xhc3M9XCJwaSBwaS1xdWVzdGlvbi1jaXJjbGUgc3gtaGVscFwiXG4gICAgICAgW3BUb29sdGlwXT1cImtpbmRIZWxwXCIgW2VzY2FwZV09XCJmYWxzZVwiIHRvb2x0aXBQb3NpdGlvbj1cImxlZnRcIlxuICAgICAgIHRvb2x0aXBTdHlsZUNsYXNzPVwic3gtaGVscC10aXBcIlxuICAgICAgIFthdHRyLmFyaWEtbGFiZWxdPVwiJ0Fib3V0IHRoZSAnICsga2luZCArICcgY2hhcnQnXCIgdGFiaW5kZXg9XCIwXCI+PC9pPlxuICA8L2Rpdj5cblxuICA8IS0tIFRoZSBoZWF0bWFwJ3Mgcm93cyBhcmUgYSBnZW5lIExJU1QsIHNvIGl0IG5lZWRzIGl0cyBvd24gcGlja2VyOiB0aGUgb3RoZXJcbiAgICAgICBraW5kcyBjaGFydCB3aGF0ZXZlciBzaW5nbGUgc291cmNlIHRoZSBtYXAgaXMgY29sb3VyZWQgYnkuIC0tPlxuICA8ZGl2IGNsYXNzPVwic3gtY29udHJvbHNcIiAqbmdJZj1cImtpbmQgPT09ICdoZWF0bWFwJ1wiPlxuICAgIDxwLW11bHRpU2VsZWN0IFtvcHRpb25zXT1cImdlbmVPcHRpb25zXCIgb3B0aW9uTGFiZWw9XCJsYWJlbFwiIG9wdGlvblZhbHVlPVwidmFsdWVcIlxuICAgICAgICAgICAgICAgICAgIFtuZ01vZGVsXT1cImhlYXRtYXBHZW5lc1wiIChuZ01vZGVsQ2hhbmdlKT1cIm9uSGVhdG1hcEdlbmVzKCRldmVudClcIlxuICAgICAgICAgICAgICAgICAgIFtmaWx0ZXJdPVwidHJ1ZVwiIGZpbHRlckJ5PVwibGFiZWxcIiAob25GaWx0ZXIpPVwib25HZW5lRmlsdGVyKCRldmVudC5maWx0ZXIpXCJcbiAgICAgICAgICAgICAgICAgICBbdmlydHVhbFNjcm9sbF09XCJ0cnVlXCIgW3ZpcnR1YWxTY3JvbGxJdGVtU2l6ZV09XCIzMFwiIGFwcGVuZFRvPVwiYm9keVwiXG4gICAgICAgICAgICAgICAgICAgW3Nob3dUb2dnbGVBbGxdPVwiZmFsc2VcIiBbbWF4U2VsZWN0ZWRMYWJlbHNdPVwiM1wiXG4gICAgICAgICAgICAgICAgICAgc2VsZWN0ZWRJdGVtc0xhYmVsPVwiezB9IGdlbmVzXCJcbiAgICAgICAgICAgICAgICAgICBwbGFjZWhvbGRlcj1cIlBpY2sgZ2VuZXMgZm9yIHRoZSByb3dzXCJcbiAgICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIk9uZSByb3cgcGVyIGdlbmUsIGF2ZXJhZ2VkIHdpdGhpbiBlYWNoIGdyb3VwXCJcbiAgICAgICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJ0b3BcIiBzdHlsZUNsYXNzPVwic3gtZ2VuZXNcIj48L3AtbXVsdGlTZWxlY3Q+XG4gICAgPHAtY2hlY2tib3ggW2JpbmFyeV09XCJ0cnVlXCIgW25nTW9kZWxdPVwiaGVhdG1hcFpTY29yZVwiXG4gICAgICAgICAgICAgICAgKG9uQ2hhbmdlKT1cIm9uSGVhdG1hcFpTY29yZSgkZXZlbnQuY2hlY2tlZClcIlxuICAgICAgICAgICAgICAgIGxhYmVsPVwiei1zY29yZVwiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJTY2FsZSBlYWNoIGdlbmUgYWNyb3NzIHRoZSBncm91cHMuIFdpdGhvdXQgaXQgb25lIGhpZ2hseS1leHByZXNzZWQgZ2VuZSBzYXR1cmF0ZXMgdGhlIGNvbG91ciBzY2FsZSBhbmQgdGhlIHJlc3QgcmVhZHMgYXMgYmxhbmtcIlxuICAgICAgICAgICAgICAgIHRvb2x0aXBQb3NpdGlvbj1cInRvcFwiPjwvcC1jaGVja2JveD5cbiAgPC9kaXY+XG5cbiAgPCEtLSBEZXRhY2ggYXBwbGllcyB0byBFVkVSWSBraW5kOiB0aGUgZGlhbG9nIGlzIG9ubHkgc28gd2lkZSwgYW5kIGEgY291bnRzIHBsb3QgcmVhZFxuICAgICAgIGFnYWluc3QgYSBoZWF0bWFwIGlzIHRoZSBzYW1lIGFyZ3VtZW50IGFzIGEgVU1BUCByZWFkIGFnYWluc3QgdGhlIG1hcC4gLS0+XG4gIDxkaXYgY2xhc3M9XCJzeC1jb250cm9sc1wiPlxuICAgIDwhLS0gQSBwaWNrZXIgb25seSB3aGVuIHRoZSBkYXRhc2V0IHB1Ymxpc2hlcyBtb3JlIHRoYW4gb25lIGVtYmVkZGluZy4gLS0+XG4gICAgPHAtZHJvcGRvd24gKm5nSWY9XCJraW5kID09PSAnZW1iZWRkaW5nJyAmJiBlbWJlZGRpbmdzLmxlbmd0aCA+IDFcIlxuICAgICAgICAgICAgICAgIFtvcHRpb25zXT1cImVtYmVkZGluZ3NcIiBvcHRpb25MYWJlbD1cImxhYmVsXCIgb3B0aW9uVmFsdWU9XCJuYW1lXCJcbiAgICAgICAgICAgICAgICBbbmdNb2RlbF09XCJlbWJlZGRpbmc/Lm5hbWVcIiAobmdNb2RlbENoYW5nZSk9XCJvbkVtYmVkZGluZygkZXZlbnQpXCJcbiAgICAgICAgICAgICAgICBhcHBlbmRUbz1cImJvZHlcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiV2hpY2ggZW1iZWRkaW5nIHRvIGRyYXdcIiB0b29sdGlwUG9zaXRpb249XCJ0b3BcIj48L3AtZHJvcGRvd24+XG4gICAgPCEtLSBPZmZlcmVkIG9ubHkgZm9yIGFuIGVtYmVkZGluZyB0aGlzIGRhdGFzZXQgZG9lcyBub3QgcHVibGlzaC4gSXQgcnVucyBpbiBhIFdlYlxuICAgICAgICAgV29ya2VyIG9uIHRoZSBHUFUsIHNvIHRoZSBwYW5lbCBzdGF5cyB1c2FibGUgd2hpbGUgaXQgZ29lcy4gLS0+XG4gICAgPHAtYnV0dG9uICpuZ0lmPVwia2luZCA9PT0gJ2VtYmVkZGluZycgJiYgaXNDb21wdXRhYmxlICYmICFpc0NvbXB1dGVkICYmICFpc0NvbXB1dGluZ1wiXG4gICAgICAgICAgICAgIGljb249XCJwaSBwaS1wbGF5XCIgc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgIFtsYWJlbF09XCInQ29tcHV0ZSAoJyArIGNvbXB1dGVFc3RpbWF0ZUxhYmVsICsgJyknXCJcbiAgICAgICAgICAgICAgcFRvb2x0aXA9XCJSdW4gdC1TTkUgaGVyZSwgaW4gYSBiYWNrZ3JvdW5kIHdvcmtlciBvbiB0aGUgR1BVLCBmcm9tIHRoaXMgZGF0YXNldCdzIFBDQS4gVGhlIHBhbmVsIHN0YXlzIHVzYWJsZSwgYW5kIGl0IGNhbiBiZSBjYW5jZWxsZWQuXCJcbiAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwidG9wXCJcbiAgICAgICAgICAgICAgKG9uQ2xpY2spPVwiY29tcHV0ZUVtYmVkZGluZygpXCI+PC9wLWJ1dHRvbj5cbiAgICA8cC1idXR0b24gKm5nSWY9XCJpc0NvbXB1dGluZ1wiXG4gICAgICAgICAgICAgIGljb249XCJwaSBwaS10aW1lc1wiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIiBzZXZlcml0eT1cInNlY29uZGFyeVwiXG4gICAgICAgICAgICAgIGxhYmVsPVwiQ2FuY2VsXCIgKG9uQ2xpY2spPVwiY2FuY2VsQ29tcHV0ZSgpXCI+PC9wLWJ1dHRvbj5cbiAgICA8cC1idXR0b24gW2ljb25dPVwiZGV0YWNoZWQgPyAncGkgcGktd2luZG93LW1pbmltaXplJyA6ICdwaSBwaS1leHRlcm5hbC1saW5rJ1wiXG4gICAgICAgICAgICAgIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICBbbGFiZWxdPVwiZGV0YWNoZWQgPyAnUHV0IGJhY2snIDogJ0RldGFjaCdcIlxuICAgICAgICAgICAgICBbcFRvb2x0aXBdPVwiZGV0YWNoZWRcbiAgICAgICAgICAgICAgICA/ICdTaG93IHRoaXMgY2hhcnQgaW4gdGhlIHBhbmVsIGFnYWluJ1xuICAgICAgICAgICAgICAgIDogJ09wZW4gdGhpcyBjaGFydCBpbiBpdHMgb3duIHdpbmRvdywgYWxvbmdzaWRlIHRoaXMgcGFuZWwnXCJcbiAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwidG9wXCJcbiAgICAgICAgICAgICAgKG9uQ2xpY2spPVwidG9nZ2xlRGV0YWNoZWQoKVwiPjwvcC1idXR0b24+XG4gIDwvZGl2PlxuXG4gIDwhLS0gUHJvZ3Jlc3Mgd2hpbGUgYSBjb21wdXRhdGlvbiBydW5zLiBgbW9kZT1cImluZGV0ZXJtaW5hdGVcImAgdW50aWwgdGhlIGZpcnN0IHJlcG9ydCxcbiAgICAgICBiZWNhdXNlIHRoZSBydW4gc3RhcnRzIHdpdGggYSBuZWlnaGJvdXIgZ3JhcGggYW5kIHRoZXJlIGlzIG5vdGhpbmcgaG9uZXN0IHRvIHNob3dcbiAgICAgICBhcyBhIHBlcmNlbnRhZ2UgeWV0LiAtLT5cbiAgPGRpdiBjbGFzcz1cInN4LWNvbXB1dGVcIiAqbmdJZj1cImlzQ29tcHV0aW5nXCI+XG4gICAgPHAtcHJvZ3Jlc3NCYXJcbiAgICAgIFttb2RlXT1cImNvbXB1dGVGcmFjdGlvbiA9PT0gbnVsbCA/ICdpbmRldGVybWluYXRlJyA6ICdkZXRlcm1pbmF0ZSdcIlxuICAgICAgW3ZhbHVlXT1cImNvbXB1dGVGcmFjdGlvbiA9PT0gbnVsbCA/IDAgOiAoY29tcHV0ZUZyYWN0aW9uICogMTAwKVwiXG4gICAgICBbc2hvd1ZhbHVlXT1cImNvbXB1dGVGcmFjdGlvbiAhPT0gbnVsbFwiXG4gICAgICBbc3R5bGVdPVwieyBoZWlnaHQ6ICcwLjZyZW0nIH1cIj48L3AtcHJvZ3Jlc3NCYXI+XG4gICAgPHNtYWxsIGNsYXNzPVwic3gtaGludFwiPlxuICAgICAge3sgY29tcHV0ZUZyYWN0aW9uID09PSBudWxsID8gJ0J1aWxkaW5nIHRoZSBuZWlnaGJvdXIgZ3JhcGjigKYnIDogJ09wdGltaXNpbmfigKYnIH19XG4gICAgICA8bmctY29udGFpbmVyICpuZ0lmPVwiY29tcHV0ZUJhY2tlbmRcIj4gb24ge3sgY29tcHV0ZUJhY2tlbmQgfX08L25nLWNvbnRhaW5lcj5cbiAgICA8L3NtYWxsPlxuICAgIDxzbWFsbCBjbGFzcz1cInN4LWhpbnQgc3gtd2FyblwiICpuZ0lmPVwiY29tcHV0ZU1lc3NhZ2VcIj57eyBjb21wdXRlTWVzc2FnZSB9fTwvc21hbGw+XG4gIDwvZGl2PlxuICA8c21hbGwgY2xhc3M9XCJzeC1oaW50IHN4LXdhcm5cIiAqbmdJZj1cImNvbXB1dGVFcnJvclwiPnt7IGNvbXB1dGVFcnJvciB9fTwvc21hbGw+XG4gIDwhLS0gV2l0aGhlbGQgcmF0aGVyIHRoYW4gb2ZmZXJlZCB3aXRoIGEgd2FybmluZzogYW4gaG91ci1sb25nIGpvYiBpcyBub3QgYSBjaG9pY2UgYVxuICAgICAgIHJlYWRlciBjYW4gbWVhbmluZ2Z1bGx5IGNvbnNlbnQgdG8gZnJvbSBhIGRpYWxvZy4gLS0+XG4gIDxzbWFsbCBjbGFzcz1cInN4LWhpbnRcIiAqbmdJZj1cImtpbmQgPT09ICdlbWJlZGRpbmcnICYmIHRzbmVUb29MYXJnZVwiPnt7IHRzbmVUb29MYXJnZU5vdGUgfX08L3NtYWxsPlxuXG4gIDxkaXYgW2lkXT1cImNoYXJ0RGl2XCIgY2xhc3M9XCJzeC1wbG90XCIgKm5nSWY9XCIhZGV0YWNoZWRcIj48L2Rpdj5cblxuICA8IS0tIERldGFjaGVkOiB0aGUgY2hhcnQga2VlcHMgZHJhd2luZywgaW4gaXRzIG93biB3aW5kb3cuIFNheXMgc28gcmF0aGVyIHRoYW5cbiAgICAgICBsZWF2aW5nIGEgYmxhbmsgcGFuZWwgdGhhdCBsb29rcyBicm9rZW4uIC0tPlxuICA8ZGl2IGNsYXNzPVwic3gtZGV0YWNoZWRcIiAqbmdJZj1cImRldGFjaGVkXCI+XG4gICAgU2hvd24gaW4gaXRzIG93biB3aW5kb3cuXG4gIDwvZGl2PlxuXG4gIDxuZy1jb250YWluZXIgKm5nVGVtcGxhdGVPdXRsZXQ9XCJoaW50c1wiPjwvbmctY29udGFpbmVyPlxuXG48L2Rpdj5cblxuPCEtLSBEZWNsYXJlZCBvbmNlIGFuZCByZW5kZXJlZCBpbiBib3RoIHRoZSBwYW5lbCBhbmQgdGhlIGRldGFjaGVkIHdpbmRvdzogdGhlIGxhZGRlciBpc1xuICAgICBkZWxpYmVyYXRlbHkgbm90IG11dHVhbGx5IGV4Y2x1c2l2ZSAoYSBoZWF0bWFwIHN0aWxsIG5lZWRzIGl0cyBncm91cGluZyBwcm9tcHQpLCBzb1xuICAgICBjb2xsYXBzaW5nIGl0IHRvIGEgc2luZ2xlIGV4cHJlc3Npb24gd291bGQgZHJvcCBhIGhpbnQgcmF0aGVyIHRoYW4gbW92ZSBpdC4gLS0+XG48bmctdGVtcGxhdGUgI2hpbnRzPlxuICA8c21hbGwgY2xhc3M9XCJzeC1oaW50XCIgKm5nSWY9XCJub3RpY2VcIj57eyBub3RpY2UgfX08L3NtYWxsPlxuICA8c21hbGwgY2xhc3M9XCJzeC1oaW50XCIgKm5nSWY9XCIhbm90aWNlICYmIHN1Z2dlc3RzR3JvdXBpbmdcIj5cbiAgICBQaWNrIGEgZ3JvdXBpbmcgdG8gY29tcGFyZSBjYXRlZ29yaWVzLlxuICA8L3NtYWxsPlxuICA8c21hbGwgY2xhc3M9XCJzeC1oaW50XCIgKm5nSWY9XCIhbm90aWNlICYmIHNlbGVjdGlvbkNvdW50ICYmIGtpbmQgPT09ICdoaXN0b2dyYW0nXCI+XG4gICAgVGhlIHNlbGVjdGlvbiBpcyBvdmVybGFpZCBvbiB0aGUgZnVsbCBkaXN0cmlidXRpb24uXG4gIDwvc21hbGw+XG4gIDxzbWFsbCBjbGFzcz1cInN4LWhpbnRcIiAqbmdJZj1cIiFub3RpY2UgJiYgc2VsZWN0aW9uQ291bnQgJiYga2luZCAhPT0gJ2hpc3RvZ3JhbScgJiYga2luZCAhPT0gJ2hlYXRtYXAnXCI+XG4gICAgU2hvd2luZyB0aGUgc2VsZWN0aW9uIG9ubHkuXG4gIDwvc21hbGw+XG4gIDxzbWFsbCBjbGFzcz1cInN4LWhpbnRcIiAqbmdJZj1cIiFub3RpY2UgJiYga2luZCA9PT0gJ2hlYXRtYXAnXCI+e3sgaGVhdG1hcE5vdGUgfX08L3NtYWxsPlxuICA8c21hbGwgY2xhc3M9XCJzeC1oaW50XCIgKm5nSWY9XCIhbm90aWNlICYmIGtpbmQgPT09ICdlbWJlZGRpbmcnXCI+e3sgZW1iZWRkaW5nTm90ZSB9fTwvc21hbGw+XG48L25nLXRlbXBsYXRlPlxuXG48IS0tIFRoZSBkZXRhY2hlZCBjaGFydC4gSXRzIG93biBub24tbW9kYWwsIGRyYWdnYWJsZSwgcmVzaXphYmxlIHdpbmRvdyBzbyBpdCBjYW4gYmUgcHV0XG4gICAgIGJlc2lkZSB0aGUgc3BhdGlhbC1vbWljcyBkaWFsb2cuIGBhcHBlbmRUbz1cImJvZHlcImAga2VlcHMgaXQgb3V0IG9mIHRoZSBkaWFsb2cnc1xuICAgICBzdGFja2luZyBjb250ZXh0LCBvciBpdCB3b3VsZCBiZSBjbGlwcGVkIGJ5IHRoZSBwYW5lbCBpdCBpcyBtZWFudCB0byBzaXQgbmV4dCB0by4gLS0+XG48cC1kaWFsb2cgKm5nSWY9XCJkZXRhY2hlZFwiXG4gICAgICAgICAgW2hlYWRlcl09XCJkZXRhY2hlZFRpdGxlXCJcbiAgICAgICAgICBbdmlzaWJsZV09XCJ0cnVlXCIgKHZpc2libGVDaGFuZ2UpPVwidG9nZ2xlRGV0YWNoZWQoKVwiXG4gICAgICAgICAgW21vZGFsXT1cImZhbHNlXCIgW3Jlc2l6YWJsZV09XCJ0cnVlXCIgW2RyYWdnYWJsZV09XCJ0cnVlXCJcbiAgICAgICAgICBwb3NpdGlvbj1cInJpZ2h0XCIgYXBwZW5kVG89XCJib2R5XCJcbiAgICAgICAgICAob25TaG93KT1cIm9uRGV0YWNoZWRXaW5kb3dTaG93bigpXCJcbiAgICAgICAgICAob25SZXNpemVFbmQpPVwib25EZXRhY2hlZFJlc2l6ZUVuZCgpXCJcbiAgICAgICAgICBbc3R5bGVdPVwieyB3aWR0aDogJzM0cmVtJywgaGVpZ2h0OiAnMzByZW0nIH1cIlxuICAgICAgICAgIFtjb250ZW50U3R5bGVdPVwie1xuICAgICAgICAgICAgZGlzcGxheTogJ2ZsZXgnLCBmbGV4RGlyZWN0aW9uOiAnY29sdW1uJywgZ2FwOiAnMC4yNXJlbScsXG4gICAgICAgICAgICBwYWRkaW5nOiAnMC40cmVtIDAuNnJlbSAwLjVyZW0nLCBmbGV4OiAnMSAxIGF1dG8nLCBtaW5IZWlnaHQ6ICcwJyxcbiAgICAgICAgICAgIG92ZXJmbG93OiAnYXV0bydcbiAgICAgICAgICB9XCJcbiAgICAgICAgICBzdHlsZUNsYXNzPVwic3BhdGlhbC1kZXRhY2hlZC1kaWFsb2dcIj5cbiAgPGRpdiBbaWRdPVwiZGV0YWNoZWREaXZcIiBjbGFzcz1cInN4LWRldGFjaGVkLXBsb3RcIlxuICAgICAgIFtjbGFzcy5zeC1wbG90LWZpeGVkXT1cImhhc0ZpeGVkSGVpZ2h0XCI+PC9kaXY+XG4gIDxuZy1jb250YWluZXIgKm5nVGVtcGxhdGVPdXRsZXQ9XCJoaW50c1wiPjwvbmctY29udGFpbmVyPlxuPC9wLWRpYWxvZz5cbiJdfQ==
|