@jax-data-science/sci-image-visualizer 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +208 -4
  2. package/esm2022/index.mjs +20 -1
  3. package/esm2022/lib/contracts/display-types.mjs +20 -2
  4. package/esm2022/lib/contracts/image.contract.mjs +1 -1
  5. package/esm2022/lib/contracts/plot-type.mjs +36 -1
  6. package/esm2022/lib/contracts/ports/spatial-data.port.mjs +3 -0
  7. package/esm2022/lib/contracts/spatial-dataset.contract.mjs +45 -0
  8. package/esm2022/lib/contracts/visualizer.contract.mjs +1 -1
  9. package/esm2022/lib/implementations/napari-js/napari-axes-labels.mjs +10 -19
  10. package/esm2022/lib/implementations/napari-js/napari-region-overlay.mjs +71 -7
  11. package/esm2022/lib/implementations/napari-js/napari-scale-bar.mjs +1 -1
  12. package/esm2022/lib/implementations/napari-js/napari-spatial-tooltip.mjs +96 -0
  13. package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +1934 -55
  14. package/esm2022/lib/implementations/napari-js/napari-zoom.mjs +18 -0
  15. package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +5 -3
  16. package/esm2022/lib/implementations/osd/osd-region-overlay.mjs +3 -4
  17. package/esm2022/lib/implementations/osd/osd-zoom.mjs +20 -0
  18. package/esm2022/lib/implementations/plotly/omics-trace-builders.mjs +555 -0
  19. package/esm2022/lib/implementations/plotly/plotly.service.mjs +13 -7
  20. package/esm2022/lib/implementations/spatial-data-http/spatial-data-http.service.mjs +304 -0
  21. package/esm2022/lib/implementations/spatial-data-http/spatial-wire.mjs +179 -0
  22. package/esm2022/lib/routing-visualizer.service.mjs +223 -12
  23. package/esm2022/lib/spatial/embedding-compute.mjs +142 -0
  24. package/esm2022/lib/spatial/gene-search.mjs +77 -0
  25. package/esm2022/lib/spatial/spatial-density.mjs +224 -0
  26. package/esm2022/lib/spatial/spatial-encoding.mjs +232 -0
  27. package/esm2022/lib/spatial/spatial-expression.mjs +257 -0
  28. package/esm2022/lib/spatial/spatial-framing.mjs +60 -0
  29. package/esm2022/lib/spatial/spatial-heatmap.mjs +185 -0
  30. package/esm2022/lib/spatial/spatial-hover.mjs +79 -0
  31. package/esm2022/lib/spatial/spatial-sections.mjs +96 -0
  32. package/esm2022/lib/spatial/spatial-selection.mjs +264 -0
  33. package/esm2022/lib/spatial/spatial-volume-image.mjs +163 -0
  34. package/esm2022/lib/spatial/tsne-worker.mjs +10 -0
  35. package/esm2022/lib/spatial/world-grid.mjs +77 -0
  36. package/esm2022/lib/spatial-controls/spatial-charts/spatial-charts.component.mjs +1278 -0
  37. package/esm2022/lib/spatial-controls/spatial-controls.component.mjs +621 -0
  38. package/esm2022/lib/store/spatial-selection.service.mjs +47 -0
  39. package/esm2022/lib/store/visualizer-store.service.mjs +23 -1
  40. package/esm2022/lib/toolbar/toolbar.component.mjs +30 -7
  41. package/esm2022/lib/visualization.module.mjs +23 -5
  42. package/esm2022/lib/visualizer.component.mjs +283 -22
  43. 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
  44. 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
  45. package/fesm2022/jax-data-science-sci-image-visualizer-tsne-worker-B_nXDGf0.mjs +12 -0
  46. package/fesm2022/jax-data-science-sci-image-visualizer-tsne-worker-B_nXDGf0.mjs.map +1 -0
  47. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +15769 -8316
  48. package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
  49. package/fesm2022/tsne.worker.js +19091 -0
  50. package/index.d.ts +15 -0
  51. package/lib/contracts/display-types.d.ts +131 -0
  52. package/lib/contracts/image.contract.d.ts +7 -0
  53. package/lib/contracts/plot-type.d.ts +42 -2
  54. package/lib/contracts/ports/spatial-data.port.d.ts +90 -0
  55. package/lib/contracts/spatial-dataset.contract.d.ts +281 -0
  56. package/lib/contracts/visualizer.contract.d.ts +99 -1
  57. package/lib/implementations/napari-js/napari-axes-labels.d.ts +9 -7
  58. package/lib/implementations/napari-js/napari-region-overlay.d.ts +38 -4
  59. package/lib/implementations/napari-js/napari-scale-bar.d.ts +7 -3
  60. package/lib/implementations/napari-js/napari-spatial-tooltip.d.ts +32 -0
  61. package/lib/implementations/napari-js/napari-visualizer.service.d.ts +497 -16
  62. package/lib/implementations/napari-js/napari-zoom.d.ts +15 -0
  63. package/lib/implementations/osd/osd-zoom.d.ts +19 -0
  64. package/lib/implementations/plotly/omics-trace-builders.d.ts +193 -0
  65. package/lib/implementations/plotly/plotly.service.d.ts +9 -0
  66. package/lib/implementations/spatial-data-http/spatial-data-http.service.d.ts +123 -0
  67. package/lib/implementations/spatial-data-http/spatial-wire.d.ts +115 -0
  68. package/lib/routing-visualizer.service.d.ts +45 -6
  69. package/lib/spatial/embedding-compute.d.ts +73 -0
  70. package/lib/spatial/gene-search.d.ts +48 -0
  71. package/lib/spatial/spatial-density.d.ts +73 -0
  72. package/lib/spatial/spatial-encoding.d.ts +155 -0
  73. package/lib/spatial/spatial-expression.d.ts +144 -0
  74. package/lib/spatial/spatial-framing.d.ts +37 -0
  75. package/lib/spatial/spatial-heatmap.d.ts +111 -0
  76. package/lib/spatial/spatial-hover.d.ts +49 -0
  77. package/lib/spatial/spatial-sections.d.ts +59 -0
  78. package/lib/spatial/spatial-selection.d.ts +113 -0
  79. package/lib/spatial/spatial-volume-image.d.ts +67 -0
  80. package/lib/spatial/tsne-worker.d.ts +7 -0
  81. package/lib/spatial/world-grid.d.ts +58 -0
  82. package/lib/spatial-controls/spatial-charts/spatial-charts.component.d.ts +439 -0
  83. package/lib/spatial-controls/spatial-controls.component.d.ts +257 -0
  84. package/lib/store/spatial-selection.service.d.ts +27 -0
  85. package/lib/store/visualizer-store.service.d.ts +13 -0
  86. package/lib/toolbar/toolbar.component.d.ts +19 -3
  87. package/lib/visualization.module.d.ts +28 -24
  88. package/lib/visualizer.component.d.ts +73 -2
  89. package/package.json +6 -2
  90. package/src/lib/assets/spatial-omics-3d.svg +31 -0
  91. 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BhdGlhbC1jaGFydHMuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vc3JjL2xpYi9zcGF0aWFsLWNvbnRyb2xzL3NwYXRpYWwtY2hhcnRzL3NwYXRpYWwtY2hhcnRzLmNvbXBvbmVudC50cyIsIi4uLy4uLy4uLy4uLy4uL3NyYy9saWIvc3BhdGlhbC1jb250cm9scy9zcGF0aWFsLWNoYXJ0cy9zcGF0aWFsLWNoYXJ0cy5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ1UsU0FBUyxFQUFFLE1BQU0sRUFBRSxLQUFLLEdBQ3hDLE1BQU0sZUFBZSxDQUFDO0FBQ3ZCLE9BQU8sRUFBRSxZQUFZLEVBQUUsYUFBYSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQ25ELE9BQU8sS0FBSyxNQUFNLE1BQU0sb0JBQW9CLENBQUM7QUFFN0MsT0FBTyxFQUFFLFVBQVUsRUFBaUMsTUFBTSxxQ0FBcUMsQ0FBQztBQUtoRyxPQUFPLEVBQW9DLG9CQUFvQixFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDdkcsT0FBTyxFQUNpQixjQUFjLEVBQUUsYUFBYSxHQUNwRCxNQUFNLGlDQUFpQyxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxhQUFhLEVBQUUsYUFBYSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDN0UsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQzNELE9BQU8sRUFBbUIsbUJBQW1CLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUN2RixPQUFPLEVBQzBCLG9CQUFvQixFQUFFLGdCQUFnQixFQUFFLGtCQUFrQixFQUN6RixnQkFBZ0IsRUFBRSxvQkFBb0IsRUFBRSxZQUFZLEVBQUUsYUFBYSxFQUFFLFdBQVcsRUFBRSxlQUFlLEdBQ2xHLE1BQU0sbURBQW1ELENBQUM7Ozs7Ozs7Ozs7OztBQUUzRDs7Ozs7Ozs7O0dBU0c7QUFDSCxNQUFNLGlCQUFpQixHQUFHLG1CQUFtQixDQUFDO0FBQzlDLFNBQVMsa0JBQWtCO0lBQ3pCLElBQUksT0FBTyxRQUFRLEtBQUssV0FBVztRQUFFLE9BQU87SUFDNUMsSUFBSSxRQUFRLENBQUMsY0FBYyxDQUFDLGlCQUFpQixDQUFDO1FBQUUsT0FBTztJQUN2RCxNQUFNLEtBQUssR0FBRyxRQUFRLENBQUMsYUFBYSxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQzlDLEtBQUssQ0FBQyxFQUFFLEdBQUcsaUJBQWlCLENBQUM7SUFDN0IsS0FBSyxDQUFDLFdBQVcsR0FBRyxnRUFBZ0U7VUFDaEYseURBQXlELENBQUM7SUFDOUQsUUFBUSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDbkMsQ0FBQztBQUVELHNGQUFzRjtBQUN0RixJQUFJLGdCQUFnQixHQUFHLENBQUMsQ0FBQztBQUd6QiwwRUFBMEU7QUFDMUUsTUFBTSxZQUFZLEdBQUc7SUFDbkIsV0FBVyxFQUFFLEtBQUs7SUFDbEIsVUFBVSxFQUFFLElBQUk7SUFDaEIsc0JBQXNCLEVBQUUsQ0FBQyxTQUFTLEVBQUUsVUFBVSxFQUFFLGFBQWEsQ0FBQztDQUMvRCxDQUFDO0FBRUY7Ozs7OztHQU1HO0FBQ0gsTUFBTSxnQkFBZ0IsR0FBRztJQUN2QixXQUFXLEVBQUUsS0FBSztJQUNsQixVQUFVLEVBQUUsSUFBSTtJQUNoQixVQUFVLEVBQUUsSUFBSTtJQUNoQixzQkFBc0IsRUFBRSxDQUFDLGFBQWEsQ0FBQztDQUN4QyxDQUFDO0FBRUY7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7QUFNSCxNQUFNLE9BQU8sc0JBQXNCO0lBQ2pDOzs7OztPQUtHO0lBQ0gsSUFBYSxNQUFNLENBQUMsRUFBVztRQUM3QixNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDO1FBQzFCLElBQUksQ0FBQyxRQUFRLEdBQUcsRUFBRSxDQUFDO1FBQ25CLDBFQUEwRTtRQUMxRSwwRUFBMEU7UUFDMUUsOEJBQThCO1FBQzlCLElBQUksRUFBRSxJQUFJLENBQUMsR0FBRztZQUFFLFVBQVUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxLQUFLLElBQUksQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQzthQUNuRCxJQUFJLEVBQUU7WUFBRSxLQUFLLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztJQUNsQyxDQUFDO0lBQ0QsSUFBSSxNQUFNO1FBQ1IsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDO0lBQ3ZCLENBQUM7YUFxQ3VCLHFCQUFnQixHQUErQztRQUNyRixFQUFFLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLFdBQVcsRUFBRTtRQUMxQyxFQUFFLEtBQUssRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLFFBQVEsRUFBRTtRQUNwQyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRTtLQUMvQixBQUp1QyxDQUl0QzthQUNzQixzQkFBaUIsR0FBK0M7UUFDdEYsRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxRQUFRLEVBQUU7S0FDckMsQUFGd0MsQ0FFdkM7SUFFRjtzRUFDa0U7YUFDMUMsaUJBQVksR0FBK0M7UUFDakYsRUFBRSxLQUFLLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBRSxTQUFTLEVBQUU7S0FDdkMsQUFGbUMsQ0FFbEM7SUFFRjs7Ozs7OztPQU9HO2FBQ3FCLGVBQVUsR0FBMkI7UUFDM0QsRUFBRSxJQUFJLEVBQUUsWUFBWSxFQUFFLEtBQUssRUFBRSxpQkFBaUIsRUFBRSxJQUFJLEVBQUUsQ0FBQyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUU7UUFDeEUsRUFBRSxJQUFJLEVBQUUsY0FBYyxFQUFFLEtBQUssRUFBRSxvQkFBb0IsRUFBRSxJQUFJLEVBQUUsQ0FBQyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUU7S0FDOUUsQUFIaUMsQ0FHaEM7SUFFRiwrRUFBK0U7YUFDdkQsbUJBQWMsR0FBRyxZQUFZLEFBQWYsQ0FBZ0I7SUFFdEQ7Ozs7Ozs7Ozs7OztPQVlHO2FBQ2Esa0NBQTZCLEdBQUcsSUFBSSxBQUFQLENBQVE7SUFFckQsNEVBQTRFO2FBQ3BELG9CQUFlLEdBQUcsRUFBRSxPQUFPLEVBQUUsRUFBRSxFQUFFLFlBQVksRUFBRSxJQUFJLEVBQUUsQUFBdEMsQ0FBdUM7SUFvQzlFLG9FQUFvRTtJQUNwRSx1RkFBdUY7SUFDdkYsd0ZBQXdGO0lBQ3hGLHNGQUFzRjthQUM5RCxvQkFBZSxHQUErQztRQUNwRixFQUFFLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLFdBQVcsRUFBRTtLQUMzQyxBQUZzQyxDQUVyQztJQUVGOzs4REFFMEQ7SUFDMUQsSUFBSSxXQUFXO1FBQ2IsT0FBTztZQUNMLEdBQUcsQ0FBQyxJQUFJLENBQUMsV0FBVztnQkFDbEIsQ0FBQyxDQUFDLHNCQUFzQixDQUFDLGlCQUFpQjtnQkFDMUMsQ0FBQyxDQUFDLHNCQUFzQixDQUFDLGdCQUFnQixDQUFDO1lBQzVDLEdBQUcsc0JBQXNCLENBQUMsWUFBWTtZQUN0QyxpRkFBaUY7WUFDakYsaUZBQWlGO1lBQ2pGLHVDQUF1QztZQUN2QyxHQUFHLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxzQkFBc0IsQ0FBQyxlQUFlLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztTQUM5RSxDQUFDO0lBQ0osQ0FBQztJQXNCRDttRUFDK0Q7YUFDdkMseUJBQW9CLEdBQUcsR0FBRyxBQUFOLENBQU87SUFDbkQ7Ozs7Ozs7O09BUUc7YUFDcUIsd0JBQW1CLEdBQUcsRUFBRSxBQUFMLENBQU07SUF5Q2pELFlBQWlELEdBQWdCO1FBQWhCLFFBQUcsR0FBSCxHQUFHLENBQWE7UUF4TnpELGFBQVEsR0FBRyxJQUFJLENBQUM7UUFFeEI7OztxREFHNkM7UUFDN0M7O3NFQUU4RDtRQUM3QyxRQUFHLEdBQUcsRUFBRSxnQkFBZ0IsQ0FBQztRQUNqQyxhQUFRLEdBQUcsdUJBQXVCLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUN0RDs7Ozs7Ozs7O1dBU0c7UUFDTSxnQkFBVyxHQUFHLDJCQUEyQixJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7UUFFN0Q7Ozs7Ozs7Ozs7O1dBV0c7UUFDSCxhQUFRLEdBQUcsS0FBSyxDQUFDO1FBa0RqQix5RkFBeUY7UUFDeEUsYUFBUSxHQUFHLElBQUksR0FBRyxFQUE0QixDQUFDO1FBRWhFLHVDQUF1QztRQUMvQixlQUFVLEdBQStCLElBQUksQ0FBQztRQUV0RDs7Ozs7Ozs7O1dBU0c7UUFDSyxpQkFBWSxHQUFHLENBQUMsQ0FBQztRQUV6QixvRkFBb0Y7UUFDcEYsb0JBQWUsR0FBa0IsSUFBSSxDQUFDO1FBRXRDLHFGQUFxRjtRQUNyRixtQkFBYyxHQUFrQixJQUFJLENBQUM7UUFFckMsbUJBQWMsR0FBa0IsSUFBSSxDQUFDO1FBRXJDLGlCQUFZLEdBQWtCLElBQUksQ0FBQztRQUVuQyw2RUFBNkU7UUFDN0UsaUJBQVksR0FBRyxLQUFLLENBQUM7UUFFckIsb0VBQW9FO1FBQzVELHFCQUFnQixHQUFHLENBQUMsQ0FBQztRQTBCN0IsYUFBUSxHQUE0QixJQUFJLENBQUM7UUFDekMsU0FBSSxHQUFtQixXQUFXLENBQUM7UUFDbkMsb0VBQW9FO1FBQ3BFLGVBQVUsR0FBMkIsRUFBRSxDQUFDO1FBQ3hDLGNBQVMsR0FBZ0MsSUFBSSxDQUFDO1FBQ3RDLG9CQUFlLEdBQTRCLElBQUksQ0FBQztRQUN4RDs0REFDb0Q7UUFDNUMsbUJBQWMsR0FBcUUsSUFBSSxDQUFDO1FBQ2hHOzs7Ozs7V0FNRztRQUNILGlCQUFZLEdBQWEsRUFBRSxDQUFDO1FBQzVCO2lGQUN5RTtRQUN6RSxrQkFBYSxHQUFHLElBQUksQ0FBQztRQWNyQixrRUFBa0U7UUFDMUQsa0JBQWEsR0FBRyxDQUFDLENBQUM7UUFDMUIsZ0JBQVcsR0FBdUMsRUFBRSxDQUFDO1FBQ3JEOztpQ0FFeUI7UUFDakIsY0FBUyxHQUFhLEVBQUUsQ0FBQztRQUNqQzswREFDa0Q7UUFDMUMsY0FBUyxHQUFHLEVBQUUsQ0FBQztRQUN2Qjt5RUFDaUU7UUFDaEQsY0FBUyxHQUFHLElBQUksR0FBRyxFQUF3QixDQUFDO1FBQzdELDZFQUE2RTtRQUM3RSxZQUFPLEdBQWtCLElBQUksQ0FBQztRQUM5QixpQkFBWSxHQUE4QyxFQUFFLENBQUM7UUFFN0QseURBQXlEO1FBQ3pELFlBQU8sR0FBMEIsSUFBSSxDQUFDO1FBQ3RDLG1CQUFjLEdBQUcsQ0FBQyxDQUFDO1FBQ25CLCtFQUErRTtRQUMvRSxXQUFNLEdBQWtCLElBQUksQ0FBQztRQUM3QixTQUFJLEdBQUcsS0FBSyxDQUFDO1FBRUwsU0FBSSxHQUFxQixFQUFFLEdBQUcsb0JBQW9CLEVBQUUsQ0FBQztRQUNyRCxjQUFTLEdBQXlCLGNBQWMsRUFBRSxDQUFDO1FBQ25ELFdBQU0sR0FBd0IsSUFBSSxDQUFDO1FBQzNDO2lGQUN5RTtRQUNqRSxnQkFBVyxHQUF5QixJQUFJLENBQUM7UUFDekMsYUFBUSxHQUF5QixJQUFJLENBQUM7UUFDOUM7bUZBQzJFO1FBQ25FLFVBQUssR0FBRyxDQUFDLENBQUM7UUFDbEIsZ0ZBQWdGO1FBQ3hFLGVBQVUsR0FBRyxDQUFDLENBQUM7UUFDdkIsd0RBQXdEO1FBQ2hELFdBQU0sR0FBRyxLQUFLLENBQUM7UUFDTixTQUFJLEdBQUcsSUFBSSxZQUFZLEVBQUUsQ0FBQztRQWdGM0M7Ozs7OztXQU1HO1FBQ0ssZ0JBQVcsR0FBa0IsSUFBSSxDQUFDO0lBckYwQixDQUFDO0lBRXJFLFFBQVE7UUFDTixrRkFBa0Y7UUFDbEYseURBQXlEO1FBQ3pELGtCQUFrQixFQUFFLENBQUM7UUFDckIsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLGtCQUFrQixFQUFFLEVBQUUsSUFBSSxJQUFJLENBQUM7UUFDeEQsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRO1lBQUUsT0FBTztRQUUzQixJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxhQUFhLENBQUM7WUFDMUIsSUFBSSxDQUFDLFFBQVEsQ0FBQyxhQUFhLEVBQUUsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLGFBQWEsRUFBRTtTQUM3RCxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxJQUFJLEVBQUUsU0FBUyxDQUFDLEVBQUUsRUFBRTtZQUNqQyxNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsT0FBTyxFQUFFLElBQUksS0FBSyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxJQUFJO21CQUMvRCxJQUFJLENBQUMsT0FBTyxFQUFFLElBQUksS0FBSyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUM7WUFDcEQsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7WUFDakIsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDO1lBQzVCLElBQUksQ0FBQyxTQUFTLEdBQUcsU0FBUyxDQUFDO1lBQzNCLElBQUksQ0FBQyxjQUFjLEdBQUcsU0FBUyxDQUFDLEtBQUssQ0FBQztZQUN0Qyx3RUFBd0U7WUFDeEUscUVBQXFFO1lBQ3JFLHVFQUF1RTtZQUN2RSw4REFBOEQ7WUFDOUQsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLElBQUksYUFBYSxFQUFFLENBQUM7Z0JBQ2xDLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDO2dCQUNuQixLQUFLLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNyQixDQUFDO2lCQUFNLENBQUM7Z0JBQ04sMEVBQTBFO2dCQUMxRSxLQUFLLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNyQixDQUFDO1FBQ0gsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUVKLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsV0FBVyxFQUFFLENBQUMsU0FBUyxDQUFDLENBQUMsT0FBTyxFQUFFLEVBQUU7WUFDOUQsSUFBSSxDQUFDLFlBQVksR0FBRztnQkFDbEIsRUFBRSxLQUFLLEVBQUUsYUFBYSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUU7Z0JBQ3JDLEdBQUcsQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLGtCQUFrQixFQUFFLElBQUksRUFBRSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLEVBQUUsS0FBSyxFQUFFLENBQUMsRUFBRSxLQUFLLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQzthQUNwRixDQUFDO1lBQ0YsOEVBQThFO1lBQzlFLElBQUksSUFBSSxDQUFDLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsS0FBSyxLQUFLLElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO2dCQUM3RSxJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQztnQkFDcEIsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUM7WUFDdkIsQ0FBQztZQUNELDJFQUEyRTtZQUMzRSwwRUFBMEU7WUFDMUUseURBQXlEO1lBQ3pELElBQUksQ0FBQyxTQUFTLEdBQUcsQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLFFBQVEsRUFBRSxLQUFLLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQztZQUN2RCxJQUFJLENBQUMsU0FBUyxHQUFHLEVBQUUsQ0FBQztZQUNwQixtRkFBbUY7WUFDbkYsMEVBQTBFO1lBQzFFLE1BQU0sS0FBSyxHQUFHLElBQUksR0FBRyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQztZQUN0QyxJQUFJLENBQUMsWUFBWSxHQUFHLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDbEUsSUFBSSxDQUFDLGtCQUFrQixFQUFFLENBQUM7WUFDMUIsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUV2QiwrRUFBK0U7WUFDL0UsZ0ZBQWdGO1lBQ2hGLG1EQUFtRDtZQUNuRCxJQUFJLENBQUMsUUFBUSxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQ3RCLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztZQUN0QixNQUFNLFNBQVMsR0FBRyxPQUFPLEVBQUUsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsT0FBTyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7WUFDckUsbUZBQW1GO1lBQ25GLGdGQUFnRjtZQUNoRiwwQkFBMEI7WUFDMUIsTUFBTSxNQUFNLEdBQUcsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsS0FBSyxJQUFJLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1lBQ3JFLE1BQU0sT0FBTyxHQUFHLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEtBQUssSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztZQUM3RSxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsT0FBTyxFQUFFLFlBQVksQ0FBQyxLQUFLLElBQUksQ0FBQyxDQUFDO1lBQ3pELElBQUksQ0FBQyxZQUFZLEdBQUcsTUFBTSxJQUFJLENBQUMsT0FBTzttQkFDakMsSUFBSSxDQUFDLGdCQUFnQixHQUFHLHNCQUFzQixDQUFDLDZCQUE2QixDQUFDO1lBQ2xGLElBQUksQ0FBQyxVQUFVLEdBQUcsTUFBTSxJQUFJLENBQUMsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFDLFlBQVk7Z0JBQ3hELENBQUMsQ0FBQyxDQUFDLEdBQUcsU0FBUyxFQUFFLEdBQUcsc0JBQXNCLENBQUMsVUFBVSxDQUFDO2dCQUN0RCxDQUFDLENBQUMsU0FBUyxDQUFDO1lBQ2QsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxJQUFJLElBQUksQ0FBQztZQUM1QyxJQUFJLENBQUMsZUFBZSxHQUFHLElBQUksQ0FBQztZQUM1QixJQUFJLENBQUMsY0FBYyxHQUFHLElBQUksQ0FBQztZQUMzQixtRkFBbUY7WUFDbkYsSUFBSSxJQUFJLENBQUMsSUFBSSxLQUFLLFdBQVcsSUFBSSxJQUFJLENBQUMsVUFBVSxDQUFDLE1BQU0sS0FBSyxDQUFDO2dCQUFFLElBQUksQ0FBQyxJQUFJLEdBQUcsV0FBVyxDQUFDO1FBQ3pGLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDTixDQUFDO0lBV0Q7Ozs7OztPQU1HO0lBQ0gsTUFBTTtRQUNKLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQzVCLENBQUM7SUFFRDs7Ozs7Ozs7O09BU0c7SUFDSyxLQUFLLENBQUMsR0FBVztRQUN2QixNQUFNLEVBQUUsR0FBRyxRQUFRLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3hDLElBQUksQ0FBQyxFQUFFO1lBQUUsT0FBTztRQUNoQixNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUN6Qyw0RUFBNEU7UUFDNUUsd0VBQXdFO1FBQ3hFLElBQUksS0FBSyxJQUFJLENBQUM7WUFBRSxPQUFPO1FBQ3ZCLElBQUksQ0FBQztZQUNILE1BQU0sQ0FBQyxRQUFRLENBQUMsRUFBRSxFQUFFLElBQUksQ0FBQyxXQUFXLEtBQUssSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO1FBQ2xGLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCx1QkFBdUI7UUFDekIsQ0FBQztJQUNILENBQUM7SUFFRCxXQUFXO1FBQ1QsSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUN4QixtRkFBbUY7UUFDbkYsb0ZBQW9GO1FBQ3BGLGNBQWM7UUFDZCxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7UUFDdEIsOEVBQThFO1FBQzlFLHVEQUF1RDtRQUN2RCxLQUFLLE1BQU0sR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQztZQUNwRCxJQUFJLENBQUM7Z0JBQ0gsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUNwQixDQUFDO1lBQUMsTUFBTSxDQUFDO2dCQUNQLG9FQUFvRTtZQUN0RSxDQUFDO1FBQ0gsQ0FBQztJQUNILENBQUM7SUFFRCxlQUFlO1FBQ2IsMEVBQTBFO1FBQzFFLEtBQUssSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO0lBQ3JCLENBQUM7SUFFRCxNQUFNLENBQUMsSUFBb0I7UUFDekIsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7UUFDakIsSUFBSSxJQUFJLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDdkIsd0VBQXdFO1lBQ3hFLGtFQUFrRTtZQUNsRSxJQUFJLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxLQUFLLENBQUMsSUFBSSxJQUFJLENBQUMsT0FBTyxFQUFFLElBQUksS0FBSyxTQUFTLEVBQUUsQ0FBQztnQkFDdkUsSUFBSSxDQUFDLFlBQVksR0FBRyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDMUMsQ0FBQztZQUNELEtBQUssQ0FBQyxLQUFLLElBQUksRUFBRTtnQkFDZixNQUFNLElBQUksQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO2dCQUNuQyxNQUFNLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO2dCQUM5QixLQUFLLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNyQixDQUFDLENBQUMsRUFBRSxDQUFDO1lBQ0wsT0FBTztRQUNULENBQUM7UUFDRCxLQUFLLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztJQUNyQixDQUFDO0lBRUQ7Ozs7T0FJRztJQUNLLEtBQUssQ0FBQyxxQkFBcUI7UUFDakMsSUFBSSxJQUFJLENBQUMsUUFBUSxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVE7WUFBRSxPQUFPO1FBQzVDLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEVBQUUsS0FBSyxDQUFDO1FBQzVELElBQUksQ0FBQyxLQUFLO1lBQUUsT0FBTztRQUNuQixNQUFNLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDOUIsQ0FBQztJQUVELGVBQWUsQ0FBQyxFQUFXO1FBQ3pCLElBQUksQ0FBQyxhQUFhLEdBQUcsRUFBRSxDQUFDO1FBQ3hCLEtBQUssSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO0lBQ3JCLENBQUM7SUFFRCwyREFBMkQ7SUFDM0QsSUFBSSxXQUFXO1FBQ2IsSUFBSSxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sS0FBSyxDQUFDO1lBQUUsT0FBTyxzQ0FBc0MsQ0FBQztRQUNsRixNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsY0FBYyxHQUFHLENBQUM7ZUFDbEMsSUFBSSxDQUFDLGNBQWMsSUFBSSxzQkFBc0IsQ0FBQyxvQkFBb0IsQ0FBQztRQUN4RSxJQUFJLE9BQU8sRUFBRSxDQUFDO1lBQ1osT0FBTyxpQ0FBaUMsSUFBSSxDQUFDLGNBQWMsS0FBSztrQkFDNUQseUVBQXlFLENBQUM7UUFDaEYsQ0FBQztRQUNELE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxjQUFjLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDLENBQUMsV0FBVyxDQUFDO1FBQzNFLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxhQUFhO1lBQy9CLENBQUMsQ0FBQyxzR0FBc0c7WUFDeEcsQ0FBQyxDQUFDLDZEQUE2RCxDQUFDO1FBQ2xFLHVFQUF1RTtRQUN2RSxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsYUFBYSxHQUFHLENBQUM7WUFDbkMsQ0FBQyxDQUFDLGdCQUFnQixzQkFBc0IsQ0FBQyxtQkFBbUIsZ0JBQWdCO2tCQUN4RSxHQUFHLHNCQUFzQixDQUFDLG1CQUFtQixHQUFHLElBQUksQ0FBQyxhQUFhLElBQUk7a0JBQ3RFLHNEQUFzRDtZQUMxRCxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQ1AsT0FBTyw0Q0FBNEMsSUFBSSxDQUFDLE9BQU8sSUFBSSxPQUFPLFVBQVUsS0FBSyxHQUFHO2NBQ3hGLEdBQUcsTUFBTSxHQUFHLE1BQU0sRUFBRSxDQUFDO0lBQzNCLENBQUM7SUFFRCxpQ0FBaUM7SUFDakMsS0FBSyxDQUFDLGNBQWMsQ0FBQyxLQUFlO1FBQ2xDLElBQUksQ0FBQyxZQUFZLEdBQUcsS0FBSyxJQUFJLEVBQUUsQ0FBQztRQUNoQywrRUFBK0U7UUFDL0UsSUFBSSxDQUFDLGtCQUFrQixFQUFFLENBQUM7UUFDMUIsTUFBTSxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztRQUM5QixLQUFLLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztJQUNyQixDQUFDO0lBRUQ7Ozs7OztPQU1HO0lBQ0ssS0FBSyxDQUFDLGdCQUFnQjtRQUM1QixNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDO1FBQy9CLElBQUksQ0FBQyxRQUFRO1lBQUUsT0FBTztRQUN0QixNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3hFLElBQUksT0FBTyxDQUFDLE1BQU0sS0FBSyxDQUFDO1lBQUUsT0FBTztRQUNqQyxNQUFNLElBQUksR0FBRyxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUM7UUFDMUIsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7UUFDakIsSUFBSSxDQUFDO1lBQ0gsS0FBSyxNQUFNLElBQUksSUFBSSxPQUFPLEVBQUUsQ0FBQztnQkFDM0IsTUFBTSxNQUFNLEdBQUcsTUFBTSxRQUFRLENBQUMsZ0JBQWdCLENBQUMsRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7Z0JBQzFFLElBQUksSUFBSSxLQUFLLElBQUksQ0FBQyxLQUFLO29CQUFFLE9BQU87Z0JBQ2hDLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsQ0FBQztZQUNuQyxDQUFDO1lBQ0QsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUM7UUFDckIsQ0FBQztRQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7WUFDYixJQUFJLElBQUksS0FBSyxJQUFJLENBQUMsS0FBSztnQkFBRSxPQUFPO1lBQ2hDLElBQUksQ0FBQyxNQUFNLEdBQUcsZ0NBQWlDLEdBQWEsRUFBRSxPQUFPLElBQUksR0FBRyxFQUFFLENBQUM7UUFDakYsQ0FBQztnQkFBUyxDQUFDO1lBQ1QsSUFBSSxJQUFJLEtBQUssSUFBSSxDQUFDLEtBQUs7Z0JBQUUsSUFBSSxDQUFDLElBQUksR0FBRyxLQUFLLENBQUM7UUFDN0MsQ0FBQztJQUNILENBQUM7SUFFRCxLQUFLLENBQUMsU0FBUyxDQUFDLElBQW1CO1FBQ2pDLElBQUksQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDO1FBQ3BCLElBQUksQ0FBQyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDNUIsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUM7WUFDckIsS0FBSyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDbkIsT0FBTztRQUNULENBQUM7UUFDRCw4RUFBOEU7UUFDOUUsK0NBQStDO1FBQy9DLE1BQU0sSUFBSSxHQUFHLEVBQUUsSUFBSSxDQUFDLFVBQVUsQ0FBQztRQUMvQixJQUFJLENBQUM7WUFDSCxNQUFNLElBQUksR0FBRyxNQUFNLElBQUksQ0FBQyxRQUFRLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ3ZELElBQUksSUFBSSxLQUFLLElBQUksQ0FBQyxVQUFVO2dCQUFFLE9BQU87WUFDckMsSUFBSSxDQUFDLFFBQVEsR0FBRyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsS0FBSyxFQUFFLFVBQVUsRUFBRSxJQUFJLENBQUMsVUFBVSxFQUFFLE1BQU0sRUFBRSxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDMUYsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLElBQUksSUFBSSxLQUFLLElBQUksQ0FBQyxVQUFVO2dCQUFFLE9BQU87WUFDckMsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUM7WUFDckIsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUM7UUFDdEIsQ0FBQztRQUNELEtBQUssSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO0lBQ3JCLENBQUM7SUFFRCwyRUFBMkU7SUFDM0UsSUFBSSxnQkFBZ0I7UUFDbEIsT0FBTyxvQkFBb0IsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxJQUFJLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQztJQUMxRixDQUFDO0lBRUQsSUFBSSxPQUFPO1FBQ1QsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPO1lBQUUsT0FBTyxFQUFFLENBQUM7UUFDN0IsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLFFBQVEsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUM7SUFDM0YsQ0FBQztJQUVELDJFQUEyRTtJQUUzRSwwREFBMEQ7SUFDbEQsS0FBSyxDQUFDLE1BQU07UUFDbEIsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQztRQUMvQixNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQztRQUNqQyxNQUFNLElBQUksR0FBRyxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUM7UUFDMUIsSUFBSSxDQUFDLFFBQVEsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ3pCLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDO1lBQ25CLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDO1lBQ3hCLElBQUksQ0FBQyxNQUFNLEdBQUcsUUFBUTtnQkFDcEIsQ0FBQyxDQUFDLGlFQUFpRTtnQkFDbkUsQ0FBQyxDQUFDLElBQUksQ0FBQztZQUNULEtBQUssSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ25CLE9BQU87UUFDVCxDQUFDO1FBQ0QsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7UUFDakIseUVBQXlFO1FBQ3pFLHdFQUF3RTtRQUN4RSw0Q0FBNEM7UUFDNUMsTUFBTSxhQUFhLEdBQUcsTUFBTSxDQUFDLElBQUksS0FBSyxRQUFRO2VBQ3pDLENBQUMsUUFBUSxDQUFDLGtCQUFrQixFQUFFLElBQUksRUFBRSxDQUFDLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNqRSxJQUFJLENBQUM7WUFDSCxJQUFJLGFBQWEsRUFBRSxDQUFDO2dCQUNsQixNQUFNLElBQUksR0FBRyxNQUFNLFFBQVEsQ0FBQyxlQUFlLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO2dCQUN6RCxJQUFJLElBQUksS0FBSyxJQUFJLENBQUMsS0FBSztvQkFBRSxPQUFPLENBQUMsYUFBYTtnQkFDOUMsSUFBSSxDQUFDLFdBQVcsR0FBRyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsS0FBSyxFQUFFLFVBQVUsRUFBRSxJQUFJLENBQUMsVUFBVSxFQUFFLE1BQU0sRUFBRSxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7Z0JBQzNGLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDO2dCQUNuQixJQUFJLENBQUMsSUFBSSxHQUFHLFFBQVEsQ0FBQztnQkFDckIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUM7WUFDckIsQ0FBQztpQkFBTSxDQUFDO2dCQUNOLE1BQU0sTUFBTSxHQUFHLE1BQU0sUUFBUSxDQUFDLGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxDQUFDO2dCQUN2RCxJQUFJLElBQUksS0FBSyxJQUFJLENBQUMsS0FBSztvQkFBRSxPQUFPO2dCQUNoQyxJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztnQkFDckIsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLENBQUM7Z0JBQ3hCLElBQUksSUFBSSxDQUFDLElBQUksS0FBSyxRQUFRO29CQUFFLElBQUksQ0FBQyxJQUFJLEdBQUcsV0FBVyxDQUFDO2dCQUNwRCxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQztZQUNyQixDQUFDO1FBQ0gsQ0FBQztRQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7WUFDYixJQUFJLElBQUksS0FBSyxJQUFJLENBQUMsS0FBSztnQkFBRSxPQUFPO1lBQ2hDLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDO1lBQ25CLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDO1lBQ3hCLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxNQUFNLENBQUMsSUFBSSwyQkFBNEIsR0FBYSxFQUFFLE9BQU8sSUFBSSxHQUFHLEVBQUUsQ0FBQztRQUMzRixDQUFDO2dCQUFTLENBQUM7WUFDVCxJQUFJLElBQUksS0FBSyxJQUFJLENBQUMsS0FBSztnQkFBRSxJQUFJLENBQUMsSUFBSSxHQUFHLEtBQUssQ0FBQztRQUM3QyxDQUFDO1FBQ0QsS0FBSyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7SUFDckIsQ0FBQztJQUVEOzs7Ozs7Ozs7O09BVUc7SUFDSyxLQUFLLENBQUMsZUFBZTtRQUMzQixNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDO1FBQy9CLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUM7UUFDNUIsSUFBSSxDQUFDLFFBQVEsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ3ZCLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUNqQixPQUFPO1FBQ1QsQ0FBQztRQUNELElBQUksQ0FBQyxRQUFRLENBQUMsWUFBWSxFQUFFLENBQUM7WUFDM0IsSUFBSSxDQUFDLE1BQU0sR0FBRyw2Q0FBNkMsQ0FBQztZQUM1RCxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7WUFDakIsT0FBTztRQUNULENBQUM7UUFFRCxvRkFBb0Y7UUFDcEYsaUVBQWlFO1FBQ2pFLE1BQU0sSUFBSSxHQUFHLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQztRQUMxQixtRkFBbUY7UUFDbkYsaUZBQWlGO1FBQ2pGLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUMzQyxJQUFJLEtBQUssRUFBRSxDQUFDO1lBQ1YsSUFBSSxDQUFDLGVBQWUsR0FBRyxLQUFLLENBQUM7UUFDL0IsQ0FBQzthQUFNLElBQUksSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO1lBQzdCLG9GQUFvRjtZQUNwRixvRUFBb0U7WUFDcEUsSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQ2pCLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDO1lBQ25CLE9BQU87UUFDVCxDQUFDO2FBQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxlQUFlLElBQUksSUFBSSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsSUFBSSxLQUFLLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUNqRixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztZQUNqQixJQUFJLENBQUM7Z0JBQ0gsTUFBTSxNQUFNLEdBQUcsTUFBTSxRQUFRLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztnQkFDdEQsSUFBSSxJQUFJLEtBQUssSUFBSSxDQUFDLEtBQUs7b0JBQUUsT0FBTztnQkFDaEMsSUFBSSxDQUFDLGVBQWUsR0FBRyxNQUFNLENBQUM7WUFDaEMsQ0FBQztZQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7Z0JBQ2IsSUFBSSxJQUFJLEtBQUssSUFBSSxDQUFDLEtBQUs7b0JBQUUsT0FBTztnQkFDaEMsSUFBSSxDQUFDLE1BQU0sR0FBRyxrQkFBa0IsSUFBSSxDQUFDLEtBQUssSUFBSSxJQUFJLENBQUMsSUFBSSxJQUFJO3NCQUN2RCxHQUFJLEdBQWEsRUFBRSxPQUFPLElBQUksR0FBRyxFQUFFLENBQUM7Z0JBQ3hDLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztnQkFDakIsT0FBTztZQUNULENBQUM7b0JBQVMsQ0FBQztnQkFDVCxJQUFJLElBQUksS0FBSyxJQUFJLENBQUMsS0FBSztvQkFBRSxJQUFJLENBQUMsSUFBSSxHQUFHLEtBQUssQ0FBQztZQUM3QyxDQUFDO1FBQ0gsQ0FBQztRQUNELE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxlQUFlLENBQUM7UUFDcEMsSUFBSSxDQUFDLE1BQU07WUFBRSxPQUFPO1FBRXBCLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUM7UUFDL0IsTUFBTSxFQUFFLEdBQUcsUUFBUSxDQUFDLGNBQWMsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUMzQyxJQUFJLENBQUMsRUFBRTtZQUFFLE9BQU8sQ0FBQyw0Q0FBNEM7UUFDN0QsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUUvQixJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQztRQUNuQixNQUFNLEtBQUssR0FBRztZQUNaLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQztZQUNYLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQztZQUNYLCtFQUErRTtZQUMvRSxpRkFBaUY7WUFDakYsNkNBQTZDO1lBQzdDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUNwQyxLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUssSUFBSSxJQUFJLENBQUMsSUFBSTtZQUM5QixPQUFPLEVBQUUsSUFBSSxDQUFDLE9BQU87WUFDckIsMEVBQTBFO1lBQzFFLEdBQUcsQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxFQUFFLGFBQWEsRUFBRSxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUNwRSxHQUFHLENBQUMsSUFBSSxDQUFDLFdBQVc7Z0JBQ2xCLENBQUMsQ0FBQztvQkFDQSxVQUFVLEVBQUU7d0JBQ1YsS0FBSyxFQUFFLElBQUksQ0FBQyxXQUFXLENBQUMsS0FBSzt3QkFDN0IsS0FBSyxFQUFFLElBQUksQ0FBQyxXQUFXLENBQUMsVUFBVTt3QkFDbEMsTUFBTSxFQUFFLElBQUksQ0FBQyxXQUFXLENBQUMsTUFBTTtxQkFDaEM7aUJBQ0Y7Z0JBQ0QsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUNQLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsU0FBUyxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUN2RSw4RUFBOEU7WUFDOUUsaUNBQWlDO1lBQ2pDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztTQUMxQixDQUFDO1FBQ0YsTUFBTSxJQUFJLENBQUMsSUFBSSxDQUFDLG9CQUFvQixDQUFDLEtBQUssQ0FBQyxFQUFFLGVBQWUsQ0FBQyxLQUFLLENBQUMsRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLENBQUMsQ0FBQztRQUMvRixJQUFJLENBQUMsc0JBQXNCLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDdEMsQ0FBQztJQUVEOzs7Ozs7Ozs7O09BVUc7SUFDSyxzQkFBc0IsQ0FBQyxHQUFXO1FBQ3hDLE1BQU0sRUFBRSxHQUFHLFFBQVEsQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFzQyxDQUFDO1FBQzdFLElBQUksQ0FBQyxFQUFFLEVBQUUsRUFBRTtZQUFFLE9BQU87UUFDcEIsRUFBRSxDQUFDLGtCQUFrQixFQUFFLENBQUMsaUJBQWlCLENBQUMsQ0FBQztRQUMzQyxFQUFFLENBQUMsa0JBQWtCLEVBQUUsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO1FBQzNDLEVBQUUsQ0FBQyxFQUFFLENBQUMsaUJBQWlCLEVBQUUsQ0FBQyxFQUFFLEVBQUUsRUFBRTtZQUM5QixpRkFBaUY7WUFDakYsd0VBQXdFO1lBQ3hFLE1BQU0sTUFBTSxHQUFJLEVBQWtELEVBQUUsTUFBTSxJQUFJLEVBQUUsQ0FBQztZQUNqRixNQUFNLE9BQU8sR0FBYSxFQUFFLENBQUM7WUFDN0IsS0FBSyxNQUFNLENBQUMsSUFBSSxNQUFNLEVBQUUsQ0FBQztnQkFDdkIsc0ZBQXNGO2dCQUN0Riw2RUFBNkU7Z0JBQzdFLGdEQUFnRDtnQkFDaEQsTUFBTSxFQUFFLEdBQUksQ0FBOEIsQ0FBQyxVQUFVLENBQUM7Z0JBQ3RELElBQUksT0FBTyxFQUFFLEtBQUssUUFBUTtvQkFBRSxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1lBQy9DLENBQUM7WUFDRCxJQUFJLE9BQU8sQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7Z0JBQ3pCLElBQUksQ0FBQyxRQUFRLEVBQUUsY0FBYyxFQUFFLENBQUM7Z0JBQ2hDLE9BQU87WUFDVCxDQUFDO1lBQ0QsSUFBSSxDQUFDLFFBQVEsRUFBRSxhQUFhLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDeEMsQ0FBQyxDQUFDLENBQUM7UUFDSCw0REFBNEQ7UUFDNUQsRUFBRSxDQUFDLEVBQUUsQ0FBQyxpQkFBaUIsRUFBRSxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLGNBQWMsRUFBRSxDQUFDLENBQUM7SUFDbEUsQ0FBQztJQUVEOzs7Ozs7O09BT0c7SUFDSyx3QkFBd0IsQ0FBQyxHQUFXO1FBQzFDLE1BQU0sRUFBRSxHQUFHLFFBQVEsQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFzQyxDQUFDO1FBQzdFLElBQUksQ0FBQyxFQUFFLEVBQUUsa0JBQWtCO1lBQUUsT0FBTztRQUNwQyxFQUFFLENBQUMsa0JBQWtCLENBQUMsaUJBQWlCLENBQUMsQ0FBQztRQUN6QyxFQUFFLENBQUMsa0JBQWtCLENBQUMsaUJBQWlCLENBQUMsQ0FBQztJQUMzQyxDQUFDO0lBRUQ7Ozs7Ozs7T0FPRztJQUNLLFFBQVEsQ0FDZCxFQUFlO1FBRWYsTUFBTSxJQUFJLEdBQUksRUFNWixDQUFDLFdBQVcsQ0FBQztRQUNmLElBQUksQ0FBQyxJQUFJO1lBQUUsT0FBTyxJQUFJLENBQUMsQ0FBQyx5QkFBeUI7UUFDakQsSUFBSSxJQUFJLENBQUMsS0FBSyxFQUFFLE1BQU07WUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDN0QscUZBQXFGO1FBQ3JGLHNFQUFzRTtRQUN0RSxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsS0FBSyxFQUFFLFNBQVMsS0FBSyxLQUFLLElBQUksSUFBSSxDQUFDLEtBQUssRUFBRSxTQUFTLEtBQUssS0FBSyxDQUFDO1FBQ2xGLElBQUksTUFBTSxJQUFJLElBQUksQ0FBQyxLQUFLLEVBQUUsS0FBSyxJQUFJLElBQUksQ0FBQyxLQUFLLEVBQUUsS0FBSyxFQUFFLENBQUM7WUFDckQsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFLENBQUMsRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssRUFBRSxDQUFDLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDO1FBQ2xFLENBQUM7UUFDRCxPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRDs7Ozs7OztPQU9HO0lBQ0gsSUFBSSxjQUFjO1FBQ2hCLE9BQU8sSUFBSSxDQUFDLFdBQVcsS0FBSyxJQUFJLENBQUM7SUFDbkMsQ0FBQztJQUVELDRFQUE0RTtJQUM1RSxJQUFJLFVBQVU7UUFDWixPQUFPLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUM7SUFDMUQsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0gsSUFBSSxhQUFhO1FBQ2YsSUFBSSxJQUFJLENBQUMsSUFBSSxLQUFLLFdBQVcsRUFBRSxDQUFDO1lBQzlCLE9BQU8sSUFBSSxDQUFDLFNBQVMsRUFBRSxLQUFLLElBQUksSUFBSSxDQUFDLFNBQVMsRUFBRSxJQUFJLElBQUksV0FBVyxDQUFDO1FBQ3RFLENBQUM7UUFDRCxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLEtBQUssS0FBSyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsS0FBSyxJQUFJLE9BQU8sQ0FBQztRQUNwRixPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEdBQUcsS0FBSyxNQUFNLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDO0lBQzdELENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSCxjQUFjO1FBQ1osTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQztRQUNoQyxJQUFJLENBQUMsUUFBUSxHQUFHLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQztRQUMvQixJQUFJLENBQUM7WUFDSCxNQUFNLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ3hCLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCw2QkFBNkI7UUFDL0IsQ0FBQztRQUNELGtGQUFrRjtRQUNsRixxRkFBcUY7UUFDckYsc0ZBQXNGO1FBQ3RGLG1EQUFtRDtRQUNuRCxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVE7WUFBRSxVQUFVLENBQUMsR0FBRyxFQUFFLENBQUMsS0FBSyxJQUFJLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDOUQsQ0FBQztJQUVELHdGQUF3RjtJQUN4RixxQkFBcUI7UUFDbkIsS0FBSyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7SUFDckIsQ0FBQztJQUVELDZEQUE2RDtJQUM3RCxtQkFBbUI7UUFDakIsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLENBQUM7SUFDL0IsQ0FBQztJQUVEOzs7Ozs7T0FNRztJQUNLLGtCQUFrQjtRQUN4QixJQUFJLENBQUMsV0FBVyxHQUFHLGNBQWMsQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLElBQUksQ0FBQyxTQUFTLEVBQUUsSUFBSSxDQUFDLFlBQVksQ0FBQzthQUNqRixHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsRUFBRSxLQUFLLEVBQUUsQ0FBQyxFQUFFLEtBQUssRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDMUMsQ0FBQztJQUVELHNGQUFzRjtJQUN0RixZQUFZLENBQUMsS0FBYTtRQUN4QixJQUFJLENBQUMsU0FBUyxHQUFHLEtBQUssSUFBSSxFQUFFLENBQUM7UUFDN0IsSUFBSSxDQUFDLGtCQUFrQixFQUFFLENBQUM7SUFDNUIsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0gsSUFBSSxzQkFBc0I7UUFDeEIsTUFBTSxDQUFDLEdBQUcsSUFBSSxDQUFDLGdCQUFnQixDQUFDO1FBQ2hDLE1BQU0sRUFBRSxPQUFPLEVBQUUsWUFBWSxFQUFFLEdBQUcsc0JBQXNCLENBQUMsZUFBZSxDQUFDO1FBQ3pFLE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLEdBQUcsQ0FBQyxDQUFDLEdBQUcsWUFBWSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNwRSxDQUFDO0lBRUQscURBQXFEO0lBQ3JELElBQUksb0JBQW9CO1FBQ3RCLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxzQkFBc0IsQ0FBQztRQUN6QyxJQUFJLElBQUksR0FBRyxFQUFFO1lBQUUsT0FBTyxJQUFJLElBQUksR0FBRyxDQUFDO1FBQ2xDLE9BQU8sSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksR0FBRyxFQUFFLENBQUMsTUFBTSxDQUFDO0lBQ3pDLENBQUM7SUFFRCxzRUFBc0U7SUFDdEUsSUFBSSxnQkFBZ0I7UUFDbEIsTUFBTSxDQUFDLEdBQUcsSUFBSSxDQUFDLGdCQUFnQixDQUFDO1FBQ2hDLE9BQU8saUNBQWlDLENBQUMsQ0FBQyxjQUFjLEVBQUUsd0JBQXdCO2NBQzlFLHVDQUF1QyxJQUFJLENBQUMsb0JBQW9CLG1CQUFtQjtjQUNuRixtREFBbUQsQ0FBQztJQUMxRCxDQUFDO0lBRUQsZ0ZBQWdGO0lBQ2hGLElBQUksWUFBWTtRQUNkLE9BQU8sQ0FBQyxDQUFDLElBQUksQ0FBQyxTQUFTLElBQUksSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ3RFLENBQUM7SUFFRCw0REFBNEQ7SUFDNUQsSUFBSSxVQUFVO1FBQ1osT0FBTyxDQUFDLENBQUMsSUFBSSxDQUFDLFNBQVMsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3BFLENBQUM7SUFFRCxJQUFJLFdBQVc7UUFDYixPQUFPLENBQUMsQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLE9BQU8sQ0FBQztJQUNwQyxDQUFDO0lBRUQ7Ozs7Ozs7T0FPRztJQUNILEtBQUssQ0FBQyxnQkFBZ0I7UUFDcEIsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQztRQUM1QixNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDO1FBQy9CLElBQUksQ0FBQyxJQUFJLElBQUksQ0FBQyxRQUFRLEVBQUUsWUFBWSxJQUFJLElBQUksQ0FBQyxXQUFXO1lBQUUsT0FBTztRQUNqRSxJQUFJLENBQUMsWUFBWSxHQUFHLElBQUksQ0FBQztRQUN6QixJQUFJLENBQUMsY0FBYyxHQUFHLElBQUksQ0FBQztRQUMzQixJQUFJLENBQUMsZUFBZSxHQUFHLElBQUksQ0FBQztRQUM1QixJQUFJLENBQUMsY0FBYyxHQUFHLElBQUksQ0FBQztRQUUzQixrRkFBa0Y7UUFDbEYsc0ZBQXNGO1FBQ3RGLHNGQUFzRjtRQUN0Riw2Q0FBNkM7UUFDN0MsTUFBTSxJQUFJLEdBQUcsRUFBRSxJQUFJLENBQUMsWUFBWSxDQUFDO1FBQ2pDLE1BQU0sVUFBVSxHQUFHLEdBQUcsRUFBRSxDQUFDLElBQUksS0FBSyxJQUFJLENBQUMsWUFBWSxDQUFDO1FBQ3BELElBQUksR0FBRyxHQUErQixJQUFJLENBQUM7UUFFM0MsSUFBSSxDQUFDO1lBQ0gsa0ZBQWtGO1lBQ2xGLGtGQUFrRjtZQUNsRixNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyxhQUFhLENBQUMsUUFBUSxDQUFDLENBQUM7WUFDbEQsSUFBSSxVQUFVLEVBQUU7Z0JBQUUsT0FBTztZQUN6QixJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7Z0JBQ1osSUFBSSxDQUFDLFlBQVksR0FBRyxzQ0FBc0MsQ0FBQztnQkFDM0QsT0FBTztZQUNULENBQUM7WUFDRCxHQUFHLEdBQUcsSUFBSSxtQkFBbUIsRUFBRSxDQUFDO1lBQ2hDLElBQUksQ0FBQyxVQUFVLEdBQUcsR0FBRyxDQUFDO1lBQ3RCLE1BQU0sTUFBTSxHQUFHLE1BQU0sR0FBRyxDQUFDLEdBQUcsQ0FDMUI7Z0JBQ0UsTUFBTSxFQUFFLE1BQU0sQ0FBQyxNQUFNO2dCQUNyQixJQUFJLEVBQUUsTUFBTSxDQUFDLElBQUk7Z0JBQ2pCLEtBQUssRUFBRSxNQUFNLENBQUMsS0FBSztnQkFDbkIsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJO2FBQ2hCLEVBQ0Q7Z0JBQ0UsR0FBRyxJQUFJO2dCQUNQLEtBQUssRUFBRSxDQUFDLElBQUksQ0FBQyxLQUFLLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQztxQkFDN0IsT0FBTyxDQUFDLHNCQUFzQixDQUFDLGNBQWMsRUFBRSxFQUFFLENBQUM7YUFDdEQsRUFDRCxDQUFDLFFBQXlCLEVBQUUsRUFBRTtnQkFDNUIsOEVBQThFO2dCQUM5RSxJQUFJLFVBQVUsRUFBRTtvQkFBRSxPQUFPO2dCQUN6QixJQUFJLENBQUMsZUFBZSxHQUFHLFFBQVEsQ0FBQyxRQUFRLENBQUM7Z0JBQ3pDLElBQUksQ0FBQyxjQUFjLEdBQUcsUUFBUSxDQUFDLE9BQU8sSUFBSSxJQUFJLENBQUMsY0FBYyxDQUFDO2dCQUM5RCxJQUFJLFFBQVEsQ0FBQyxPQUFPO29CQUFFLElBQUksQ0FBQyxjQUFjLEdBQUcsUUFBUSxDQUFDLE9BQU8sQ0FBQztZQUMvRCxDQUFDLENBQ0YsQ0FBQztZQUNGLGlGQUFpRjtZQUNqRiw2RUFBNkU7WUFDN0UsaUNBQWlDO1lBQ2pDLElBQUksTUFBTSxJQUFJLENBQUMsVUFBVSxFQUFFLEVBQUUsQ0FBQztnQkFDNUIsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsQ0FBQztnQkFDckMsSUFBSSxDQUFDLGVBQWUsR0FBRyxNQUFNLENBQUM7Z0JBQzlCLE1BQU0sSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ3RCLENBQUM7UUFDSCxDQUFDO1FBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztZQUNiLElBQUksQ0FBQyxVQUFVLEVBQUU7Z0JBQUUsSUFBSSxDQUFDLFlBQVksR0FBSSxHQUFhLEVBQUUsT0FBTyxJQUFJLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNoRixDQUFDO2dCQUFTLENBQUM7WUFDVCxHQUFHLEVBQUUsU0FBUyxFQUFFLENBQUM7WUFDakIsaUZBQWlGO1lBQ2pGLGdGQUFnRjtZQUNoRiw2RUFBNkU7WUFDN0UsSUFBSSxJQUFJLENBQUMsVUFBVSxLQUFLLEdBQUcsRUFBRSxDQUFDO2dCQUM1QixJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQztnQkFDdkIsSUFBSSxDQUFDLGVBQWUsR0FBRyxJQUFJLENBQUM7WUFDOUIsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRUQsa0VBQWtFO0lBQ2xFLGFBQWE7UUFDWCxJQUFJLENBQUMsVUFBVSxFQUFFLE1BQU0sRUFBRSxDQUFDO0lBQzVCLENBQUM7SUFFRDs7Ozs7OztPQU9HO0lBQ0ssY0FBYztRQUNwQixJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7UUFDcEIsSUFBSSxDQUFDLFVBQVUsRUFBRSxTQUFTLEVBQUUsQ0FBQztRQUM3QixJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQztRQUN2QixJQUFJLENBQUMsZUFBZSxHQUFHLElBQUksQ0FBQztRQUM1QixJQUFJLENBQUMsY0FBYyxHQUFHLElBQUksQ0FBQztRQUMzQixJQUFJLENBQUMsY0FBYyxHQUFHLElBQUksQ0FBQztJQUM3QixDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSyxLQUFLLENBQUMsYUFBYSxDQUN6QixRQUEwQjtRQUUxQixNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsVUFBVTthQUMvQixNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEtBQUssSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsQ0FBQzthQUM3RSxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNuQyxLQUFLLE1BQU0sU0FBUyxJQUFJLFVBQVUsRUFBRSxDQUFDO1lBQ25DLElBQUksQ0FBQztnQkFDSCxNQUFNLEdBQUcsR0FBRyxNQUFNLFFBQVEsQ0FBQyxZQUFhLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO2dCQUN6RCxNQUFNLE1BQU0sR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUMsRUFBRSxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUM7Z0JBQ3pELE1BQU0sSUFBSSxHQUFHLEdBQUcsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDO2dCQUMxQixNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO2dCQUM1QixNQUFNLE1BQU0sR0FBRyxJQUFJLFlBQVksQ0FBQyxJQUFJLEdBQUcsS0FBSyxDQUFDLENBQUM7Z0JBQzlDLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxJQUFJLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztvQkFDOUIsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLEtBQUssRUFBRSxDQUFDLEVBQUU7d0JBQUUsTUFBTSxDQUFDLENBQUMsR0FBRyxLQUFLLEdBQUcsQ0FBQyxDQUFDLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO2dCQUN2RSxDQUFDO2dCQUNELE9BQU8sRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxDQUFDO1lBQ2pDLENBQUM7WUFBQyxNQUFNLENBQUM7Z0JBQ1AscUVBQXFFO1lBQ3ZFLENBQUM7UUFDSCxDQUFDO1FBQ0QsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQseUVBQXlFO0lBQ3pFLFdBQVcsQ0FBQyxJQUFZO1FBQ3RCLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxLQUFLLElBQUksQ0FBQyxDQUFDO1FBQzFELElBQUksQ0FBQyxJQUFJLElBQUksSUFBSSxDQUFDLElBQUksS0FBSyxJQUFJLENBQUMsU0FBUyxFQUFFLElBQUk7WUFBRSxPQUFPO1FBQ3hELElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDO1FBQ3RCLElBQUksQ0FBQyxlQUFlLEdBQUcsSUFBSSxDQUFDO1FBQzVCLEtBQUssSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO0lBQ3JCLENBQUM7SUFFRDs7Ozs7Ozs7O09BU0c7SUFDSCxJQUFJLFFBQVE7UUFDVixRQUFRLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUNsQixLQUFLLFFBQVE7Z0JBQ1gsT0FBTyw0RUFBNEU7c0JBQy9FLGdGQUFnRjtzQkFDaEYsMkVBQTJFO3NCQUMzRSwyQkFBMkIsQ0FBQztZQUNsQyxLQUFLLFdBQVc7Z0JBQ2QsT0FBTyxrRUFBa0U7c0JBQ3JFLG9FQUFvRTtzQkFDcEUsMkVBQTJFO3NCQUMzRSw2QkFBNkIsQ0FBQztZQUNwQyxLQUFLLFFBQVE7Z0JBQ1gsT0FBTywyRUFBMkU7c0JBQzlFLDZFQUE2RTtzQkFDN0UscUVBQXFFLENBQUM7WUFDNUUsS0FBSyxLQUFLO2dCQUNSLE9BQU8sMkVBQTJFO3NCQUM5RSw2RUFBNkU7c0JBQzdFLDJEQUEyRCxDQUFDO1lBQ2xFLEtBQUssU0FBUztnQkFDWixPQUFPLDBFQUEwRTtzQkFDN0Usd0VBQXdFO3NCQUN4RSwyRUFBMkU7c0JBQzNFLDZFQUE2RTtzQkFDN0UsMEVBQTBFO3NCQUMxRSxtQ0FBbUMsQ0FBQztZQUMxQyxLQUFLLFdBQVc7Z0JBQ2QsT0FBTyxJQUFJLENBQUMsYUFBYSxDQUFDO1lBQzVCO2dCQUNFLE9BQU8sRUFBRSxDQUFDO1FBQ2QsQ0FBQztJQUNILENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSCxJQUFZLGFBQWE7UUFDdkIsTUFBTSxNQUFNLEdBQUcscUVBQXFFO2NBQ2hGLDJFQUEyRSxDQUFDO1FBQ2hGLE1BQU0sSUFBSSxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxLQUFLLElBQUksSUFBSSxDQUFDLFNBQVMsRUFBRSxJQUFJLElBQUksRUFBRSxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDakYsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDekIsT0FBTywwRUFBMEU7a0JBQzdFLHdFQUF3RTtrQkFDeEUsaUZBQWlGO2tCQUNqRixnRkFBZ0Y7a0JBQ2hGLG1EQUFtRCxHQUFHLE1BQU0sQ0FBQztRQUNuRSxDQUFDO1FBQ0QsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztZQUNwRCxPQUFPLDBFQUEwRTtrQkFDN0UsdUVBQXVFO2tCQUN2RSxzRUFBc0U7a0JBQ3RFLGlGQUFpRjtrQkFDakYsbURBQW1ELEdBQUcsTUFBTSxDQUFDO1FBQ25FLENBQUM7UUFDRCxPQUFPLHlFQUF5RTtjQUM1RSxtRkFBbUY7Y0FDbkYsa0ZBQWtGO2NBQ2xGLDRFQUE0RTtjQUM1RSx3Q0FBd0MsR0FBRyxNQUFNLENBQUM7SUFDeEQsQ0FBQztJQUVELHdEQUF3RDtJQUN4RCxJQUFJLGFBQWE7UUFDZixNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsZUFBZSxFQUFFLElBQUksSUFBSSxJQUFJLENBQUMsU0FBUyxDQUFDO1FBQzFELElBQUksQ0FBQyxJQUFJO1lBQUUsT0FBTyxzQ0FBc0MsQ0FBQztRQUN6RCxzRkFBc0Y7UUFDdEYsb0ZBQW9GO1FBQ3BGLGlFQUFpRTtRQUNqRSxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsT0FBTztZQUMxQixDQUFDLENBQUMsaUJBQWlCLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEtBQUssSUFBSSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLElBQUk7a0JBQ3pELGlDQUFpQztZQUNyQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQ1AsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLFdBQVc7WUFDL0IsQ0FBQyxDQUFDLDZCQUE2QjtZQUMvQixDQUFDLENBQUMsaUVBQWlFLENBQUM7UUFDdEUsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLEdBQUcsQ0FBQztZQUNsQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxjQUFjLEVBQUUsNEJBQTRCO1lBQ3ZFLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDUCxPQUFPLEdBQUcsSUFBSSxDQUFDLEtBQUssSUFBSSxJQUFJLENBQUMsSUFBSSxJQUFJLFFBQVEsR0FBRyxHQUFHLEdBQUcsT0FBTyxFQUFFLENBQUM7SUFDbEUsQ0FBQztJQUVPLFNBQVM7UUFDZixJQUFJLENBQUM7WUFDSCwyRUFBMkU7WUFDM0UsK0RBQStEO1lBQy9ELE1BQU0sQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQ2hDLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxtQkFBbUI7UUFDckIsQ0FBQztJQUNILENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSyxLQUFLLENBQUMsSUFBSSxDQUNoQixNQUFlLEVBQUUsTUFBZSxFQUFFLFNBQWtCLFlBQVksRUFBRSxHQUFHLEdBQUcsSUFBSSxDQUFDLFVBQVU7UUFFdkYsTUFBTSxNQUFNLEdBQUksTUFBc0MsRUFBRSxNQUFNLENBQUM7UUFDL0QsSUFBSSxDQUFDLFdBQVcsR0FBRyxPQUFPLE1BQU0sS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDO1FBQzlELE1BQU0sTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsTUFBZSxFQUFFLE1BQWUsRUFBRSxNQUFlLENBQUMsQ0FBQztJQUM3RSxDQUFDO0lBRU8sS0FBSyxDQUFDLE1BQU07UUFDbEIsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRO1lBQUUsT0FBTztRQUMzQixzRkFBc0Y7UUFDdEYsa0ZBQWtGO1FBQ2xGLHdEQUF3RDtRQUN4RCxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDO1FBQy9CLElBQUksQ0FBQyxRQUFRLENBQUMsY0FBYyxDQUFDLE1BQU0sQ0FBQztZQUFFLE9BQU87UUFDN0Msc0ZBQXNGO1FBQ3RGLElBQUksSUFBSSxDQUFDLElBQUksS0FBSyxXQUFXO1lBQUUsSUFBSSxDQUFDLHdCQUF3QixDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ3JFLHdFQUF3RTtRQUN4RSw0RUFBNEU7UUFDNUUsMkRBQTJEO1FBQzNELElBQUksSUFBSSxDQUFDLElBQUksS0FBSyxTQUFTLEVBQUUsQ0FBQztZQUM1QixNQUFNLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztZQUMzQixPQUFPO1FBQ1QsQ0FBQztRQUNELG1GQUFtRjtRQUNuRiw4REFBOEQ7UUFDOUQsSUFBSSxJQUFJLENBQUMsSUFBSSxLQUFLLFdBQVcsRUFBRSxDQUFDO1lBQzlCLE1BQU0sSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO1lBQzdCLE9BQU87UUFDVCxDQUFDO1FBQ0QsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLElBQUksQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQztZQUN6RCxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7WUFDakIsT0FBTztRQUNULENBQUM7UUFDRCxJQUFJLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUNyQixNQUFNLE1BQU0sR0FBRztnQkFDYixLQUFLLEVBQUUsSUFBSSxDQUFDLFdBQVc7Z0JBQ3ZCLFNBQVMsRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJO2dCQUNoRSxJQUFJLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJO2FBQ3hCLENBQUM7WUFDRixNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsTUFBTSxDQUFDLEVBQUUsWUFBWSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7WUFDaEUsT0FBTztRQUNULENBQUM7UUFDRCxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU07WUFBRSxPQUFPO1FBQ3pCLE1BQU0sS0FBSyxHQUFHO1lBQ1osTUFBTSxFQUFFLElBQUksQ0FBQyxNQUFNO1lBQ25CLElBQUksRUFBRSxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUk7WUFDN0UsS0FBSyxFQUFFLG9CQUFvQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsSUFBSTtZQUM3RCxTQUFTLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSTtZQUNoRSxHQUFHLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRO1NBQ3hCLENBQUM7UUFDRixNQUFNLE1BQU0sR0FBRyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQ2xELE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsV0FBVyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQztJQUN6RCxDQUFDO0lBRUQ7Ozs7Ozs7T0FPRztJQUNLLEtBQUssQ0FBQyxhQUFhO1FBQ3pCLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUM7UUFDL0IsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLFlBQVk7YUFDNUIsR0FBRyxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxDQUFDLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUM7YUFDM0QsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUErQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUMxRSxJQUFJLENBQUMsUUFBUSxJQUFJLEtBQUssQ0FBQyxNQUFNLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQ3RELElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUNqQixPQUFPO1FBQ1QsQ0FBQztRQUVELE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztRQUN0RixNQUFNLE9BQU8sR0FBRyxDQUFDLENBQUMsUUFBUSxJQUFJLFFBQVEsQ0FBQyxNQUFNLElBQUksc0JBQXNCLENBQUMsb0JBQW9CLENBQUM7UUFDN0YsTUFBTSxLQUFLLEdBQUcsT0FBTyxJQUFJLFFBQVE7WUFDL0IsQ0FBQyxDQUFDLGFBQWEsQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsTUFBTSxFQUNsRCxzQkFBc0IsQ0FBQyxvQkFBb0IsQ0FBQztZQUM5QyxDQUFDLENBQUMsSUFBSSxDQUFDO1FBRVQsTUFBTSxNQUFNLEdBQUcsYUFBYSxDQUMxQixLQUFLLEVBQ0wsS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLEtBQUssRUFBRSxVQUFVLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLEVBQUUsRUFDM0Y7WUFDRSxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLE9BQU8sRUFBRSxLQUFLLENBQUMsT0FBTyxFQUFFLFFBQVEsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1lBQ3pELHlFQUF5RTtZQUN6RSxpREFBaUQ7WUFDakQsR0FBRyxDQUFDLENBQUMsS0FBSyxJQUFJLFFBQVEsQ0FBQyxDQUFDLENBQUMsRUFBRSxPQUFPLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUNwRCx3RUFBd0U7WUFDeEUsa0VBQWtFO1lBQ2xFLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxPQUFPLEVBQUUsc0JBQXNCLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztZQUN6RSxNQUFNLEVBQUUsSUFBSSxDQUFDLGFBQWE7U0FDM0IsQ0FDRixDQUFDO1FBQ0YsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ1osSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQ2pCLElBQUksQ0FBQyxNQUFNLEdBQUcsZ0RBQWdELENBQUM7WUFDL0QsT0FBTztRQUNULENBQUM7UUFDRCxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQztRQUNuQixJQUFJLENBQUMsYUFBYSxHQUFHLE1BQU0sQ0FBQyxVQUFVLENBQUM7UUFDdkMsTUFBTSxLQUFLLEdBQUc7WUFDWixJQUFJLEVBQUUsTUFBTSxDQUFDLElBQUk7WUFDakIsSUFBSSxFQUFFLE1BQU0sQ0FBQyxJQUFJO1lBQ2pCLE1BQU0sRUFBRSxNQUFNLENBQUMsTUFBTTtZQUNyQixNQUFNLEVBQUUsTUFBTSxDQUFDLE1BQU07WUFDckIsT0FBTyxFQUFFLElBQUksQ0FBQyxhQUFhO1lBQzNCLFVBQVUsRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxPQUFPLElBQUksRUFBRSxDQUFDO1NBQzVELENBQUM7UUFDRixNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsa0JBQWtCLENBQUMsS0FBSyxDQUFDLEVBQUUsYUFBYSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDbkUsQ0FBQzs4R0E5ckNVLHNCQUFzQixrQkEyT2IsVUFBVTtrR0EzT25CLHNCQUFzQixvRkM5Rm5DLHNyUkEwSkE7OzJGRDVEYSxzQkFBc0I7a0JBTGxDLFNBQVM7K0JBQ0UsZ0JBQWdCOzswQkErT2IsTUFBTTsyQkFBQyxVQUFVO3lDQXBPakIsTUFBTTtzQkFBbEIsS0FBSyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIEFmdGVyVmlld0luaXQsIENvbXBvbmVudCwgSW5qZWN0LCBJbnB1dCwgT25EZXN0cm95LCBPbkluaXQsXG59IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgU3Vic2NyaXB0aW9uLCBjb21iaW5lTGF0ZXN0IH0gZnJvbSAncnhqcyc7XG5pbXBvcnQgKiBhcyBQbG90bHkgZnJvbSAncGxvdGx5LmpzLWRpc3QtbWluJztcblxuaW1wb3J0IHsgVklTVUFMSVpFUiwgSVZpc3VhbGl6ZXIsIElTcGF0aWFsQ29udHJvbHMgfSBmcm9tICcuLi8uLi9jb250cmFjdHMvdmlzdWFsaXplci5jb250cmFjdCc7XG5pbXBvcnQge1xuICBTcGF0aWFsRW1iZWRkaW5nLFxuICBTcGF0aWFsRW1iZWRkaW5nTWV0YSxcbn0gZnJvbSAnLi4vLi4vY29udHJhY3RzL3NwYXRpYWwtZGF0YXNldC5jb250cmFjdCc7XG5pbXBvcnQgeyBTcGF0aWFsQ29sb3JCeSwgU3BhdGlhbFZpZXdTdGF0ZSwgREVGQVVMVF9TUEFUSUFMX1ZJRVcgfSBmcm9tICcuLi8uLi9jb250cmFjdHMvZGlzcGxheS10eXBlcyc7XG5pbXBvcnQge1xuICBTcGF0aWFsU2VsZWN0aW9uTWFzaywgZW1wdHlTZWxlY3Rpb24sIG1hc2tUb0luZGljZXMsXG59IGZyb20gJy4uLy4uL3NwYXRpYWwvc3BhdGlhbC1zZWxlY3Rpb24nO1xuaW1wb3J0IHsgY2VsbHNBc0dyb3VwcywgaGVhdG1hcE1hdHJpeCB9IGZyb20gJy4uLy4uL3NwYXRpYWwvc3BhdGlhbC1oZWF0bWFwJztcbmltcG9ydCB7IGdlbmVPcHRpb25zRm9yIH0gZnJvbSAnLi4vLi4vc3BhdGlhbC9nZW5lLXNlYXJjaCc7XG5pbXBvcnQgeyBDb21wdXRlUHJvZ3Jlc3MsIEVtYmVkZGluZ0NvbXB1dGVSdW4gfSBmcm9tICcuLi8uLi9zcGF0aWFsL2VtYmVkZGluZy1jb21wdXRlJztcbmltcG9ydCB7XG4gIE9taWNzQ2hhcnRLaW5kLCBPbWljc0dyb3VwaW5nLCBiZW5lZml0c0Zyb21Hcm91cGluZywgYnVpbGRDb3VudFRyYWNlcywgYnVpbGRIZWF0bWFwVHJhY2VzLFxuICBidWlsZE9taWNzVHJhY2VzLCBidWlsZEVtYmVkZGluZ1RyYWNlcywgY291bnRzTGF5b3V0LCBoZWF0bWFwTGF5b3V0LCBvbWljc0xheW91dCwgZW1iZWRkaW5nTGF5b3V0LFxufSBmcm9tICcuLi8uLi9pbXBsZW1lbnRhdGlvbnMvcGxvdGx5L29taWNzLXRyYWNlLWJ1aWxkZXJzJztcblxuLyoqXG4gKiBXaWRlbiB0aGUgaGVscCB0b29sdGlwLCBvbmNlIHBlciBkb2N1bWVudC5cbiAqXG4gKiBQcmltZU5HIGNhcHMgYC5wLXRvb2x0aXBgIGF0IDEyLjVyZW0gYW5kIGFwcGVuZHMgaXQgdG8gYDxib2R5PmAsIHNvIGEgY29tcG9uZW50XG4gKiBzdHlsZXNoZWV0IGNhbm5vdCByZWFjaCBpdCDigJQgdGhlIHNhbWUgcmVhc29uIHRoZSBkZXRhY2hlZCBkaWFsb2cgaXMgc2l6ZWQgaW5saW5lLiBBdFxuICogMjAwIHB4IHRoZSBjaGFydCBleHBsYW5hdGlvbnMgcmVuZGVyIGFzIGEgdGFsbCB0aHJlYWQgb2YgdHdvLXdvcmQgbGluZXMsIHdoaWNoIGlzIHdvcnNlXG4gKiB0aGFuIG5vdCBzaG93aW5nIHRoZW0uIFRoZSBsaWJyYXJ5IHNoaXBzIG5vIGdsb2JhbCBzdHlsZXNoZWV0IHRvIHB1dCB0aGlzIGluLCBzbyBvbmVcbiAqIHJ1bGUgaXMgaW5qZWN0ZWQgaW5zdGVhZDogc2NvcGVkIHRvIHRoaXMgY29tcG9uZW50J3Mgb3duIHRvb2x0aXAgY2xhc3MsIGlkZW1wb3RlbnQsIGFuZFxuICogc2tpcHBlZCB3aGVyZSB0aGVyZSBpcyBubyBkb2N1bWVudC5cbiAqL1xuY29uc3QgSEVMUF9USVBfU1RZTEVfSUQgPSAnc3gtaGVscC10aXAtc3R5bGUnO1xuZnVuY3Rpb24gZW5zdXJlSGVscFRpcFN0eWxlKCk6IHZvaWQge1xuICBpZiAodHlwZW9mIGRvY3VtZW50ID09PSAndW5kZWZpbmVkJykgcmV0dXJuO1xuICBpZiAoZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoSEVMUF9USVBfU1RZTEVfSUQpKSByZXR1cm47XG4gIGNvbnN0IHN0eWxlID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3R5bGUnKTtcbiAgc3R5bGUuaWQgPSBIRUxQX1RJUF9TVFlMRV9JRDtcbiAgc3R5bGUudGV4dENvbnRlbnQgPSAnLnN4LWhlbHAtdGlwIC5wLXRvb2x0aXAtdGV4dCB7IG1heC13aWR0aDogbm9uZTsgd2lkdGg6IDI0cmVtOyAnXG4gICAgKyAnbGluZS1oZWlnaHQ6IDEuNDU7IH1cXG4uc3gtaGVscC10aXAgeyBtYXgtd2lkdGg6IG5vbmU7IH0nO1xuICBkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKHN0eWxlKTtcbn1cblxuLyoqIFBlci1pbnN0YW5jZSBjaGFydC1kaXYgaWQgc291cmNlIOKAlCBzZWUge0BsaW5rIFNwYXRpYWxDaGFydHNDb21wb25lbnQuY2hhcnREaXZ9LiAqL1xubGV0IGNoYXJ0SW5zdGFuY2VTZXEgPSAwO1xuXG5cbi8qKiBQbG90bHkgY29uZmlnOiBhIHN0YXRpYy1pc2ggYW5hbHlzaXMgY2hhcnQsIG5vdCBhbiBlZGl0YWJsZSBmaWd1cmUuICovXG5jb25zdCBDSEFSVF9DT05GSUcgPSB7XG4gIGRpc3BsYXlsb2dvOiBmYWxzZSxcbiAgcmVzcG9uc2l2ZTogdHJ1ZSxcbiAgbW9kZUJhckJ1dHRvbnNUb1JlbW92ZTogWydsYXNzbzJkJywgJ3NlbGVjdDJkJywgJ2F1dG9TY2FsZTJkJ10sXG59O1xuXG4vKipcbiAqIENvbmZpZyBmb3IgdGhlIGVtYmVkZGluZywgd2hpY2ggdW5saWtlIHRoZSBkaXN0cmlidXRpb25zIGlzIGEgcGxvdCB5b3UgU0VMRUNUIGluLlxuICpcbiAqIFRoZSBsYXNzbyBhbmQgYm94LXNlbGVjdCBhcmUga2VwdCDigJQgZHJhd2luZyByb3VuZCBhIGNsdXN0ZXIgaXMgaG93IGEgcG9wdWxhdGlvbiBnZXRzXG4gKiBwaWNrZWQgb3V0IG9mIGEgVU1BUCDigJQgYW5kIHRoZSB3aGVlbCB6b29tcywgYmVjYXVzZSBleHBsb3JpbmcgYW4gZW1iZWRkaW5nIG1lYW5zXG4gKiB6b29taW5nIGludG8gYSBjbHVzdGVyIGFuZCByZWFjaGluZyBmb3IgYSB0b29sYmFyIGJ1dHRvbiB0byBkbyBpdCBicmVha3MgdGhhdC5cbiAqL1xuY29uc3QgRU1CRURESU5HX0NPTkZJRyA9IHtcbiAgZGlzcGxheWxvZ286IGZhbHNlLFxuICByZXNwb25zaXZlOiB0cnVlLFxuICBzY3JvbGxab29tOiB0cnVlLFxuICBtb2RlQmFyQnV0dG9uc1RvUmVtb3ZlOiBbJ2F1dG9TY2FsZTJkJ10sXG59O1xuXG4vKipcbiAqIERpc3RyaWJ1dGlvbiBjaGFydHMgb3ZlciB0aGUgc3BhdGlhbC1vbWljcyB2YWx1ZXMg4oCUIGhpc3RvZ3JhbSwgdmlvbGluLCBib3gg4oCUXG4gKiAqKmxpbmtlZCB0byB0aGUgbWFwKio6IHRoZXkgY2hhcnQgd2hhdGV2ZXIgdGhlIG1hcCBpcyBjb2xvdXJlZCBieSwgYW5kIG5hcnJvd1xuICogdG8gdGhlIGN1cnJlbnQgc2VsZWN0aW9uLlxuICpcbiAqIEVtYmVkZGVkIElOU0lERSBgPHNwYXRpYWwtY29udHJvbHM+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==