@jax-data-science/sci-image-visualizer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +404 -0
- package/esm2022/index.mjs +52 -0
- package/esm2022/jax-data-science-sci-image-visualizer.mjs +5 -0
- package/esm2022/lib/channel-histogram/channel-histogram.component.mjs +398 -0
- package/esm2022/lib/contracts/capabilities.contract.mjs +32 -0
- package/esm2022/lib/contracts/cell-segmenter.contract.mjs +12 -0
- package/esm2022/lib/contracts/channel-histogram-api.contract.mjs +14 -0
- package/esm2022/lib/contracts/colormap-lut.mjs +95 -0
- package/esm2022/lib/contracts/coordinate-transform.contract.mjs +2 -0
- package/esm2022/lib/contracts/display-types.mjs +8 -0
- package/esm2022/lib/contracts/image.contract.mjs +11 -0
- package/esm2022/lib/contracts/intensity.mjs +28 -0
- package/esm2022/lib/contracts/plot-type.mjs +112 -0
- package/esm2022/lib/contracts/ports/image-state.port.mjs +3 -0
- package/esm2022/lib/contracts/ports/preferences.port.mjs +3 -0
- package/esm2022/lib/contracts/ports/region-io.port.mjs +3 -0
- package/esm2022/lib/contracts/ports/tile-access.port.mjs +3 -0
- package/esm2022/lib/contracts/region-editor-api.contract.mjs +4 -0
- package/esm2022/lib/contracts/region-overlay.contract.mjs +2 -0
- package/esm2022/lib/contracts/region-store.contract.mjs +2 -0
- package/esm2022/lib/contracts/sam.contract.mjs +12 -0
- package/esm2022/lib/contracts/toolbar-config.mjs +8 -0
- package/esm2022/lib/contracts/visualizer.contract.mjs +10 -0
- package/esm2022/lib/contracts/viz-config.mjs +3 -0
- package/esm2022/lib/hex-color-picker/hex-color-picker.component.mjs +260 -0
- package/esm2022/lib/implementations/base-store-visualizer.mjs +76 -0
- package/esm2022/lib/implementations/napari-js/napari-axes-labels.mjs +106 -0
- package/esm2022/lib/implementations/napari-js/napari-region-overlay.mjs +847 -0
- package/esm2022/lib/implementations/napari-js/napari-scale-bar.mjs +92 -0
- package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +2283 -0
- package/esm2022/lib/implementations/napari-js/napari-volume-z-handle.mjs +126 -0
- package/esm2022/lib/implementations/osd/display-pipeline.mjs +143 -0
- package/esm2022/lib/implementations/osd/histogram-sampler.mjs +217 -0
- package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +1780 -0
- package/esm2022/lib/implementations/osd/osd-coordinate-transform.mjs +25 -0
- package/esm2022/lib/implementations/osd/osd-coords.mjs +53 -0
- package/esm2022/lib/implementations/osd/osd-lib.mjs +20 -0
- package/esm2022/lib/implementations/osd/osd-region-overlay.mjs +1237 -0
- package/esm2022/lib/implementations/osd/osd-scale-bar.mjs +88 -0
- package/esm2022/lib/implementations/osd/slice-cache.mjs +488 -0
- package/esm2022/lib/implementations/osd/tile-client.mjs +29 -0
- package/esm2022/lib/implementations/plotly/plotly-coordinate-transform.mjs +37 -0
- package/esm2022/lib/implementations/plotly/plotly-region-overlay.mjs +31 -0
- package/esm2022/lib/implementations/plotly/plotly-trace-builders.mjs +183 -0
- package/esm2022/lib/implementations/plotly/plotly.service.mjs +2137 -0
- package/esm2022/lib/implementations/simple-slice-access.service.mjs +99 -0
- package/esm2022/lib/models/bezier.mjs +120 -0
- package/esm2022/lib/models/class-preset.mjs +46 -0
- package/esm2022/lib/models/geometry.mjs +198 -0
- package/esm2022/lib/models/region.mjs +166 -0
- package/esm2022/lib/models/shape.mjs +53 -0
- package/esm2022/lib/plot.utilities.mjs +693 -0
- package/esm2022/lib/processing/image-converter.service.mjs +90 -0
- package/esm2022/lib/processing/image-crop.mjs +62 -0
- package/esm2022/lib/processing/processing-image.mjs +50 -0
- package/esm2022/lib/provide-visualization.mjs +63 -0
- package/esm2022/lib/region-editor/mask-raster.mjs +95 -0
- package/esm2022/lib/region-editor/mask-worker.mjs +10 -0
- package/esm2022/lib/region-editor/region-editor.component.mjs +1190 -0
- package/esm2022/lib/region-ops.service.mjs +301 -0
- package/esm2022/lib/render-orchestrator.mjs +87 -0
- package/esm2022/lib/routing-visualizer.service.mjs +533 -0
- package/esm2022/lib/store/class-color.util.mjs +52 -0
- package/esm2022/lib/store/region-store.service.mjs +1029 -0
- package/esm2022/lib/store/visualizer-store.service.mjs +352 -0
- package/esm2022/lib/toolbar/brush/brush-tool.service.mjs +546 -0
- package/esm2022/lib/toolbar/crop/slide-crop.mjs +57 -0
- package/esm2022/lib/toolbar/segmentation/cell-segment-tool.service.mjs +115 -0
- package/esm2022/lib/toolbar/segmentation/cellpose-segmenter.service.mjs +84 -0
- package/esm2022/lib/toolbar/segmentation/onnx-sam-session.mjs +133 -0
- package/esm2022/lib/toolbar/segmentation/ort-runtime-config.mjs +31 -0
- package/esm2022/lib/toolbar/segmentation/sam-model-registry.mjs +108 -0
- package/esm2022/lib/toolbar/segmentation/sam-onnx-core.mjs +159 -0
- package/esm2022/lib/toolbar/segmentation/sam-point-tool.service.mjs +255 -0
- package/esm2022/lib/toolbar/segmentation/sam-prompt.mjs +94 -0
- package/esm2022/lib/toolbar/segmentation/sam-tool.service.mjs +188 -0
- package/esm2022/lib/toolbar/toolbar.component.mjs +311 -0
- package/esm2022/lib/toolbar/vertex-eraser/vertex-eraser-tool.service.mjs +234 -0
- package/esm2022/lib/toolbar/wand/wand-tool.service.mjs +499 -0
- package/esm2022/lib/toolbar/wand/wand.service.mjs +921 -0
- package/esm2022/lib/toolbar/zoom-to-box/zoom-to-box-tool.service.mjs +157 -0
- package/esm2022/lib/visualization.module.mjs +162 -0
- package/esm2022/lib/visualizer.component.mjs +1597 -0
- package/fesm2022/jax-data-science-sci-image-visualizer-mask-worker-uhepXUhM.mjs +12 -0
- package/fesm2022/jax-data-science-sci-image-visualizer-mask-worker-uhepXUhM.mjs.map +1 -0
- package/fesm2022/jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs +327 -0
- package/fesm2022/jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs.map +1 -0
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +21314 -0
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -0
- package/fesm2022/mask.worker.js +989 -0
- package/fesm2022/onnx-sam.worker.js +202 -0
- package/index.d.ts +39 -0
- package/lib/channel-histogram/channel-histogram.component.d.ts +112 -0
- package/lib/contracts/capabilities.contract.d.ts +30 -0
- package/lib/contracts/cell-segmenter.contract.d.ts +40 -0
- package/lib/contracts/channel-histogram-api.contract.d.ts +98 -0
- package/lib/contracts/colormap-lut.d.ts +21 -0
- package/lib/contracts/coordinate-transform.contract.d.ts +43 -0
- package/lib/contracts/display-types.d.ts +41 -0
- package/lib/contracts/image.contract.d.ts +72 -0
- package/lib/contracts/intensity.d.ts +18 -0
- package/lib/contracts/plot-type.d.ts +108 -0
- package/lib/contracts/ports/image-state.port.d.ts +29 -0
- package/lib/contracts/ports/preferences.port.d.ts +19 -0
- package/lib/contracts/ports/region-io.port.d.ts +29 -0
- package/lib/contracts/ports/tile-access.port.d.ts +27 -0
- package/lib/contracts/region-editor-api.contract.d.ts +83 -0
- package/lib/contracts/region-overlay.contract.d.ts +47 -0
- package/lib/contracts/region-store.contract.d.ts +74 -0
- package/lib/contracts/sam.contract.d.ts +98 -0
- package/lib/contracts/toolbar-config.d.ts +24 -0
- package/lib/contracts/visualizer.contract.d.ts +346 -0
- package/lib/contracts/viz-config.d.ts +25 -0
- package/lib/hex-color-picker/hex-color-picker.component.d.ts +47 -0
- package/lib/implementations/base-store-visualizer.d.ts +71 -0
- package/lib/implementations/napari-js/napari-axes-labels.d.ts +47 -0
- package/lib/implementations/napari-js/napari-region-overlay.d.ts +115 -0
- package/lib/implementations/napari-js/napari-scale-bar.d.ts +31 -0
- package/lib/implementations/napari-js/napari-visualizer.service.d.ts +455 -0
- package/lib/implementations/napari-js/napari-volume-z-handle.d.ts +46 -0
- package/lib/implementations/osd/display-pipeline.d.ts +47 -0
- package/lib/implementations/osd/histogram-sampler.d.ts +85 -0
- package/lib/implementations/osd/openseadragon-visualizer.service.d.ts +476 -0
- package/lib/implementations/osd/osd-coordinate-transform.d.ts +17 -0
- package/lib/implementations/osd/osd-coords.d.ts +16 -0
- package/lib/implementations/osd/osd-lib.d.ts +19 -0
- package/lib/implementations/osd/osd-region-overlay.d.ts +191 -0
- package/lib/implementations/osd/osd-scale-bar.d.ts +17 -0
- package/lib/implementations/osd/slice-cache.d.ts +161 -0
- package/lib/implementations/osd/tile-client.d.ts +32 -0
- package/lib/implementations/plotly/plotly-coordinate-transform.d.ts +19 -0
- package/lib/implementations/plotly/plotly-region-overlay.d.ts +23 -0
- package/lib/implementations/plotly/plotly-trace-builders.d.ts +60 -0
- package/lib/implementations/plotly/plotly.service.d.ts +594 -0
- package/lib/implementations/simple-slice-access.service.d.ts +64 -0
- package/lib/models/bezier.d.ts +59 -0
- package/lib/models/class-preset.d.ts +39 -0
- package/lib/models/geometry.d.ts +66 -0
- package/lib/models/region.d.ts +132 -0
- package/lib/models/shape.d.ts +44 -0
- package/lib/plot.utilities.d.ts +107 -0
- package/lib/processing/image-converter.service.d.ts +28 -0
- package/lib/processing/image-crop.d.ts +18 -0
- package/lib/processing/processing-image.d.ts +17 -0
- package/lib/provide-visualization.d.ts +34 -0
- package/lib/region-editor/mask-raster.d.ts +61 -0
- package/lib/region-editor/mask-worker.d.ts +7 -0
- package/lib/region-editor/region-editor.component.d.ts +305 -0
- package/lib/region-ops.service.d.ts +92 -0
- package/lib/render-orchestrator.d.ts +59 -0
- package/lib/routing-visualizer.service.d.ts +227 -0
- package/lib/store/class-color.util.d.ts +26 -0
- package/lib/store/region-store.service.d.ts +315 -0
- package/lib/store/visualizer-store.service.d.ts +123 -0
- package/lib/toolbar/brush/brush-tool.service.d.ts +143 -0
- package/lib/toolbar/crop/slide-crop.d.ts +30 -0
- package/lib/toolbar/segmentation/cell-segment-tool.service.d.ts +32 -0
- package/lib/toolbar/segmentation/cellpose-segmenter.service.d.ts +39 -0
- package/lib/toolbar/segmentation/onnx-sam-session.d.ts +25 -0
- package/lib/toolbar/segmentation/ort-runtime-config.d.ts +4 -0
- package/lib/toolbar/segmentation/sam-model-registry.d.ts +36 -0
- package/lib/toolbar/segmentation/sam-onnx-core.d.ts +48 -0
- package/lib/toolbar/segmentation/sam-point-tool.service.d.ts +59 -0
- package/lib/toolbar/segmentation/sam-prompt.d.ts +34 -0
- package/lib/toolbar/segmentation/sam-tool.service.d.ts +50 -0
- package/lib/toolbar/toolbar.component.d.ts +167 -0
- package/lib/toolbar/vertex-eraser/vertex-eraser-tool.service.d.ts +64 -0
- package/lib/toolbar/wand/wand-tool.service.d.ts +149 -0
- package/lib/toolbar/wand/wand.service.d.ts +133 -0
- package/lib/toolbar/zoom-to-box/zoom-to-box-tool.service.d.ts +49 -0
- package/lib/visualization.module.d.ts +45 -0
- package/lib/visualizer.component.d.ts +378 -0
- package/package.json +73 -0
- package/src/lib/assets/3d-coordinates.svg +49 -0
- package/src/lib/assets/cells.svg +1 -0
- package/src/lib/assets/click.svg +1 -0
- package/src/lib/assets/colormap-luts.json +1 -0
- package/src/lib/assets/contour.svg +48 -0
- package/src/lib/assets/cube-3d.svg +1 -0
- package/src/lib/assets/edit-selection-white.svg +47 -0
- package/src/lib/assets/heatmap.svg +1 -0
- package/src/lib/assets/icons/colormap-Pastel1.png +0 -0
- package/src/lib/assets/icons/colormap-accent.png +0 -0
- package/src/lib/assets/icons/colormap-afmhot.png +0 -0
- package/src/lib/assets/icons/colormap-autumn.png +0 -0
- package/src/lib/assets/icons/colormap-berlin.png +0 -0
- package/src/lib/assets/icons/colormap-binary.png +0 -0
- package/src/lib/assets/icons/colormap-blackbody.png +0 -0
- package/src/lib/assets/icons/colormap-bluered.png +0 -0
- package/src/lib/assets/icons/colormap-blues.png +0 -0
- package/src/lib/assets/icons/colormap-bone.png +0 -0
- package/src/lib/assets/icons/colormap-brbg.png +0 -0
- package/src/lib/assets/icons/colormap-brg.png +0 -0
- package/src/lib/assets/icons/colormap-bugn.png +0 -0
- package/src/lib/assets/icons/colormap-bupu.png +0 -0
- package/src/lib/assets/icons/colormap-bwr.png +0 -0
- package/src/lib/assets/icons/colormap-cividis.png +0 -0
- package/src/lib/assets/icons/colormap-cmrmap.png +0 -0
- package/src/lib/assets/icons/colormap-cool.png +0 -0
- package/src/lib/assets/icons/colormap-coolwarm.png +0 -0
- package/src/lib/assets/icons/colormap-copper.png +0 -0
- package/src/lib/assets/icons/colormap-cubehelix.png +0 -0
- package/src/lib/assets/icons/colormap-dark2.png +0 -0
- package/src/lib/assets/icons/colormap-earth.png +0 -0
- package/src/lib/assets/icons/colormap-electric.png +0 -0
- package/src/lib/assets/icons/colormap-flag.png +0 -0
- package/src/lib/assets/icons/colormap-gist_earth.png +0 -0
- package/src/lib/assets/icons/colormap-gist_gray.png +0 -0
- package/src/lib/assets/icons/colormap-gist_heat.png +0 -0
- package/src/lib/assets/icons/colormap-gist_ncar.png +0 -0
- package/src/lib/assets/icons/colormap-gist_rainbow.png +0 -0
- package/src/lib/assets/icons/colormap-gist_stern.png +0 -0
- package/src/lib/assets/icons/colormap-gist_yarg.png +0 -0
- package/src/lib/assets/icons/colormap-gnbu.png +0 -0
- package/src/lib/assets/icons/colormap-gnuplot.png +0 -0
- package/src/lib/assets/icons/colormap-gnuplot2.png +0 -0
- package/src/lib/assets/icons/colormap-gray.png +0 -0
- package/src/lib/assets/icons/colormap-greens-inv.png +0 -0
- package/src/lib/assets/icons/colormap-greens.png +0 -0
- package/src/lib/assets/icons/colormap-greys-inv.png +0 -0
- package/src/lib/assets/icons/colormap-greys.png +0 -0
- package/src/lib/assets/icons/colormap-hot.png +0 -0
- package/src/lib/assets/icons/colormap-hsv.png +0 -0
- package/src/lib/assets/icons/colormap-inferno.png +0 -0
- package/src/lib/assets/icons/colormap-jet.png +0 -0
- package/src/lib/assets/icons/colormap-magma.png +0 -0
- package/src/lib/assets/icons/colormap-managua.png +0 -0
- package/src/lib/assets/icons/colormap-nipy_spectral.png +0 -0
- package/src/lib/assets/icons/colormap-ocean.png +0 -0
- package/src/lib/assets/icons/colormap-oranges.png +0 -0
- package/src/lib/assets/icons/colormap-orrd.png +0 -0
- package/src/lib/assets/icons/colormap-pastel2.png +0 -0
- package/src/lib/assets/icons/colormap-picnic.png +0 -0
- package/src/lib/assets/icons/colormap-pink.png +0 -0
- package/src/lib/assets/icons/colormap-piyg.png +0 -0
- package/src/lib/assets/icons/colormap-placeholder.png +0 -0
- package/src/lib/assets/icons/colormap-plasma.png +0 -0
- package/src/lib/assets/icons/colormap-portland.png +0 -0
- package/src/lib/assets/icons/colormap-prgn.png +0 -0
- package/src/lib/assets/icons/colormap-prism.png +0 -0
- package/src/lib/assets/icons/colormap-pubu.png +0 -0
- package/src/lib/assets/icons/colormap-pubugn.png +0 -0
- package/src/lib/assets/icons/colormap-puor.png +0 -0
- package/src/lib/assets/icons/colormap-purd.png +0 -0
- package/src/lib/assets/icons/colormap-purples.png +0 -0
- package/src/lib/assets/icons/colormap-rainbow.png +0 -0
- package/src/lib/assets/icons/colormap-rdbu.png +0 -0
- package/src/lib/assets/icons/colormap-rdgy.png +0 -0
- package/src/lib/assets/icons/colormap-rdpu.png +0 -0
- package/src/lib/assets/icons/colormap-rdylbu.png +0 -0
- package/src/lib/assets/icons/colormap-rdylgn.png +0 -0
- package/src/lib/assets/icons/colormap-reds.png +0 -0
- package/src/lib/assets/icons/colormap-seismic.png +0 -0
- package/src/lib/assets/icons/colormap-set1.png +0 -0
- package/src/lib/assets/icons/colormap-set2.png +0 -0
- package/src/lib/assets/icons/colormap-set3.png +0 -0
- package/src/lib/assets/icons/colormap-spectral.png +0 -0
- package/src/lib/assets/icons/colormap-spring.png +0 -0
- package/src/lib/assets/icons/colormap-summer.png +0 -0
- package/src/lib/assets/icons/colormap-tab10.png +0 -0
- package/src/lib/assets/icons/colormap-tab20.png +0 -0
- package/src/lib/assets/icons/colormap-tab20b.png +0 -0
- package/src/lib/assets/icons/colormap-tab20c.png +0 -0
- package/src/lib/assets/icons/colormap-terrain.png +0 -0
- package/src/lib/assets/icons/colormap-turbo.png +0 -0
- package/src/lib/assets/icons/colormap-twilight.png +0 -0
- package/src/lib/assets/icons/colormap-twilight_shifted.png +0 -0
- package/src/lib/assets/icons/colormap-vanimo.png +0 -0
- package/src/lib/assets/icons/colormap-viridis.png +0 -0
- package/src/lib/assets/icons/colormap-winter.png +0 -0
- package/src/lib/assets/icons/colormap-wistia.png +0 -0
- package/src/lib/assets/icons/colormap-ylgn.png +0 -0
- package/src/lib/assets/icons/colormap-ylgnbu-inv.png +0 -0
- package/src/lib/assets/icons/colormap-ylgnbu.png +0 -0
- package/src/lib/assets/icons/colormap-ylorbr.png +0 -0
- package/src/lib/assets/icons/colormap-ylorrd-inv.png +0 -0
- package/src/lib/assets/icons/colormap-ylorrd.png +0 -0
- package/src/lib/assets/interactive-segmentation-cursor.svg +1 -0
- package/src/lib/assets/invert.svg +1 -0
- package/src/lib/assets/isosurface.svg +7 -0
- package/src/lib/assets/mask.svg +1 -0
- package/src/lib/assets/paintbrush.svg +1 -0
- package/src/lib/assets/picture-segmentation.svg +43 -0
- package/src/lib/assets/polygon-vertices.svg +13 -0
- package/src/lib/assets/polyline.svg +3 -0
- package/src/lib/assets/redo.svg +39 -0
- package/src/lib/assets/region-editor.svg +37 -0
- package/src/lib/assets/region-move.svg +7 -0
- package/src/lib/assets/smooth.svg +56 -0
- package/src/lib/assets/spline-chart.svg +7 -0
- package/src/lib/assets/surface.svg +37 -0
- package/src/lib/assets/test/download-379.svg +1 -0
- package/src/lib/assets/test/download-98.svg +1 -0
- package/src/lib/assets/test/json-1.svg +1 -0
- package/src/lib/assets/test/load-53.svg +1 -0
- package/src/lib/assets/test/mask-44.svg +1 -0
- package/src/lib/assets/test/mask-download-44.svg +15 -0
- package/src/lib/assets/test/mask-save-44.svg +11 -0
- package/src/lib/assets/test/overlay-1.svg +1 -0
- package/src/lib/assets/test/overlay-4.svg +1 -0
- package/src/lib/assets/test/region-26.svg +1 -0
- package/src/lib/assets/test/save-56.svg +1 -0
- package/src/lib/assets/test/upload-43.svg +1 -0
- package/src/lib/assets/to-bezier.svg +15 -0
- package/src/lib/assets/to-polygon.svg +11 -0
- package/src/lib/assets/undo.svg +38 -0
- package/src/lib/assets/vertex-add.svg +12 -0
- package/src/lib/assets/vertex-delete.svg +12 -0
- package/src/lib/assets/wand.svg +54 -0
- package/src/lib/assets/zoom-box.svg +10 -0
|
@@ -0,0 +1,1190 @@
|
|
|
1
|
+
import { Component, Inject, ViewChild } from '@angular/core';
|
|
2
|
+
import { OverlayPanel } from 'primeng/overlaypanel';
|
|
3
|
+
import { saveAs } from 'file-saver';
|
|
4
|
+
import { Subject, Subscription } from 'rxjs';
|
|
5
|
+
import { debounceTime, switchMap } from 'rxjs/operators';
|
|
6
|
+
import { Polygon, Rectangle, Region, MultiPolygon } from '../models/region';
|
|
7
|
+
import { defaultPresetSet } from '../models/class-preset';
|
|
8
|
+
import { colorForLabel } from '../store/class-color.util';
|
|
9
|
+
import { ConfirmationService, MessageService } from 'primeng/api';
|
|
10
|
+
import { REGION_EDITOR_API } from '../contracts/region-editor-api.contract';
|
|
11
|
+
import { REGION_IO_PORT } from '../contracts/ports/region-io.port';
|
|
12
|
+
import { regionToParts, scaleParts, maskScaleFor } from './mask-raster';
|
|
13
|
+
import * as i0 from "@angular/core";
|
|
14
|
+
import * as i1 from "primeng/api";
|
|
15
|
+
import * as i2 from "@angular/common";
|
|
16
|
+
import * as i3 from "@angular/forms";
|
|
17
|
+
import * as i4 from "primeng/toolbar";
|
|
18
|
+
import * as i5 from "primeng/button";
|
|
19
|
+
import * as i6 from "primeng/tooltip";
|
|
20
|
+
import * as i7 from "primeng/dropdown";
|
|
21
|
+
import * as i8 from "primeng/dialog";
|
|
22
|
+
import * as i9 from "primeng/progressbar";
|
|
23
|
+
import * as i10 from "primeng/ripple";
|
|
24
|
+
import * as i11 from "primeng/table";
|
|
25
|
+
import * as i12 from "primeng/paginator";
|
|
26
|
+
import * as i13 from "primeng/overlaypanel";
|
|
27
|
+
import * as i14 from "primeng/confirmdialog";
|
|
28
|
+
import * as i15 from "primeng/inputtext";
|
|
29
|
+
import * as i16 from "primeng/checkbox";
|
|
30
|
+
import * as i17 from "primeng/radiobutton";
|
|
31
|
+
import * as i18 from "primeng/selectbutton";
|
|
32
|
+
import * as i19 from "../hex-color-picker/hex-color-picker.component";
|
|
33
|
+
export class RegionEditorComponent {
|
|
34
|
+
constructor(regionApi, messageService, confirmationService, regionIo) {
|
|
35
|
+
this.regionApi = regionApi;
|
|
36
|
+
this.messageService = messageService;
|
|
37
|
+
this.confirmationService = confirmationService;
|
|
38
|
+
this.regionIo = regionIo;
|
|
39
|
+
this.Array = Array;
|
|
40
|
+
this.regions = [];
|
|
41
|
+
this.regionsCopy = [];
|
|
42
|
+
this.selectedRegions = [];
|
|
43
|
+
this.displayHelpDialog = false;
|
|
44
|
+
this.showColorDialog = false;
|
|
45
|
+
/** One colour editor per unique class among the selected regions, shown in the
|
|
46
|
+
* "edit colour of selected regions" dialog. Unclassified regions (no label)
|
|
47
|
+
* are grouped under a single entry with an empty `label`. */
|
|
48
|
+
this.classColorEdits = [];
|
|
49
|
+
this.labelColors = new Map();
|
|
50
|
+
this.selectedLabelColor = '';
|
|
51
|
+
/** "Edit class on selected rows" popup: chosen existing class, and a new-class name. */
|
|
52
|
+
this.bulkClass = '';
|
|
53
|
+
this.newBulkClass = '';
|
|
54
|
+
// ── annotation-class presets (jit-ui#70) ──
|
|
55
|
+
/** Local mirror of the per-user preset set (chip strip, Class dropdown, dialog). */
|
|
56
|
+
this.presetSet = defaultPresetSet();
|
|
57
|
+
/** Class applied to newly drawn/added regions and to bulk chip-clicks. */
|
|
58
|
+
this.activeClass = null;
|
|
59
|
+
this.showManageDialog = false;
|
|
60
|
+
/** Working copy edited inside the Manage-classes dialog; committed on Apply/Done. */
|
|
61
|
+
this.presetDraft = null;
|
|
62
|
+
/** class name (match-mode keyed) → number of regions currently using it. */
|
|
63
|
+
this.classCounts = new Map();
|
|
64
|
+
/** Fallback class that a region reverts to when its class is deleted (jit-ui#70). */
|
|
65
|
+
this.defaultClassName = 'Region';
|
|
66
|
+
/** Classes as shown in the panel: sorted by region count (desc), then name. */
|
|
67
|
+
this.displayClasses = [];
|
|
68
|
+
this.matchModeOptions = [
|
|
69
|
+
{ label: 'Exact', value: 'exact' },
|
|
70
|
+
{ label: 'Normalized', value: 'normalized' },
|
|
71
|
+
];
|
|
72
|
+
this.paginatorFirst = 0;
|
|
73
|
+
this.paginatorRows = 10;
|
|
74
|
+
this.rowsPerPageOptions = [10, 25, 50];
|
|
75
|
+
/** Regions whose Class cell is currently in edit mode.
|
|
76
|
+
* Using object identity as the key avoids needing a unique id field. */
|
|
77
|
+
this.editingLabelRegions = new Set();
|
|
78
|
+
this.showSaveAsDialog = false;
|
|
79
|
+
this.saveAsFilename = '';
|
|
80
|
+
this.saveAsFileExists = false;
|
|
81
|
+
/** True while a GeoJSON persist is serializing/uploading — drives the dialog's
|
|
82
|
+
* progress bar and Cancel button (parity with the Save-mask dialog). */
|
|
83
|
+
this.saveAsBusy = false;
|
|
84
|
+
this._saveAsCheck$ = new Subject();
|
|
85
|
+
this.showExportDialog = false;
|
|
86
|
+
this.exportFilename = '';
|
|
87
|
+
this.showSaveMaskDialog = false;
|
|
88
|
+
this.saveMaskFilename = '';
|
|
89
|
+
/** Mask type chosen in the Save-mask dialog: a binary foreground/background
|
|
90
|
+
* mask, or a multi-class mask with a distinct id per region. */
|
|
91
|
+
this.maskMode = 'binary';
|
|
92
|
+
/** True while the mask worker is rasterizing/encoding — drives the progress
|
|
93
|
+
* bar and the Cancel button in the dialog. */
|
|
94
|
+
this.maskBusy = false;
|
|
95
|
+
/** 0–100 rasterization progress; switches to indeterminate during encoding. */
|
|
96
|
+
this.maskProgress = 0;
|
|
97
|
+
this.maskEncoding = false;
|
|
98
|
+
this.pendingMaskFilename = '';
|
|
99
|
+
/**
|
|
100
|
+
* PrimeNG p-table multi-selection mode toggle. When true, single-click
|
|
101
|
+
* selects one row (replacing prior); meta/ctrl-click toggles a row in/out
|
|
102
|
+
* of the selection. When false, every click toggles. Default true to
|
|
103
|
+
* match the most common spreadsheet-like UX.
|
|
104
|
+
*/
|
|
105
|
+
this.metaKey = true;
|
|
106
|
+
this._updatingFromEditor = false;
|
|
107
|
+
this._suppressSelectionSyncToPlot = false;
|
|
108
|
+
this._regionSub = new Subscription();
|
|
109
|
+
this._selectedIdxSub = new Subscription();
|
|
110
|
+
this._metaSub = new Subscription();
|
|
111
|
+
this._presetSub = new Subscription();
|
|
112
|
+
/** trackBy for the class rows so re-sorting doesn't re-create the pickers. */
|
|
113
|
+
this.trackByClassName = (_, c) => c.name;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Deep copy of regions for dirty-tracking (`regionsCopy`). Returns plain-object
|
|
117
|
+
* clones — only structural equality matters here, not prototypes.
|
|
118
|
+
* TODO: replace with `structuredClone` once verified.
|
|
119
|
+
*/
|
|
120
|
+
deepClone(items) {
|
|
121
|
+
return items.map((item) => {
|
|
122
|
+
if (Array.isArray(item)) {
|
|
123
|
+
return this.deepClone(item);
|
|
124
|
+
}
|
|
125
|
+
else if (typeof item === 'object' && item !== null) {
|
|
126
|
+
const cloned = {};
|
|
127
|
+
for (const key in item) {
|
|
128
|
+
cloned[key] = this.deepClone([item[key]])[0];
|
|
129
|
+
}
|
|
130
|
+
return cloned;
|
|
131
|
+
}
|
|
132
|
+
return item;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
ngOnInit() {
|
|
136
|
+
this.showShapeLabel = this.regionApi.getShowShapeLabel();
|
|
137
|
+
this.shapeColor = this.regionApi.getShapeColor();
|
|
138
|
+
this.fillColor = this.regionApi.getFillColor();
|
|
139
|
+
// Annotation-class presets (jit-ui#70): mirror the set for the chip strip,
|
|
140
|
+
// the Class dropdown and the manage dialog. Guarded with optional calls so
|
|
141
|
+
// partial API mocks in tests (which omit the preset methods) don't throw.
|
|
142
|
+
const initialPresets = this.regionApi.getPresetSet?.();
|
|
143
|
+
if (initialPresets)
|
|
144
|
+
this.presetSet = initialPresets;
|
|
145
|
+
const presets$ = this.regionApi.getPresetSet$?.();
|
|
146
|
+
if (presets$) {
|
|
147
|
+
this._presetSub = presets$.subscribe((set) => {
|
|
148
|
+
if (set)
|
|
149
|
+
this.presetSet = set;
|
|
150
|
+
this.updateDisplayClasses();
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// Seed from the visualizer's current regions — already scoped to the
|
|
154
|
+
// active image by the per-image cache, and handed back as neutral Region
|
|
155
|
+
// objects (no backend shape format involved).
|
|
156
|
+
// Annotation regions only — intensity-profile lines are owned by the
|
|
157
|
+
// intensity tool and excluded by the contract, so the editor never sees them.
|
|
158
|
+
this.regions = this.applyRegionColors(this.regionApi.getAnnotationRegions());
|
|
159
|
+
this.regionsCopy = this.deepClone(this.regions);
|
|
160
|
+
this.syncClassesFromRegions(this.regions);
|
|
161
|
+
this.recomputeClassCounts();
|
|
162
|
+
// The update event is just a change signal; re-read the regions from the
|
|
163
|
+
// visualizer rather than parsing whatever payload it carries.
|
|
164
|
+
this._regionSub = this.regionApi.getRegionUpdateEvent().subscribe(() => {
|
|
165
|
+
if (this._updatingFromEditor)
|
|
166
|
+
return;
|
|
167
|
+
const updated = this.applyRegionColors(this.regionApi.getAnnotationRegions());
|
|
168
|
+
// preserve selection for regions that still exist (by id, since name
|
|
169
|
+
// is a user-editable display label and may collide)
|
|
170
|
+
const selectedIds = new Set(this.selectedRegions.map((r) => r.id));
|
|
171
|
+
this.regions = updated;
|
|
172
|
+
this.regionsCopy = this.deepClone(this.regions);
|
|
173
|
+
this.selectedRegions = updated.filter((r) => selectedIds.has(r.id));
|
|
174
|
+
this.clampPaginatorFirst();
|
|
175
|
+
this.syncClassesFromRegions(updated);
|
|
176
|
+
this.recomputeClassCounts();
|
|
177
|
+
});
|
|
178
|
+
this._saveAsCheck$.pipe(debounceTime(400), switchMap(name => this.regionIo.roiFileExists(name))).subscribe({
|
|
179
|
+
next: exists => { this.saveAsFileExists = exists; },
|
|
180
|
+
error: () => { this.saveAsFileExists = false; },
|
|
181
|
+
});
|
|
182
|
+
// Physical pixel size of the active image (for region areas in µm²/mm²).
|
|
183
|
+
// Mirror PlotlyService.currentMpp: the calibration may sit on any channel
|
|
184
|
+
// entry, not necessarily [0], so pick the first entry with a positive mppX
|
|
185
|
+
// rather than reading [0] blindly. Fall back to square pixels (mppY = mppX)
|
|
186
|
+
// when only one axis is reported.
|
|
187
|
+
this._metaSub = this.regionApi.getImageMeta().subscribe((meta) => {
|
|
188
|
+
const { mppX, mppY } = this.pickMpp(meta);
|
|
189
|
+
this.mppX = mppX;
|
|
190
|
+
this.mppY = mppY;
|
|
191
|
+
});
|
|
192
|
+
this._selectedIdxSub = this.regionApi.getSelectedRegions$().subscribe((selected) => {
|
|
193
|
+
if (this._suppressSelectionSyncToPlot)
|
|
194
|
+
return;
|
|
195
|
+
// Map the contract's selected regions to the editor's own instances by id.
|
|
196
|
+
const next = selected
|
|
197
|
+
.map((s) => this.regions.find((r) => r.id === s.id))
|
|
198
|
+
.filter((r) => !!r);
|
|
199
|
+
const same = next.length === this.selectedRegions.length &&
|
|
200
|
+
next.every((r, i) => r.id === this.selectedRegions[i]?.id);
|
|
201
|
+
if (!same)
|
|
202
|
+
this.selectedRegions = next;
|
|
203
|
+
// Scroll the paginator so the most-recently-selected row is visible.
|
|
204
|
+
if (next.length > 0) {
|
|
205
|
+
const lastIdx = this.regions.findIndex((r) => r.id === next[next.length - 1].id);
|
|
206
|
+
if (lastIdx >= 0) {
|
|
207
|
+
const pageStart = Math.floor(lastIdx / this.paginatorRows) * this.paginatorRows;
|
|
208
|
+
if (pageStart !== this.paginatorFirst)
|
|
209
|
+
this.paginatorFirst = pageStart;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
ngOnDestroy() {
|
|
215
|
+
this._regionSub.unsubscribe();
|
|
216
|
+
this._selectedIdxSub.unsubscribe();
|
|
217
|
+
this._metaSub.unsubscribe();
|
|
218
|
+
this._presetSub.unsubscribe();
|
|
219
|
+
if (this._saveAsTimer !== undefined)
|
|
220
|
+
clearTimeout(this._saveAsTimer);
|
|
221
|
+
this._saveAsSub?.unsubscribe();
|
|
222
|
+
this.teardownMaskWorker();
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Table → Plot: PrimeNG fires onRowSelect/onRowUnselect after updating
|
|
226
|
+
* `selectedRegions`. Push the selected regions to the contract by identity —
|
|
227
|
+
* the package maps them to its internal index space (which includes the
|
|
228
|
+
* intensity-profile lines the editor never sees).
|
|
229
|
+
*/
|
|
230
|
+
onSelectionChanged() {
|
|
231
|
+
this._suppressSelectionSyncToPlot = true;
|
|
232
|
+
try {
|
|
233
|
+
this.regionApi.setSelectedRegions(this.selectedRegions ?? []);
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
this._suppressSelectionSyncToPlot = false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Apply classification colours and refresh the label→colour map for regions
|
|
241
|
+
* coming from the visualizer. The visualizer hands back neutral `Region`
|
|
242
|
+
* objects with bounds, colour and label already populated, so the editor no
|
|
243
|
+
* longer parses any backend-specific shape format — it only fills in a
|
|
244
|
+
* fallback colour and rebuilds its local label→colour lookup.
|
|
245
|
+
*/
|
|
246
|
+
applyRegionColors(regions) {
|
|
247
|
+
for (const region of regions) {
|
|
248
|
+
region.color =
|
|
249
|
+
region.color ||
|
|
250
|
+
this.regionApi.getClassificationColors().get(region.label ?? '') ||
|
|
251
|
+
this.shapeColor;
|
|
252
|
+
}
|
|
253
|
+
// Rebuild labelColors: seed from persisted map, then overlay actual region colors
|
|
254
|
+
this.labelColors.clear();
|
|
255
|
+
for (const [label, color] of this.regionApi.getClassificationColors()) {
|
|
256
|
+
this.labelColors.set(label, color);
|
|
257
|
+
}
|
|
258
|
+
for (const region of regions) {
|
|
259
|
+
if (region.label && region.color) {
|
|
260
|
+
this.labelColors.set(region.label, region.color);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return regions;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Push edits from the editor to the diagram in live-edit mode (no save /
|
|
267
|
+
* cancel buttons). The `_updatingFromEditor` guard is still used so the
|
|
268
|
+
* resulting regionUpdateEvent doesn't bounce back as an external change.
|
|
269
|
+
*/
|
|
270
|
+
setRegionsFromEditor(fillColor) {
|
|
271
|
+
this._updatingFromEditor = true;
|
|
272
|
+
// isRegionSaveOn=true so changes commit to the region store (and the per-image
|
|
273
|
+
// cache) immediately. setAnnotationRegions preserves the intensity-profile
|
|
274
|
+
// lines internally, so editor edits never disturb them.
|
|
275
|
+
this.regionApi.setAnnotationRegions(this.regions, this.showShapeLabel, true, fillColor ?? this.fillColor);
|
|
276
|
+
this._updatingFromEditor = false;
|
|
277
|
+
// The editor's own commit is ignored by the region-update subscription (the
|
|
278
|
+
// _updatingFromEditor guard), so refresh here: a class label typed in the
|
|
279
|
+
// Class column gets added to the list, and the panel counts/order stay in
|
|
280
|
+
// sync after any edit. (jit-ui#70)
|
|
281
|
+
this.syncClassesFromRegions(this.regions);
|
|
282
|
+
this.recomputeClassCounts();
|
|
283
|
+
}
|
|
284
|
+
/** Set one region's outline colour from the per-row picker and commit live.
|
|
285
|
+
* Remembers it as the label's colour so same-class regions added later match
|
|
286
|
+
* (jit-ui#85 — the Region Editor's per-region Color column). */
|
|
287
|
+
changeRegionColor(region, color) {
|
|
288
|
+
if (!color || region.color === color)
|
|
289
|
+
return;
|
|
290
|
+
region.color = color;
|
|
291
|
+
region.colorOverridden = true; // explicit per-region colour — preserve it against preset (re)apply (jit-ui#70)
|
|
292
|
+
if (region.label)
|
|
293
|
+
this.labelColors.set(region.label, color);
|
|
294
|
+
this.setRegionsFromEditor();
|
|
295
|
+
}
|
|
296
|
+
addRectangle() {
|
|
297
|
+
const region = new Region();
|
|
298
|
+
region.bounds = new Rectangle();
|
|
299
|
+
region.bounds.width = 512;
|
|
300
|
+
region.bounds.height = 512;
|
|
301
|
+
region.label = this.activeClass ?? 'Region';
|
|
302
|
+
// id and a non-colliding name are minted by the visualizer's setRegions
|
|
303
|
+
this.regions = [...this.regions, region];
|
|
304
|
+
this.setRegionsFromEditor();
|
|
305
|
+
this.regionsCopy = this.deepClone(this.regions);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Update the label of the region on enter key pressed (when editing a label cell in the table)
|
|
309
|
+
* @param region
|
|
310
|
+
* @param setRegion
|
|
311
|
+
*/
|
|
312
|
+
labelRegionUpdate(region, setRegion = false) {
|
|
313
|
+
if (region.label) {
|
|
314
|
+
// if label doesn't exist
|
|
315
|
+
if (!this.labelColors.has(region.label)) {
|
|
316
|
+
this.labelColors.set(region.label, this.shapeColor);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// update colors of the regions
|
|
320
|
+
for (const reg of this.regions) {
|
|
321
|
+
if (!reg.color) {
|
|
322
|
+
if (reg.label && this.labelColors.has(reg.label)) {
|
|
323
|
+
reg.color = this.labelColors.get(reg.label);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// update labels map
|
|
328
|
+
this.labelColors.clear();
|
|
329
|
+
for (const reg of this.regions) {
|
|
330
|
+
if (reg.label && reg.color) {
|
|
331
|
+
this.labelColors.set(reg.label, reg.color);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (setRegion) {
|
|
335
|
+
this.setRegionsFromEditor();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
xRectUpdate(region, event) {
|
|
339
|
+
const rectangle = region.bounds;
|
|
340
|
+
if (rectangle && rectangle instanceof Rectangle) {
|
|
341
|
+
rectangle.x = event.value;
|
|
342
|
+
this.setRegionsFromEditor();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
yRectUpdate(region, event) {
|
|
346
|
+
const rectangle = region.bounds;
|
|
347
|
+
if (rectangle && rectangle instanceof Rectangle) {
|
|
348
|
+
rectangle.y = event.value;
|
|
349
|
+
this.setRegionsFromEditor();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
widthRectUpdate(region, event) {
|
|
353
|
+
if (event.value === null || event.value === undefined)
|
|
354
|
+
return;
|
|
355
|
+
const rectangle = region.bounds;
|
|
356
|
+
if (rectangle && rectangle instanceof Rectangle) {
|
|
357
|
+
const reg = this.regionsCopy.filter((element) => element.id === region.id);
|
|
358
|
+
if (reg.length) {
|
|
359
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
360
|
+
// @ts-ignore
|
|
361
|
+
const oldWidth = reg[0].bounds.width;
|
|
362
|
+
const diffWidth = event.value - oldWidth;
|
|
363
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
364
|
+
// @ts-ignore
|
|
365
|
+
rectangle.x = Math.round(reg[0].bounds.x - diffWidth / 2);
|
|
366
|
+
}
|
|
367
|
+
rectangle.width = event.value;
|
|
368
|
+
this.setRegionsFromEditor();
|
|
369
|
+
this.regionsCopy = this.deepClone(this.regions);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
heightRectUpdate(region, event) {
|
|
373
|
+
if (event.value === null || event.value === undefined)
|
|
374
|
+
return;
|
|
375
|
+
const rectangle = region.bounds;
|
|
376
|
+
if (rectangle && rectangle instanceof Rectangle) {
|
|
377
|
+
const reg = this.regionsCopy.filter((element) => element.id === region.id);
|
|
378
|
+
if (reg.length) {
|
|
379
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
380
|
+
// @ts-ignore
|
|
381
|
+
const oldHeight = reg[0].bounds.height;
|
|
382
|
+
const diffHeight = event.value - oldHeight;
|
|
383
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
384
|
+
// @ts-ignore
|
|
385
|
+
rectangle.y = Math.round(reg[0].bounds.y - diffHeight / 2);
|
|
386
|
+
}
|
|
387
|
+
rectangle.height = event.value;
|
|
388
|
+
this.setRegionsFromEditor();
|
|
389
|
+
this.regionsCopy = this.deepClone(this.regions);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
onPageChange(event) {
|
|
393
|
+
this.paginatorFirst = event.first ?? 0;
|
|
394
|
+
this.paginatorRows = event.rows ?? this.paginatorRows;
|
|
395
|
+
}
|
|
396
|
+
/** Select every region in the table (and highlight them on the diagram). */
|
|
397
|
+
selectAllRegions() {
|
|
398
|
+
this.selectedRegions = [...this.regions];
|
|
399
|
+
this.onSelectionChanged();
|
|
400
|
+
}
|
|
401
|
+
deleteSelectedRegions() {
|
|
402
|
+
if (this.selectedRegions && this.selectedRegions.length > 0) {
|
|
403
|
+
// Filter by id — object identity isn't reliable across re-parses,
|
|
404
|
+
// and name is a user-editable label that may collide.
|
|
405
|
+
const removed = new Set(this.selectedRegions.map((r) => r.id));
|
|
406
|
+
this.regions = this.regions.filter((r) => !removed.has(r.id));
|
|
407
|
+
this.selectedRegions = [];
|
|
408
|
+
this.clampPaginatorFirst();
|
|
409
|
+
this.setRegionsFromEditor();
|
|
410
|
+
// Sync the cleared selection with the plot's highlight state.
|
|
411
|
+
this.onSelectionChanged();
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
clearAllRegions() {
|
|
415
|
+
this.confirmationService.confirm({
|
|
416
|
+
key: 'positionDialog',
|
|
417
|
+
message: 'Are you sure you want to delete all regions?',
|
|
418
|
+
accept: () => {
|
|
419
|
+
this.regions = [];
|
|
420
|
+
this.selectedRegions = [];
|
|
421
|
+
this.paginatorFirst = 0;
|
|
422
|
+
this.setRegionsFromEditor();
|
|
423
|
+
this.onSelectionChanged();
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
addPolygon() {
|
|
428
|
+
const region = new Region();
|
|
429
|
+
region.bounds = new Polygon();
|
|
430
|
+
region.label = this.activeClass ?? 'Region';
|
|
431
|
+
region.bounds.ypoints = [0, 0, 0];
|
|
432
|
+
region.bounds.xpoints = [0, 0, 0];
|
|
433
|
+
region.bounds.coordinates = [
|
|
434
|
+
[0, 0],
|
|
435
|
+
[0, 0],
|
|
436
|
+
[0, 0],
|
|
437
|
+
];
|
|
438
|
+
region.bounds.npoints = 3;
|
|
439
|
+
this.regions = [...this.regions, region];
|
|
440
|
+
this.setRegionsFromEditor();
|
|
441
|
+
this.regionsCopy = this.deepClone(this.regions);
|
|
442
|
+
}
|
|
443
|
+
deleteRegion(shapeIdx) {
|
|
444
|
+
const removed = this.regions[shapeIdx];
|
|
445
|
+
this.regions = this.regions.filter((_, i) => i !== shapeIdx);
|
|
446
|
+
if (removed) {
|
|
447
|
+
this.selectedRegions = this.selectedRegions.filter((r) => r.id !== removed.id);
|
|
448
|
+
}
|
|
449
|
+
this.clampPaginatorFirst();
|
|
450
|
+
this.setRegionsFromEditor();
|
|
451
|
+
// Re-emit the (possibly trimmed) selection so the plot's highlight stays
|
|
452
|
+
// in sync with what's still in the table.
|
|
453
|
+
this.onSelectionChanged();
|
|
454
|
+
}
|
|
455
|
+
clampPaginatorFirst() {
|
|
456
|
+
const maxFirst = Math.max(0, Math.floor((this.regions.length - 1) / this.paginatorRows) * this.paginatorRows);
|
|
457
|
+
if (this.paginatorFirst > maxFirst) {
|
|
458
|
+
this.paginatorFirst = maxFirst;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
changeShowShapeLabel(showLabel) {
|
|
462
|
+
this.setRegionsFromEditor();
|
|
463
|
+
}
|
|
464
|
+
isEditingLabel(region) {
|
|
465
|
+
return this.editingLabelRegions.has(region);
|
|
466
|
+
}
|
|
467
|
+
startEditLabel(region, event) {
|
|
468
|
+
event?.stopPropagation(); // don't toggle row selection
|
|
469
|
+
this.editingLabelRegions.add(region);
|
|
470
|
+
}
|
|
471
|
+
stopEditLabel(region, commit, event) {
|
|
472
|
+
event?.stopPropagation();
|
|
473
|
+
this.editingLabelRegions.delete(region);
|
|
474
|
+
if (commit) {
|
|
475
|
+
this.labelRegionUpdate(region, true);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
isRectangle(region) {
|
|
479
|
+
return region.bounds instanceof Rectangle;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Region area for display: in µm² (or mm²) when the image reports a physical
|
|
483
|
+
* pixel size, otherwise in px². Empty string for degenerate regions.
|
|
484
|
+
*/
|
|
485
|
+
regionArea(region) {
|
|
486
|
+
const px = this.areaInPixels(region);
|
|
487
|
+
if (px <= 0)
|
|
488
|
+
return '';
|
|
489
|
+
if (this.mppX && this.mppY && this.mppX > 0 && this.mppY > 0) {
|
|
490
|
+
const um2 = px * this.mppX * this.mppY;
|
|
491
|
+
return um2 >= 1e6 ? `${this.fmtArea(um2 / 1e6)} mm²` : `${this.fmtArea(um2)} µm²`;
|
|
492
|
+
}
|
|
493
|
+
return `${this.fmtArea(px)} px²`;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Choose the physical pixel size (µm/pixel) for area display from the image
|
|
497
|
+
* meta. The calibration may sit on any channel entry — not necessarily [0] —
|
|
498
|
+
* so pick the first entry with a positive mppX (mirrors
|
|
499
|
+
* PlotlyService.currentMpp). Falls back to square pixels (mppY = mppX) when
|
|
500
|
+
* only one axis is reported, so a scaled image shows µm²/mm² rather than px².
|
|
501
|
+
*/
|
|
502
|
+
pickMpp(meta) {
|
|
503
|
+
const m = Array.isArray(meta)
|
|
504
|
+
? (meta.find((e) => e && (e.mppX ?? 0) > 0) ?? meta[0])
|
|
505
|
+
: undefined;
|
|
506
|
+
const mx = m && (m.mppX ?? 0) > 0 ? m.mppX : undefined;
|
|
507
|
+
const my = m && (m.mppY ?? 0) > 0 ? m.mppY : undefined;
|
|
508
|
+
return { mppX: mx, mppY: my ?? mx };
|
|
509
|
+
}
|
|
510
|
+
/** Pixel area: width·height for rectangles, shoelace formula for polygons.
|
|
511
|
+
* Interior rings (holes) are subtracted, so a donut reports the annulus area,
|
|
512
|
+
* not the filled exterior (jit-ui#85). */
|
|
513
|
+
areaInPixels(region) {
|
|
514
|
+
const b = region.bounds;
|
|
515
|
+
if (b instanceof Rectangle)
|
|
516
|
+
return Math.abs(b.width * b.height);
|
|
517
|
+
if (b instanceof Polygon)
|
|
518
|
+
return this.polygonArea(b);
|
|
519
|
+
// Multi-part region: sum each part's (exterior − holes) area (jit-ui#85).
|
|
520
|
+
if (b instanceof MultiPolygon) {
|
|
521
|
+
return b.polygons.reduce((sum, p) => sum + this.polygonArea(p), 0);
|
|
522
|
+
}
|
|
523
|
+
return 0;
|
|
524
|
+
}
|
|
525
|
+
/** Polygon area: exterior shoelace minus each interior ring (hole). */
|
|
526
|
+
polygonArea(p) {
|
|
527
|
+
if ((p.xpoints?.length ?? 0) < 3)
|
|
528
|
+
return 0;
|
|
529
|
+
let a = this.ringArea(p.xpoints, p.ypoints);
|
|
530
|
+
if (p.holes) {
|
|
531
|
+
for (const ring of p.holes)
|
|
532
|
+
a -= this.ringArea(ring.map(pt => pt[0]), ring.map(pt => pt[1]));
|
|
533
|
+
}
|
|
534
|
+
return Math.max(0, a);
|
|
535
|
+
}
|
|
536
|
+
/** Absolute shoelace area of a single ring. */
|
|
537
|
+
ringArea(xs, ys) {
|
|
538
|
+
const n = xs?.length ?? 0;
|
|
539
|
+
if (n < 3)
|
|
540
|
+
return 0;
|
|
541
|
+
let a = 0;
|
|
542
|
+
for (let i = 0, j = n - 1; i < n; j = i++)
|
|
543
|
+
a += (xs[j] + xs[i]) * (ys[j] - ys[i]);
|
|
544
|
+
return Math.abs(a / 2);
|
|
545
|
+
}
|
|
546
|
+
fmtArea(n) {
|
|
547
|
+
return (n >= 1000 ? Math.round(n) : Math.round(n * 100) / 100).toLocaleString();
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* This method rounds all the rectangle regions lengths to multiple of 512
|
|
551
|
+
*/
|
|
552
|
+
roundRectangleLengths() {
|
|
553
|
+
for (const region of this.regions) {
|
|
554
|
+
if (region.bounds instanceof Rectangle) {
|
|
555
|
+
const rect = region.bounds;
|
|
556
|
+
rect.height = Math.round(rect.height / 512) * 512;
|
|
557
|
+
rect.width = Math.round(rect.width / 512) * 512;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
this.setRegionsFromEditor();
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Stop key arrow event propagation
|
|
564
|
+
* @param event
|
|
565
|
+
*/
|
|
566
|
+
disableArrowKeys(event) {
|
|
567
|
+
if (event.key === 'ArrowDown' ||
|
|
568
|
+
event.key === 'Down' ||
|
|
569
|
+
event.key === 'ArrowUp' ||
|
|
570
|
+
event.key === 'Up' ||
|
|
571
|
+
event.key === 'ArrowLeft' ||
|
|
572
|
+
event.key === 'Left' ||
|
|
573
|
+
event.key === 'ArrowRight' ||
|
|
574
|
+
event.key === 'Right') {
|
|
575
|
+
event.stopPropagation();
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
changeShapeColor(event) {
|
|
579
|
+
this.shapeColor = event.value;
|
|
580
|
+
for (const region of this.regions) {
|
|
581
|
+
if (region.label === this.selectedLabelColor && this.labelColors.has(region.label)) {
|
|
582
|
+
this.labelColors.set(region.label, this.shapeColor);
|
|
583
|
+
region.color = this.shapeColor;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
this.setRegionsFromEditor(this.fillColor);
|
|
587
|
+
}
|
|
588
|
+
showHelp() {
|
|
589
|
+
this.displayHelpDialog = true;
|
|
590
|
+
}
|
|
591
|
+
/** Open the colour dialog for the currently-selected region(s), building one
|
|
592
|
+
* colour picker per unique class in the selection. Each picker is seeded with
|
|
593
|
+
* the class's current colour (the label→colour map, falling back to the first
|
|
594
|
+
* selected region of that class). */
|
|
595
|
+
openColorDialog() {
|
|
596
|
+
if (!this.selectedRegions?.length)
|
|
597
|
+
return;
|
|
598
|
+
const seedByLabel = new Map();
|
|
599
|
+
for (const region of this.selectedRegions) {
|
|
600
|
+
const label = region.label?.trim() ?? '';
|
|
601
|
+
if (seedByLabel.has(label))
|
|
602
|
+
continue;
|
|
603
|
+
seedByLabel.set(label, (label ? this.labelColors.get(label) : undefined) ?? region.color ?? this.shapeColor);
|
|
604
|
+
}
|
|
605
|
+
this.classColorEdits = [...seedByLabel].map(([label, color]) => ({ label, color }));
|
|
606
|
+
this.showColorDialog = true;
|
|
607
|
+
}
|
|
608
|
+
/** Apply each class's chosen colour to the selected regions of that class and
|
|
609
|
+
* commit live. Remembers the colour per class so same-class regions added
|
|
610
|
+
* later match. */
|
|
611
|
+
applyColorToSelected() {
|
|
612
|
+
const colorByLabel = new Map(this.classColorEdits.map((e) => [e.label, e.color]));
|
|
613
|
+
for (const region of this.selectedRegions ?? []) {
|
|
614
|
+
const label = region.label?.trim() ?? '';
|
|
615
|
+
const color = colorByLabel.get(label);
|
|
616
|
+
if (!color)
|
|
617
|
+
continue;
|
|
618
|
+
region.color = color;
|
|
619
|
+
region.colorOverridden = true; // explicit colour — preserve against preset (re)apply (jit-ui#70)
|
|
620
|
+
if (label)
|
|
621
|
+
this.labelColors.set(label, color);
|
|
622
|
+
}
|
|
623
|
+
this.setRegionsFromEditor();
|
|
624
|
+
this.showColorDialog = false;
|
|
625
|
+
}
|
|
626
|
+
// ── annotation-class presets (jit-ui#70) ─────────────────────────────
|
|
627
|
+
/** Resolve the display colour for a class name (preset colour or deterministic fallback). */
|
|
628
|
+
colorForName(name) {
|
|
629
|
+
return name ? colorForLabel(name, this.presetSet) : this.shapeColor;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Ensure every class label present in the loaded regions exists in the preset
|
|
633
|
+
* list, auto-adding any that are missing (using the region's already-resolved
|
|
634
|
+
* colour). This makes classes carried in a loaded/imported GeoJSON show up as
|
|
635
|
+
* chips + Class-dropdown options (a p-dropdown can't display a value that isn't
|
|
636
|
+
* an option) and lets the user manage them. The placeholder 'legend' and empty
|
|
637
|
+
* labels are ignored. (jit-ui#70)
|
|
638
|
+
*/
|
|
639
|
+
syncClassesFromRegions(regions) {
|
|
640
|
+
const normalized = this.presetSet.matchMode === 'normalized';
|
|
641
|
+
const keyOf = (l) => (normalized ? l.trim().toLowerCase() : l);
|
|
642
|
+
const known = new Set(this.presetSet.classes.map((c) => keyOf(c.name)));
|
|
643
|
+
const added = new Set();
|
|
644
|
+
for (const r of regions) {
|
|
645
|
+
const label = r.label?.trim();
|
|
646
|
+
if (!label || label === 'legend')
|
|
647
|
+
continue;
|
|
648
|
+
const k = keyOf(label);
|
|
649
|
+
if (known.has(k) || added.has(k))
|
|
650
|
+
continue;
|
|
651
|
+
added.add(k);
|
|
652
|
+
this.regionApi.upsertClass({
|
|
653
|
+
name: label,
|
|
654
|
+
color: r.color || colorForLabel(label, this.presetSet),
|
|
655
|
+
source: 'auto',
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
// ── Docked Classes panel (jit-ui#70) ─────────────────────────────────
|
|
660
|
+
/** Rebuild the per-class region counts shown in the panel. */
|
|
661
|
+
recomputeClassCounts() {
|
|
662
|
+
const normalized = this.presetSet.matchMode === 'normalized';
|
|
663
|
+
const counts = new Map();
|
|
664
|
+
for (const r of this.regions) {
|
|
665
|
+
if (!r.label)
|
|
666
|
+
continue;
|
|
667
|
+
const k = normalized ? r.label.trim().toLowerCase() : r.label;
|
|
668
|
+
counts.set(k, (counts.get(k) ?? 0) + 1);
|
|
669
|
+
}
|
|
670
|
+
this.classCounts = counts;
|
|
671
|
+
this.updateDisplayClasses();
|
|
672
|
+
}
|
|
673
|
+
/** Rebuild the panel's display order — most-used classes first, then by name. */
|
|
674
|
+
updateDisplayClasses() {
|
|
675
|
+
this.displayClasses = [...this.presetSet.classes].sort((a, b) => {
|
|
676
|
+
const d = this.classCount(b.name) - this.classCount(a.name);
|
|
677
|
+
return d !== 0 ? d : a.name.localeCompare(b.name);
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
/** Number of regions currently using a class (keyed by the active match mode). */
|
|
681
|
+
classCount(name) {
|
|
682
|
+
const k = this.presetSet.matchMode === 'normalized' ? name.trim().toLowerCase() : name;
|
|
683
|
+
return this.classCounts.get(k) ?? 0;
|
|
684
|
+
}
|
|
685
|
+
/** Recolour a class from its panel swatch and repaint its (non-overridden) regions. */
|
|
686
|
+
setClassColor(name, color) {
|
|
687
|
+
if (!color)
|
|
688
|
+
return;
|
|
689
|
+
this.regionApi.setClassificationColor(name, color);
|
|
690
|
+
this.setRegionsFromEditor();
|
|
691
|
+
}
|
|
692
|
+
/** Tooltip for a class's delete button in the panel. */
|
|
693
|
+
deleteClassTooltip(name) {
|
|
694
|
+
const inUse = this.classCount(name) > 0;
|
|
695
|
+
if (name === this.defaultClassName && inUse)
|
|
696
|
+
return 'The default class cannot be removed while in use';
|
|
697
|
+
return inUse ? 'Remove class — its regions revert to Region' : 'Remove class';
|
|
698
|
+
}
|
|
699
|
+
/** Remove a class. Any regions still using it fall back to the default
|
|
700
|
+
* "Region" class, so they aren't orphaned (and the class isn't immediately
|
|
701
|
+
* re-added from its regions by syncClassesFromRegions). */
|
|
702
|
+
deleteClass(name) {
|
|
703
|
+
this.reassignRegionsToDefaultClass([name]);
|
|
704
|
+
this.regionApi.removeClass(name);
|
|
705
|
+
if (this.activeClass === name)
|
|
706
|
+
this.activeClass = null;
|
|
707
|
+
this.setRegionsFromEditor();
|
|
708
|
+
}
|
|
709
|
+
/** Regions labelled with any of `removedNames` revert to the default
|
|
710
|
+
* {@link defaultClassName} ("Region"): its class colour, override cleared.
|
|
711
|
+
* Skips the default itself so deleting "Region" can't self-reassign. (jit-ui#70) */
|
|
712
|
+
reassignRegionsToDefaultClass(removedNames) {
|
|
713
|
+
const norm = this.presetSet.matchMode === 'normalized';
|
|
714
|
+
const keyOf = (s) => (norm ? s.trim().toLowerCase() : s);
|
|
715
|
+
const removed = new Set(removedNames.map(keyOf));
|
|
716
|
+
removed.delete(keyOf(this.defaultClassName));
|
|
717
|
+
if (!removed.size)
|
|
718
|
+
return;
|
|
719
|
+
const color = colorForLabel(this.defaultClassName, this.presetSet);
|
|
720
|
+
for (const r of this.regions) {
|
|
721
|
+
if (r.label && removed.has(keyOf(r.label))) {
|
|
722
|
+
r.label = this.defaultClassName;
|
|
723
|
+
r.colorOverridden = false;
|
|
724
|
+
r.color = color;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
/** Stamp a class (and its preset/fallback colour) onto one region from the Class
|
|
729
|
+
* dropdown; choosing a class opts the region back into the preset colour. */
|
|
730
|
+
applyPresetToRegion(region, className) {
|
|
731
|
+
const name = (className ?? '').trim();
|
|
732
|
+
region.label = name;
|
|
733
|
+
region.colorOverridden = false;
|
|
734
|
+
if (name)
|
|
735
|
+
region.color = colorForLabel(name, this.presetSet);
|
|
736
|
+
this.setRegionsFromEditor();
|
|
737
|
+
}
|
|
738
|
+
/** Make a class active (used for new regions) and, if rows are selected, apply
|
|
739
|
+
* it to them in one commit. */
|
|
740
|
+
selectActiveClass(name) {
|
|
741
|
+
this.activeClass = name;
|
|
742
|
+
this.applyClassToSelected(name);
|
|
743
|
+
}
|
|
744
|
+
/** Apply a class (label + preset/fallback colour) to the current selection and commit. */
|
|
745
|
+
applyClassToSelected(name) {
|
|
746
|
+
const selected = this.selectedRegions ?? [];
|
|
747
|
+
if (!selected.length)
|
|
748
|
+
return;
|
|
749
|
+
for (const r of selected) {
|
|
750
|
+
r.label = name;
|
|
751
|
+
r.colorOverridden = false;
|
|
752
|
+
r.color = colorForLabel(name, this.presetSet);
|
|
753
|
+
}
|
|
754
|
+
this.setRegionsFromEditor();
|
|
755
|
+
}
|
|
756
|
+
/** "Edit class on selected rows" popup — apply the chosen existing class. */
|
|
757
|
+
applyBulkClass() {
|
|
758
|
+
if (!this.bulkClass)
|
|
759
|
+
return;
|
|
760
|
+
this.applyClassToSelected(this.bulkClass);
|
|
761
|
+
this.overlayPanel?.hide();
|
|
762
|
+
}
|
|
763
|
+
/** "Edit class on selected rows" popup — add a new class and set it on the selection. */
|
|
764
|
+
addAndApplyBulkClass() {
|
|
765
|
+
const name = this.newBulkClass.trim();
|
|
766
|
+
if (!name)
|
|
767
|
+
return;
|
|
768
|
+
const norm = this.presetSet.matchMode === 'normalized';
|
|
769
|
+
const exists = this.presetSet.classes.some((c) => norm ? c.name.trim().toLowerCase() === name.toLowerCase() : c.name === name);
|
|
770
|
+
if (!exists) {
|
|
771
|
+
this.regionApi.upsertClass({ name, color: colorForLabel(name, this.presetSet), source: 'user' });
|
|
772
|
+
}
|
|
773
|
+
this.applyClassToSelected(name);
|
|
774
|
+
this.bulkClass = name;
|
|
775
|
+
this.newBulkClass = '';
|
|
776
|
+
this.overlayPanel?.hide();
|
|
777
|
+
}
|
|
778
|
+
// ── Manage-classes dialog ──
|
|
779
|
+
openManageDialog() {
|
|
780
|
+
this.presetDraft = this.clonePresetSet(this.presetSet);
|
|
781
|
+
this.showManageDialog = true;
|
|
782
|
+
}
|
|
783
|
+
clonePresetSet(s) {
|
|
784
|
+
return {
|
|
785
|
+
classes: (s.classes ?? []).map((c) => ({ ...c })),
|
|
786
|
+
fallbackPalette: [...(s.fallbackPalette ?? [])],
|
|
787
|
+
autoPromote: !!s.autoPromote,
|
|
788
|
+
matchMode: s.matchMode === 'normalized' ? 'normalized' : 'exact',
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
addPresetClass() {
|
|
792
|
+
this.presetDraft?.classes.push({ name: '', color: '#888888', source: 'user' });
|
|
793
|
+
}
|
|
794
|
+
removePresetClass(i) {
|
|
795
|
+
this.presetDraft?.classes.splice(i, 1);
|
|
796
|
+
}
|
|
797
|
+
addFallbackColor() {
|
|
798
|
+
this.presetDraft?.fallbackPalette.push('#888888');
|
|
799
|
+
}
|
|
800
|
+
removeFallbackColor(i) {
|
|
801
|
+
this.presetDraft?.fallbackPalette.splice(i, 1);
|
|
802
|
+
}
|
|
803
|
+
/** Commit the draft: drop blank/duplicate names, persist, and recolour
|
|
804
|
+
* non-overridden regions with the updated presets. */
|
|
805
|
+
applyManageDialog(close) {
|
|
806
|
+
if (!this.presetDraft)
|
|
807
|
+
return;
|
|
808
|
+
// De-duplicate using the active match mode's key so normalized mode can't keep
|
|
809
|
+
// both "Tumor" and "tumor" (which would collide at runtime in findPreset()).
|
|
810
|
+
const norm = this.presetDraft.matchMode === 'normalized';
|
|
811
|
+
const seen = new Set();
|
|
812
|
+
const classes = [];
|
|
813
|
+
for (const c of this.presetDraft.classes) {
|
|
814
|
+
const name = (c.name ?? '').trim();
|
|
815
|
+
const key = norm ? name.toLowerCase() : name;
|
|
816
|
+
if (!name || seen.has(key))
|
|
817
|
+
continue;
|
|
818
|
+
seen.add(key);
|
|
819
|
+
classes.push({ ...c, name });
|
|
820
|
+
}
|
|
821
|
+
this.presetDraft.classes = classes;
|
|
822
|
+
// Classes dropped in the dialog: their regions revert to the default "Region"
|
|
823
|
+
// class (else syncClassesFromRegions would just re-add them) (jit-ui#70).
|
|
824
|
+
const keyOf = (s) => (norm ? s.trim().toLowerCase() : s);
|
|
825
|
+
const kept = new Set(classes.map((c) => keyOf(c.name)));
|
|
826
|
+
const removed = this.presetSet.classes
|
|
827
|
+
.map((c) => c.name)
|
|
828
|
+
.filter((n) => !kept.has(keyOf(n)));
|
|
829
|
+
this.reassignRegionsToDefaultClass(removed);
|
|
830
|
+
this.regionApi.setPresetSet(this.presetDraft);
|
|
831
|
+
this.setRegionsFromEditor(); // recolour existing (non-overridden) regions from the new presets
|
|
832
|
+
if (close)
|
|
833
|
+
this.showManageDialog = false;
|
|
834
|
+
}
|
|
835
|
+
resetPresetsToDefaults() {
|
|
836
|
+
this.regionApi.resetPresets();
|
|
837
|
+
this.presetDraft = this.clonePresetSet(this.regionApi.getPresetSet());
|
|
838
|
+
this.setRegionsFromEditor();
|
|
839
|
+
}
|
|
840
|
+
exportPresets() {
|
|
841
|
+
const json = JSON.stringify(this.regionApi.getPresetSet(), null, 2);
|
|
842
|
+
saveAs(new Blob([json], { type: 'application/json' }), 'annotation-classes.json');
|
|
843
|
+
}
|
|
844
|
+
importPresets(event) {
|
|
845
|
+
const input = event.target;
|
|
846
|
+
const file = input.files?.[0];
|
|
847
|
+
if (!file)
|
|
848
|
+
return;
|
|
849
|
+
const reader = new FileReader();
|
|
850
|
+
reader.onload = (e) => {
|
|
851
|
+
try {
|
|
852
|
+
const set = JSON.parse(e.target.result);
|
|
853
|
+
this.regionApi.setPresetSet(set);
|
|
854
|
+
this.presetDraft = this.clonePresetSet(this.regionApi.getPresetSet());
|
|
855
|
+
this.setRegionsFromEditor();
|
|
856
|
+
this.messageService.add({ key: 'app-toast', severity: 'success',
|
|
857
|
+
summary: 'Classes imported', detail: 'Annotation classes loaded.' });
|
|
858
|
+
}
|
|
859
|
+
catch (err) {
|
|
860
|
+
this.messageService.add({ key: 'app-toast', severity: 'error',
|
|
861
|
+
summary: 'Import failed', detail: `${err}` });
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
reader.readAsText(file);
|
|
865
|
+
}
|
|
866
|
+
importRois(event) {
|
|
867
|
+
const input = event.target;
|
|
868
|
+
const file = input.files?.[0];
|
|
869
|
+
if (!file)
|
|
870
|
+
return;
|
|
871
|
+
const reader = new FileReader();
|
|
872
|
+
reader.onload = (e) => {
|
|
873
|
+
const fileContent = e.target.result;
|
|
874
|
+
try {
|
|
875
|
+
this.regions = this.regionApi.importRegions(fileContent);
|
|
876
|
+
// set label colors
|
|
877
|
+
for (const region of this.regions) {
|
|
878
|
+
this.labelRegionUpdate(region, false);
|
|
879
|
+
}
|
|
880
|
+
this.syncClassesFromRegions(this.regions);
|
|
881
|
+
this.setRegionsFromEditor();
|
|
882
|
+
}
|
|
883
|
+
catch (event) {
|
|
884
|
+
this.messageService.add({
|
|
885
|
+
severity: 'error',
|
|
886
|
+
summary: 'Error importing the file',
|
|
887
|
+
detail: `${event}`,
|
|
888
|
+
});
|
|
889
|
+
console.error('Error reading the file: ' + event);
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
reader.readAsText(file);
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Regions to serialize on save/export. For a single-file z-stack the store
|
|
896
|
+
* keeps only the current slice live, so pull EVERY slice's annotations (each
|
|
897
|
+
* tagged with its zero-based Region.z) to write one combined z-indexed
|
|
898
|
+
* geojson (jit-ui#93). Otherwise (single-plane image, or a folder stack whose
|
|
899
|
+
* slices save to their own per-slice files) the current set. (jit-ui#93)
|
|
900
|
+
*/
|
|
901
|
+
regionsForSave() {
|
|
902
|
+
if (this.regionApi.isStackMode() && this.regionApi.getStackSaveLayout() === 'combined') {
|
|
903
|
+
return this.regionApi.getSliceAnnotationRegions();
|
|
904
|
+
}
|
|
905
|
+
return this.regions;
|
|
906
|
+
}
|
|
907
|
+
exportRois() {
|
|
908
|
+
if (!this.regionsForSave().length)
|
|
909
|
+
return;
|
|
910
|
+
const name = this.regionIo.getSelectedFileName();
|
|
911
|
+
const stem = name ? name.substring(0, name.lastIndexOf('.')) : 'rois';
|
|
912
|
+
this.exportFilename = `${stem}.geojson`;
|
|
913
|
+
this.showExportDialog = true;
|
|
914
|
+
}
|
|
915
|
+
confirmExport() {
|
|
916
|
+
const filename = this.exportFilename.trim();
|
|
917
|
+
const regions = this.regionsForSave();
|
|
918
|
+
if (!filename || !regions.length)
|
|
919
|
+
return;
|
|
920
|
+
this.showExportDialog = false;
|
|
921
|
+
const jsonString = this.regionApi.getGeoJsonString(regions);
|
|
922
|
+
const blob = new Blob([jsonString], { type: 'application/json' });
|
|
923
|
+
saveAs(blob, filename);
|
|
924
|
+
}
|
|
925
|
+
/** Open the "Save mask" dialog, seeded with `<image-stem>_mask.png`. */
|
|
926
|
+
openSaveMaskDialog() {
|
|
927
|
+
if (!this.regions.length)
|
|
928
|
+
return;
|
|
929
|
+
const name = this.regionIo.getSelectedFileName();
|
|
930
|
+
// Strip the extension only when there is one; an extension-less name keeps
|
|
931
|
+
// its whole stem (a leading-dot dotfile is treated as having no extension)
|
|
932
|
+
// so we never produce a bare "_mask.png".
|
|
933
|
+
const dot = name ? name.lastIndexOf('.') : -1;
|
|
934
|
+
const stem = name ? (dot > 0 ? name.substring(0, dot) : name) : 'regions';
|
|
935
|
+
this.saveMaskFilename = `${stem}_mask.png`;
|
|
936
|
+
this.maskMode = 'binary';
|
|
937
|
+
this.showSaveMaskDialog = true;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Rasterize the regions to the chosen mask type and download as a PNG. The
|
|
941
|
+
* heavy work (full-res rasterize + PNG encode) runs in a Web Worker so the UI
|
|
942
|
+
* never freezes on large/whole-slide images and the job can be cancelled
|
|
943
|
+
* (jit-ui#95). The dialog stays open showing a progress bar until done.
|
|
944
|
+
*/
|
|
945
|
+
confirmSaveMask() {
|
|
946
|
+
const filename = this.saveMaskFilename.trim();
|
|
947
|
+
if (!filename || !this.regions.length || this.maskBusy)
|
|
948
|
+
return;
|
|
949
|
+
const size = this.regionApi.getMaskImageSize();
|
|
950
|
+
if (!size) {
|
|
951
|
+
this.maskError('No image size is available, so the mask cannot be sized.');
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
// Whole-slide images exceed the browser's typed-array/memory limits, so cap
|
|
955
|
+
// the mask to a safe pixel budget and scale the geometry to match.
|
|
956
|
+
const scale = maskScaleFor(size.width, size.height);
|
|
957
|
+
const width = Math.max(1, Math.round(size.width * scale));
|
|
958
|
+
const height = Math.max(1, Math.round(size.height * scale));
|
|
959
|
+
if (scale < 1) {
|
|
960
|
+
this.messageService.add({
|
|
961
|
+
key: 'app-toast',
|
|
962
|
+
severity: 'info',
|
|
963
|
+
summary: 'Mask downscaled',
|
|
964
|
+
detail: `Image too large for a full-resolution mask; saving at ${width}×${height}.`,
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
const regions = this.regions.map((r) => scaleParts(regionToParts(r), scale));
|
|
968
|
+
this.pendingMaskFilename = filename;
|
|
969
|
+
this.maskBusy = true;
|
|
970
|
+
this.maskEncoding = false;
|
|
971
|
+
this.maskProgress = 0;
|
|
972
|
+
const payload = {
|
|
973
|
+
width, height,
|
|
974
|
+
originalWidth: size.width, originalHeight: size.height, scale,
|
|
975
|
+
mode: this.maskMode,
|
|
976
|
+
sourceName: this.regionIo.getSelectedFileName(),
|
|
977
|
+
regions,
|
|
978
|
+
};
|
|
979
|
+
// createMaskWorker() is async (the worker module is imported dynamically so
|
|
980
|
+
// its import.meta never reaches the CommonJS test compile). Wire up once it
|
|
981
|
+
// resolves — unless the user cancelled while it was loading.
|
|
982
|
+
this.createMaskWorker().then((worker) => {
|
|
983
|
+
if (!this.maskBusy) {
|
|
984
|
+
worker.terminate();
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
this.maskWorker = worker;
|
|
988
|
+
worker.onmessage = ({ data }) => {
|
|
989
|
+
switch (data?.type) {
|
|
990
|
+
case 'progress':
|
|
991
|
+
this.maskProgress = data.total ? Math.round((data.done / data.total) * 100) : 0;
|
|
992
|
+
break;
|
|
993
|
+
case 'encoding':
|
|
994
|
+
this.maskEncoding = true;
|
|
995
|
+
break;
|
|
996
|
+
case 'done':
|
|
997
|
+
this.finishMask(new Blob([data.png], { type: 'image/png' }));
|
|
998
|
+
break;
|
|
999
|
+
case 'error':
|
|
1000
|
+
this.maskError(data.error || 'The mask could not be generated.');
|
|
1001
|
+
break;
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
worker.onerror = () => this.maskError('The mask worker failed.');
|
|
1005
|
+
worker.postMessage(payload);
|
|
1006
|
+
}).catch(() => this.maskError('The mask worker failed to start.'));
|
|
1007
|
+
}
|
|
1008
|
+
/** Cancel an in-progress mask export: terminate the worker and reset state. */
|
|
1009
|
+
cancelSaveMask() {
|
|
1010
|
+
this.teardownMaskWorker();
|
|
1011
|
+
this.maskBusy = false;
|
|
1012
|
+
this.maskEncoding = false;
|
|
1013
|
+
this.maskProgress = 0;
|
|
1014
|
+
}
|
|
1015
|
+
/** Worker factory — async so the worker module (and its `import.meta.url`,
|
|
1016
|
+
* which the CommonJS test compile rejects) is loaded via a dynamic import,
|
|
1017
|
+
* exactly like the segmentation worker. Overridable in tests. */
|
|
1018
|
+
async createMaskWorker() {
|
|
1019
|
+
const { createMaskWorker } = await import('./mask-worker');
|
|
1020
|
+
return createMaskWorker();
|
|
1021
|
+
}
|
|
1022
|
+
finishMask(blob) {
|
|
1023
|
+
this.teardownMaskWorker();
|
|
1024
|
+
this.maskBusy = false;
|
|
1025
|
+
this.maskEncoding = false;
|
|
1026
|
+
this.showSaveMaskDialog = false;
|
|
1027
|
+
saveAs(blob, this.pendingMaskFilename);
|
|
1028
|
+
}
|
|
1029
|
+
maskError(detail) {
|
|
1030
|
+
this.teardownMaskWorker();
|
|
1031
|
+
this.maskBusy = false;
|
|
1032
|
+
this.maskEncoding = false;
|
|
1033
|
+
this.messageService.add({
|
|
1034
|
+
key: 'app-toast',
|
|
1035
|
+
severity: 'error',
|
|
1036
|
+
summary: 'Could not create mask',
|
|
1037
|
+
detail,
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
teardownMaskWorker() {
|
|
1041
|
+
this.maskWorker?.terminate();
|
|
1042
|
+
this.maskWorker = undefined;
|
|
1043
|
+
}
|
|
1044
|
+
persistRegions() {
|
|
1045
|
+
// Folder stack: write each slice's regions back to its own slice-file's
|
|
1046
|
+
// sibling geojson — no single-filename prompt (the filenames are the slice
|
|
1047
|
+
// files'). (jit-ui#93)
|
|
1048
|
+
if (this.regionApi.isStackMode() && this.regionApi.getStackSaveLayout() === 'per-slice-file') {
|
|
1049
|
+
this.saveStackSlices();
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
const name = this.regionIo.getSelectedFileName();
|
|
1053
|
+
if (!name || !this.regionsForSave().length)
|
|
1054
|
+
return;
|
|
1055
|
+
this.saveAsFilename = name.substring(0, name.lastIndexOf('.')) + '.geojson';
|
|
1056
|
+
this.saveAsFileExists = false;
|
|
1057
|
+
this.showSaveAsDialog = true;
|
|
1058
|
+
this._saveAsCheck$.next(this.saveAsFilename);
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Save a folder stack's regions as one geojson per slice-file (jit-ui#93).
|
|
1062
|
+
* Groups the store's per-slice regions by slice index and serializes each on
|
|
1063
|
+
* the default plane (z=0) — each slice-file is itself one plane, and the
|
|
1064
|
+
* loader re-derives the slice index from the file's position in the series.
|
|
1065
|
+
* Slices cleared since load are included (empty) so their file is overwritten.
|
|
1066
|
+
*/
|
|
1067
|
+
saveStackSlices() {
|
|
1068
|
+
const bySlice = this.regionApi.getStackSaveAnnotationSlices();
|
|
1069
|
+
if (!bySlice.size)
|
|
1070
|
+
return;
|
|
1071
|
+
const slices = [];
|
|
1072
|
+
for (const [z, regs] of bySlice) {
|
|
1073
|
+
const flat = regs.map((r) => Object.assign(new Region(), r, { z: 0 }));
|
|
1074
|
+
slices.push({ z, geoJsonStr: this.regionApi.getGeoJsonString(flat) });
|
|
1075
|
+
}
|
|
1076
|
+
this.saveAsBusy = true;
|
|
1077
|
+
this._saveAsSub = this.regionIo.saveSliceGeoJsons(slices).subscribe({
|
|
1078
|
+
next: () => {
|
|
1079
|
+
this.saveAsBusy = false;
|
|
1080
|
+
this.messageService.add({
|
|
1081
|
+
key: 'app-toast',
|
|
1082
|
+
severity: 'success',
|
|
1083
|
+
summary: 'Regions saved',
|
|
1084
|
+
detail: `Saved ROIs for ${slices.length} slice${slices.length === 1 ? '' : 's'}`,
|
|
1085
|
+
});
|
|
1086
|
+
},
|
|
1087
|
+
error: (err) => {
|
|
1088
|
+
this.saveAsBusy = false;
|
|
1089
|
+
this.messageService.add({
|
|
1090
|
+
key: 'app-toast',
|
|
1091
|
+
severity: 'error',
|
|
1092
|
+
summary: 'Error saving regions',
|
|
1093
|
+
detail: `${err?.message ?? err}`,
|
|
1094
|
+
});
|
|
1095
|
+
},
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
checkSaveAsFileExists() {
|
|
1099
|
+
this._saveAsCheck$.next(this.saveAsFilename);
|
|
1100
|
+
}
|
|
1101
|
+
confirmSaveAs() {
|
|
1102
|
+
if (!this.regionIo.getSelectedFileName())
|
|
1103
|
+
return;
|
|
1104
|
+
const filename = this.saveAsFilename.trim();
|
|
1105
|
+
if (!filename)
|
|
1106
|
+
return;
|
|
1107
|
+
const doSave = () => {
|
|
1108
|
+
// Keep the dialog open showing an (indeterminate) progress bar while the
|
|
1109
|
+
// regions serialize and upload, then close on success. Defer one tick so
|
|
1110
|
+
// the bar paints before a large synchronous GeoJSON serialize.
|
|
1111
|
+
this.saveAsBusy = true;
|
|
1112
|
+
this._saveAsTimer = setTimeout(() => {
|
|
1113
|
+
this._saveAsTimer = undefined;
|
|
1114
|
+
let geoJsonStr;
|
|
1115
|
+
try {
|
|
1116
|
+
geoJsonStr = this.regionApi.getGeoJsonString(this.regionsForSave());
|
|
1117
|
+
}
|
|
1118
|
+
catch (err) {
|
|
1119
|
+
this.saveAsBusy = false;
|
|
1120
|
+
this.messageService.add({
|
|
1121
|
+
key: 'app-toast',
|
|
1122
|
+
severity: 'error',
|
|
1123
|
+
summary: 'Error saving regions',
|
|
1124
|
+
detail: `${err?.message ?? err}`,
|
|
1125
|
+
});
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
this._saveAsSub = this.regionIo.saveGeoJson(geoJsonStr, filename).subscribe({
|
|
1129
|
+
next: () => {
|
|
1130
|
+
this.saveAsBusy = false;
|
|
1131
|
+
this.showSaveAsDialog = false;
|
|
1132
|
+
this.messageService.add({
|
|
1133
|
+
key: 'app-toast',
|
|
1134
|
+
severity: 'success',
|
|
1135
|
+
summary: 'Regions saved',
|
|
1136
|
+
detail: `Saved as ${filename}`,
|
|
1137
|
+
});
|
|
1138
|
+
},
|
|
1139
|
+
error: (err) => {
|
|
1140
|
+
this.saveAsBusy = false;
|
|
1141
|
+
this.messageService.add({
|
|
1142
|
+
key: 'app-toast',
|
|
1143
|
+
severity: 'error',
|
|
1144
|
+
summary: 'Error saving regions',
|
|
1145
|
+
detail: `${err.message || err}`,
|
|
1146
|
+
});
|
|
1147
|
+
},
|
|
1148
|
+
});
|
|
1149
|
+
});
|
|
1150
|
+
};
|
|
1151
|
+
if (this.saveAsFileExists) {
|
|
1152
|
+
this.confirmationService.confirm({
|
|
1153
|
+
message: `"${filename}" already exists. Do you want to overwrite it?`,
|
|
1154
|
+
header: 'Confirm Overwrite',
|
|
1155
|
+
icon: 'pi pi-exclamation-triangle',
|
|
1156
|
+
accept: doSave,
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
else {
|
|
1160
|
+
doSave();
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
/** Cancel an in-progress GeoJSON persist: abort the deferred serialize and/or
|
|
1164
|
+
* the in-flight upload, and reset state. */
|
|
1165
|
+
cancelSaveAs() {
|
|
1166
|
+
if (this._saveAsTimer !== undefined) {
|
|
1167
|
+
clearTimeout(this._saveAsTimer);
|
|
1168
|
+
this._saveAsTimer = undefined;
|
|
1169
|
+
}
|
|
1170
|
+
this._saveAsSub?.unsubscribe();
|
|
1171
|
+
this._saveAsSub = undefined;
|
|
1172
|
+
this.saveAsBusy = false;
|
|
1173
|
+
}
|
|
1174
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: RegionEditorComponent, deps: [{ token: REGION_EDITOR_API }, { token: i1.MessageService }, { token: i1.ConfirmationService }, { token: REGION_IO_PORT }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1175
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.9", type: RegionEditorComponent, selector: "region-editor", viewQueries: [{ propertyName: "overlayPanel", first: true, predicate: ["op"], descendants: true }], ngImport: i0, template: "\n<p-toolbar styleClass=\"region-toolbar\">\n <div class=\"p-toolbar-group-start align-items-center flex-wrap gap-1\">\n <p-button icon=\"pi pi-tag\" size=\"small\"\n [text]=\"!showShapeLabel\"\n pTooltip=\"Show region labels.\"\n tooltipPosition=\"bottom\"\n (onClick)=\"showShapeLabel = !showShapeLabel; changeShowShapeLabel(showShapeLabel)\">\n </p-button>\n <span class=\"toolbar-separator\"></span>\n <!-- New regions are now created from the diagram toolbar\n (rectangle, polyline, freeform, wand). -->\n <p-button icon=\"pi pi-upload\" size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n pTooltip=\"Upload and display regions from geojson file.\"\n tooltipPosition=\"bottom\"\n (click)=\"roiFileInput.click()\">\n </p-button>\n <input #roiFileInput type=\"file\" accept=\".geojson,.json\" hidden\n (change)=\"importRois($event); roiFileInput.value = ''\"/>\n <p-button size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n pTooltip=\"Download regions as geojson file.\"\n tooltipPosition=\"bottom\" icon=\"pi pi-download\"\n (click)=\"exportRois()\">\n </p-button>\n <p-button icon=\"pi pi-save\" size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n pTooltip=\"Save regions to server.\"\n tooltipPosition=\"bottom\"\n (click)=\"persistRegions()\"\n [disabled]=\"!regions || !regions.length\">\n </p-button>\n <p-button size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n pTooltip=\"Download mask of the regions as PNG\"\n tooltipPosition=\"bottom\"\n (click)=\"openSaveMaskDialog()\"\n [disabled]=\"!regions || !regions.length\">\n <!-- References the file so Inkscape edits to mask.svg show up directly\n (the lib's assets/** are served at assets/plotting/). The\n toolbar-icon-dark filter recolours the black SVG to the theme blue,\n same as region-editor.svg and the other SVG toolbar icons. -->\n <img src=\"assets/plotting/mask.svg\" class=\"toolbar-icon-dark\"\n style=\"width: 1rem; height: 1rem; vertical-align: middle;\"\n alt=\"Download mask\"/>\n </p-button>\n <p-button label=\"x512\" size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n pTooltip=\"Round all rectangle regions\n to width and height that are multiple of 512\" tooltipPosition=\"bottom\"\n (click)=\"roundRectangleLengths()\">\n </p-button>\n <p-button icon=\"pi pi-check-square\" size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n pTooltip=\"Select all regions\" tooltipPosition=\"bottom\"\n (click)=\"selectAllRegions()\"\n [disabled]=\"!regions || !regions.length\">\n </p-button>\n <p-button icon=\"pi pi-tags\" size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n pTooltip=\"Edit class on selected rows\"\n tooltipPosition=\"bottom\"\n (click)=\"(!selectedRegions || !selectedRegions.length)? op.hide() : op.toggle($event)\"\n [disabled]=\"!selectedRegions || !selectedRegions.length\">\n </p-button>\n <p-button icon=\"pi pi-palette\" size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n pTooltip=\"Edit colour of selected regions\"\n tooltipPosition=\"bottom\"\n (click)=\"openColorDialog()\"\n [disabled]=\"!selectedRegions || !selectedRegions.length\">\n </p-button>\n <p-overlayPanel #op (onHide)=\"bulkClass=''; newBulkClass=''\">\n <div class=\"flex flex-column gap-2\" style=\"width: 18rem\">\n <span class=\"font-medium text-900\">Set class on {{ selectedRegions?.length || 0 }} selected region(s)</span>\n <!-- Selecting a class applies it immediately (choosing an option closes the\n overlay, so there's no separate Apply button). -->\n <p-dropdown styleClass=\"w-full\" appendTo=\"body\" [options]=\"presetSet.classes\"\n optionLabel=\"name\" optionValue=\"name\" [(ngModel)]=\"bulkClass\"\n placeholder=\"Choose a class\u2026\"\n [filter]=\"presetSet.classes.length > 8\" filterBy=\"name\"\n (onChange)=\"applyBulkClass()\">\n <ng-template let-c pTemplate=\"item\">\n <span class=\"dd-value\"><span class=\"chip-swatch\" [style.background]=\"c.color\"></span>{{ c.name }}</span>\n </ng-template>\n </p-dropdown>\n <div class=\"p-inputgroup\">\n <input type=\"text\" pInputText placeholder=\"\u2026or a new class\" [(ngModel)]=\"newBulkClass\"\n (keydown.enter)=\"addAndApplyBulkClass()\"/>\n <button pButton type=\"button\" icon=\"pi pi-plus\" pTooltip=\"Add class & set it\"\n tooltipPosition=\"bottom\" [disabled]=\"!newBulkClass.trim()\"\n (click)=\"addAndApplyBulkClass()\"></button>\n </div>\n </div>\n </p-overlayPanel>\n <p-button icon=\"pi pi-trash\" size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n (click)=\"deleteSelectedRegions()\"\n pTooltip=\"Delete selected regions\" tooltipPosition=\"bottom\"\n [disabled]=\"!selectedRegions || !selectedRegions.length\"></p-button>\n <p-button icon=\"pi pi-times-circle\" size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n (click)=\"clearAllRegions()\"\n pTooltip=\"Delete all regions\" tooltipPosition=\"bottom\"\n [disabled]=\"!regions || !regions.length\"></p-button>\n <p-button icon=\"pi pi-question\" size=\"small\" [text]=\"true\"\n (click)=\"showHelp()\" pTooltip=\"Help\" tooltipPosition=\"bottom\"></p-button>\n </div>\n</p-toolbar>\n\n<!-- Docked \"Classes\" panel (jit-ui#70): the always-visible list of annotation\n classes above the regions table. Click a row to make it the active class\n (new regions inherit it; with rows selected it re-classifies them); recolour\n via the swatch; the badge shows how many regions use the class. \"Manage\"\n opens the full editor (fallback palette, auto-add, matching, import/export). -->\n<div class=\"classes-panel\">\n <div class=\"classes-panel-header\">\n <span class=\"classes-panel-title\">Classes</span>\n <button pButton type=\"button\" icon=\"pi pi-cog\" label=\"Manage\"\n class=\"p-button-sm p-button-text\"\n pTooltip=\"Manage classes \u2014 add / remove, palette, matching, import/export\"\n tooltipPosition=\"bottom\" (click)=\"openManageDialog()\"></button>\n </div>\n <div class=\"classes-panel-body\">\n <div *ngFor=\"let c of displayClasses; trackBy: trackByClassName\"\n class=\"class-row\" [class.active]=\"activeClass === c.name\"\n (click)=\"selectActiveClass(c.name)\"\n [pTooltip]=\"selectedRegions?.length\n ? 'Apply \u201C' + c.name + '\u201D to the selected region(s)'\n : 'Set \u201C' + c.name + '\u201D active \u2014 new regions use it'\"\n tooltipPosition=\"bottom\">\n <span class=\"row-swatch\" (click)=\"$event.stopPropagation()\">\n <hex-color-picker [color]=\"c.color\" (colorChange)=\"setClassColor(c.name, $event)\"></hex-color-picker>\n </span>\n <span class=\"row-name\">{{ c.name }}</span>\n <span class=\"row-count\" [class.zero]=\"classCount(c.name) === 0\">{{ classCount(c.name) }}</span>\n <button pButton type=\"button\" icon=\"pi pi-trash\"\n class=\"p-button-sm p-button-text p-button-warning row-del\"\n [disabled]=\"c.name === defaultClassName && classCount(c.name) > 0\"\n [pTooltip]=\"deleteClassTooltip(c.name)\"\n tooltipPosition=\"bottom\"\n (click)=\"$event.stopPropagation(); deleteClass(c.name)\"></button>\n </div>\n <div *ngIf=\"!displayClasses.length\" class=\"classes-empty\">\n No classes yet \u2014 add one from Manage.\n </div>\n </div>\n</div>\n\n<div class=\"region-editor-content\">\n <p-table #dt [value]=\"regions.slice(paginatorFirst, paginatorFirst + paginatorRows)\"\n [tableStyle]=\"{'width': '100%'}\" [(selection)]=\"selectedRegions\"\n selectionMode=\"multiple\" [metaKeySelection]=\"metaKey\"\n (onRowSelect)=\"onSelectionChanged()\" (onRowUnselect)=\"onSelectionChanged()\"\n [resizableColumns]=\"true\"\n [rowHover]=\"true\" dataKey=\"id\" styleClass=\"p-datatable-sm region-editor-table\"\n [scrollable]=\"true\" scrollHeight=\"100%\">\n <ng-template pTemplate=\"header\">\n <!-- Hide the column header when there are no regions to show. -->\n <tr *ngIf=\"regions.length > 0\">\n <!-- Class column: shrinks to fit \"Region\" by default, grows with longer labels. -->\n <th class=\"class-col-header\" pResizableColumn>Class <p-sortIcon field=\"label\"></p-sortIcon></th>\n <th class=\"surface-col-header\" pResizableColumn>Surface</th>\n <!-- Trash column: just enough for the icon. -->\n <th style=\"width:2rem\"></th>\n </tr>\n </ng-template>\n <ng-template pTemplate=\"body\" let-region let-shapeIdx=\"rowIndex\">\n <tr [pSelectableRow]=\"region\" [pSelectableRowIndex]=\"shapeIdx\">\n <td class=\"class-cell\">\n <ng-container *ngIf=\"!isEditingLabel(region); else editLabelTpl\">\n <span class=\"class-cell-display\">\n <!-- Quick-pick a preset class (its colour follows); the pencil is for\n a free-form name / rename (jit-ui#70). -->\n <p-dropdown class=\"class-dropdown\" appendTo=\"body\"\n [options]=\"presetSet.classes\" optionLabel=\"name\" optionValue=\"name\"\n [ngModel]=\"region.label\" [autoDisplayFirst]=\"false\"\n (onChange)=\"applyPresetToRegion(region, $event.value)\">\n <ng-template pTemplate=\"selectedItem\">\n <span class=\"dd-value\">\n <span class=\"chip-swatch\" [style.background]=\"colorForName(region.label)\"></span>\n <span class=\"class-name\" [pTooltip]=\"region.label\" tooltipPosition=\"bottom\">{{ region.label || '\u2014' }}</span>\n </span>\n </ng-template>\n <ng-template let-c pTemplate=\"item\">\n <span class=\"dd-value\">\n <span class=\"chip-swatch\" [style.background]=\"c.color\"></span>\n <span>{{ c.name }}</span>\n </span>\n </ng-template>\n </p-dropdown>\n <i class=\"pi pi-pencil icon-hover ml-2\"\n pTooltip=\"Rename / free-form label\" tooltipPosition=\"bottom\"\n (click)=\"startEditLabel(region, $event)\"></i>\n </span>\n </ng-container>\n <ng-template #editLabelTpl>\n <span class=\"p-input-icon-right class-cell-edit\">\n <i class=\"pi pi-times icon-hover\"\n pTooltip=\"Close\" tooltipPosition=\"bottom\"\n (click)=\"stopEditLabel(region, false, $event)\"></i>\n <input type=\"text\" pInputText class=\"p-inputtext-sm w-full\"\n [(ngModel)]=\"region.label\"\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"stopEditLabel(region, true, $event)\"\n (keydown.escape)=\"stopEditLabel(region, false, $event)\"/>\n </span>\n </ng-template>\n </td>\n <!-- Surface area (\u00B5m\u00B2/mm\u00B2 when the image is scaled, else px\u00B2). -->\n <td class=\"surface-cell\">{{ regionArea(region) || '\u2014' }}</td>\n <td>\n <button pButton pRipple icon=\"pi pi-trash\"\n\t\t\t\t\t\t\tclass=\"p-button-sm p-button-rounded p-button-text p-button-warning\"\n (click)=\"deleteRegion(paginatorFirst + shapeIdx)\"></button>\n </td>\n </tr>\n </ng-template>\n </p-table>\n</div>\n\n<p-paginator *ngIf=\"regions.length > 0\"\n [rows]=\"paginatorRows\" [totalRecords]=\"regions.length\"\n [first]=\"paginatorFirst\"\n [rowsPerPageOptions]=\"rowsPerPageOptions\"\n [showCurrentPageReport]=\"true\"\n currentPageReportTemplate=\"{first} to {last} of {totalRecords}\"\n (onPageChange)=\"onPageChange($event)\"\n styleClass=\"region-editor-paginator\">\n</p-paginator>\n\n<!-- Live-edit mode: no Save/Cancel \u2014 every edit commits immediately to the\n active image's region cache. -->\n\n\n<p-confirmDialog #cd [style]=\"{width: '24rem', height: '14rem'}\" key=\"positionDialog\"\n [baseZIndex]=\"10000\" rejectButtonStyleClass=\"p-button-outlined\">\n <ng-template pTemplate=\"footer\">\n <button type=\"button\" pButton icon=\"pi pi-check\" label=\"Yes\" (click)=\"cd.accept()\"></button>\n <button type=\"button\" class=\"p-button-outlined\" pButton icon=\"pi pi-times\" label=\"No\"\n (click)=\"cd.reject()\"></button>\n </ng-template>\n</p-confirmDialog>\n\n<p-dialog header=\"Save Regions As\" [(visible)]=\"showSaveAsDialog\" [modal]=\"true\" appendTo=\"body\"\n [style]=\"{width: '30rem'}\" [draggable]=\"false\" [resizable]=\"false\"\n [closable]=\"!saveAsBusy\" [closeOnEscape]=\"!saveAsBusy\">\n <!-- Idle: filename + overwrite warning. -->\n <div *ngIf=\"!saveAsBusy\" class=\"flex flex-column gap-3 pt-2\">\n <label for=\"geojson-filename\" class=\"font-medium\">GeoJSON filename</label>\n <input id=\"geojson-filename\" type=\"text\" pInputText\n [(ngModel)]=\"saveAsFilename\"\n (ngModelChange)=\"checkSaveAsFileExists()\"\n class=\"w-full\" />\n <small *ngIf=\"saveAsFileExists\" class=\"p-error\">\n <i class=\"pi pi-exclamation-triangle mr-1\"></i>\n This file already exists and will be overwritten.\n </small>\n </div>\n\n <!-- Busy: progress while regions serialize + upload. -->\n <div *ngIf=\"saveAsBusy\" class=\"flex flex-column gap-3 pt-2\">\n <span>Saving regions\u2026</span>\n <p-progressBar mode=\"indeterminate\" [style]=\"{height: '0.75rem'}\"></p-progressBar>\n </div>\n\n <ng-template pTemplate=\"footer\">\n <p-button *ngIf=\"!saveAsBusy\" label=\"Cancel\" icon=\"pi pi-times\" [text]=\"true\"\n (onClick)=\"showSaveAsDialog = false\"></p-button>\n <p-button *ngIf=\"!saveAsBusy\" label=\"Save\" icon=\"pi pi-save\"\n [disabled]=\"!saveAsFilename || !saveAsFilename.trim()\"\n (onClick)=\"confirmSaveAs()\"></p-button>\n <p-button *ngIf=\"saveAsBusy\" label=\"Cancel\" icon=\"pi pi-times\" [text]=\"true\"\n (onClick)=\"cancelSaveAs()\"></p-button>\n </ng-template>\n</p-dialog>\n\n<p-dialog header=\"Export Regions\" [(visible)]=\"showExportDialog\" [modal]=\"true\" appendTo=\"body\"\n [style]=\"{width: '30rem'}\" [draggable]=\"false\" [resizable]=\"false\">\n <div class=\"flex flex-column gap-3 pt-2\">\n <label for=\"export-filename\" class=\"font-medium\">GeoJSON filename</label>\n <input id=\"export-filename\" type=\"text\" pInputText\n [(ngModel)]=\"exportFilename\"\n class=\"w-full\" />\n </div>\n <ng-template pTemplate=\"footer\">\n <p-button label=\"Cancel\" icon=\"pi pi-times\" [text]=\"true\"\n (onClick)=\"showExportDialog = false\"></p-button>\n <p-button label=\"Export\" icon=\"pi pi-download\"\n [disabled]=\"!exportFilename || !exportFilename.trim()\"\n (onClick)=\"confirmExport()\"></p-button>\n </ng-template>\n</p-dialog>\n\n<p-dialog header=\"Save Mask\" [(visible)]=\"showSaveMaskDialog\" [modal]=\"true\" appendTo=\"body\"\n [style]=\"{width: '30rem'}\" [draggable]=\"false\" [resizable]=\"false\"\n [closable]=\"!maskBusy\" [closeOnEscape]=\"!maskBusy\">\n <!-- Idle: filename + mask-type form. -->\n <div *ngIf=\"!maskBusy\" class=\"flex flex-column gap-3 pt-2\">\n <label for=\"mask-filename\" class=\"font-medium\">Mask filename</label>\n <input id=\"mask-filename\" type=\"text\" pInputText\n [(ngModel)]=\"saveMaskFilename\"\n class=\"w-full\" />\n <span class=\"font-medium\">Mask type</span>\n <div class=\"flex align-items-center gap-2\">\n <p-radioButton inputId=\"mask-binary\" name=\"maskMode\" value=\"binary\"\n [(ngModel)]=\"maskMode\"></p-radioButton>\n <label for=\"mask-binary\">Binary <span class=\"text-color-secondary\">(regions 1, background 0)</span></label>\n </div>\n <div class=\"flex align-items-center gap-2\">\n <p-radioButton inputId=\"mask-multiclass\" name=\"maskMode\" value=\"multiclass\"\n [(ngModel)]=\"maskMode\"></p-radioButton>\n <label for=\"mask-multiclass\">Multi-class <span class=\"text-color-secondary\">(unique id per region, background 0)</span></label>\n </div>\n </div>\n\n <!-- Busy: progress while the worker rasterizes + encodes the PNG. -->\n <div *ngIf=\"maskBusy\" class=\"flex flex-column gap-3 pt-2\">\n <span>{{ maskEncoding ? 'Encoding PNG\u2026' : 'Rasterizing regions\u2026' }}</span>\n <p-progressBar [mode]=\"maskEncoding ? 'indeterminate' : 'determinate'\"\n [value]=\"maskProgress\"\n [style]=\"{height: '0.75rem'}\"></p-progressBar>\n </div>\n\n <ng-template pTemplate=\"footer\">\n <p-button *ngIf=\"!maskBusy\" label=\"Cancel\" icon=\"pi pi-times\" [text]=\"true\"\n (onClick)=\"showSaveMaskDialog = false\"></p-button>\n <p-button *ngIf=\"!maskBusy\" label=\"Download\" icon=\"pi pi-download\"\n [disabled]=\"!saveMaskFilename || !saveMaskFilename.trim()\"\n (onClick)=\"confirmSaveMask()\"></p-button>\n <p-button *ngIf=\"maskBusy\" label=\"Cancel\" icon=\"pi pi-times\" [text]=\"true\"\n (onClick)=\"cancelSaveMask()\"></p-button>\n </ng-template>\n</p-dialog>\n\n<p-dialog header=\"Region colour\" [(visible)]=\"showColorDialog\" [modal]=\"false\" appendTo=\"body\"\n [style]=\"{width: '24rem'}\" [draggable]=\"true\" [resizable]=\"false\">\n <div class=\"flex flex-column gap-3 pt-2\">\n <span>Outline colour per class for the\n <b>{{ selectedRegions?.length || 0 }}</b> selected region(s):</span>\n <div *ngFor=\"let edit of classColorEdits; let i = index\"\n class=\"flex align-items-center gap-3\">\n <span class=\"font-medium flex-1\">{{ edit.label || 'Unclassified' }}</span>\n <hex-color-picker [color]=\"edit.color\"\n (colorChange)=\"classColorEdits[i].color = $event\"></hex-color-picker>\n </div>\n </div>\n <ng-template pTemplate=\"footer\">\n <p-button label=\"Cancel\" icon=\"pi pi-times\" [text]=\"true\" (onClick)=\"showColorDialog = false\">\n </p-button>\n <p-button label=\"Apply\" icon=\"pi pi-check\" (onClick)=\"applyColorToSelected()\">\n </p-button>\n </ng-template>\n</p-dialog>\n\n<!-- Manage annotation classes (jit-ui#70): CRUD + fallback palette + matching/auto-add\n settings + reset/import/export. Edits a working copy, committed on Apply/Done. -->\n<p-dialog header=\"Manage annotation classes\" [(visible)]=\"showManageDialog\" [modal]=\"true\" appendTo=\"body\"\n [style]=\"{width: '34rem'}\" [draggable]=\"true\" [resizable]=\"false\">\n <div *ngIf=\"presetDraft\" class=\"manage-dialog flex flex-column gap-3 pt-2\">\n <div class=\"manage-classes flex flex-column gap-2\">\n <div *ngFor=\"let c of presetDraft.classes; let i = index\"\n class=\"manage-class-row flex align-items-center gap-2\">\n <hex-color-picker [color]=\"c.color\" (colorChange)=\"c.color = $event\"></hex-color-picker>\n <input type=\"text\" pInputText class=\"p-inputtext-sm flex-1\"\n [(ngModel)]=\"c.name\" placeholder=\"Class name\"/>\n <button pButton type=\"button\" icon=\"pi pi-trash\"\n class=\"p-button-sm p-button-text p-button-warning\"\n pTooltip=\"Remove class\" tooltipPosition=\"bottom\"\n (click)=\"removePresetClass(i)\"></button>\n </div>\n <button pButton type=\"button\" icon=\"pi pi-plus\" label=\"Add class\"\n class=\"p-button-sm p-button-text align-self-start\" (click)=\"addPresetClass()\"></button>\n </div>\n\n <div class=\"manage-section flex flex-column gap-2\">\n <span class=\"section-title font-medium\">Fallback palette\n <small class=\"text-color-secondary\">\u2014 colours for classes not listed</small></span>\n <div class=\"fallback-palette flex align-items-center flex-wrap gap-2\">\n <span *ngFor=\"let col of presetDraft.fallbackPalette; let i = index\"\n class=\"fallback-swatch flex align-items-center\">\n <hex-color-picker [color]=\"col\" (colorChange)=\"presetDraft.fallbackPalette[i] = $event\"></hex-color-picker>\n <i class=\"pi pi-times icon-hover ml-1\" pTooltip=\"Remove\" tooltipPosition=\"bottom\"\n (click)=\"removeFallbackColor(i)\"></i>\n </span>\n <button pButton type=\"button\" icon=\"pi pi-plus\"\n class=\"p-button-sm p-button-text\" pTooltip=\"Add fallback colour\"\n tooltipPosition=\"bottom\" (click)=\"addFallbackColor()\"></button>\n </div>\n </div>\n\n <div class=\"flex align-items-center gap-2\">\n <p-checkbox [(ngModel)]=\"presetDraft.autoPromote\" [binary]=\"true\" inputId=\"autoPromote\"></p-checkbox>\n <label for=\"autoPromote\">Auto-add detected classes to this list as they appear</label>\n </div>\n\n <div class=\"flex align-items-center gap-3\">\n <span class=\"font-medium\">Label matching</span>\n <p-selectButton [options]=\"matchModeOptions\" [(ngModel)]=\"presetDraft.matchMode\"\n optionLabel=\"label\" optionValue=\"value\"></p-selectButton>\n </div>\n </div>\n\n <ng-template pTemplate=\"footer\">\n <div class=\"manage-footer flex align-items-center flex-wrap gap-2 w-full\">\n <span class=\"flex align-items-center flex-wrap gap-1\">\n <p-button label=\"Reset\" icon=\"pi pi-refresh\" [text]=\"true\"\n (onClick)=\"resetPresetsToDefaults()\"></p-button>\n <p-button label=\"Import\" icon=\"pi pi-upload\" [text]=\"true\"\n (onClick)=\"presetFileInput.click()\"></p-button>\n <input #presetFileInput type=\"file\" accept=\".json,application/json\" hidden\n (change)=\"importPresets($event); presetFileInput.value = ''\"/>\n <p-button label=\"Export\" icon=\"pi pi-download\" [text]=\"true\"\n (onClick)=\"exportPresets()\"></p-button>\n </span>\n <span class=\"flex-1\"></span>\n <!-- Apply persists to the server and closes; it replaces the old Apply+Done pair. -->\n <p-button label=\"Apply\" icon=\"pi pi-check\" (onClick)=\"applyManageDialog(true)\"></p-button>\n </div>\n </ng-template>\n</p-dialog>\n\n<p-dialog header=\"Region editor help\" [(visible)]=\"displayHelpDialog\" [style]=\"{width: '50vw'}\"\n [maximizable]=\"true\" [draggable]=\"true\" appendTo=\"body\">\n <p>\n The Region Editor lists every region drawn on the currently displayed image.\n Open it with the\n <img src=\"assets/plotting/region-editor.svg\" width=\"14\" height=\"14\" alt=\"Region Editor\"/>\n button in the diagram toolbar: it opens as a floating dialog you can drag,\n resize, and leave open while you draw on the image.\n Regions are tracked <b>per image</b>: switching to another image hides\n these and shows that image's regions; switching back restores them.\n Edits in the table apply <b>live</b> to the diagram \u2014 there is no save or\n cancel.\n </p>\n\n <h3>Creating regions</h3>\n <p>\n Use the diagram toolbar tools to draw new regions:\n <i class=\"pi pi-stop\"></i> rectangle,\n <object data=\"assets/icons/polyline.svg\" width=\"14\" height=\"14\"></object> polyline,\n <i class=\"pi pi-pencil\"></i> freeform path, brush, or\n <object data=\"assets/icons/wand.svg\" width=\"14\" height=\"14\"></object> wand.\n Anything you draw appears immediately in the table, with the class name\n \"<b>Region</b>\" by default.\n </p>\n\n <h3>The table columns</h3>\n <ul>\n <li><b>Class</b> \u2014 the region's class. Pick a class from the dropdown (its\n colour follows), or use the <i class=\"pi pi-pencil\"></i> for a free-form name.</li>\n <li><b>Surface</b> \u2014 the region's area, in \u00B5m\u00B2/mm\u00B2 when the image carries a\n physical pixel size, otherwise in px\u00B2. Holes (donuts) and multi-part\n regions are accounted for.</li>\n <li>The trash icon at the end of a row deletes that one region.</li>\n <li>Use \"<b>x512</b>\" to round all rectangle widths/heights to the\n nearest multiple of 512.</li>\n </ul>\n <p>Region geometry is edited on the diagram itself (drag vertices, the vertex\n tools, brush/wand, and the right-click region actions), not in this table.</p>\n\n <h3>Selecting rows</h3>\n <ul>\n <li>Click a row to select it. The corresponding region is highlighted in the\n diagram (Plotly shows its edit handles; the OpenSeadragon image view\n highlights it and shows its handles in select mode).</li>\n <li><kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+click to add or remove a row from the\n selection. <kbd>Shift</kbd>+click to extend the selection in a range.</li>\n <li>Selecting a region in the diagram highlights the matching row in the\n table.</li>\n <li>Clicks on the colour picker, the trash icon, or the active class-edit\n input do not change the selection \u2014 that's standard PrimeNG behaviour.</li>\n </ul>\n\n <h3>Bulk actions on the selected rows</h3>\n <ul>\n <li><i class=\"pi pi-check-square\"></i> Select every region in the table at once (also a\n right-click action on the image).</li>\n <li><i class=\"pi pi-tags\"></i> Edit the label on every selected row at once.</li>\n <li><i class=\"pi pi-trash\"></i> Delete every selected region \u2014 both from\n the table and the diagram.</li>\n <li><i class=\"pi pi-times-circle\"></i> Delete every region on this image.</li>\n </ul>\n\n <h3>Annotation classes & colours (presets)</h3>\n <p>\n Preset a class once \u2014 a <b>name</b> and a <b>colour</b> \u2014 and every region with\n that class name is drawn in that colour, <b>including regions loaded from a\n GeoJSON later</b>. The preset colour is the source of truth: on load it\n <b>overrides the colour stored in the file</b>, so a given annotation type always\n looks the same across images and sessions.\n </p>\n <p>Set up your classes in the annotation-class manager \u2014\n <i class=\"pi pi-cog\"></i> <b>Manage</b>:</p>\n <ol>\n <li><b>Add class</b>, then type the class <b>name exactly as it appears in your\n data</b> (e.g. <code>Tumor</code>) and pick its <b>colour</b> from the swatch.\n Repeat for each class.</li>\n <li>Optionally: edit the <b>fallback palette</b> (colours given to classes that\n aren't in your list), toggle <b>auto-add</b> (adds unmatched classes found on\n load), and choose <b>exact</b> vs. <b>normalized</b> name matching \u2014 normalized\n ignores case and surrounding spaces, so <code>tumor</code> matches\n <code>Tumor</code>.</li>\n <li><b>Apply.</b> Presets are saved <b>per user</b> on the server, so they follow\n you across images and sessions. You can also <b>reset to defaults</b> or\n <b>import / export</b> the set as JSON to share it.</li>\n </ol>\n <p>\n Next time you open or <i class=\"pi pi-upload\"></i> import a GeoJSON whose regions\n carry those class names, each region is shown in its preset colour automatically \u2014\n no re-colouring needed. A class that isn't in your list instead gets a stable\n colour from the fallback palette.\n </p>\n <p>Day to day:</p>\n <ul>\n <li>The <b>class strip</b> above the table lists your presets with their colours.\n Click a class to make it <b>active</b> (new regions inherit it); with rows\n selected, clicking a class re-classifies them.</li>\n <li>The <b>Class</b> column dropdown re-classifies one region to a preset (its\n colour follows), or use the <i class=\"pi pi-pencil\"></i> for a free-form name.</li>\n <li>Recolour a class from its swatch (class strip or Manage) and every region of\n that class updates at once.</li>\n <li>Deleting a class reassigns its regions to the default <b>Region</b> class.</li>\n <li>A colour you set on a <b>single</b> region (<i class=\"pi pi-palette\"></i>\n Selected regions) is kept and is not overridden by its class.</li>\n </ul>\n\n <h3>Region actions (right-click the diagram)</h3>\n <p>Right-click a region \u2014 or the current multi-selection \u2014 on the diagram for\n the \"Selected region(s)\" actions:</p>\n <ul>\n <li><b>Merge / group</b> \u2014 combine 2+ selected regions into one (it may end\n up multi-part if the pieces don't touch).</li>\n <li><b>Ungroup</b> \u2014 split a multi-part region back into separate regions.</li>\n <li><b>Inverse</b> \u2014 replace the selection with everything outside it (the\n regions become holes in the image rectangle).</li>\n <li><b>Simplify</b> \u2014 reduce the vertex count (Douglas\u2013Peucker) by a pixel\n threshold; pick a preset or a custom value.</li>\n <li><b>Convert to B\u00E9zier / to polygon</b> and <b>Delete</b>.</li>\n </ul>\n\n <h3>Display options</h3>\n <ul>\n <li><i class=\"pi pi-tag\"></i> Toggle showing class labels on every\n region in the diagram (slower with many regions).</li>\n </ul>\n\n <h3>Import / export</h3>\n <ul>\n <li><i class=\"pi pi-upload\"></i> Import regions from a GeoJSON file.</li>\n <li><i class=\"pi pi-download\"></i> Export the current image's regions to\n a GeoJSON file. (The per-image cache is in-memory only \u2014 exporting is\n how you persist regions across sessions.)</li>\n </ul>\n\n <ng-template pTemplate=\"footer\">\n <p-button icon=\"pi pi-check\" (click)=\"displayHelpDialog=false\" label=\"Ok\" styleClass=\"p-button-text\">\n </p-button>\n </ng-template>\n</p-dialog>\n\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden}.region-editor-content{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}::ng-deep .region-editor-content .p-3{padding:0!important}::ng-deep .region-editor-content>p-table{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}::ng-deep .region-editor-content .p-datatable.region-editor-table{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}::ng-deep .region-editor-content .p-datatable.region-editor-table>.p-datatable-header{flex:0 0 auto}::ng-deep .region-editor-content .p-datatable.region-editor-table>.p-datatable-wrapper,::ng-deep .region-editor-content .p-datatable.region-editor-table>.p-datatable-table-container,::ng-deep .region-editor-content .p-datatable.region-editor-table .p-scroller-viewport{flex:1 1 0;min-height:0;height:100%;max-height:100%;overflow:auto}:host>p-paginator{display:block;flex:0 0 auto;border-top:1px solid var(--surface-border);padding:.25rem 0}::ng-deep region-editor .p-toolbar.region-toolbar{padding:.25rem .5rem;border-radius:0;border-left:none;border-right:none;border-top:none}::ng-deep region-editor .p-toolbar.region-toolbar .p-button.p-button-icon-only{width:auto;padding:.25rem .5rem}::ng-deep region-editor .p-toolbar.region-toolbar .p-button{padding:.25rem .5rem}.toolbar-icon-dark{filter:brightness(0) invert(47%) sepia(98%) saturate(567%) hue-rotate(181deg) brightness(103%) contrast(98%)}.class-col-header,.class-cell{white-space:nowrap;max-width:16rem}.class-cell .class-cell-display{display:flex;align-items:center;min-width:0}.class-cell .class-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.class-cell .icon-hover{flex:0 0 auto}.class-cell .icon-hover{cursor:pointer;color:var(--text-color-secondary);transition:color .15s ease}.class-cell .icon-hover:hover{color:var(--primary-color)}.class-cell .class-cell-edit{display:inline-block;width:100%}::ng-deep .p-datatable-sm .p-datatable-tbody>tr>td .p-button.p-button-rounded,::ng-deep .p-datatable-sm .p-datatable-tbody>tr>td .p-button.p-button-icon-only{width:1.5rem;height:1.5rem;padding:0}::ng-deep .p-datatable-sm .p-datatable-tbody>tr>td .p-button.p-button-rounded .p-button-icon,::ng-deep .p-datatable-sm .p-datatable-tbody>tr>td .p-button.p-button-icon-only .p-button-icon{font-size:.75rem}::ng-deep .p-datatable-sm .p-datatable-tbody>tr>td .p-inputtext{padding:.25rem .5rem;font-size:.875rem}::ng-deep .p-datatable-sm .p-paginator{padding:.15rem .25rem!important;min-height:unset!important}::ng-deep .p-datatable-sm .p-paginator,::ng-deep .p-datatable-sm .p-paginator *{font-size:.75rem!important}::ng-deep .p-datatable-sm .p-paginator .p-paginator-element{min-width:1.5rem!important;height:1.5rem!important}::ng-deep .p-datatable-sm .p-paginator .p-paginator-current{font-size:.75rem!important;height:1.5rem!important;min-width:unset!important}::ng-deep .p-datatable-sm .p-paginator .p-dropdown{height:1.5rem!important}::ng-deep .p-datatable-sm .p-paginator .p-dropdown .p-dropdown-label{padding:.1rem .25rem!important;line-height:1.2!important}::ng-deep .p-datatable-sm .p-paginator .p-dropdown .p-dropdown-trigger{width:1.25rem!important}::ng-deep .p-paginator-rpp-options .p-dropdown-panel .p-dropdown-items .p-dropdown-item{padding:.25rem .5rem!important;font-size:.75rem!important}.region-area{font-size:.8rem;font-weight:600;color:var(--primary-color, #3399ff)}.toolbar-separator{border-left:1px solid var(--surface-border);height:1.5rem;margin:0 .25rem;align-self:center}kbd{background:#e8e8e8;border:1px solid #bbb;border-radius:3px;padding:1px 5px;font-family:monospace;font-size:.85em}.classes-panel{display:flex;flex-direction:column;flex:0 0 auto;border-bottom:1px solid var(--surface-border)}.classes-panel-header{display:flex;align-items:center;gap:.35rem;padding:.35rem .5rem;border-bottom:1px solid var(--surface-border)}.classes-panel-title{font-size:.75rem;font-weight:600;letter-spacing:.03em;text-transform:uppercase;color:var(--text-color-secondary);margin-right:auto}.classes-panel-body{height:7rem;min-height:2.5rem;max-height:60vh;overflow-x:hidden;overflow-y:auto;resize:vertical}::ng-deep .classes-panel-body .hex-picker-swatch{width:1.25rem;height:1.25rem;border-width:1px}.class-row{display:flex;align-items:center;gap:.5rem;padding:.1rem .5rem;cursor:pointer;border-left:3px solid transparent}.class-row:hover{background:var(--surface-hover, rgba(0, 0, 0, .04))}.class-row.active{background:var(--highlight-bg, rgba(51, 153, 255, .12));border-left-color:var(--primary-color)}.class-row .row-swatch{flex:0 0 auto;display:inline-flex}.class-row .row-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.85rem}.class-row.active .row-name{color:var(--primary-color);font-weight:600}.class-row .row-count{flex:0 0 auto;min-width:1.5rem;text-align:center;font-size:.72rem;font-variant-numeric:tabular-nums;color:var(--text-color-secondary);background:var(--surface-200, rgba(0, 0, 0, .06));border-radius:10px;padding:0 .4rem}.class-row .row-count.zero{opacity:.5}.class-row .row-del{flex:0 0 auto}.classes-empty{font-size:.8rem;color:var(--text-color-secondary);padding:.5rem}::ng-deep .classes-panel-body .class-row .p-button.p-button-icon-only{width:1.75rem;height:1.75rem;padding:0}.chip-swatch{display:inline-block;width:.75rem;height:.75rem;border-radius:50%;border:1px solid rgba(0,0,0,.2);flex:0 0 auto}.class-cell .class-dropdown{min-width:0;flex:1 1 auto}::ng-deep .class-cell .class-dropdown.p-dropdown{width:100%}::ng-deep .class-cell .class-dropdown .p-dropdown-label{padding:.15rem .5rem;display:flex;align-items:center}.dd-value{display:inline-flex;align-items:center;gap:.4rem;min-width:0}.dd-value .class-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.manage-dialog .manage-class-row hex-color-picker,.manage-dialog .fallback-palette hex-color-picker{flex:0 0 auto}.manage-dialog .fallback-palette .icon-hover{cursor:pointer;color:var(--text-color-secondary)}.manage-dialog .fallback-palette .icon-hover:hover{color:var(--primary-color)}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i4.Toolbar, selector: "p-toolbar", inputs: ["style", "styleClass", "ariaLabelledBy"] }, { kind: "directive", type: i1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "directive", type: i5.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain"] }, { kind: "component", type: i5.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: i6.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: i7.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: i8.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: i9.ProgressBar, selector: "p-progressBar", inputs: ["value", "showValue", "styleClass", "style", "unit", "mode", "color"] }, { kind: "directive", type: i10.Ripple, selector: "[pRipple]" }, { kind: "component", type: i11.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "style", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "scrollDirection", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "responsive", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "autoLayout", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll", "virtualRowHeight"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i11.SelectableRow, selector: "[pSelectableRow]", inputs: ["pSelectableRow", "pSelectableRowIndex", "pSelectableRowDisabled"] }, { kind: "directive", type: i11.ResizableColumn, selector: "[pResizableColumn]", inputs: ["pResizableColumnDisabled"] }, { kind: "component", type: i11.SortIcon, selector: "p-sortIcon", inputs: ["field"] }, { kind: "component", type: i12.Paginator, selector: "p-paginator", inputs: ["pageLinkSize", "style", "styleClass", "alwaysShow", "dropdownAppendTo", "templateLeft", "templateRight", "appendTo", "dropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showFirstLastIcon", "totalRecords", "rows", "rowsPerPageOptions", "showJumpToPageDropdown", "showJumpToPageInput", "jumpToPageItemTemplate", "showPageLinks", "locale", "dropdownItemTemplate", "first"], outputs: ["onPageChange"] }, { kind: "component", type: i13.OverlayPanel, selector: "p-overlayPanel", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "showCloseIcon", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }, { kind: "component", type: i14.ConfirmDialog, selector: "p-confirmDialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "visible", "position"], outputs: ["onHide"] }, { kind: "directive", type: i15.InputText, selector: "[pInputText]", inputs: ["variant"] }, { kind: "component", type: i16.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: i17.RadioButton, selector: "p-radioButton", inputs: ["value", "formControlName", "name", "disabled", "label", "variant", "tabindex", "inputId", "ariaLabelledBy", "ariaLabel", "style", "styleClass", "labelStyleClass", "autofocus"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i18.SelectButton, selector: "p-selectButton", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "unselectable", "tabindex", "multiple", "allowEmpty", "style", "styleClass", "ariaLabelledBy", "disabled", "dataKey", "autofocus"], outputs: ["onOptionClick", "onChange"] }, { kind: "component", type: i19.HexColorPickerComponent, selector: "hex-color-picker", inputs: ["color"], outputs: ["colorChange"] }] }); }
|
|
1176
|
+
}
|
|
1177
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: RegionEditorComponent, decorators: [{
|
|
1178
|
+
type: Component,
|
|
1179
|
+
args: [{ selector: 'region-editor', template: "\n<p-toolbar styleClass=\"region-toolbar\">\n <div class=\"p-toolbar-group-start align-items-center flex-wrap gap-1\">\n <p-button icon=\"pi pi-tag\" size=\"small\"\n [text]=\"!showShapeLabel\"\n pTooltip=\"Show region labels.\"\n tooltipPosition=\"bottom\"\n (onClick)=\"showShapeLabel = !showShapeLabel; changeShowShapeLabel(showShapeLabel)\">\n </p-button>\n <span class=\"toolbar-separator\"></span>\n <!-- New regions are now created from the diagram toolbar\n (rectangle, polyline, freeform, wand). -->\n <p-button icon=\"pi pi-upload\" size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n pTooltip=\"Upload and display regions from geojson file.\"\n tooltipPosition=\"bottom\"\n (click)=\"roiFileInput.click()\">\n </p-button>\n <input #roiFileInput type=\"file\" accept=\".geojson,.json\" hidden\n (change)=\"importRois($event); roiFileInput.value = ''\"/>\n <p-button size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n pTooltip=\"Download regions as geojson file.\"\n tooltipPosition=\"bottom\" icon=\"pi pi-download\"\n (click)=\"exportRois()\">\n </p-button>\n <p-button icon=\"pi pi-save\" size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n pTooltip=\"Save regions to server.\"\n tooltipPosition=\"bottom\"\n (click)=\"persistRegions()\"\n [disabled]=\"!regions || !regions.length\">\n </p-button>\n <p-button size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n pTooltip=\"Download mask of the regions as PNG\"\n tooltipPosition=\"bottom\"\n (click)=\"openSaveMaskDialog()\"\n [disabled]=\"!regions || !regions.length\">\n <!-- References the file so Inkscape edits to mask.svg show up directly\n (the lib's assets/** are served at assets/plotting/). The\n toolbar-icon-dark filter recolours the black SVG to the theme blue,\n same as region-editor.svg and the other SVG toolbar icons. -->\n <img src=\"assets/plotting/mask.svg\" class=\"toolbar-icon-dark\"\n style=\"width: 1rem; height: 1rem; vertical-align: middle;\"\n alt=\"Download mask\"/>\n </p-button>\n <p-button label=\"x512\" size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n pTooltip=\"Round all rectangle regions\n to width and height that are multiple of 512\" tooltipPosition=\"bottom\"\n (click)=\"roundRectangleLengths()\">\n </p-button>\n <p-button icon=\"pi pi-check-square\" size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n pTooltip=\"Select all regions\" tooltipPosition=\"bottom\"\n (click)=\"selectAllRegions()\"\n [disabled]=\"!regions || !regions.length\">\n </p-button>\n <p-button icon=\"pi pi-tags\" size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n pTooltip=\"Edit class on selected rows\"\n tooltipPosition=\"bottom\"\n (click)=\"(!selectedRegions || !selectedRegions.length)? op.hide() : op.toggle($event)\"\n [disabled]=\"!selectedRegions || !selectedRegions.length\">\n </p-button>\n <p-button icon=\"pi pi-palette\" size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n pTooltip=\"Edit colour of selected regions\"\n tooltipPosition=\"bottom\"\n (click)=\"openColorDialog()\"\n [disabled]=\"!selectedRegions || !selectedRegions.length\">\n </p-button>\n <p-overlayPanel #op (onHide)=\"bulkClass=''; newBulkClass=''\">\n <div class=\"flex flex-column gap-2\" style=\"width: 18rem\">\n <span class=\"font-medium text-900\">Set class on {{ selectedRegions?.length || 0 }} selected region(s)</span>\n <!-- Selecting a class applies it immediately (choosing an option closes the\n overlay, so there's no separate Apply button). -->\n <p-dropdown styleClass=\"w-full\" appendTo=\"body\" [options]=\"presetSet.classes\"\n optionLabel=\"name\" optionValue=\"name\" [(ngModel)]=\"bulkClass\"\n placeholder=\"Choose a class\u2026\"\n [filter]=\"presetSet.classes.length > 8\" filterBy=\"name\"\n (onChange)=\"applyBulkClass()\">\n <ng-template let-c pTemplate=\"item\">\n <span class=\"dd-value\"><span class=\"chip-swatch\" [style.background]=\"c.color\"></span>{{ c.name }}</span>\n </ng-template>\n </p-dropdown>\n <div class=\"p-inputgroup\">\n <input type=\"text\" pInputText placeholder=\"\u2026or a new class\" [(ngModel)]=\"newBulkClass\"\n (keydown.enter)=\"addAndApplyBulkClass()\"/>\n <button pButton type=\"button\" icon=\"pi pi-plus\" pTooltip=\"Add class & set it\"\n tooltipPosition=\"bottom\" [disabled]=\"!newBulkClass.trim()\"\n (click)=\"addAndApplyBulkClass()\"></button>\n </div>\n </div>\n </p-overlayPanel>\n <p-button icon=\"pi pi-trash\" size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n (click)=\"deleteSelectedRegions()\"\n pTooltip=\"Delete selected regions\" tooltipPosition=\"bottom\"\n [disabled]=\"!selectedRegions || !selectedRegions.length\"></p-button>\n <p-button icon=\"pi pi-times-circle\" size=\"small\" [text]=\"true\" styleClass=\"mr-2\"\n (click)=\"clearAllRegions()\"\n pTooltip=\"Delete all regions\" tooltipPosition=\"bottom\"\n [disabled]=\"!regions || !regions.length\"></p-button>\n <p-button icon=\"pi pi-question\" size=\"small\" [text]=\"true\"\n (click)=\"showHelp()\" pTooltip=\"Help\" tooltipPosition=\"bottom\"></p-button>\n </div>\n</p-toolbar>\n\n<!-- Docked \"Classes\" panel (jit-ui#70): the always-visible list of annotation\n classes above the regions table. Click a row to make it the active class\n (new regions inherit it; with rows selected it re-classifies them); recolour\n via the swatch; the badge shows how many regions use the class. \"Manage\"\n opens the full editor (fallback palette, auto-add, matching, import/export). -->\n<div class=\"classes-panel\">\n <div class=\"classes-panel-header\">\n <span class=\"classes-panel-title\">Classes</span>\n <button pButton type=\"button\" icon=\"pi pi-cog\" label=\"Manage\"\n class=\"p-button-sm p-button-text\"\n pTooltip=\"Manage classes \u2014 add / remove, palette, matching, import/export\"\n tooltipPosition=\"bottom\" (click)=\"openManageDialog()\"></button>\n </div>\n <div class=\"classes-panel-body\">\n <div *ngFor=\"let c of displayClasses; trackBy: trackByClassName\"\n class=\"class-row\" [class.active]=\"activeClass === c.name\"\n (click)=\"selectActiveClass(c.name)\"\n [pTooltip]=\"selectedRegions?.length\n ? 'Apply \u201C' + c.name + '\u201D to the selected region(s)'\n : 'Set \u201C' + c.name + '\u201D active \u2014 new regions use it'\"\n tooltipPosition=\"bottom\">\n <span class=\"row-swatch\" (click)=\"$event.stopPropagation()\">\n <hex-color-picker [color]=\"c.color\" (colorChange)=\"setClassColor(c.name, $event)\"></hex-color-picker>\n </span>\n <span class=\"row-name\">{{ c.name }}</span>\n <span class=\"row-count\" [class.zero]=\"classCount(c.name) === 0\">{{ classCount(c.name) }}</span>\n <button pButton type=\"button\" icon=\"pi pi-trash\"\n class=\"p-button-sm p-button-text p-button-warning row-del\"\n [disabled]=\"c.name === defaultClassName && classCount(c.name) > 0\"\n [pTooltip]=\"deleteClassTooltip(c.name)\"\n tooltipPosition=\"bottom\"\n (click)=\"$event.stopPropagation(); deleteClass(c.name)\"></button>\n </div>\n <div *ngIf=\"!displayClasses.length\" class=\"classes-empty\">\n No classes yet \u2014 add one from Manage.\n </div>\n </div>\n</div>\n\n<div class=\"region-editor-content\">\n <p-table #dt [value]=\"regions.slice(paginatorFirst, paginatorFirst + paginatorRows)\"\n [tableStyle]=\"{'width': '100%'}\" [(selection)]=\"selectedRegions\"\n selectionMode=\"multiple\" [metaKeySelection]=\"metaKey\"\n (onRowSelect)=\"onSelectionChanged()\" (onRowUnselect)=\"onSelectionChanged()\"\n [resizableColumns]=\"true\"\n [rowHover]=\"true\" dataKey=\"id\" styleClass=\"p-datatable-sm region-editor-table\"\n [scrollable]=\"true\" scrollHeight=\"100%\">\n <ng-template pTemplate=\"header\">\n <!-- Hide the column header when there are no regions to show. -->\n <tr *ngIf=\"regions.length > 0\">\n <!-- Class column: shrinks to fit \"Region\" by default, grows with longer labels. -->\n <th class=\"class-col-header\" pResizableColumn>Class <p-sortIcon field=\"label\"></p-sortIcon></th>\n <th class=\"surface-col-header\" pResizableColumn>Surface</th>\n <!-- Trash column: just enough for the icon. -->\n <th style=\"width:2rem\"></th>\n </tr>\n </ng-template>\n <ng-template pTemplate=\"body\" let-region let-shapeIdx=\"rowIndex\">\n <tr [pSelectableRow]=\"region\" [pSelectableRowIndex]=\"shapeIdx\">\n <td class=\"class-cell\">\n <ng-container *ngIf=\"!isEditingLabel(region); else editLabelTpl\">\n <span class=\"class-cell-display\">\n <!-- Quick-pick a preset class (its colour follows); the pencil is for\n a free-form name / rename (jit-ui#70). -->\n <p-dropdown class=\"class-dropdown\" appendTo=\"body\"\n [options]=\"presetSet.classes\" optionLabel=\"name\" optionValue=\"name\"\n [ngModel]=\"region.label\" [autoDisplayFirst]=\"false\"\n (onChange)=\"applyPresetToRegion(region, $event.value)\">\n <ng-template pTemplate=\"selectedItem\">\n <span class=\"dd-value\">\n <span class=\"chip-swatch\" [style.background]=\"colorForName(region.label)\"></span>\n <span class=\"class-name\" [pTooltip]=\"region.label\" tooltipPosition=\"bottom\">{{ region.label || '\u2014' }}</span>\n </span>\n </ng-template>\n <ng-template let-c pTemplate=\"item\">\n <span class=\"dd-value\">\n <span class=\"chip-swatch\" [style.background]=\"c.color\"></span>\n <span>{{ c.name }}</span>\n </span>\n </ng-template>\n </p-dropdown>\n <i class=\"pi pi-pencil icon-hover ml-2\"\n pTooltip=\"Rename / free-form label\" tooltipPosition=\"bottom\"\n (click)=\"startEditLabel(region, $event)\"></i>\n </span>\n </ng-container>\n <ng-template #editLabelTpl>\n <span class=\"p-input-icon-right class-cell-edit\">\n <i class=\"pi pi-times icon-hover\"\n pTooltip=\"Close\" tooltipPosition=\"bottom\"\n (click)=\"stopEditLabel(region, false, $event)\"></i>\n <input type=\"text\" pInputText class=\"p-inputtext-sm w-full\"\n [(ngModel)]=\"region.label\"\n (click)=\"$event.stopPropagation()\"\n (keydown.enter)=\"stopEditLabel(region, true, $event)\"\n (keydown.escape)=\"stopEditLabel(region, false, $event)\"/>\n </span>\n </ng-template>\n </td>\n <!-- Surface area (\u00B5m\u00B2/mm\u00B2 when the image is scaled, else px\u00B2). -->\n <td class=\"surface-cell\">{{ regionArea(region) || '\u2014' }}</td>\n <td>\n <button pButton pRipple icon=\"pi pi-trash\"\n\t\t\t\t\t\t\tclass=\"p-button-sm p-button-rounded p-button-text p-button-warning\"\n (click)=\"deleteRegion(paginatorFirst + shapeIdx)\"></button>\n </td>\n </tr>\n </ng-template>\n </p-table>\n</div>\n\n<p-paginator *ngIf=\"regions.length > 0\"\n [rows]=\"paginatorRows\" [totalRecords]=\"regions.length\"\n [first]=\"paginatorFirst\"\n [rowsPerPageOptions]=\"rowsPerPageOptions\"\n [showCurrentPageReport]=\"true\"\n currentPageReportTemplate=\"{first} to {last} of {totalRecords}\"\n (onPageChange)=\"onPageChange($event)\"\n styleClass=\"region-editor-paginator\">\n</p-paginator>\n\n<!-- Live-edit mode: no Save/Cancel \u2014 every edit commits immediately to the\n active image's region cache. -->\n\n\n<p-confirmDialog #cd [style]=\"{width: '24rem', height: '14rem'}\" key=\"positionDialog\"\n [baseZIndex]=\"10000\" rejectButtonStyleClass=\"p-button-outlined\">\n <ng-template pTemplate=\"footer\">\n <button type=\"button\" pButton icon=\"pi pi-check\" label=\"Yes\" (click)=\"cd.accept()\"></button>\n <button type=\"button\" class=\"p-button-outlined\" pButton icon=\"pi pi-times\" label=\"No\"\n (click)=\"cd.reject()\"></button>\n </ng-template>\n</p-confirmDialog>\n\n<p-dialog header=\"Save Regions As\" [(visible)]=\"showSaveAsDialog\" [modal]=\"true\" appendTo=\"body\"\n [style]=\"{width: '30rem'}\" [draggable]=\"false\" [resizable]=\"false\"\n [closable]=\"!saveAsBusy\" [closeOnEscape]=\"!saveAsBusy\">\n <!-- Idle: filename + overwrite warning. -->\n <div *ngIf=\"!saveAsBusy\" class=\"flex flex-column gap-3 pt-2\">\n <label for=\"geojson-filename\" class=\"font-medium\">GeoJSON filename</label>\n <input id=\"geojson-filename\" type=\"text\" pInputText\n [(ngModel)]=\"saveAsFilename\"\n (ngModelChange)=\"checkSaveAsFileExists()\"\n class=\"w-full\" />\n <small *ngIf=\"saveAsFileExists\" class=\"p-error\">\n <i class=\"pi pi-exclamation-triangle mr-1\"></i>\n This file already exists and will be overwritten.\n </small>\n </div>\n\n <!-- Busy: progress while regions serialize + upload. -->\n <div *ngIf=\"saveAsBusy\" class=\"flex flex-column gap-3 pt-2\">\n <span>Saving regions\u2026</span>\n <p-progressBar mode=\"indeterminate\" [style]=\"{height: '0.75rem'}\"></p-progressBar>\n </div>\n\n <ng-template pTemplate=\"footer\">\n <p-button *ngIf=\"!saveAsBusy\" label=\"Cancel\" icon=\"pi pi-times\" [text]=\"true\"\n (onClick)=\"showSaveAsDialog = false\"></p-button>\n <p-button *ngIf=\"!saveAsBusy\" label=\"Save\" icon=\"pi pi-save\"\n [disabled]=\"!saveAsFilename || !saveAsFilename.trim()\"\n (onClick)=\"confirmSaveAs()\"></p-button>\n <p-button *ngIf=\"saveAsBusy\" label=\"Cancel\" icon=\"pi pi-times\" [text]=\"true\"\n (onClick)=\"cancelSaveAs()\"></p-button>\n </ng-template>\n</p-dialog>\n\n<p-dialog header=\"Export Regions\" [(visible)]=\"showExportDialog\" [modal]=\"true\" appendTo=\"body\"\n [style]=\"{width: '30rem'}\" [draggable]=\"false\" [resizable]=\"false\">\n <div class=\"flex flex-column gap-3 pt-2\">\n <label for=\"export-filename\" class=\"font-medium\">GeoJSON filename</label>\n <input id=\"export-filename\" type=\"text\" pInputText\n [(ngModel)]=\"exportFilename\"\n class=\"w-full\" />\n </div>\n <ng-template pTemplate=\"footer\">\n <p-button label=\"Cancel\" icon=\"pi pi-times\" [text]=\"true\"\n (onClick)=\"showExportDialog = false\"></p-button>\n <p-button label=\"Export\" icon=\"pi pi-download\"\n [disabled]=\"!exportFilename || !exportFilename.trim()\"\n (onClick)=\"confirmExport()\"></p-button>\n </ng-template>\n</p-dialog>\n\n<p-dialog header=\"Save Mask\" [(visible)]=\"showSaveMaskDialog\" [modal]=\"true\" appendTo=\"body\"\n [style]=\"{width: '30rem'}\" [draggable]=\"false\" [resizable]=\"false\"\n [closable]=\"!maskBusy\" [closeOnEscape]=\"!maskBusy\">\n <!-- Idle: filename + mask-type form. -->\n <div *ngIf=\"!maskBusy\" class=\"flex flex-column gap-3 pt-2\">\n <label for=\"mask-filename\" class=\"font-medium\">Mask filename</label>\n <input id=\"mask-filename\" type=\"text\" pInputText\n [(ngModel)]=\"saveMaskFilename\"\n class=\"w-full\" />\n <span class=\"font-medium\">Mask type</span>\n <div class=\"flex align-items-center gap-2\">\n <p-radioButton inputId=\"mask-binary\" name=\"maskMode\" value=\"binary\"\n [(ngModel)]=\"maskMode\"></p-radioButton>\n <label for=\"mask-binary\">Binary <span class=\"text-color-secondary\">(regions 1, background 0)</span></label>\n </div>\n <div class=\"flex align-items-center gap-2\">\n <p-radioButton inputId=\"mask-multiclass\" name=\"maskMode\" value=\"multiclass\"\n [(ngModel)]=\"maskMode\"></p-radioButton>\n <label for=\"mask-multiclass\">Multi-class <span class=\"text-color-secondary\">(unique id per region, background 0)</span></label>\n </div>\n </div>\n\n <!-- Busy: progress while the worker rasterizes + encodes the PNG. -->\n <div *ngIf=\"maskBusy\" class=\"flex flex-column gap-3 pt-2\">\n <span>{{ maskEncoding ? 'Encoding PNG\u2026' : 'Rasterizing regions\u2026' }}</span>\n <p-progressBar [mode]=\"maskEncoding ? 'indeterminate' : 'determinate'\"\n [value]=\"maskProgress\"\n [style]=\"{height: '0.75rem'}\"></p-progressBar>\n </div>\n\n <ng-template pTemplate=\"footer\">\n <p-button *ngIf=\"!maskBusy\" label=\"Cancel\" icon=\"pi pi-times\" [text]=\"true\"\n (onClick)=\"showSaveMaskDialog = false\"></p-button>\n <p-button *ngIf=\"!maskBusy\" label=\"Download\" icon=\"pi pi-download\"\n [disabled]=\"!saveMaskFilename || !saveMaskFilename.trim()\"\n (onClick)=\"confirmSaveMask()\"></p-button>\n <p-button *ngIf=\"maskBusy\" label=\"Cancel\" icon=\"pi pi-times\" [text]=\"true\"\n (onClick)=\"cancelSaveMask()\"></p-button>\n </ng-template>\n</p-dialog>\n\n<p-dialog header=\"Region colour\" [(visible)]=\"showColorDialog\" [modal]=\"false\" appendTo=\"body\"\n [style]=\"{width: '24rem'}\" [draggable]=\"true\" [resizable]=\"false\">\n <div class=\"flex flex-column gap-3 pt-2\">\n <span>Outline colour per class for the\n <b>{{ selectedRegions?.length || 0 }}</b> selected region(s):</span>\n <div *ngFor=\"let edit of classColorEdits; let i = index\"\n class=\"flex align-items-center gap-3\">\n <span class=\"font-medium flex-1\">{{ edit.label || 'Unclassified' }}</span>\n <hex-color-picker [color]=\"edit.color\"\n (colorChange)=\"classColorEdits[i].color = $event\"></hex-color-picker>\n </div>\n </div>\n <ng-template pTemplate=\"footer\">\n <p-button label=\"Cancel\" icon=\"pi pi-times\" [text]=\"true\" (onClick)=\"showColorDialog = false\">\n </p-button>\n <p-button label=\"Apply\" icon=\"pi pi-check\" (onClick)=\"applyColorToSelected()\">\n </p-button>\n </ng-template>\n</p-dialog>\n\n<!-- Manage annotation classes (jit-ui#70): CRUD + fallback palette + matching/auto-add\n settings + reset/import/export. Edits a working copy, committed on Apply/Done. -->\n<p-dialog header=\"Manage annotation classes\" [(visible)]=\"showManageDialog\" [modal]=\"true\" appendTo=\"body\"\n [style]=\"{width: '34rem'}\" [draggable]=\"true\" [resizable]=\"false\">\n <div *ngIf=\"presetDraft\" class=\"manage-dialog flex flex-column gap-3 pt-2\">\n <div class=\"manage-classes flex flex-column gap-2\">\n <div *ngFor=\"let c of presetDraft.classes; let i = index\"\n class=\"manage-class-row flex align-items-center gap-2\">\n <hex-color-picker [color]=\"c.color\" (colorChange)=\"c.color = $event\"></hex-color-picker>\n <input type=\"text\" pInputText class=\"p-inputtext-sm flex-1\"\n [(ngModel)]=\"c.name\" placeholder=\"Class name\"/>\n <button pButton type=\"button\" icon=\"pi pi-trash\"\n class=\"p-button-sm p-button-text p-button-warning\"\n pTooltip=\"Remove class\" tooltipPosition=\"bottom\"\n (click)=\"removePresetClass(i)\"></button>\n </div>\n <button pButton type=\"button\" icon=\"pi pi-plus\" label=\"Add class\"\n class=\"p-button-sm p-button-text align-self-start\" (click)=\"addPresetClass()\"></button>\n </div>\n\n <div class=\"manage-section flex flex-column gap-2\">\n <span class=\"section-title font-medium\">Fallback palette\n <small class=\"text-color-secondary\">\u2014 colours for classes not listed</small></span>\n <div class=\"fallback-palette flex align-items-center flex-wrap gap-2\">\n <span *ngFor=\"let col of presetDraft.fallbackPalette; let i = index\"\n class=\"fallback-swatch flex align-items-center\">\n <hex-color-picker [color]=\"col\" (colorChange)=\"presetDraft.fallbackPalette[i] = $event\"></hex-color-picker>\n <i class=\"pi pi-times icon-hover ml-1\" pTooltip=\"Remove\" tooltipPosition=\"bottom\"\n (click)=\"removeFallbackColor(i)\"></i>\n </span>\n <button pButton type=\"button\" icon=\"pi pi-plus\"\n class=\"p-button-sm p-button-text\" pTooltip=\"Add fallback colour\"\n tooltipPosition=\"bottom\" (click)=\"addFallbackColor()\"></button>\n </div>\n </div>\n\n <div class=\"flex align-items-center gap-2\">\n <p-checkbox [(ngModel)]=\"presetDraft.autoPromote\" [binary]=\"true\" inputId=\"autoPromote\"></p-checkbox>\n <label for=\"autoPromote\">Auto-add detected classes to this list as they appear</label>\n </div>\n\n <div class=\"flex align-items-center gap-3\">\n <span class=\"font-medium\">Label matching</span>\n <p-selectButton [options]=\"matchModeOptions\" [(ngModel)]=\"presetDraft.matchMode\"\n optionLabel=\"label\" optionValue=\"value\"></p-selectButton>\n </div>\n </div>\n\n <ng-template pTemplate=\"footer\">\n <div class=\"manage-footer flex align-items-center flex-wrap gap-2 w-full\">\n <span class=\"flex align-items-center flex-wrap gap-1\">\n <p-button label=\"Reset\" icon=\"pi pi-refresh\" [text]=\"true\"\n (onClick)=\"resetPresetsToDefaults()\"></p-button>\n <p-button label=\"Import\" icon=\"pi pi-upload\" [text]=\"true\"\n (onClick)=\"presetFileInput.click()\"></p-button>\n <input #presetFileInput type=\"file\" accept=\".json,application/json\" hidden\n (change)=\"importPresets($event); presetFileInput.value = ''\"/>\n <p-button label=\"Export\" icon=\"pi pi-download\" [text]=\"true\"\n (onClick)=\"exportPresets()\"></p-button>\n </span>\n <span class=\"flex-1\"></span>\n <!-- Apply persists to the server and closes; it replaces the old Apply+Done pair. -->\n <p-button label=\"Apply\" icon=\"pi pi-check\" (onClick)=\"applyManageDialog(true)\"></p-button>\n </div>\n </ng-template>\n</p-dialog>\n\n<p-dialog header=\"Region editor help\" [(visible)]=\"displayHelpDialog\" [style]=\"{width: '50vw'}\"\n [maximizable]=\"true\" [draggable]=\"true\" appendTo=\"body\">\n <p>\n The Region Editor lists every region drawn on the currently displayed image.\n Open it with the\n <img src=\"assets/plotting/region-editor.svg\" width=\"14\" height=\"14\" alt=\"Region Editor\"/>\n button in the diagram toolbar: it opens as a floating dialog you can drag,\n resize, and leave open while you draw on the image.\n Regions are tracked <b>per image</b>: switching to another image hides\n these and shows that image's regions; switching back restores them.\n Edits in the table apply <b>live</b> to the diagram \u2014 there is no save or\n cancel.\n </p>\n\n <h3>Creating regions</h3>\n <p>\n Use the diagram toolbar tools to draw new regions:\n <i class=\"pi pi-stop\"></i> rectangle,\n <object data=\"assets/icons/polyline.svg\" width=\"14\" height=\"14\"></object> polyline,\n <i class=\"pi pi-pencil\"></i> freeform path, brush, or\n <object data=\"assets/icons/wand.svg\" width=\"14\" height=\"14\"></object> wand.\n Anything you draw appears immediately in the table, with the class name\n \"<b>Region</b>\" by default.\n </p>\n\n <h3>The table columns</h3>\n <ul>\n <li><b>Class</b> \u2014 the region's class. Pick a class from the dropdown (its\n colour follows), or use the <i class=\"pi pi-pencil\"></i> for a free-form name.</li>\n <li><b>Surface</b> \u2014 the region's area, in \u00B5m\u00B2/mm\u00B2 when the image carries a\n physical pixel size, otherwise in px\u00B2. Holes (donuts) and multi-part\n regions are accounted for.</li>\n <li>The trash icon at the end of a row deletes that one region.</li>\n <li>Use \"<b>x512</b>\" to round all rectangle widths/heights to the\n nearest multiple of 512.</li>\n </ul>\n <p>Region geometry is edited on the diagram itself (drag vertices, the vertex\n tools, brush/wand, and the right-click region actions), not in this table.</p>\n\n <h3>Selecting rows</h3>\n <ul>\n <li>Click a row to select it. The corresponding region is highlighted in the\n diagram (Plotly shows its edit handles; the OpenSeadragon image view\n highlights it and shows its handles in select mode).</li>\n <li><kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+click to add or remove a row from the\n selection. <kbd>Shift</kbd>+click to extend the selection in a range.</li>\n <li>Selecting a region in the diagram highlights the matching row in the\n table.</li>\n <li>Clicks on the colour picker, the trash icon, or the active class-edit\n input do not change the selection \u2014 that's standard PrimeNG behaviour.</li>\n </ul>\n\n <h3>Bulk actions on the selected rows</h3>\n <ul>\n <li><i class=\"pi pi-check-square\"></i> Select every region in the table at once (also a\n right-click action on the image).</li>\n <li><i class=\"pi pi-tags\"></i> Edit the label on every selected row at once.</li>\n <li><i class=\"pi pi-trash\"></i> Delete every selected region \u2014 both from\n the table and the diagram.</li>\n <li><i class=\"pi pi-times-circle\"></i> Delete every region on this image.</li>\n </ul>\n\n <h3>Annotation classes & colours (presets)</h3>\n <p>\n Preset a class once \u2014 a <b>name</b> and a <b>colour</b> \u2014 and every region with\n that class name is drawn in that colour, <b>including regions loaded from a\n GeoJSON later</b>. The preset colour is the source of truth: on load it\n <b>overrides the colour stored in the file</b>, so a given annotation type always\n looks the same across images and sessions.\n </p>\n <p>Set up your classes in the annotation-class manager \u2014\n <i class=\"pi pi-cog\"></i> <b>Manage</b>:</p>\n <ol>\n <li><b>Add class</b>, then type the class <b>name exactly as it appears in your\n data</b> (e.g. <code>Tumor</code>) and pick its <b>colour</b> from the swatch.\n Repeat for each class.</li>\n <li>Optionally: edit the <b>fallback palette</b> (colours given to classes that\n aren't in your list), toggle <b>auto-add</b> (adds unmatched classes found on\n load), and choose <b>exact</b> vs. <b>normalized</b> name matching \u2014 normalized\n ignores case and surrounding spaces, so <code>tumor</code> matches\n <code>Tumor</code>.</li>\n <li><b>Apply.</b> Presets are saved <b>per user</b> on the server, so they follow\n you across images and sessions. You can also <b>reset to defaults</b> or\n <b>import / export</b> the set as JSON to share it.</li>\n </ol>\n <p>\n Next time you open or <i class=\"pi pi-upload\"></i> import a GeoJSON whose regions\n carry those class names, each region is shown in its preset colour automatically \u2014\n no re-colouring needed. A class that isn't in your list instead gets a stable\n colour from the fallback palette.\n </p>\n <p>Day to day:</p>\n <ul>\n <li>The <b>class strip</b> above the table lists your presets with their colours.\n Click a class to make it <b>active</b> (new regions inherit it); with rows\n selected, clicking a class re-classifies them.</li>\n <li>The <b>Class</b> column dropdown re-classifies one region to a preset (its\n colour follows), or use the <i class=\"pi pi-pencil\"></i> for a free-form name.</li>\n <li>Recolour a class from its swatch (class strip or Manage) and every region of\n that class updates at once.</li>\n <li>Deleting a class reassigns its regions to the default <b>Region</b> class.</li>\n <li>A colour you set on a <b>single</b> region (<i class=\"pi pi-palette\"></i>\n Selected regions) is kept and is not overridden by its class.</li>\n </ul>\n\n <h3>Region actions (right-click the diagram)</h3>\n <p>Right-click a region \u2014 or the current multi-selection \u2014 on the diagram for\n the \"Selected region(s)\" actions:</p>\n <ul>\n <li><b>Merge / group</b> \u2014 combine 2+ selected regions into one (it may end\n up multi-part if the pieces don't touch).</li>\n <li><b>Ungroup</b> \u2014 split a multi-part region back into separate regions.</li>\n <li><b>Inverse</b> \u2014 replace the selection with everything outside it (the\n regions become holes in the image rectangle).</li>\n <li><b>Simplify</b> \u2014 reduce the vertex count (Douglas\u2013Peucker) by a pixel\n threshold; pick a preset or a custom value.</li>\n <li><b>Convert to B\u00E9zier / to polygon</b> and <b>Delete</b>.</li>\n </ul>\n\n <h3>Display options</h3>\n <ul>\n <li><i class=\"pi pi-tag\"></i> Toggle showing class labels on every\n region in the diagram (slower with many regions).</li>\n </ul>\n\n <h3>Import / export</h3>\n <ul>\n <li><i class=\"pi pi-upload\"></i> Import regions from a GeoJSON file.</li>\n <li><i class=\"pi pi-download\"></i> Export the current image's regions to\n a GeoJSON file. (The per-image cache is in-memory only \u2014 exporting is\n how you persist regions across sessions.)</li>\n </ul>\n\n <ng-template pTemplate=\"footer\">\n <p-button icon=\"pi pi-check\" (click)=\"displayHelpDialog=false\" label=\"Ok\" styleClass=\"p-button-text\">\n </p-button>\n </ng-template>\n</p-dialog>\n\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden}.region-editor-content{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}::ng-deep .region-editor-content .p-3{padding:0!important}::ng-deep .region-editor-content>p-table{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}::ng-deep .region-editor-content .p-datatable.region-editor-table{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}::ng-deep .region-editor-content .p-datatable.region-editor-table>.p-datatable-header{flex:0 0 auto}::ng-deep .region-editor-content .p-datatable.region-editor-table>.p-datatable-wrapper,::ng-deep .region-editor-content .p-datatable.region-editor-table>.p-datatable-table-container,::ng-deep .region-editor-content .p-datatable.region-editor-table .p-scroller-viewport{flex:1 1 0;min-height:0;height:100%;max-height:100%;overflow:auto}:host>p-paginator{display:block;flex:0 0 auto;border-top:1px solid var(--surface-border);padding:.25rem 0}::ng-deep region-editor .p-toolbar.region-toolbar{padding:.25rem .5rem;border-radius:0;border-left:none;border-right:none;border-top:none}::ng-deep region-editor .p-toolbar.region-toolbar .p-button.p-button-icon-only{width:auto;padding:.25rem .5rem}::ng-deep region-editor .p-toolbar.region-toolbar .p-button{padding:.25rem .5rem}.toolbar-icon-dark{filter:brightness(0) invert(47%) sepia(98%) saturate(567%) hue-rotate(181deg) brightness(103%) contrast(98%)}.class-col-header,.class-cell{white-space:nowrap;max-width:16rem}.class-cell .class-cell-display{display:flex;align-items:center;min-width:0}.class-cell .class-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.class-cell .icon-hover{flex:0 0 auto}.class-cell .icon-hover{cursor:pointer;color:var(--text-color-secondary);transition:color .15s ease}.class-cell .icon-hover:hover{color:var(--primary-color)}.class-cell .class-cell-edit{display:inline-block;width:100%}::ng-deep .p-datatable-sm .p-datatable-tbody>tr>td .p-button.p-button-rounded,::ng-deep .p-datatable-sm .p-datatable-tbody>tr>td .p-button.p-button-icon-only{width:1.5rem;height:1.5rem;padding:0}::ng-deep .p-datatable-sm .p-datatable-tbody>tr>td .p-button.p-button-rounded .p-button-icon,::ng-deep .p-datatable-sm .p-datatable-tbody>tr>td .p-button.p-button-icon-only .p-button-icon{font-size:.75rem}::ng-deep .p-datatable-sm .p-datatable-tbody>tr>td .p-inputtext{padding:.25rem .5rem;font-size:.875rem}::ng-deep .p-datatable-sm .p-paginator{padding:.15rem .25rem!important;min-height:unset!important}::ng-deep .p-datatable-sm .p-paginator,::ng-deep .p-datatable-sm .p-paginator *{font-size:.75rem!important}::ng-deep .p-datatable-sm .p-paginator .p-paginator-element{min-width:1.5rem!important;height:1.5rem!important}::ng-deep .p-datatable-sm .p-paginator .p-paginator-current{font-size:.75rem!important;height:1.5rem!important;min-width:unset!important}::ng-deep .p-datatable-sm .p-paginator .p-dropdown{height:1.5rem!important}::ng-deep .p-datatable-sm .p-paginator .p-dropdown .p-dropdown-label{padding:.1rem .25rem!important;line-height:1.2!important}::ng-deep .p-datatable-sm .p-paginator .p-dropdown .p-dropdown-trigger{width:1.25rem!important}::ng-deep .p-paginator-rpp-options .p-dropdown-panel .p-dropdown-items .p-dropdown-item{padding:.25rem .5rem!important;font-size:.75rem!important}.region-area{font-size:.8rem;font-weight:600;color:var(--primary-color, #3399ff)}.toolbar-separator{border-left:1px solid var(--surface-border);height:1.5rem;margin:0 .25rem;align-self:center}kbd{background:#e8e8e8;border:1px solid #bbb;border-radius:3px;padding:1px 5px;font-family:monospace;font-size:.85em}.classes-panel{display:flex;flex-direction:column;flex:0 0 auto;border-bottom:1px solid var(--surface-border)}.classes-panel-header{display:flex;align-items:center;gap:.35rem;padding:.35rem .5rem;border-bottom:1px solid var(--surface-border)}.classes-panel-title{font-size:.75rem;font-weight:600;letter-spacing:.03em;text-transform:uppercase;color:var(--text-color-secondary);margin-right:auto}.classes-panel-body{height:7rem;min-height:2.5rem;max-height:60vh;overflow-x:hidden;overflow-y:auto;resize:vertical}::ng-deep .classes-panel-body .hex-picker-swatch{width:1.25rem;height:1.25rem;border-width:1px}.class-row{display:flex;align-items:center;gap:.5rem;padding:.1rem .5rem;cursor:pointer;border-left:3px solid transparent}.class-row:hover{background:var(--surface-hover, rgba(0, 0, 0, .04))}.class-row.active{background:var(--highlight-bg, rgba(51, 153, 255, .12));border-left-color:var(--primary-color)}.class-row .row-swatch{flex:0 0 auto;display:inline-flex}.class-row .row-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.85rem}.class-row.active .row-name{color:var(--primary-color);font-weight:600}.class-row .row-count{flex:0 0 auto;min-width:1.5rem;text-align:center;font-size:.72rem;font-variant-numeric:tabular-nums;color:var(--text-color-secondary);background:var(--surface-200, rgba(0, 0, 0, .06));border-radius:10px;padding:0 .4rem}.class-row .row-count.zero{opacity:.5}.class-row .row-del{flex:0 0 auto}.classes-empty{font-size:.8rem;color:var(--text-color-secondary);padding:.5rem}::ng-deep .classes-panel-body .class-row .p-button.p-button-icon-only{width:1.75rem;height:1.75rem;padding:0}.chip-swatch{display:inline-block;width:.75rem;height:.75rem;border-radius:50%;border:1px solid rgba(0,0,0,.2);flex:0 0 auto}.class-cell .class-dropdown{min-width:0;flex:1 1 auto}::ng-deep .class-cell .class-dropdown.p-dropdown{width:100%}::ng-deep .class-cell .class-dropdown .p-dropdown-label{padding:.15rem .5rem;display:flex;align-items:center}.dd-value{display:inline-flex;align-items:center;gap:.4rem;min-width:0}.dd-value .class-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.manage-dialog .manage-class-row hex-color-picker,.manage-dialog .fallback-palette hex-color-picker{flex:0 0 auto}.manage-dialog .fallback-palette .icon-hover{cursor:pointer;color:var(--text-color-secondary)}.manage-dialog .fallback-palette .icon-hover:hover{color:var(--primary-color)}\n"] }]
|
|
1180
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
1181
|
+
type: Inject,
|
|
1182
|
+
args: [REGION_EDITOR_API]
|
|
1183
|
+
}] }, { type: i1.MessageService }, { type: i1.ConfirmationService }, { type: undefined, decorators: [{
|
|
1184
|
+
type: Inject,
|
|
1185
|
+
args: [REGION_IO_PORT]
|
|
1186
|
+
}] }], propDecorators: { overlayPanel: [{
|
|
1187
|
+
type: ViewChild,
|
|
1188
|
+
args: ['op']
|
|
1189
|
+
}] } });
|
|
1190
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVnaW9uLWVkaXRvci5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL3JlZ2lvbi1lZGl0b3IvcmVnaW9uLWVkaXRvci5jb21wb25lbnQudHMiLCIuLi8uLi8uLi8uLi9zcmMvbGliL3JlZ2lvbi1lZGl0b3IvcmVnaW9uLWVkaXRvci5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sRUFBcUIsU0FBUyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ2hGLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNwRCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQ3BDLE9BQU8sRUFBRSxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQzdDLE9BQU8sRUFBRSxZQUFZLEVBQUUsU0FBUyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFekQsT0FBTyxFQUFFLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxFQUFFLFlBQVksRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQzVFLE9BQU8sRUFBMEIsZ0JBQWdCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUNsRixPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFFMUQsT0FBTyxFQUFFLG1CQUFtQixFQUFFLGNBQWMsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNsRSxPQUFPLEVBQW9CLGlCQUFpQixFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFDOUYsT0FBTyxFQUFnQixjQUFjLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUNqRixPQUFPLEVBQUUsYUFBYSxFQUFFLFVBQVUsRUFBRSxZQUFZLEVBQUUsTUFBTSxlQUFlLENBQUM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQVN4RSxNQUFNLE9BQU8scUJBQXFCO0lBa0doQyxZQUNxQyxT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QUMsY0FBYzt5Q0FuR1AsWUFBWTtzQkFBNUIsU0FBUzt1QkFBQyxJQUFJIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29tcG9uZW50LCBJbmplY3QsIE9uRGVzdHJveSwgT25Jbml0LCBWaWV3Q2hpbGQgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IE92ZXJsYXlQYW5lbCB9IGZyb20gJ3ByaW1lbmcvb3ZlcmxheXBhbmVsJztcbmltcG9ydCB7IHNhdmVBcyB9IGZyb20gJ2ZpbGUtc2F2ZXInO1xuaW1wb3J0IHsgU3ViamVjdCwgU3Vic2NyaXB0aW9uIH0gZnJvbSAncnhqcyc7XG5pbXBvcnQgeyBkZWJvdW5jZVRpbWUsIHN3aXRjaE1hcCB9IGZyb20gJ3J4anMvb3BlcmF0b3JzJztcblxuaW1wb3J0IHsgUG9seWdvbiwgUmVjdGFuZ2xlLCBSZWdpb24sIE11bHRpUG9seWdvbiB9IGZyb20gJy4uL21vZGVscy9yZWdpb24nO1xuaW1wb3J0IHsgUHJlc2V0U2V0LCBDbGFzc1ByZXNldCwgZGVmYXVsdFByZXNldFNldCB9IGZyb20gJy4uL21vZGVscy9jbGFzcy1wcmVzZXQnO1xuaW1wb3J0IHsgY29sb3JGb3JMYWJlbCB9IGZyb20gJy4uL3N0b3JlL2NsYXNzLWNvbG9yLnV0aWwnO1xuaW1wb3J0IHsgSUltYWdlTWV0YWRhdGEgfSBmcm9tICcuLi9jb250cmFjdHMvaW1hZ2UuY29udHJhY3QnO1xuaW1wb3J0IHsgQ29uZmlybWF0aW9uU2VydmljZSwgTWVzc2FnZVNlcnZpY2UgfSBmcm9tICdwcmltZW5nL2FwaSc7XG5pbXBvcnQgeyBJUmVnaW9uRWRpdG9yQXBpLCBSRUdJT05fRURJVE9SX0FQSSB9IGZyb20gJy4uL2NvbnRyYWN0cy9yZWdpb24tZWRpdG9yLWFwaS5jb250cmFjdCc7XG5pbXBvcnQgeyBSZWdpb25Jb1BvcnQsIFJFR0lPTl9JT19QT1JUIH0gZnJvbSAnLi4vY29udHJhY3RzL3BvcnRzL3JlZ2lvbi1pby5wb3J0JztcbmltcG9ydCB7IHJlZ2lvblRvUGFydHMsIHNjYWxlUGFydHMsIG1hc2tTY2FsZUZvciB9IGZyb20gJy4vbWFzay1yYXN0ZXInO1xuXG5AQ29tcG9uZW50KHtcbiAgLy8gQ2Fub25pY2FsIHByZWZpeGVkIHNlbGVjdG9yIGZpcnN0OyB0aGUgdW5wcmVmaXhlZCBvcmlnaW5hbCBpcyBrZXB0IGFzIGFuXG4gIC8vIGFsaWFzIGZvciBvbmUgcmVsZWFzZSAocHJlLXB1YmxpY2F0aW9uIGJhY2stY29tcGF0KS5cbiAgc2VsZWN0b3I6ICdyZWdpb24tZWRpdG9yJyxcbiAgdGVtcGxhdGVVcmw6ICcuL3JlZ2lvbi1lZGl0b3IuY29tcG9uZW50Lmh0bWwnLFxuICBzdHlsZVVybHM6IFsnLi9yZWdpb24tZWRpdG9yLmNvbXBvbmVudC5zY3NzJ10sXG59KVxuZXhwb3J0IGNsYXNzIFJlZ2lvbkVkaXRvckNvbXBvbmVudCBpbXBsZW1lbnRzIE9uSW5pdCwgT25EZXN0cm95IHtcbiAgcHJvdGVjdGVkIHJlYWRvbmx5IEFycmF5ID0gQXJyYXk7XG5cbiAgQFZpZXdDaGlsZCgnb3AnKSBvdmVybGF5UGFuZWwhOiBPdmVybGF5UGFuZWw7XG5cbiAgcmVnaW9uczogUmVnaW9uW10gPSBbXTtcbiAgcmVnaW9uc0NvcHk6IFJlZ2lvbltdID0gW107XG4gIHNlbGVjdGVkUmVnaW9uczogUmVnaW9uW10gPSBbXTtcbiAgc2hvd1NoYXBlTGFiZWwhOiBib29sZWFuO1xuICBzaGFwZUNvbG9yITogc3RyaW5nO1xuICBmaWxsQ29sb3IhOiBzdHJpbmc7XG4gIGRpc3BsYXlIZWxwRGlhbG9nID0gZmFsc2U7XG4gIHNob3dDb2xvckRpYWxvZyA9IGZhbHNlO1xuICAvKiogT25lIGNvbG91ciBlZGl0b3IgcGVyIHVuaXF1ZSBjbGFzcyBhbW9uZyB0aGUgc2VsZWN0ZWQgcmVnaW9ucywgc2hvd24gaW4gdGhlXG4gICAqICBcImVkaXQgY29sb3VyIG9mIHNlbGVjdGVkIHJlZ2lvbnNcIiBkaWFsb2cuIFVuY2xhc3NpZmllZCByZWdpb25zIChubyBsYWJlbClcbiAgICogIGFyZSBncm91cGVkIHVuZGVyIGEgc2luZ2xlIGVudHJ5IHdpdGggYW4gZW1wdHkgYGxhYmVsYC4gKi9cbiAgY2xhc3NDb2xvckVkaXRzOiB7IGxhYmVsOiBzdHJpbmc7IGNvbG9yOiBzdHJpbmcgfVtdID0gW107XG4gIGxhYmVsQ29sb3JzOiBNYXA8c3RyaW5nLCBzdHJpbmc+ID0gbmV3IE1hcCgpO1xuICBzZWxlY3RlZExhYmVsQ29sb3IgPSAnJztcbiAgLyoqIFwiRWRpdCBjbGFzcyBvbiBzZWxlY3RlZCByb3dzXCIgcG9wdXA6IGNob3NlbiBleGlzdGluZyBjbGFzcywgYW5kIGEgbmV3LWNsYXNzIG5hbWUuICovXG4gIGJ1bGtDbGFzcyA9ICcnO1xuICBuZXdCdWxrQ2xhc3MgPSAnJztcblxuICAvLyDilIDilIAgYW5ub3RhdGlvbi1jbGFzcyBwcmVzZXRzIChqaXQtdWkjNzApIOKUgOKUgFxuICAvKiogTG9jYWwgbWlycm9yIG9mIHRoZSBwZXItdXNlciBwcmVzZXQgc2V0IChjaGlwIHN0cmlwLCBDbGFzcyBkcm9wZG93biwgZGlhbG9nKS4gKi9cbiAgcHJlc2V0U2V0OiBQcmVzZXRTZXQgPSBkZWZhdWx0UHJlc2V0U2V0KCk7XG4gIC8qKiBDbGFzcyBhcHBsaWVkIHRvIG5ld2x5IGRyYXduL2FkZGVkIHJlZ2lvbnMgYW5kIHRvIGJ1bGsgY2hpcC1jbGlja3MuICovXG4gIGFjdGl2ZUNsYXNzOiBzdHJpbmcgfCBudWxsID0gbnVsbDtcbiAgc2hvd01hbmFnZURpYWxvZyA9IGZhbHNlO1xuICAvKiogV29ya2luZyBjb3B5IGVkaXRlZCBpbnNpZGUgdGhlIE1hbmFnZS1jbGFzc2VzIGRpYWxvZzsgY29tbWl0dGVkIG9uIEFwcGx5L0RvbmUuICovXG4gIHByZXNldERyYWZ0OiBQcmVzZXRTZXQgfCBudWxsID0gbnVsbDtcbiAgLyoqIGNsYXNzIG5hbWUgKG1hdGNoLW1vZGUga2V5ZWQpIOKGkiBudW1iZXIgb2YgcmVnaW9ucyBjdXJyZW50bHkgdXNpbmcgaXQuICovXG4gIGNsYXNzQ291bnRzID0gbmV3IE1hcDxzdHJpbmcsIG51bWJlcj4oKTtcbiAgLyoqIEZhbGxiYWNrIGNsYXNzIHRoYXQgYSByZWdpb24gcmV2ZXJ0cyB0byB3aGVuIGl0cyBjbGFzcyBpcyBkZWxldGVkIChqaXQtdWkjNzApLiAqL1xuICByZWFkb25seSBkZWZhdWx0Q2xhc3NOYW1lID0gJ1JlZ2lvbic7XG4gIC8qKiBDbGFzc2VzIGFzIHNob3duIGluIHRoZSBwYW5lbDogc29ydGVkIGJ5IHJlZ2lvbiBjb3VudCAoZGVzYyksIHRoZW4gbmFtZS4gKi9cbiAgZGlzcGxheUNsYXNzZXM6IENsYXNzUHJlc2V0W10gPSBbXTtcbiAgcmVhZG9ubHkgbWF0Y2hNb2RlT3B0aW9ucyA9IFtcbiAgICB7IGxhYmVsOiAnRXhhY3QnLCB2YWx1ZTogJ2V4YWN0JyB9LFxuICAgIHsgbGFiZWw6ICdOb3JtYWxpemVkJywgdmFsdWU6ICdub3JtYWxpemVkJyB9LFxuICBdO1xuXG4gIHBhZ2luYXRvckZpcnN0ID0gMDtcbiAgcGFnaW5hdG9yUm93cyA9IDEwO1xuICByZWFkb25seSByb3dzUGVyUGFnZU9wdGlvbnMgPSBbMTAsIDI1LCA1MF07XG4gIC8qKiBSZWdpb25zIHdob3NlIENsYXNzIGNlbGwgaXMgY3VycmVudGx5IGluIGVkaXQgbW9kZS5cbiAgICogIFVzaW5nIG9iamVjdCBpZGVudGl0eSBhcyB0aGUga2V5IGF2b2lkcyBuZWVkaW5nIGEgdW5pcXVlIGlkIGZpZWxkLiAqL1xuICBlZGl0aW5nTGFiZWxSZWdpb25zID0gbmV3IFNldDxhbnk+KCk7XG5cbiAgc2hvd1NhdmVBc0RpYWxvZyA9IGZhbHNlO1xuICBzYXZlQXNGaWxlbmFtZSA9ICcnO1xuICBzYXZlQXNGaWxlRXhpc3RzID0gZmFsc2U7XG4gIC8qKiBUcnVlIHdoaWxlIGEgR2VvSlNPTiBwZXJzaXN0IGlzIHNlcmlhbGl6aW5nL3VwbG9hZGluZyDigJQgZHJpdmVzIHRoZSBkaWFsb2cnc1xuICAgKiAgcHJvZ3Jlc3MgYmFyIGFuZCBDYW5jZWwgYnV0dG9uIChwYXJpdHkgd2l0aCB0aGUgU2F2ZS1tYXNrIGRpYWxvZykuICovXG4gIHNhdmVBc0J1c3kgPSBmYWxzZTtcbiAgcHJpdmF0ZSBfc2F2ZUFzQ2hlY2skID0gbmV3IFN1YmplY3Q8c3RyaW5nPigpO1xuICBwcml2YXRlIF9zYXZlQXNTdWI/OiBTdWJzY3JpcHRpb247XG4gIC8qKiBIYW5kbGUgZm9yIHRoZSBkZWZlcnJlZC1zZXJpYWxpemUgdGltZXIgc28gQ2FuY2VsL2Rlc3Ryb3kgY2FuIGFib3J0IGl0XG4gICAqICBiZWZvcmUgaXQgZmlyZXMgKG90aGVyd2lzZSB0aGUgdXBsb2FkIHdvdWxkIHN0aWxsIHN0YXJ0KS4gKi9cbiAgcHJpdmF0ZSBfc2F2ZUFzVGltZXI/OiBSZXR1cm5UeXBlPHR5cGVvZiBzZXRUaW1lb3V0PjtcblxuICBzaG93RXhwb3J0RGlhbG9nID0gZmFsc2U7XG4gIGV4cG9ydEZpbGVuYW1lID0gJyc7XG5cbiAgc2hvd1NhdmVNYXNrRGlhbG9nID0gZmFsc2U7XG4gIHNhdmVNYXNrRmlsZW5hbWUgPSAnJztcbiAgLyoqIE1hc2sgdHlwZSBjaG9zZW4gaW4gdGhlIFNhdmUtbWFzayBkaWFsb2c6IGEgYmluYXJ5IGZvcmVncm91bmQvYmFja2dyb3VuZFxuICAgKiAgbWFzaywgb3IgYSBtdWx0aS1jbGFzcyBtYXNrIHdpdGggYSBkaXN0aW5jdCBpZCBwZXIgcmVnaW9uLiAqL1xuICBtYXNrTW9kZTogJ2JpbmFyeScgfCAnbXVsdGljbGFzcycgPSAnYmluYXJ5JztcbiAgLyoqIFRydWUgd2hpbGUgdGhlIG1hc2sgd29ya2VyIGlzIHJhc3Rlcml6aW5nL2VuY29kaW5nIOKAlCBkcml2ZXMgdGhlIHByb2dyZXNzXG4gICAqICBiYXIgYW5kIHRoZSBDYW5jZWwgYnV0dG9uIGluIHRoZSBkaWFsb2cuICovXG4gIG1hc2tCdXN5ID0gZmFsc2U7XG4gIC8qKiAw4oCTMTAwIHJhc3Rlcml6YXRpb24gcHJvZ3Jlc3M7IHN3aXRjaGVzIHRvIGluZGV0ZXJtaW5hdGUgZHVyaW5nIGVuY29kaW5nLiAqL1xuICBtYXNrUHJvZ3Jlc3MgPSAwO1xuICBtYXNrRW5jb2RpbmcgPSBmYWxzZTtcbiAgcHJpdmF0ZSBtYXNrV29ya2VyPzogV29ya2VyO1xuICBwcml2YXRlIHBlbmRpbmdNYXNrRmlsZW5hbWUgPSAnJztcblxuICAvKipcbiAgICogUHJpbWVORyBwLXRhYmxlIG11bHRpLXNlbGVjdGlvbiBtb2RlIHRvZ2dsZS4gV2hlbiB0cnVlLCBzaW5nbGUtY2xpY2tcbiAgICogc2VsZWN0cyBvbmUgcm93IChyZXBsYWNpbmcgcHJpb3IpOyBtZXRhL2N0cmwtY2xpY2sgdG9nZ2xlcyBhIHJvdyBpbi9vdXRcbiAgICogb2YgdGhlIHNlbGVjdGlvbi4gV2hlbiBmYWxzZSwgZXZlcnkgY2xpY2sgdG9nZ2xlcy4gRGVmYXVsdCB0cnVlIHRvXG4gICAqIG1hdGNoIHRoZSBtb3N0IGNvbW1vbiBzcHJlYWRzaGVldC1saWtlIFVYLlxuICAgKi9cbiAgbWV0YUtleSA9IHRydWU7XG5cbiAgcHJpdmF0ZSBfdXBkYXRpbmdGcm9tRWRpdG9yID0gZmFsc2U7XG4gIHByaXZhdGUgX3N1cHByZXNzU2VsZWN0aW9uU3luY1RvUGxvdCA9IGZhbHNlO1xuICBwcml2YXRlIF9yZWdpb25TdWIgPSBuZXcgU3Vic2NyaXB0aW9uKCk7XG4gIHByaXZhdGUgX3NlbGVjdGVkSWR4U3ViID0gbmV3IFN1YnNjcmlwdGlvbigpO1xuICBwcml2YXRlIF9tZXRhU3ViID0gbmV3IFN1YnNjcmlwdGlvbigpO1xuICBwcml2YXRlIF9wcmVzZXRTdWIgPSBuZXcgU3Vic2NyaXB0aW9uKCk7XG5cbiAgLyoqIFBoeXNpY2FsIHBpeGVsIHNpemUgKMK1bS9waXhlbCkgb2YgdGhlIGFjdGl2ZSBpbWFnZSwgZm9yIHJlZ2lvbiBhcmVhcyBpblxuICAgKiAgwrVtwrIuIFVuZGVmaW5lZCB3aGVuIHRoZSBmb3JtYXQgcmVwb3J0cyBubyBwaHlzaWNhbCBzaXplLiAqL1xuICBwcml2YXRlIG1wcFg/OiBudW1iZXI7XG4gIHByaXZhdGUgbXBwWT86IG51bWJlcjtcblxuICBjb25zdHJ1Y3RvcihcbiAgICBASW5qZWN0KFJFR0lPTl9FRElUT1JfQVBJKSBwcml2YXRlIHJlZ2lvbkFwaTogSVJlZ2lvbkVkaXRvckFwaSxcbiAgICBwdWJsaWMgbWVzc2FnZVNlcnZpY2U6IE1lc3NhZ2VTZXJ2aWNlLFxuICAgIHByaXZhdGUgY29uZmlybWF0aW9uU2VydmljZTogQ29uZmlybWF0aW9uU2VydmljZSxcbiAgICBASW5qZWN0KFJFR0lPTl9JT19QT1JUKSBwcml2YXRlIHJlZ2lvbklvOiBSZWdpb25Jb1BvcnQsXG4gICkge31cblxuICAvKipcbiAgICogRGVlcCBjb3B5IG9mIHJlZ2lvbnMgZm9yIGRpcnR5LXRyYWNraW5nIChgcmVnaW9uc0NvcHlgKS4gUmV0dXJucyBwbGFpbi1vYmplY3RcbiAgICogY2xvbmVzIOKAlCBvbmx5IHN0cnVjdHVyYWwgZXF1YWxpdHkgbWF0dGVycyBoZXJlLCBub3QgcHJvdG90eXBlcy5cbiAgICogVE9ETzogcmVwbGFjZSB3aXRoIGBzdHJ1Y3R1cmVkQ2xvbmVgIG9uY2UgdmVyaWZpZWQuXG4gICAqL1xuICBwcml2YXRlIGRlZXBDbG9uZTxUPihpdGVtczogVFtdKTogVFtdIHtcbiAgICByZXR1cm4gaXRlbXMubWFwKChpdGVtKSA9PiB7XG4gICAgICBpZiAoQXJyYXkuaXNBcnJheShpdGVtKSkge1xuICAgICAgICByZXR1cm4gdGhpcy5kZWVwQ2xvbmUoaXRlbSkgYXMgdW5rbm93biBhcyBUO1xuICAgICAgfSBlbHNlIGlmICh0eXBlb2YgaXRlbSA9PT0gJ29iamVjdCcgJiYgaXRlbSAhPT0gbnVsbCkge1xuICAgICAgICBjb25zdCBjbG9uZWQ6IGFueSA9IHt9O1xuICAgICAgICBmb3IgKGNvbnN0IGtleSBpbiBpdGVtKSB7XG4gICAgICAgICAgY2xvbmVkW2tleV0gPSB0aGlzLmRlZXBDbG9uZShbaXRlbVtrZXldXSlbMF07XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGNsb25lZDtcbiAgICAgIH1cbiAgICAgIHJldHVybiBpdGVtO1xuICAgIH0pO1xuICB9XG5cbiAgbmdPbkluaXQoKSB7XG4gICAgdGhpcy5zaG93U2hhcGVMYWJlbCA9IHRoaXMucmVnaW9uQXBpLmdldFNob3dTaGFwZUxhYmVsKCk7XG4gICAgdGhpcy5zaGFwZUNvbG9yID0gdGhpcy5yZWdpb25BcGkuZ2V0U2hhcGVDb2xvcigpO1xuICAgIHRoaXMuZmlsbENvbG9yID0gdGhpcy5yZWdpb25BcGkuZ2V0RmlsbENvbG9yKCk7XG5cbiAgICAvLyBBbm5vdGF0aW9uLWNsYXNzIHByZXNldHMgKGppdC11aSM3MCk6IG1pcnJvciB0aGUgc2V0IGZvciB0aGUgY2hpcCBzdHJpcCxcbiAgICAvLyB0aGUgQ2xhc3MgZHJvcGRvd24gYW5kIHRoZSBtYW5hZ2UgZGlhbG9nLiBHdWFyZGVkIHdpdGggb3B0aW9uYWwgY2FsbHMgc29cbiAgICAvLyBwYXJ0aWFsIEFQSSBtb2NrcyBpbiB0ZXN0cyAod2hpY2ggb21pdCB0aGUgcHJlc2V0IG1ldGhvZHMpIGRvbid0IHRocm93LlxuICAgIGNvbnN0IGluaXRpYWxQcmVzZXRzID0gdGhpcy5yZWdpb25BcGkuZ2V0UHJlc2V0U2V0Py4oKTtcbiAgICBpZiAoaW5pdGlhbFByZXNldHMpIHRoaXMucHJlc2V0U2V0ID0gaW5pdGlhbFByZXNldHM7XG4gICAgY29uc3QgcHJlc2V0cyQgPSB0aGlzLnJlZ2lvbkFwaS5nZXRQcmVzZXRTZXQkPy4oKTtcbiAgICBpZiAocHJlc2V0cyQpIHtcbiAgICAgIHRoaXMuX3ByZXNldFN1YiA9IHByZXNldHMkLnN1YnNjcmliZSgoc2V0KSA9PiB7XG4gICAgICAgIGlmIChzZXQpIHRoaXMucHJlc2V0U2V0ID0gc2V0O1xuICAgICAgICB0aGlzLnVwZGF0ZURpc3BsYXlDbGFzc2VzKCk7XG4gICAgICB9KTtcbiAgICB9XG4gICAgLy8gU2VlZCBmcm9tIHRoZSB2aXN1YWxpemVyJ3MgY3VycmVudCByZWdpb25zIOKAlCBhbHJlYWR5IHNjb3BlZCB0byB0aGVcbiAgICAvLyBhY3RpdmUgaW1hZ2UgYnkgdGhlIHBlci1pbWFnZSBjYWNoZSwgYW5kIGhhbmRlZCBiYWNrIGFzIG5ldXRyYWwgUmVnaW9uXG4gICAgLy8gb2JqZWN0cyAobm8gYmFja2VuZCBzaGFwZSBmb3JtYXQgaW52b2x2ZWQpLlxuICAgIC8vIEFubm90YXRpb24gcmVnaW9ucyBvbmx5IOKAlCBpbnRlbnNpdHktcHJvZmlsZSBsaW5lcyBhcmUgb3duZWQgYnkgdGhlXG4gICAgLy8gaW50ZW5zaXR5IHRvb2wgYW5kIGV4Y2x1ZGVkIGJ5IHRoZSBjb250cmFjdCwgc28gdGhlIGVkaXRvciBuZXZlciBzZWVzIHRoZW0uXG4gICAgdGhpcy5yZWdpb25zID0gdGhpcy5hcHBseVJlZ2lvbkNvbG9ycyh0aGlzLnJlZ2lvbkFwaS5nZXRBbm5vdGF0aW9uUmVnaW9ucygpKTtcbiAgICB0aGlzLnJlZ2lvbnNDb3B5ID0gdGhpcy5kZWVwQ2xvbmUodGhpcy5yZWdpb25zKTtcbiAgICB0aGlzLnN5bmNDbGFzc2VzRnJvbVJlZ2lvbnModGhpcy5yZWdpb25zKTtcbiAgICB0aGlzLnJlY29tcHV0ZUNsYXNzQ291bnRzKCk7XG5cbiAgICAvLyBUaGUgdXBkYXRlIGV2ZW50IGlzIGp1c3QgYSBjaGFuZ2Ugc2lnbmFsOyByZS1yZWFkIHRoZSByZWdpb25zIGZyb20gdGhlXG4gICAgLy8gdmlzdWFsaXplciByYXRoZXIgdGhhbiBwYXJzaW5nIHdoYXRldmVyIHBheWxvYWQgaXQgY2Fycmllcy5cbiAgICB0aGlzLl9yZWdpb25TdWIgPSB0aGlzLnJlZ2lvbkFwaS5nZXRSZWdpb25VcGRhdGVFdmVudCgpLnN1YnNjcmliZSgoKSA9PiB7XG4gICAgICBpZiAodGhpcy5fdXBkYXRpbmdGcm9tRWRpdG9yKSByZXR1cm47XG4gICAgICBjb25zdCB1cGRhdGVkID0gdGhpcy5hcHBseVJlZ2lvbkNvbG9ycyh0aGlzLnJlZ2lvbkFwaS5nZXRBbm5vdGF0aW9uUmVnaW9ucygpKTtcbiAgICAgIC8vIHByZXNlcnZlIHNlbGVjdGlvbiBmb3IgcmVnaW9ucyB0aGF0IHN0aWxsIGV4aXN0IChieSBpZCwgc2luY2UgbmFtZVxuICAgICAgLy8gaXMgYSB1c2VyLWVkaXRhYmxlIGRpc3BsYXkgbGFiZWwgYW5kIG1heSBjb2xsaWRlKVxuICAgICAgY29uc3Qgc2VsZWN0ZWRJZHMgPSBuZXcgU2V0KHRoaXMuc2VsZWN0ZWRSZWdpb25zLm1hcCgocikgPT4gci5pZCkpO1xuICAgICAgdGhpcy5yZWdpb25zID0gdXBkYXRlZDtcbiAgICAgIHRoaXMucmVnaW9uc0NvcHkgPSB0aGlzLmRlZXBDbG9uZSh0aGlzLnJlZ2lvbnMpO1xuICAgICAgdGhpcy5zZWxlY3RlZFJlZ2lvbnMgPSB1cGRhdGVkLmZpbHRlcigocikgPT4gc2VsZWN0ZWRJZHMuaGFzKHIuaWQpKTtcbiAgICAgIHRoaXMuY2xhbXBQYWdpbmF0b3JGaXJzdCgpO1xuICAgICAgdGhpcy5zeW5jQ2xhc3Nlc0Zyb21SZWdpb25zKHVwZGF0ZWQpO1xuICAgICAgdGhpcy5yZWNvbXB1dGVDbGFzc0NvdW50cygpO1xuICAgIH0pO1xuXG4gICAgdGhpcy5fc2F2ZUFzQ2hlY2skLnBpcGUoXG4gICAgICBkZWJvdW5jZVRpbWUoNDAwKSxcbiAgICAgIHN3aXRjaE1hcChuYW1lID0+IHRoaXMucmVnaW9uSW8ucm9pRmlsZUV4aXN0cyhuYW1lKSksXG4gICAgKS5zdWJzY3JpYmUoe1xuICAgICAgbmV4dDogZXhpc3RzID0+IHsgdGhpcy5zYXZlQXNGaWxlRXhpc3RzID0gZXhpc3RzOyB9LFxuICAgICAgZXJyb3I6ICgpID0+IHsgdGhpcy5zYXZlQXNGaWxlRXhpc3RzID0gZmFsc2U7IH0sXG4gICAgfSk7XG5cbiAgICAvLyBQaHlzaWNhbCBwaXhlbCBzaXplIG9mIHRoZSBhY3RpdmUgaW1hZ2UgKGZvciByZWdpb24gYXJlYXMgaW4gwrVtwrIvbW3CsikuXG4gICAgLy8gTWlycm9yIFBsb3RseVNlcnZpY2UuY3VycmVudE1wcDogdGhlIGNhbGlicmF0aW9uIG1heSBzaXQgb24gYW55IGNoYW5uZWxcbiAgICAvLyBlbnRyeSwgbm90IG5lY2Vzc2FyaWx5IFswXSwgc28gcGljayB0aGUgZmlyc3QgZW50cnkgd2l0aCBhIHBvc2l0aXZlIG1wcFhcbiAgICAvLyByYXRoZXIgdGhhbiByZWFkaW5nIFswXSBibGluZGx5LiBGYWxsIGJhY2sgdG8gc3F1YXJlIHBpeGVscyAobXBwWSA9IG1wcFgpXG4gICAgLy8gd2hlbiBvbmx5IG9uZSBheGlzIGlzIHJlcG9ydGVkLlxuICAgIHRoaXMuX21ldGFTdWIgPSB0aGlzLnJlZ2lvbkFwaS5nZXRJbWFnZU1ldGEoKS5zdWJzY3JpYmUoKG1ldGEpID0+IHtcbiAgICAgIGNvbnN0IHsgbXBwWCwgbXBwWSB9ID0gdGhpcy5waWNrTXBwKG1ldGEpO1xuICAgICAgdGhpcy5tcHBYID0gbXBwWDtcbiAgICAgIHRoaXMubXBwWSA9IG1wcFk7XG4gICAgfSk7XG5cbiAgICB0aGlzLl9zZWxlY3RlZElkeFN1YiA9IHRoaXMucmVnaW9uQXBpLmdldFNlbGVjdGVkUmVnaW9ucyQoKS5zdWJzY3JpYmUoKHNlbGVjdGVkKSA9PiB7XG4gICAgICBpZiAodGhpcy5fc3VwcHJlc3NTZWxlY3Rpb25TeW5jVG9QbG90KSByZXR1cm47XG4gICAgICAvLyBNYXAgdGhlIGNvbnRyYWN0J3Mgc2VsZWN0ZWQgcmVnaW9ucyB0byB0aGUgZWRpdG9yJ3Mgb3duIGluc3RhbmNlcyBieSBpZC5cbiAgICAgIGNvbnN0IG5leHQgPSBzZWxlY3RlZFxuICAgICAgICAubWFwKChzKSA9PiB0aGlzLnJlZ2lvbnMuZmluZCgocikgPT4gci5pZCA9PT0gcy5pZCkpXG4gICAgICAgIC5maWx0ZXIoKHIpOiByIGlzIFJlZ2lvbiA9PiAhIXIpO1xuICAgICAgY29uc3Qgc2FtZSA9XG4gICAgICAgIG5leHQubGVuZ3RoID09PSB0aGlzLnNlbGVjdGVkUmVnaW9ucy5sZW5ndGggJiZcbiAgICAgICAgbmV4dC5ldmVyeSgociwgaSkgPT4gci5pZCA9PT0gdGhpcy5zZWxlY3RlZFJlZ2lvbnNbaV0/LmlkKTtcbiAgICAgIGlmICghc2FtZSkgdGhpcy5zZWxlY3RlZFJlZ2lvbnMgPSBuZXh0O1xuICAgICAgLy8gU2Nyb2xsIHRoZSBwYWdpbmF0b3Igc28gdGhlIG1vc3QtcmVjZW50bHktc2VsZWN0ZWQgcm93IGlzIHZpc2libGUuXG4gICAgICBpZiAobmV4dC5sZW5ndGggPiAwKSB7XG4gICAgICAgIGNvbnN0IGxhc3RJZHggPSB0aGlzLnJlZ2lvbnMuZmluZEluZGV4KChyKSA9PiByLmlkID09PSBuZXh0W25leHQubGVuZ3RoIC0gMV0uaWQpO1xuICAgICAgICBpZiAobGFzdElkeCA+PSAwKSB7XG4gICAgICAgICAgY29uc3QgcGFnZVN0YXJ0ID0gTWF0aC5mbG9vcihsYXN0SWR4IC8gdGhpcy5wYWdpbmF0b3JSb3dzKSAqIHRoaXMucGFnaW5hdG9yUm93cztcbiAgICAgICAgICBpZiAocGFnZVN0YXJ0ICE9PSB0aGlzLnBhZ2luYXRvckZpcnN0KSB0aGlzLnBhZ2luYXRvckZpcnN0ID0gcGFnZVN0YXJ0O1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICBuZ09uRGVzdHJveSgpIHtcbiAgICB0aGlzLl9yZWdpb25TdWIudW5zdWJzY3JpYmUoKTtcbiAgICB0aGlzLl9zZWxlY3RlZElkeFN1Yi51bnN1YnNjcmliZSgpO1xuICAgIHRoaXMuX21ldGFTdWIudW5zdWJzY3JpYmUoKTtcbiAgICB0aGlzLl9wcmVzZXRTdWIudW5zdWJzY3JpYmUoKTtcbiAgICBpZiAodGhpcy5fc2F2ZUFzVGltZXIgIT09IHVuZGVmaW5lZCkgY2xlYXJUaW1lb3V0KHRoaXMuX3NhdmVBc1RpbWVyKTtcbiAgICB0aGlzLl9zYXZlQXNTdWI/LnVuc3Vic2NyaWJlKCk7XG4gICAgdGhpcy50ZWFyZG93bk1hc2tXb3JrZXIoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBUYWJsZSDihpIgUGxvdDogUHJpbWVORyBmaXJlcyBvblJvd1NlbGVjdC9vblJvd1Vuc2VsZWN0IGFmdGVyIHVwZGF0aW5nXG4gICAqIGBzZWxlY3RlZFJlZ2lvbnNgLiBQdXNoIHRoZSBzZWxlY3RlZCByZWdpb25zIHRvIHRoZSBjb250cmFjdCBieSBpZGVudGl0eSDigJRcbiAgICogdGhlIHBhY2thZ2UgbWFwcyB0aGVtIHRvIGl0cyBpbnRlcm5hbCBpbmRleCBzcGFjZSAod2hpY2ggaW5jbHVkZXMgdGhlXG4gICAqIGludGVuc2l0eS1wcm9maWxlIGxpbmVzIHRoZSBlZGl0b3IgbmV2ZXIgc2VlcykuXG4gICAqL1xuICBvblNlbGVjdGlvbkNoYW5nZWQoKSB7XG4gICAgdGhpcy5fc3VwcHJlc3NTZWxlY3Rpb25TeW5jVG9QbG90ID0gdHJ1ZTtcbiAgICB0cnkge1xuICAgICAgdGhpcy5yZWdpb25BcGkuc2V0U2VsZWN0ZWRSZWdpb25zKHRoaXMuc2VsZWN0ZWRSZWdpb25zID8/IFtdKTtcbiAgICB9IGZpbmFsbHkge1xuICAgICAgdGhpcy5fc3VwcHJlc3NTZWxlY3Rpb25TeW5jVG9QbG90ID0gZmFsc2U7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEFwcGx5IGNsYXNzaWZpY2F0aW9uIGNvbG91cnMgYW5kIHJlZnJlc2ggdGhlIGxhYmVs4oaSY29sb3VyIG1hcCBmb3IgcmVnaW9uc1xuICAgKiBjb21pbmcgZnJvbSB0aGUgdmlzdWFsaXplci4gVGhlIHZpc3VhbGl6ZXIgaGFuZHMgYmFjayBuZXV0cmFsIGBSZWdpb25gXG4gICAqIG9iamVjdHMgd2l0aCBib3VuZHMsIGNvbG91ciBhbmQgbGFiZWwgYWxyZWFkeSBwb3B1bGF0ZWQsIHNvIHRoZSBlZGl0b3Igbm9cbiAgICogbG9uZ2VyIHBhcnNlcyBhbnkgYmFja2VuZC1zcGVjaWZpYyBzaGFwZSBmb3JtYXQg4oCUIGl0IG9ubHkgZmlsbHMgaW4gYVxuICAgKiBmYWxsYmFjayBjb2xvdXIgYW5kIHJlYnVpbGRzIGl0cyBsb2NhbCBsYWJlbOKGkmNvbG91ciBsb29rdXAuXG4gICAqL1xuICBwcml2YXRlIGFwcGx5UmVnaW9uQ29sb3JzKHJlZ2lvbnM6IFJlZ2lvbltdKTogUmVnaW9uW10ge1xuICAgIGZvciAoY29uc3QgcmVnaW9uIG9mIHJlZ2lvbnMpIHtcbiAgICAgIHJlZ2lvbi5jb2xvciA9XG4gICAgICAgIHJlZ2lvbi5jb2xvciB8fFxuICAgICAgICB0aGlzLnJlZ2lvbkFwaS5nZXRDbGFzc2lmaWNhdGlvbkNvbG9ycygpLmdldChyZWdpb24ubGFiZWwgPz8gJycpIHx8XG4gICAgICAgIHRoaXMuc2hhcGVDb2xvcjtcbiAgICB9XG4gICAgLy8gUmVidWlsZCBsYWJlbENvbG9yczogc2VlZCBmcm9tIHBlcnNpc3RlZCBtYXAsIHRoZW4gb3ZlcmxheSBhY3R1YWwgcmVnaW9uIGNvbG9yc1xuICAgIHRoaXMubGFiZWxDb2xvcnMuY2xlYXIoKTtcbiAgICBmb3IgKGNvbnN0IFtsYWJlbCwgY29sb3JdIG9mIHRoaXMucmVnaW9uQXBpLmdldENsYXNzaWZpY2F0aW9uQ29sb3JzKCkpIHtcbiAgICAgIHRoaXMubGFiZWxDb2xvcnMuc2V0KGxhYmVsLCBjb2xvcik7XG4gICAgfVxuICAgIGZvciAoY29uc3QgcmVnaW9uIG9mIHJlZ2lvbnMpIHtcbiAgICAgIGlmIChyZWdpb24ubGFiZWwgJiYgcmVnaW9uLmNvbG9yKSB7XG4gICAgICAgIHRoaXMubGFiZWxDb2xvcnMuc2V0KHJlZ2lvbi5sYWJlbCwgcmVnaW9uLmNvbG9yKTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHJlZ2lvbnM7XG4gIH1cblxuICAvKipcbiAgICogUHVzaCBlZGl0cyBmcm9tIHRoZSBlZGl0b3IgdG8gdGhlIGRpYWdyYW0gaW4gbGl2ZS1lZGl0IG1vZGUgKG5vIHNhdmUgL1xuICAgKiBjYW5jZWwgYnV0dG9ucykuIFRoZSBgX3VwZGF0aW5nRnJvbUVkaXRvcmAgZ3VhcmQgaXMgc3RpbGwgdXNlZCBzbyB0aGVcbiAgICogcmVzdWx0aW5nIHJlZ2lvblVwZGF0ZUV2ZW50IGRvZXNuJ3QgYm91bmNlIGJhY2sgYXMgYW4gZXh0ZXJuYWwgY2hhbmdlLlxuICAgKi9cbiAgcHJpdmF0ZSBzZXRSZWdpb25zRnJvbUVkaXRvcihmaWxsQ29sb3I/OiBzdHJpbmcpIHtcbiAgICB0aGlzLl91cGRhdGluZ0Zyb21FZGl0b3IgPSB0cnVlO1xuICAgIC8vIGlzUmVnaW9uU2F2ZU9uPXRydWUgc28gY2hhbmdlcyBjb21taXQgdG8gdGhlIHJlZ2lvbiBzdG9yZSAoYW5kIHRoZSBwZXItaW1hZ2VcbiAgICAvLyBjYWNoZSkgaW1tZWRpYXRlbHkuIHNldEFubm90YXRpb25SZWdpb25zIHByZXNlcnZlcyB0aGUgaW50ZW5zaXR5LXByb2ZpbGVcbiAgICAvLyBsaW5lcyBpbnRlcm5hbGx5LCBzbyBlZGl0b3IgZWRpdHMgbmV2ZXIgZGlzdHVyYiB0aGVtLlxuICAgIHRoaXMucmVnaW9uQXBpLnNldEFubm90YXRpb25SZWdpb25zKHRoaXMucmVnaW9ucywgdGhpcy5zaG93U2hhcGVMYWJlbCwgdHJ1ZSwgZmlsbENvbG9yID8/IHRoaXMuZmlsbENvbG9yKTtcbiAgICB0aGlzLl91cGRhdGluZ0Zyb21FZGl0b3IgPSBmYWxzZTtcbiAgICAvLyBUaGUgZWRpdG9yJ3Mgb3duIGNvbW1pdCBpcyBpZ25vcmVkIGJ5IHRoZSByZWdpb24tdXBkYXRlIHN1YnNjcmlwdGlvbiAodGhlXG4gICAgLy8gX3VwZGF0aW5nRnJvbUVkaXRvciBndWFyZCksIHNvIHJlZnJlc2ggaGVyZTogYSBjbGFzcyBsYWJlbCB0eXBlZCBpbiB0aGVcbiAgICAvLyBDbGFzcyBjb2x1bW4gZ2V0cyBhZGRlZCB0byB0aGUgbGlzdCwgYW5kIHRoZSBwYW5lbCBjb3VudHMvb3JkZXIgc3RheSBpblxuICAgIC8vIHN5bmMgYWZ0ZXIgYW55IGVkaXQuIChqaXQtdWkjNzApXG4gICAgdGhpcy5zeW5jQ2xhc3Nlc0Zyb21SZWdpb25zKHRoaXMucmVnaW9ucyk7XG4gICAgdGhpcy5yZWNvbXB1dGVDbGFzc0NvdW50cygpO1xuICB9XG5cbiAgLyoqIFNldCBvbmUgcmVnaW9uJ3Mgb3V0bGluZSBjb2xvdXIgZnJvbSB0aGUgcGVyLXJvdyBwaWNrZXIgYW5kIGNvbW1pdCBsaXZlLlxuICAgKiAgUmVtZW1iZXJzIGl0IGFzIHRoZSBsYWJlbCdzIGNvbG91ciBzbyBzYW1lLWNsYXNzIHJlZ2lvbnMgYWRkZWQgbGF0ZXIgbWF0Y2hcbiAgICogIChqaXQtdWkjODUg4oCUIHRoZSBSZWdpb24gRWRpdG9yJ3MgcGVyLXJlZ2lvbiBDb2xvciBjb2x1bW4pLiAqL1xuICBjaGFuZ2VSZWdpb25Db2xvcihyZWdpb246IFJlZ2lvbiwgY29sb3I6IHN0cmluZyk6IHZvaWQge1xuICAgIGlmICghY29sb3IgfHwgcmVnaW9uLmNvbG9yID09PSBjb2xvcikgcmV0dXJuO1xuICAgIHJlZ2lvbi5jb2xvciA9IGNvbG9yO1xuICAgIHJlZ2lvbi5jb2xvck92ZXJyaWRkZW4gPSB0cnVlOyAvLyBleHBsaWNpdCBwZXItcmVnaW9uIGNvbG91ciDigJQgcHJlc2VydmUgaXQgYWdhaW5zdCBwcmVzZXQgKHJlKWFwcGx5IChqaXQtdWkjNzApXG4gICAgaWYgKHJlZ2lvbi5sYWJlbCkgdGhpcy5sYWJlbENvbG9ycy5zZXQocmVnaW9uLmxhYmVsLCBjb2xvcik7XG4gICAgdGhpcy5zZXRSZWdpb25zRnJvbUVkaXRvcigpO1xuICB9XG5cbiAgYWRkUmVjdGFuZ2xlKCkge1xuICAgIGNvbnN0IHJlZ2lvbiA9IG5ldyBSZWdpb24oKTtcbiAgICByZWdpb24uYm91bmRzID0gbmV3IFJlY3RhbmdsZSgpO1xuICAgIHJlZ2lvbi5ib3VuZHMud2lkdGggPSA1MTI7XG4gICAgcmVnaW9uLmJvdW5kcy5oZWlnaHQgPSA1MTI7XG4gICAgcmVnaW9uLmxhYmVsID0gdGhpcy5hY3RpdmVDbGFzcyA/PyAnUmVnaW9uJztcbiAgICAvLyBpZCBhbmQgYSBub24tY29sbGlkaW5nIG5hbWUgYXJlIG1pbnRlZCBieSB0aGUgdmlzdWFsaXplcidzIHNldFJlZ2lvbnNcbiAgICB0aGlzLnJlZ2lvbnMgPSBbLi4udGhpcy5yZWdpb25zLCByZWdpb25dO1xuICAgIHRoaXMuc2V0UmVnaW9uc0Zyb21FZGl0b3IoKTtcbiAgICB0aGlzLnJlZ2lvbnNDb3B5ID0gdGhpcy5kZWVwQ2xvbmUodGhpcy5yZWdpb25zKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBVcGRhdGUgdGhlIGxhYmVsIG9mIHRoZSByZWdpb24gb24gZW50ZXIga2V5IHByZXNzZWQgKHdoZW4gZWRpdGluZyBhIGxhYmVsIGNlbGwgaW4gdGhlIHRhYmxlKVxuICAgKiBAcGFyYW0gcmVnaW9uXG4gICAqIEBwYXJhbSBzZXRSZWdpb25cbiAgICovXG4gIGxhYmVsUmVnaW9uVXBkYXRlKHJlZ2lvbjogUmVnaW9uLCBzZXRSZWdpb24gPSBmYWxzZSkge1xuICAgIGlmIChyZWdpb24ubGFiZWwpIHtcbiAgICAgIC8vIGlmIGxhYmVsIGRvZXNuJ3QgZXhpc3RcbiAgICAgIGlmICghdGhpcy5sYWJlbENvbG9ycy5oYXMocmVnaW9uLmxhYmVsKSkge1xuICAgICAgICB0aGlzLmxhYmVsQ29sb3JzLnNldChyZWdpb24ubGFiZWwsIHRoaXMuc2hhcGVDb2xvcik7XG4gICAgICB9XG4gICAgfVxuICAgIC8vIHVwZGF0ZSBjb2xvcnMgb2YgdGhlIHJlZ2lvbnNcbiAgICBmb3IgKGNvbnN0IHJlZyBvZiB0aGlzLnJlZ2lvbnMpIHtcbiAgICAgIGlmICghcmVnLmNvbG9yKSB7XG4gICAgICAgIGlmIChyZWcubGFiZWwgJiYgdGhpcy5sYWJlbENvbG9ycy5oYXMocmVnLmxhYmVsKSkge1xuICAgICAgICAgIHJlZy5jb2xvciA9IHRoaXMubGFiZWxDb2xvcnMuZ2V0KHJlZy5sYWJlbCk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgLy8gdXBkYXRlIGxhYmVscyBtYXBcbiAgICB0aGlzLmxhYmVsQ29sb3JzLmNsZWFyKCk7XG4gICAgZm9yIChjb25zdCByZWcgb2YgdGhpcy5yZWdpb25zKSB7XG4gICAgICBpZiAocmVnLmxhYmVsICYmIHJlZy5jb2xvcikge1xuICAgICAgICB0aGlzLmxhYmVsQ29sb3JzLnNldChyZWcubGFiZWwsIHJlZy5jb2xvcik7XG4gICAgICB9XG4gICAgfVxuICAgIGlmIChzZXRSZWdpb24pIHtcbiAgICAgIHRoaXMuc2V0UmVnaW9uc0Zyb21FZGl0b3IoKTtcbiAgICB9XG4gIH1cbiAgeFJlY3RVcGRhdGUocmVnaW9uOiBSZWdpb24sIGV2ZW50OiBhbnkpIHtcbiAgICBjb25zdCByZWN0YW5nbGUgPSByZWdpb24uYm91bmRzO1xuICAgIGlmIChyZWN0YW5nbGUgJiYgcmVjdGFuZ2xlIGluc3RhbmNlb2YgUmVjdGFuZ2xlKSB7XG4gICAgICByZWN0YW5nbGUueCA9IGV2ZW50LnZhbHVlO1xuICAgICAgdGhpcy5zZXRSZWdpb25zRnJvbUVkaXRvcigpO1xuICAgIH1cbiAgfVxuICB5UmVjdFVwZGF0ZShyZWdpb246IFJlZ2lvbiwgZXZlbnQ6IGFueSkge1xuICAgIGNvbnN0IHJlY3RhbmdsZSA9IHJlZ2lvbi5ib3VuZHM7XG4gICAgaWYgKHJlY3RhbmdsZSAmJiByZWN0YW5nbGUgaW5zdGFuY2VvZiBSZWN0YW5nbGUpIHtcbiAgICAgIHJlY3RhbmdsZS55ID0gZXZlbnQudmFsdWU7XG4gICAgICB0aGlzLnNldFJlZ2lvbnNGcm9tRWRpdG9yKCk7XG4gICAgfVxuICB9XG4gIHdpZHRoUmVjdFVwZGF0ZShyZWdpb246IFJlZ2lvbiwgZXZlbnQ6IGFueSkge1xuICAgIGlmIChldmVudC52YWx1ZSA9PT0gbnVsbCB8fCBldmVudC52YWx1ZSA9PT0gdW5kZWZpbmVkKSByZXR1cm47XG4gICAgY29uc3QgcmVjdGFuZ2xlID0gcmVnaW9uLmJvdW5kcztcbiAgICBpZiAocmVjdGFuZ2xlICYmIHJlY3RhbmdsZSBpbnN0YW5jZW9mIFJlY3RhbmdsZSkge1xuICAgICAgY29uc3QgcmVnID0gdGhpcy5yZWdpb25zQ29weS5maWx0ZXIoKGVsZW1lbnQ6IFJlZ2lvbikgPT4gZWxlbWVudC5pZCA9PT0gcmVnaW9uLmlkKTtcbiAgICAgIGlmIChyZWcubGVuZ3RoKSB7XG4gICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvYmFuLXRzLWNvbW1lbnRcbiAgICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgICBjb25zdCBvbGRXaWR0aCA9IHJlZ1swXS5ib3VuZHMud2lkdGg7XG4gICAgICAgIGNvbnN0IGRpZmZXaWR0aCA9IGV2ZW50LnZhbHVlIC0gb2xkV2lkdGg7XG4gICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvYmFuLXRzLWNvbW1lbnRcbiAgICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgICByZWN0YW5nbGUueCA9IE1hdGgucm91bmQocmVnWzBdLmJvdW5kcy54IC0gZGlmZldpZHRoIC8gMik7XG4gICAgICB9XG4gICAgICByZWN0YW5nbGUud2lkdGggPSBldmVudC52YWx1ZTtcbiAgICAgIHRoaXMuc2V0UmVnaW9uc0Zyb21FZGl0b3IoKTtcbiAgICAgIHRoaXMucmVnaW9uc0NvcHkgPSB0aGlzLmRlZXBDbG9uZSh0aGlzLnJlZ2lvbnMpO1xuICAgIH1cbiAgfVxuICBoZWlnaHRSZWN0VXBkYXRlKHJlZ2lvbjogUmVnaW9uLCBldmVudDogYW55KSB7XG4gICAgaWYgKGV2ZW50LnZhbHVlID09PSBudWxsIHx8IGV2ZW50LnZhbHVlID09PSB1bmRlZmluZWQpIHJldHVybjtcbiAgICBjb25zdCByZWN0YW5nbGUgPSByZWdpb24uYm91bmRzO1xuICAgIGlmIChyZWN0YW5nbGUgJiYgcmVjdGFuZ2xlIGluc3RhbmNlb2YgUmVjdGFuZ2xlKSB7XG4gICAgICBjb25zdCByZWcgPSB0aGlzLnJlZ2lvbnNDb3B5LmZpbHRlcigoZWxlbWVudDogUmVnaW9uKSA9PiBlbGVtZW50LmlkID09PSByZWdpb24uaWQpO1xuICAgICAgaWYgKHJlZy5sZW5ndGgpIHtcbiAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9iYW4tdHMtY29tbWVudFxuICAgICAgICAvLyBAdHMtaWdub3JlXG4gICAgICAgIGNvbnN0IG9sZEhlaWdodCA9IHJlZ1swXS5ib3VuZHMuaGVpZ2h0O1xuICAgICAgICBjb25zdCBkaWZmSGVpZ2h0ID0gZXZlbnQudmFsdWUgLSBvbGRIZWlnaHQ7XG4gICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvYmFuLXRzLWNvbW1lbnRcbiAgICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgICByZWN0YW5nbGUueSA9IE1hdGgucm91bmQocmVnWzBdLmJvdW5kcy55IC0gZGlmZkhlaWdodCAvIDIpO1xuICAgICAgfVxuICAgICAgcmVjdGFuZ2xlLmhlaWdodCA9IGV2ZW50LnZhbHVlO1xuICAgICAgdGhpcy5zZXRSZWdpb25zRnJvbUVkaXRvcigpO1xuICAgICAgdGhpcy5yZWdpb25zQ29weSA9IHRoaXMuZGVlcENsb25lKHRoaXMucmVnaW9ucyk7XG4gICAgfVxuICB9XG5cbiAgb25QYWdlQ2hhbmdlKGV2ZW50OiB7IGZpcnN0PzogbnVtYmVyOyByb3dzPzogbnVtYmVyIH0pIHtcbiAgICB0aGlzLnBhZ2luYXRvckZpcnN0ID0gZXZlbnQuZmlyc3QgPz8gMDtcbiAgICB0aGlzLnBhZ2luYXRvclJvd3MgPSBldmVudC5yb3dzID8/IHRoaXMucGFnaW5hdG9yUm93cztcbiAgfVxuXG4gIC8qKiBTZWxlY3QgZXZlcnkgcmVnaW9uIGluIHRoZSB0YWJsZSAoYW5kIGhpZ2hsaWdodCB0aGVtIG9uIHRoZSBkaWFncmFtKS4gKi9cbiAgc2VsZWN0QWxsUmVnaW9ucygpIHtcbiAgICB0aGlzLnNlbGVjdGVkUmVnaW9ucyA9IFsuLi50aGlzLnJlZ2lvbnNdO1xuICAgIHRoaXMub25TZWxlY3Rpb25DaGFuZ2VkKCk7XG4gIH1cblxuICBkZWxldGVTZWxlY3RlZFJlZ2lvbnMoKSB7XG4gICAgaWYgKHRoaXMuc2VsZWN0ZWRSZWdpb25zICYmIHRoaXMuc2VsZWN0ZWRSZWdpb25zLmxlbmd0aCA+IDApIHtcbiAgICAgIC8vIEZpbHRlciBieSBpZCDigJQgb2JqZWN0IGlkZW50aXR5IGlzbid0IHJlbGlhYmxlIGFjcm9zcyByZS1wYXJzZXMsXG4gICAgICAvLyBhbmQgbmFtZSBpcyBhIHVzZXItZWRpdGFibGUgbGFiZWwgdGhhdCBtYXkgY29sbGlkZS5cbiAgICAgIGNvbnN0IHJlbW92ZWQgPSBuZXcgU2V0KHRoaXMuc2VsZWN0ZWRSZWdpb25zLm1hcCgocikgPT4gci5pZCkpO1xuICAgICAgdGhpcy5yZWdpb25zID0gdGhpcy5yZWdpb25zLmZpbHRlcigocikgPT4gIXJlbW92ZWQuaGFzKHIuaWQpKTtcbiAgICAgIHRoaXMuc2VsZWN0ZWRSZWdpb25zID0gW107XG4gICAgICB0aGlzLmNsYW1wUGFnaW5hdG9yRmlyc3QoKTtcbiAgICAgIHRoaXMuc2V0UmVnaW9uc0Zyb21FZGl0b3IoKTtcbiAgICAgIC8vIFN5bmMgdGhlIGNsZWFyZWQgc2VsZWN0aW9uIHdpdGggdGhlIHBsb3QncyBoaWdobGlnaHQgc3RhdGUuXG4gICAgICB0aGlzLm9uU2VsZWN0aW9uQ2hhbmdlZCgpO1xuICAgIH1cbiAgfVxuXG4gIGNsZWFyQWxsUmVnaW9ucygpIHtcbiAgICB0aGlzLmNvbmZpcm1hdGlvblNlcnZpY2UuY29uZmlybSh7XG4gICAgICBrZXk6ICdwb3NpdGlvbkRpYWxvZycsXG4gICAgICBtZXNzYWdlOiAnQXJlIHlvdSBzdXJlIHlvdSB3YW50IHRvIGRlbGV0ZSBhbGwgcmVnaW9ucz8nLFxuICAgICAgYWNjZXB0OiAoKSA9PiB7XG4gICAgICAgIHRoaXMucmVnaW9ucyA9IFtdO1xuICAgICAgICB0aGlzLnNlbGVjdGVkUmVnaW9ucyA9IFtdO1xuICAgICAgICB0aGlzLnBhZ2luYXRvckZpcnN0ID0gMDtcbiAgICAgICAgdGhpcy5zZXRSZWdpb25zRnJvbUVkaXRvcigpO1xuICAgICAgICB0aGlzLm9uU2VsZWN0aW9uQ2hhbmdlZCgpO1xuICAgICAgfSxcbiAgICB9KTtcbiAgfVxuXG4gIGFkZFBvbHlnb24oKSB7XG4gICAgY29uc3QgcmVnaW9uID0gbmV3IFJlZ2lvbigpO1xuICAgIHJlZ2lvbi5ib3VuZHMgPSBuZXcgUG9seWdvbigpO1xuICAgIHJlZ2lvbi5sYWJlbCA9IHRoaXMuYWN0aXZlQ2xhc3MgPz8gJ1JlZ2lvbic7XG4gICAgcmVnaW9uLmJvdW5kcy55cG9pbnRzID0gWzAsIDAsIDBdO1xuICAgIHJlZ2lvbi5ib3VuZHMueHBvaW50cyA9IFswLCAwLCAwXTtcbiAgICByZWdpb24uYm91bmRzLmNvb3JkaW5hdGVzID0gW1xuICAgICAgWzAsIDBdLFxuICAgICAgWzAsIDBdLFxuICAgICAgWzAsIDBdLFxuICAgIF07XG4gICAgcmVnaW9uLmJvdW5kcy5ucG9pbnRzID0gMztcbiAgICB0aGlzLnJlZ2lvbnMgPSBbLi4udGhpcy5yZWdpb25zLCByZWdpb25dO1xuICAgIHRoaXMuc2V0UmVnaW9uc0Zyb21FZGl0b3IoKTtcbiAgICB0aGlzLnJlZ2lvbnNDb3B5ID0gdGhpcy5kZWVwQ2xvbmUodGhpcy5yZWdpb25zKTtcbiAgfVxuICBkZWxldGVSZWdpb24oc2hhcGVJZHg6IG51bWJlcikge1xuICAgIGNvbnN0IHJlbW92ZWQgPSB0aGlzLnJlZ2lvbnNbc2hhcGVJZHhdO1xuICAgIHRoaXMucmVnaW9ucyA9IHRoaXMucmVnaW9ucy5maWx0ZXIoKF8sIGkpID0+IGkgIT09IHNoYXBlSWR4KTtcbiAgICBpZiAocmVtb3ZlZCkge1xuICAgICAgdGhpcy5zZWxlY3RlZFJlZ2lvbnMgPSB0aGlzLnNlbGVjdGVkUmVnaW9ucy5maWx0ZXIoKHIpID0+IHIuaWQgIT09IHJlbW92ZWQuaWQpO1xuICAgIH1cbiAgICB0aGlzLmNsYW1wUGFnaW5hdG9yRmlyc3QoKTtcbiAgICB0aGlzLnNldFJlZ2lvbnNGcm9tRWRpdG9yKCk7XG4gICAgLy8gUmUtZW1pdCB0aGUgKHBvc3NpYmx5IHRyaW1tZWQpIHNlbGVjdGlvbiBzbyB0aGUgcGxvdCdzIGhpZ2hsaWdodCBzdGF5c1xuICAgIC8vIGluIHN5bmMgd2l0aCB3aGF0J3Mgc3RpbGwgaW4gdGhlIHRhYmxlLlxuICAgIHRoaXMub25TZWxlY3Rpb25DaGFuZ2VkKCk7XG4gIH1cblxuICBwcml2YXRlIGNsYW1wUGFnaW5hdG9yRmlyc3QoKSB7XG4gICAgY29uc3QgbWF4Rmlyc3QgPSBNYXRoLm1heChcbiAgICAgIDAsXG4gICAgICBNYXRoLmZsb29yKCh0aGlzLnJlZ2lvbnMubGVuZ3RoIC0gMSkgLyB0aGlzLnBhZ2luYXRvclJvd3MpICogdGhpcy5wYWdpbmF0b3JSb3dzLFxuICAgICk7XG4gICAgaWYgKHRoaXMucGFnaW5hdG9yRmlyc3QgPiBtYXhGaXJzdCkge1xuICAgICAgdGhpcy5wYWdpbmF0b3JGaXJzdCA9IG1heEZpcnN0O1xuICAgIH1cbiAgfVxuICBjaGFuZ2VTaG93U2hhcGVMYWJlbChzaG93TGFiZWw6IGJvb2xlYW4pIHtcbiAgICB0aGlzLnNldFJlZ2lvbnNGcm9tRWRpdG9yKCk7XG4gIH1cblxuICBpc0VkaXRpbmdMYWJlbChyZWdpb246IGFueSk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB0aGlzLmVkaXRpbmdMYWJlbFJlZ2lvbnMuaGFzKHJlZ2lvbik7XG4gIH1cblxuICBzdGFydEVkaXRMYWJlbChyZWdpb246IGFueSwgZXZlbnQ/OiBFdmVudCk6IHZvaWQge1xuICAgIGV2ZW50Py5zdG9wUHJvcGFnYXRpb24oKTsgLy8gZG9uJ3QgdG9nZ2xlIHJvdyBzZWxlY3Rpb25cbiAgICB0aGlzLmVkaXRpbmdMYWJlbFJlZ2lvbnMuYWRkKHJlZ2lvbik7XG4gIH1cblxuICBzdG9wRWRpdExhYmVsKHJlZ2lvbjogYW55LCBjb21taXQ6IGJvb2xlYW4sIGV2ZW50PzogRXZlbnQpOiB2b2lkIHtcbiAgICBldmVudD8uc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgdGhpcy5lZGl0aW5nTGFiZWxSZWdpb25zLmRlbGV0ZShyZWdpb24pO1xuICAgIGlmIChjb21taXQpIHtcbiAgICAgIHRoaXMubGFiZWxSZWdpb25VcGRhdGUocmVnaW9uLCB0cnVlKTtcbiAgICB9XG4gIH1cblxuICBpc1JlY3RhbmdsZShyZWdpb246IFJlZ2lvbikge1xuICAgIHJldHVybiByZWdpb24uYm91bmRzIGluc3RhbmNlb2YgUmVjdGFuZ2xlO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlZ2lvbiBhcmVhIGZvciBkaXNwbGF5OiBpbiDCtW3CsiAob3IgbW3Csikgd2hlbiB0aGUgaW1hZ2UgcmVwb3J0cyBhIHBoeXNpY2FsXG4gICAqIHBpeGVsIHNpemUsIG90aGVyd2lzZSBpbiBweMKyLiBFbXB0eSBzdHJpbmcgZm9yIGRlZ2VuZXJhdGUgcmVnaW9ucy5cbiAgICovXG4gIHJlZ2lvbkFyZWEocmVnaW9uOiBSZWdpb24pOiBzdHJpbmcge1xuICAgIGNvbnN0IHB4ID0gdGhpcy5hcmVhSW5QaXhlbHMocmVnaW9uKTtcbiAgICBpZiAocHggPD0gMCkgcmV0dXJuICcnO1xuICAgIGlmICh0aGlzLm1wcFggJiYgdGhpcy5tcHBZICYmIHRoaXMubXBwWCA+IDAgJiYgdGhpcy5tcHBZID4gMCkge1xuICAgICAgY29uc3QgdW0yID0gcHggKiB0aGlzLm1wcFggKiB0aGlzLm1wcFk7XG4gICAgICByZXR1cm4gdW0yID49IDFlNiA/IGAke3RoaXMuZm10QXJlYSh1bTIgLyAxZTYpfSBtbcKyYCA6IGAke3RoaXMuZm10QXJlYSh1bTIpfSDCtW3CsmA7XG4gICAgfVxuICAgIHJldHVybiBgJHt0aGlzLmZtdEFyZWEocHgpfSBweMKyYDtcbiAgfVxuXG4gIC8qKlxuICAgKiBDaG9vc2UgdGhlIHBoeXNpY2FsIHBpeGVsIHNpemUgKMK1bS9waXhlbCkgZm9yIGFyZWEgZGlzcGxheSBmcm9tIHRoZSBpbWFnZVxuICAgKiBtZXRhLiBUaGUgY2FsaWJyYXRpb24gbWF5IHNpdCBvbiBhbnkgY2hhbm5lbCBlbnRyeSDigJQgbm90IG5lY2Vzc2FyaWx5IFswXSDigJRcbiAgICogc28gcGljayB0aGUgZmlyc3QgZW50cnkgd2l0aCBhIHBvc2l0aXZlIG1wcFggKG1pcnJvcnNcbiAgICogUGxvdGx5U2VydmljZS5jdXJyZW50TXBwKS4gRmFsbHMgYmFjayB0byBzcXVhcmUgcGl4ZWxzIChtcHBZID0gbXBwWCkgd2hlblxuICAgKiBvbmx5IG9uZSBheGlzIGlzIHJlcG9ydGVkLCBzbyBhIHNjYWxlZCBpbWFnZSBzaG93cyDCtW3Csi9tbcKyIHJhdGhlciB0aGFuIHB4wrIuXG4gICAqL1xuICBwcml2YXRlIHBpY2tNcHAobWV0YTogSUltYWdlTWV0YWRhdGFbXSB8IHVuZGVmaW5lZCk6IHsgbXBwWD86IG51bWJlcjsgbXBwWT86IG51bWJlciB9IHtcbiAgICBjb25zdCBtID0gQXJyYXkuaXNBcnJheShtZXRhKVxuICAgICAgPyAobWV0YS5maW5kKChlKSA9PiBlICYmIChlLm1wcFggPz8gMCkgPiAwKSA/PyBtZXRhWzBdKVxuICAgICAgOiB1bmRlZmluZWQ7XG4gICAgY29uc3QgbXggPSBtICYmIChtLm1wcFggPz8gMCkgPiAwID8gKG0ubXBwWCBhcyBudW1iZXIpIDogdW5kZWZpbmVkO1xuICAgIGNvbnN0IG15ID0gbSAmJiAobS5tcHBZID8/IDApID4gMCA/IChtLm1wcFkgYXMgbnVtYmVyKSA6IHVuZGVmaW5lZDtcbiAgICByZXR1cm4geyBtcHBYOiBteCwgbXBwWTogbXkgPz8gbXggfTtcbiAgfVxuXG4gIC8qKiBQaXhlbCBhcmVhOiB3aWR0aMK3aGVpZ2h0IGZvciByZWN0YW5nbGVzLCBzaG9lbGFjZSBmb3JtdWxhIGZvciBwb2x5Z29ucy5cbiAgICogIEludGVyaW9yIHJpbmdzIChob2xlcykgYXJlIHN1YnRyYWN0ZWQsIHNvIGEgZG9udXQgcmVwb3J0cyB0aGUgYW5udWx1cyBhcmVhLFxuICAgKiAgbm90IHRoZSBmaWxsZWQgZXh0ZXJpb3IgKGppdC11aSM4NSkuICovXG4gIHByaXZhdGUgYXJlYUluUGl4ZWxzKHJlZ2lvbjogUmVnaW9uKTogbnVtYmVyIHtcbiAgICBjb25zdCBiID0gcmVnaW9uLmJvdW5kcztcbiAgICBpZiAoYiBpbnN0YW5jZW9mIFJlY3RhbmdsZSkgcmV0dXJuIE1hdGguYWJzKGIud2lkdGggKiBiLmhlaWdodCk7XG4gICAgaWYgKGIgaW5zdGFuY2VvZiBQb2x5Z29uKSByZXR1cm4gdGhpcy5wb2x5Z29uQXJlYShiKTtcbiAgICAvLyBNdWx0aS1wYXJ0IHJlZ2lvbjogc3VtIGVhY2ggcGFydCdzIChleHRlcmlvciDiiJIgaG9sZXMpIGFyZWEgKGppdC11aSM4NSkuXG4gICAgaWYgKGIgaW5zdGFuY2VvZiBNdWx0aVBvbHlnb24pIHtcbiAgICAgIHJldHVybiBiLnBvbHlnb25zLnJlZHVjZSgoc3VtLCBwKSA9PiBzdW0gKyB0aGlzLnBvbHlnb25BcmVhKHApLCAwKTtcbiAgICB9XG4gICAgcmV0dXJuIDA7XG4gIH1cblxuICAvKiogUG9seWdvbiBhcmVhOiBleHRlcmlvciBzaG9lbGFjZSBtaW51cyBlYWNoIGludGVyaW9yIHJpbmcgKGhvbGUpLiAqL1xuICBwcml2YXRlIHBvbHlnb25BcmVhKHA6IFBvbHlnb24pOiBudW1iZXIge1xuICAgIGlmICgocC54cG9pbnRzPy5sZW5ndGggPz8gMCkgPCAzKSByZXR1cm4gMDtcbiAgICBsZXQgYSA9IHRoaXMucmluZ0FyZWEocC54cG9pbnRzLCBwLnlwb2ludHMpO1xuICAgIGlmIChwLmhvbGVzKSB7XG4gICAgICBmb3IgKGNvbnN0IHJpbmcgb2YgcC5ob2xlcykgYSAtPSB0aGlzLnJpbmdBcmVhKHJpbmcubWFwKHB0ID0+IHB0WzBdKSwgcmluZy5tYXAocHQgPT4gcHRbMV0pKTtcbiAgICB9XG4gICAgcmV0dXJuIE1hdGgubWF4KDAsIGEpO1xuICB9XG5cbiAgLyoqIEFic29sdXRlIHNob2VsYWNlIGFyZWEgb2YgYSBzaW5nbGUgcmluZy4gKi9cbiAgcHJpdmF0ZSByaW5nQXJlYSh4czogbnVtYmVyW10sIHlzOiBudW1iZXJbXSk6IG51bWJlciB7XG4gICAgY29uc3QgbiA9IHhzPy5sZW5ndGggPz8gMDtcbiAgICBpZiAobiA8IDMpIHJldHVybiAwO1xuICAgIGxldCBhID0gMDtcbiAgICBmb3IgKGxldCBpID0gMCwgaiA9IG4gLSAxOyBpIDwgbjsgaiA9IGkrKykgYSArPSAoeHNbal0gKyB4c1tpXSkgKiAoeXNbal0gLSB5c1tpXSk7XG4gICAgcmV0dXJuIE1hdGguYWJzKGEgLyAyKTtcbiAgfVxuXG4gIHByaXZhdGUgZm10QXJlYShuOiBudW1iZXIpOiBzdHJpbmcge1xuICAgIHJldHVybiAobiA+PSAxMDAwID8gTWF0aC5yb3VuZChuKSA6IE1hdGgucm91bmQobiAqIDEwMCkgLyAxMDApLnRvTG9jYWxlU3RyaW5nKCk7XG4gIH1cblxuICAvKipcbiAgICogVGhpcyBtZXRob2Qgcm91bmRzIGFsbCB0aGUgcmVjdGFuZ2xlIHJlZ2lvbnMgbGVuZ3RocyB0byBtdWx0aXBsZSBvZiA1MTJcbiAgICovXG4gIHJvdW5kUmVjdGFuZ2xlTGVuZ3RocygpIHtcbiAgICBmb3IgKGNvbnN0IHJlZ2lvbiBvZiB0aGlzLnJlZ2lvbnMpIHtcbiAgICAgIGlmIChyZWdpb24uYm91bmRzIGluc3RhbmNlb2YgUmVjdGFuZ2xlKSB7XG4gICAgICAgIGNvbnN0IHJlY3QgPSByZWdpb24uYm91bmRzO1xuICAgICAgICByZWN0LmhlaWdodCA9IE1hdGgucm91bmQocmVjdC5oZWlnaHQgLyA1MTIpICogNTEyO1xuICAgICAgICByZWN0LndpZHRoID0gTWF0aC5yb3VuZChyZWN0LndpZHRoIC8gNTEyKSAqIDUxMjtcbiAgICAgIH1cbiAgICB9XG4gICAgdGhpcy5zZXRSZWdpb25zRnJvbUVkaXRvcigpO1xuICB9XG5cbiAgLyoqXG4gICAqIFN0b3Aga2V5IGFycm93IGV2ZW50IHByb3BhZ2F0aW9uXG4gICAqIEBwYXJhbSBldmVudFxuICAgKi9cbiAgZGlzYWJsZUFycm93S2V5cyhldmVudDogYW55KSB7XG4gICAgaWYgKFxuICAgICAgZXZlbnQua2V5ID09PSAnQXJyb3dEb3duJyB8fFxuICAgICAgZXZlbnQua2V5ID09PSAnRG93bicgfHxcbiAgICAgIGV2ZW50LmtleSA9PT0gJ0Fycm93VXAnIHx8XG4gICAgICBldmVudC5rZXkgPT09ICdVcCcgfHxcbiAgICAgIGV2ZW50LmtleSA9PT0gJ0Fycm93TGVmdCcgfHxcbiAgICAgIGV2ZW50LmtleSA9PT0gJ0xlZnQnIHx8XG4gICAgICBldmVudC5rZXkgPT09ICdBcnJvd1JpZ2h0JyB8fFxuICAgICAgZXZlbnQua2V5ID09PSAnUmlnaHQnXG4gICAgKSB7XG4gICAgICBldmVudC5zdG9wUHJvcGFnYXRpb24oKTtcbiAgICB9XG4gIH1cblxuICBjaGFuZ2VTaGFwZUNvbG9yKGV2ZW50OiBhbnkpIHtcbiAgICB0aGlzLnNoYXBlQ29sb3IgPSBldmVudC52YWx1ZTtcbiAgICBmb3IgKGNvbnN0IHJlZ2lvbiBvZiB0aGlzLnJlZ2lvbnMpIHtcbiAgICAgIGlmIChyZWdpb24ubGFiZWwgPT09IHRoaXMuc2VsZWN0ZWRMYWJlbENvbG9yICYmIHRoaXMubGFiZWxDb2xvcnMuaGFzKHJlZ2lvbi5sYWJlbCkpIHtcbiAgICAgICAgdGhpcy5sYWJlbENvbG9ycy5zZXQocmVnaW9uLmxhYmVsLCB0aGlzLnNoYXBlQ29sb3IpO1xuICAgICAgICByZWdpb24uY29sb3IgPSB0aGlzLnNoYXBlQ29sb3I7XG4gICAgICB9XG4gICAgfVxuICAgIHRoaXMuc2V0UmVnaW9uc0Zyb21FZGl0b3IodGhpcy5maWxsQ29sb3IpO1xuICB9XG5cbiAgc2hvd0hlbHAoKSB7XG4gICAgdGhpcy5kaXNwbGF5SGVscERpYWxvZyA9IHRydWU7XG4gIH1cblxuICAvKiogT3BlbiB0aGUgY29sb3VyIGRpYWxvZyBmb3IgdGhlIGN1cnJlbnRseS1zZWxlY3RlZCByZWdpb24ocyksIGJ1aWxkaW5nIG9uZVxuICAgKiAgY29sb3VyIHBpY2tlciBwZXIgdW5pcXVlIGNsYXNzIGluIHRoZSBzZWxlY3Rpb24uIEVhY2ggcGlja2VyIGlzIHNlZWRlZCB3aXRoXG4gICAqICB0aGUgY2xhc3MncyBjdXJyZW50IGNvbG91ciAodGhlIGxhYmVs4oaSY29sb3VyIG1hcCwgZmFsbGluZyBiYWNrIHRvIHRoZSBmaXJzdFxuICAgKiAgc2VsZWN0ZWQgcmVnaW9uIG9mIHRoYXQgY2xhc3MpLiAqL1xuICBvcGVuQ29sb3JEaWFsb2coKSB7XG4gICAgaWYgKCF0aGlzLnNlbGVjdGVkUmVnaW9ucz8ubGVuZ3RoKSByZXR1cm47XG4gICAgY29uc3Qgc2VlZEJ5TGFiZWwgPSBuZXcgTWFwPHN0cmluZywgc3RyaW5nPigpO1xuICAgIGZvciAoY29uc3QgcmVnaW9uIG9mIHRoaXMuc2VsZWN0ZWRSZWdpb25zKSB7XG4gICAgICBjb25zdCBsYWJlbCA9IHJlZ2lvbi5sYWJlbD8udHJpbSgpID8/ICcnO1xuICAgICAgaWYgKHNlZWRCeUxhYmVsLmhhcyhsYWJlbCkpIGNvbnRpbnVlO1xuICAgICAgc2VlZEJ5TGFiZWwuc2V0KFxuICAgICAgICBsYWJlbCxcbiAgICAgICAgKGxhYmVsID8gdGhpcy5sYWJlbENvbG9ycy5nZXQobGFiZWwpIDogdW5kZWZpbmVkKSA/PyByZWdpb24uY29sb3IgPz8gdGhpcy5zaGFwZUNvbG9yLFxuICAgICAgKTtcbiAgICB9XG4gICAgdGhpcy5jbGFzc0NvbG9yRWRpdHMgPSBbLi4uc2VlZEJ5TGFiZWxdLm1hcCgoW2xhYmVsLCBjb2xvcl0pID0+ICh7IGxhYmVsLCBjb2xvciB9KSk7XG4gICAgdGhpcy5zaG93Q29sb3JEaWFsb2cgPSB0cnVlO1xuICB9XG5cbiAgLyoqIEFwcGx5IGVhY2ggY2xhc3MncyBjaG9zZW4gY29sb3VyIHRvIHRoZSBzZWxlY3RlZCByZWdpb25zIG9mIHRoYXQgY2xhc3MgYW5kXG4gICAqICBjb21taXQgbGl2ZS4gUmVtZW1iZXJzIHRoZSBjb2xvdXIgcGVyIGNsYXNzIHNvIHNhbWUtY2xhc3MgcmVnaW9ucyBhZGRlZFxuICAgKiAgbGF0ZXIgbWF0Y2guICovXG4gIGFwcGx5Q29sb3JUb1NlbGVjdGVkKCkge1xuICAgIGNvbnN0IGNvbG9yQnlMYWJlbCA9IG5ldyBNYXAodGhpcy5jbGFzc0NvbG9yRWRpdHMubWFwKChlKSA9PiBbZS5sYWJlbCwgZS5jb2xvcl0pKTtcbiAgICBmb3IgKGNvbnN0IHJlZ2lvbiBvZiB0aGlzLnNlbGVjdGVkUmVnaW9ucyA/PyBbXSkge1xuICAgICAgY29uc3QgbGFiZWwgPSByZWdpb24ubGFiZWw/LnRyaW0oKSA/PyAnJztcbiAgICAgIGNvbnN0IGNvbG9yID0gY29sb3JCeUxhYmVsLmdldChsYWJlbCk7XG4gICAgICBpZiAoIWNvbG9yKSBjb250aW51ZTtcbiAgICAgIHJlZ2lvbi5jb2xvciA9IGNvbG9yO1xuICAgICAgcmVnaW9uLmNvbG9yT3ZlcnJpZGRlbiA9IHRydWU7IC8vIGV4cGxpY2l0IGNvbG91ciDigJQgcHJlc2VydmUgYWdhaW5zdCBwcmVzZXQgKHJlKWFwcGx5IChqaXQtdWkjNzApXG4gICAgICBpZiAobGFiZWwpIHRoaXMubGFiZWxDb2xvcnMuc2V0KGxhYmVsLCBjb2xvcik7XG4gICAgfVxuICAgIHRoaXMuc2V0UmVnaW9uc0Zyb21FZGl0b3IoKTtcbiAgICB0aGlzLnNob3dDb2xvckRpYWxvZyA9IGZhbHNlO1xuICB9XG5cbiAgLy8g4pSA4pSAIGFubm90YXRpb24tY2xhc3MgcHJlc2V0cyAoaml0LXVpIzcwKSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcblxuICAvKiogUmVzb2x2ZSB0aGUgZGlzcGxheSBjb2xvdXIgZm9yIGEgY2xhc3MgbmFtZSAocHJlc2V0IGNvbG91ciBvciBkZXRlcm1pbmlzdGljIGZhbGxiYWNrKS4gKi9cbiAgY29sb3JGb3JOYW1lKG5hbWU/OiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIHJldHVybiBuYW1lID8gY29sb3JGb3JMYWJlbChuYW1lLCB0aGlzLnByZXNldFNldCkgOiB0aGlzLnNoYXBlQ29sb3I7XG4gIH1cblxuICAvKipcbiAgICogRW5zdXJlIGV2ZXJ5IGNsYXNzIGxhYmVsIHByZXNlbnQgaW4gdGhlIGxvYWRlZCByZWdpb25zIGV4aXN0cyBpbiB0aGUgcHJlc2V0XG4gICAqIGxpc3QsIGF1dG8tYWRkaW5nIGFueSB0aGF0IGFyZSBtaXNzaW5nICh1c2luZyB0aGUgcmVnaW9uJ3MgYWxyZWFkeS1yZXNvbHZlZFxuICAgKiBjb2xvdXIpLiBUaGlzIG1ha2VzIGNsYXNzZXMgY2FycmllZCBpbiBhIGxvYWRlZC9pbXBvcnRlZCBHZW9KU09OIHNob3cgdXAgYXNcbiAgICogY2hpcHMgKyBDbGFzcy1kcm9wZG93biBvcHRpb25zIChhIHAtZHJvcGRvd24gY2FuJ3QgZGlzcGxheSBhIHZhbHVlIHRoYXQgaXNuJ3RcbiAgICogYW4gb3B0aW9uKSBhbmQgbGV0cyB0aGUgdXNlciBtYW5hZ2UgdGhlbS4gVGhlIHBsYWNlaG9sZGVyICdsZWdlbmQnIGFuZCBlbXB0eVxuICAgKiBsYWJlbHMgYXJlIGlnbm9yZWQuIChqaXQtdWkjNzApXG4gICAqL1xuICBwcml2YXRlIHN5bmNDbGFzc2VzRnJvbVJlZ2lvbnMocmVnaW9uczogUmVnaW9uW10pOiB2b2lkIHtcbiAgICBjb25zdCBub3JtYWxpemVkID0gdGhpcy5wcmVzZXRTZXQubWF0Y2hNb2RlID09PSAnbm9ybWFsaXplZCc7XG4gICAgY29uc3Qga2V5T2YgPSAobDogc3RyaW5nKSA9PiAobm9ybWFsaXplZCA/IGwudHJpbSgpLnRvTG93ZXJDYXNlKCkgOiBsKTtcbiAgICBjb25zdCBrbm93biA9IG5ldyBTZXQodGhpcy5wcmVzZXRTZXQuY2xhc3Nlcy5tYXAoKGMpID0+IGtleU9mKGMubmFtZSkpKTtcbiAgICBjb25zdCBhZGRlZCA9IG5ldyBTZXQ8c3RyaW5nPigpO1xuICAgIGZvciAoY29uc3QgciBvZiByZWdpb25zKSB7XG4gICAgICBjb25zdCBsYWJlbCA9IHIubGFiZWw/LnRyaW0oKTtcbiAgICAgIGlmICghbGFiZWwgfHwgbGFiZWwgPT09ICdsZWdlbmQnKSBjb250aW51ZTtcbiAgICAgIGNvbnN0IGsgPSBrZXlPZihsYWJlbCk7XG4gICAgICBpZiAoa25vd24uaGFzKGspIHx8IGFkZGVkLmhhcyhrKSkgY29udGludWU7XG4gICAgICBhZGRlZC5hZGQoayk7XG4gICAgICB0aGlzLnJlZ2lvbkFwaS51cHNlcnRDbGFzcyh7XG4gICAgICAgIG5hbWU6IGxhYmVsLFxuICAgICAgICBjb2xvcjogci5jb2xvciB8fCBjb2xvckZvckxhYmVsKGxhYmVsLCB0aGlzLnByZXNldFNldCksXG4gICAgICAgIHNvdXJjZTogJ2F1dG8nLFxuICAgICAgfSk7XG4gICAgfVxuICB9XG5cbiAgLy8g4pSA4pSAIERvY2tlZCBDbGFzc2VzIHBhbmVsIChqaXQtdWkjNzApIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG4gIC8qKiBSZWJ1aWxkIHRoZSBwZXItY2xhc3MgcmVnaW9uIGNvdW50cyBzaG93biBpbiB0aGUgcGFuZWwuICovXG4gIHByaXZhdGUgcmVjb21wdXRlQ2xhc3NDb3VudHMoKTogdm9pZCB7XG4gICAgY29uc3Qgbm9ybWFsaXplZCA9IHRoaXMucHJlc2V0U2V0Lm1hdGNoTW9kZSA9PT0gJ25vcm1hbGl6ZWQnO1xuICAgIGNvbnN0IGNvdW50cyA9IG5ldyBNYXA8c3RyaW5nLCBudW1iZXI+KCk7XG4gICAgZm9yIChjb25zdCByIG9mIHRoaXMucmVnaW9ucykge1xuICAgICAgaWYgKCFyLmxhYmVsKSBjb250aW51ZTtcbiAgICAgIGNvbnN0IGsgPSBub3JtYWxpemVkID8gci5sYWJlbC50cmltKCkudG9Mb3dlckNhc2UoKSA6IHIubGFiZWw7XG4gICAgICBjb3VudHMuc2V0KGssIChjb3VudHMuZ2V0KGspID8/IDApICsgMSk7XG4gICAgfVxuICAgIHRoaXMuY2xhc3NDb3VudHMgPSBjb3VudHM7XG4gICAgdGhpcy51cGRhdGVEaXNwbGF5Q2xhc3NlcygpO1xuICB9XG5cbiAgLyoqIFJlYnVpbGQgdGhlIHBhbmVsJ3MgZGlzcGxheSBvcmRlciDigJQgbW9zdC11c2VkIGNsYXNzZXMgZmlyc3QsIHRoZW4gYnkgbmFtZS4gKi9cbiAgcHJpdmF0ZSB1cGRhdGVEaXNwbGF5Q2xhc3NlcygpOiB2b2lkIHtcbiAgICB0aGlzLmRpc3BsYXlDbGFzc2VzID0gWy4uLnRoaXMucHJlc2V0U2V0LmNsYXNzZXNdLnNvcnQoKGEsIGIpID0+IHtcbiAgICAgIGNvbnN0IGQgPSB0aGlzLmNsYXNzQ291bnQoYi5uYW1lKSAtIHRoaXMuY2xhc3NDb3VudChhLm5hbWUpO1xuICAgICAgcmV0dXJuIGQgIT09IDAgPyBkIDogYS5uYW1lLmxvY2FsZUNvbXBhcmUoYi5uYW1lKTtcbiAgICB9KTtcbiAgfVxuXG4gIC8qKiB0cmFja0J5IGZvciB0aGUgY2xhc3Mgcm93cyBzbyByZS1zb3J0aW5nIGRvZXNuJ3QgcmUtY3JlYXRlIHRoZSBwaWNrZXJzLiAqL1xuICB0cmFja0J5Q2xhc3NOYW1lID0gKF86IG51bWJlciwgYzogQ2xhc3NQcmVzZXQpOiBzdHJpbmcgPT4gYy5uYW1lO1xuXG4gIC8qKiBOdW1iZXIgb2YgcmVnaW9ucyBjdXJyZW50bHkgdXNpbmcgYSBjbGFzcyAoa2V5ZWQgYnkgdGhlIGFjdGl2ZSBtYXRjaCBtb2RlKS4gKi9cbiAgY2xhc3NDb3VudChuYW1lOiBzdHJpbmcpOiBudW1iZXIge1xuICAgIGNvbnN0IGsgPSB0aGlzLnByZXNldFNldC5tYXRjaE1vZGUgPT09ICdub3JtYWxpemVkJyA/IG5hbWUudHJpbSgpLnRvTG93ZXJDYXNlKCkgOiBuYW1lO1xuICAgIHJldHVybiB0aGlzLmNsYXNzQ291bnRzLmdldChrKSA/PyAwO1xuICB9XG5cbiAgLyoqIFJlY29sb3VyIGEgY2xhc3MgZnJvbSBpdHMgcGFuZWwgc3dhdGNoIGFuZCByZXBhaW50IGl0cyAobm9uLW92ZXJyaWRkZW4pIHJlZ2lvbnMuICovXG4gIHNldENsYXNzQ29sb3IobmFtZTogc3RyaW5nLCBjb2xvcjogc3RyaW5nKTogdm9pZCB7XG4gICAgaWYgKCFjb2xvcikgcmV0dXJuO1xuICAgIHRoaXMucmVnaW9uQXBpLnNldENsYXNzaWZpY2F0aW9uQ29sb3IobmFtZSwgY29sb3IpO1xuICAgIHRoaXMuc2V0UmVnaW9uc0Zyb21FZGl0b3IoKTtcbiAgfVxuXG4gIC8qKiBUb29sdGlwIGZvciBhIGNsYXNzJ3MgZGVsZXRlIGJ1dHRvbiBpbiB0aGUgcGFuZWwuICovXG4gIGRlbGV0ZUNsYXNzVG9vbHRpcChuYW1lOiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIGNvbnN0IGluVXNlID0gdGhpcy5jbGFzc0NvdW50KG5hbWUpID4gMDtcbiAgICBpZiAobmFtZSA9PT0gdGhpcy5kZWZhdWx0Q2xhc3NOYW1lICYmIGluVXNlKSByZXR1cm4gJ1RoZSBkZWZhdWx0IGNsYXNzIGNhbm5vdCBiZSByZW1vdmVkIHdoaWxlIGluIHVzZSc7XG4gICAgcmV0dXJuIGluVXNlID8gJ1JlbW92ZSBjbGFzcyDigJQgaXRzIHJlZ2lvbnMgcmV2ZXJ0IHRvIFJlZ2lvbicgOiAnUmVtb3ZlIGNsYXNzJztcbiAgfVxuXG4gIC8qKiBSZW1vdmUgYSBjbGFzcy4gQW55IHJlZ2lvbnMgc3RpbGwgdXNpbmcgaXQgZmFsbCBiYWNrIHRvIHRoZSBkZWZhdWx0XG4gICAqICBcIlJlZ2lvblwiIGNsYXNzLCBzbyB0aGV5IGFyZW4ndCBvcnBoYW5lZCAoYW5kIHRoZSBjbGFzcyBpc24ndCBpbW1lZGlhdGVseVxuICAgKiAgcmUtYWRkZWQgZnJvbSBpdHMgcmVnaW9ucyBieSBzeW5jQ2xhc3Nlc0Zyb21SZWdpb25zKS4gKi9cbiAgZGVsZXRlQ2xhc3MobmFtZTogc3RyaW5nKTogdm9pZCB7XG4gICAgdGhpcy5yZWFzc2lnblJlZ2lvbnNUb0RlZmF1bHRDbGFzcyhbbmFtZV0pO1xuICAgIHRoaXMucmVnaW9uQXBpLnJlbW92ZUNsYXNzKG5hbWUpO1xuICAgIGlmICh0aGlzLmFjdGl2ZUNsYXNzID09PSBuYW1lKSB0aGlzLmFjdGl2ZUNsYXNzID0gbnVsbDtcbiAgICB0aGlzLnNldFJlZ2lvbnNGcm9tRWRpdG9yKCk7XG4gIH1cblxuICAvKiogUmVnaW9ucyBsYWJlbGxlZCB3aXRoIGFueSBvZiBgcmVtb3ZlZE5hbWVzYCByZXZlcnQgdG8gdGhlIGRlZmF1bHRcbiAgICogIHtAbGluayBkZWZhdWx0Q2xhc3NOYW1lfSAoXCJSZWdpb25cIik6IGl0cyBjbGFzcyBjb2xvdXIsIG92ZXJyaWRlIGNsZWFyZWQuXG4gICAqICBTa2lwcyB0aGUgZGVmYXVsdCBpdHNlbGYgc28gZGVsZXRpbmcgXCJSZWdpb25cIiBjYW4ndCBzZWxmLXJlYXNzaWduLiAoaml0LXVpIzcwKSAqL1xuICBwcml2YXRlIHJlYXNzaWduUmVnaW9uc1RvRGVmYXVsdENsYXNzKHJlbW92ZWROYW1lczogc3RyaW5nW10pOiB2b2lkIHtcbiAgICBjb25zdCBub3JtID0gdGhpcy5wcmVzZXRTZXQubWF0Y2hNb2RlID09PSAnbm9ybWFsaXplZCc7XG4gICAgY29uc3Qga2V5T2YgPSAoczogc3RyaW5nKSA9PiAobm9ybSA/IHMudHJpbSgpLnRvTG93ZXJDYXNlKCkgOiBzKTtcbiAgICBjb25zdCByZW1vdmVkID0gbmV3IFNldChyZW1vdmVkTmFtZXMubWFwKGtleU9mKSk7XG4gICAgcmVtb3ZlZC5kZWxldGUoa2V5T2YodGhpcy5kZWZhdWx0Q2xhc3NOYW1lKSk7XG4gICAgaWYgKCFyZW1vdmVkLnNpemUpIHJldHVybjtcbiAgICBjb25zdCBjb2xvciA9IGNvbG9yRm9yTGFiZWwodGhpcy5kZWZhdWx0Q2xhc3NOYW1lLCB0aGlzLnByZXNldFNldCk7XG4gICAgZm9yIChjb25zdCByIG9mIHRoaXMucmVnaW9ucykge1xuICAgICAgaWYgKHIubGFiZWwgJiYgcmVtb3ZlZC5oYXMoa2V5T2Yoci5sYWJlbCkpKSB7XG4gICAgICAgIHIubGFiZWwgPSB0aGlzLmRlZmF1bHRDbGFzc05hbWU7XG4gICAgICAgIHIuY29sb3JPdmVycmlkZGVuID0gZmFsc2U7XG4gICAgICAgIHIuY29sb3IgPSBjb2xvcjtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvKiogU3RhbXAgYSBjbGFzcyAoYW5kIGl0cyBwcmVzZXQvZmFsbGJhY2sgY29sb3VyKSBvbnRvIG9uZSByZWdpb24gZnJvbSB0aGUgQ2xhc3NcbiAgICogIGRyb3Bkb3duOyBjaG9vc2luZyBhIGNsYXNzIG9wdHMgdGhlIHJlZ2lvbiBiYWNrIGludG8gdGhlIHByZXNldCBjb2xvdXIuICovXG4gIGFwcGx5UHJlc2V0VG9SZWdpb24ocmVnaW9uOiBSZWdpb24sIGNsYXNzTmFtZTogc3RyaW5nKTogdm9pZCB7XG4gICAgY29uc3QgbmFtZSA9IChjbGFzc05hbWUgPz8gJycpLnRyaW0oKTtcbiAgICByZWdpb24ubGFiZWwgPSBuYW1lO1xuICAgIHJlZ2lvbi5jb2xvck92ZXJyaWRkZW4gPSBmYWxzZTtcbiAgICBpZiAobmFtZSkgcmVnaW9uLmNvbG9yID0gY29sb3JGb3JMYWJlbChuYW1lLCB0aGlzLnByZXNldFNldCk7XG4gICAgdGhpcy5zZXRSZWdpb25zRnJvbUVkaXRvcigpO1xuICB9XG5cbiAgLyoqIE1ha2UgYSBjbGFzcyBhY3RpdmUgKHVzZWQgZm9yIG5ldyByZWdpb25zKSBhbmQsIGlmIHJvd3MgYXJlIHNlbGVjdGVkLCBhcHBseVxuICAgKiAgaXQgdG8gdGhlbSBpbiBvbmUgY29tbWl0LiAqL1xuICBzZWxlY3RBY3RpdmVDbGFzcyhuYW1lOiBzdHJpbmcpOiB2b2lkIHtcbiAgICB0aGlzLmFjdGl2ZUNsYXNzID0gbmFtZTtcbiAgICB0aGlzLmFwcGx5Q2xhc3NUb1NlbGVjdGVkKG5hbWUpO1xuICB9XG5cbiAgLyoqIEFwcGx5IGEgY2xhc3MgKGxhYmVsICsgcHJlc2V0L2ZhbGxiYWNrIGNvbG91cikgdG8gdGhlIGN1cnJlbnQgc2VsZWN0aW9uIGFuZCBjb21taXQuICovXG4gIHByaXZhdGUgYXBwbHlDbGFzc1RvU2VsZWN0ZWQobmFtZTogc3RyaW5nKTogdm9pZCB7XG4gICAgY29uc3Qgc2VsZWN0ZWQgPSB0aGlzLnNlbGVjdGVkUmVnaW9ucyA/PyBbXTtcbiAgICBpZiAoIXNlbGVjdGVkLmxlbmd0aCkgcmV0dXJuO1xuICAgIGZvciAoY29uc3QgciBvZiBzZWxlY3RlZCkge1xuICAgICAgci5sYWJlbCA9IG5hbWU7XG4gICAgICByLmNvbG9yT3ZlcnJpZGRlbiA9IGZhbHNlO1xuICAgICAgci5jb2xvciA9IGNvbG9yRm9yTGFiZWwobmFtZSwgdGhpcy5wcmVzZXRTZXQpO1xuICAgIH1cbiAgICB0aGlzLnNldFJlZ2lvbnNGcm9tRWRpdG9yKCk7XG4gIH1cblxuICAvKiogXCJFZGl0IGNsYXNzIG9uIHNlbGVjdGVkIHJvd3NcIiBwb3B1cCDigJQgYXBwbHkgdGhlIGNob3NlbiBleGlzdGluZyBjbGFzcy4gKi9cbiAgYXBwbHlCdWxrQ2xhc3MoKTogdm9pZCB7XG4gICAgaWYgKCF0aGlzLmJ1bGtDbGFzcykgcmV0dXJuO1xuICAgIHRoaXMuYXBwbHlDbGFzc1RvU2VsZWN0ZWQodGhpcy5idWxrQ2xhc3MpO1xuICAgIHRoaXMub3ZlcmxheVBhbmVsPy5oaWRlKCk7XG4gIH1cblxuICAvKiogXCJFZGl0IGNsYXNzIG9uIHNlbGVjdGVkIHJvd3NcIiBwb3B1cCDigJQgYWRkIGEgbmV3IGNsYXNzIGFuZCBzZXQgaXQgb24gdGhlIHNlbGVjdGlvbi4gKi9cbiAgYWRkQW5kQXBwbHlCdWxrQ2xhc3MoKTogdm9pZCB7XG4gICAgY29uc3QgbmFtZSA9IHRoaXMubmV3QnVsa0NsYXNzLnRyaW0oKTtcbiAgICBpZiAoIW5hbWUpIHJldHVybjtcbiAgICBjb25zdCBub3JtID0gdGhpcy5wcmVzZXRTZXQubWF0Y2hNb2RlID09PSAnbm9ybWFsaXplZCc7XG4gICAgY29uc3QgZXhpc3RzID0gdGhpcy5wcmVzZXRTZXQuY2xhc3Nlcy5zb21lKChjKSA9PlxuICAgICAgbm9ybSA/IGMubmFtZS50cmltKCkudG9Mb3dlckNhc2UoKSA9PT0gbmFtZS50b0xvd2VyQ2FzZSgpIDogYy5uYW1lID09PSBuYW1lLFxuICAgICk7XG4gICAgaWYgKCFleGlzdHMpIHtcbiAgICAgIHRoaXMucmVnaW9uQXBpLnVwc2VydENsYXNzKHsgbmFtZSwgY29sb3I6IGNvbG9yRm9yTGFiZWwobmFtZSwgdGhpcy5wcmVzZXRTZXQpLCBzb3VyY2U6ICd1c2VyJyB9KTtcbiAgICB9XG4gICAgdGhpcy5hcHBseUNsYXNzVG9TZWxlY3RlZChuYW1lKTtcbiAgICB0aGlzLmJ1bGtDbGFzcyA9IG5hbWU7XG4gICAgdGhpcy5uZXdCdWxrQ2xhc3MgPSAnJztcbiAgICB0aGlzLm92ZXJsYXlQYW5lbD8uaGlkZSgpO1xuICB9XG5cbiAgLy8g4pSA4pSAIE1hbmFnZS1jbGFzc2VzIGRpYWxvZyDilIDilIBcbiAgb3Blbk1hbmFnZURpYWxvZygpOiB2b2lkIHtcbiAgICB0aGlzLnByZXNldERyYWZ0ID0gdGhpcy5jbG9uZVByZXNldFNldCh0aGlzLnByZXNldFNldCk7XG4gICAgdGhpcy5zaG93TWFuYWdlRGlhbG9nID0gdHJ1ZTtcbiAgfVxuICBwcml2YXRlIGNsb25lUHJlc2V0U2V0KHM6IFByZXNldFNldCk6IFByZXNldFNldCB7XG4gICAgcmV0dXJuIHtcbiAgICAgIGNsYXNzZXM6IChzLmNsYXNzZXMgPz8gW10pLm1hcCgoYykgPT4gKHsgLi4uYyB9KSksXG4gICAgICBmYWxsYmFja1BhbGV0dGU6IFsuLi4ocy5mYWxsYmFja1BhbGV0dGUgPz8gW10pXSxcbiAgICAgIGF1dG9Qcm9tb3RlOiAhIXMuYXV0b1Byb21vdGUsXG4gICAgICBtYXRjaE1vZGU6IHMubWF0Y2hNb2RlID09PSAnbm9ybWFsaXplZCcgPyAnbm9ybWFsaXplZCcgOiAnZXhhY3QnLFxuICAgIH07XG4gIH1cbiAgYWRkUHJlc2V0Q2xhc3MoKTogdm9pZCB7XG4gICAgdGhpcy5wcmVzZXREcmFmdD8uY2xhc3Nlcy5wdXNoKHsgbmFtZTogJycsIGNvbG9yOiAnIzg4ODg4OCcsIHNvdXJjZTogJ3VzZXInIH0pO1xuICB9XG4gIHJlbW92ZVByZXNldENsYXNzKGk6IG51bWJlcik6IHZvaWQge1xuICAgIHRoaXMucHJlc2V0RHJhZnQ/LmNsYXNzZXMuc3BsaWNlKGksIDEpO1xuICB9XG4gIGFkZEZhbGxiYWNrQ29sb3IoKTogdm9pZCB7XG4gICAgdGhpcy5wcmVzZXREcmFmdD8uZmFsbGJhY2tQYWxldHRlLnB1c2goJyM4ODg4ODgnKTtcbiAgfVxuICByZW1vdmVGYWxsYmFja0NvbG9yKGk6IG51bWJlcik6IHZvaWQge1xuICAgIHRoaXMucHJlc2V0RHJhZnQ/LmZhbGxiYWNrUGFsZXR0ZS5zcGxpY2UoaSwgMSk7XG4gIH1cbiAgLyoqIENvbW1pdCB0aGUgZHJhZnQ6IGRyb3AgYmxhbmsvZHVwbGljYXRlIG5hbWVzLCBwZXJzaXN0LCBhbmQgcmVjb2xvdXJcbiAgICogIG5vbi1vdmVycmlkZGVuIHJlZ2lvbnMgd2l0aCB0aGUgdXBkYXRlZCBwcmVzZXRzLiAqL1xuICBhcHBseU1hbmFnZURpYWxvZyhjbG9zZTogYm9vbGVhbik6IHZvaWQge1xuICAgIGlmICghdGhpcy5wcmVzZXREcmFmdCkgcmV0dXJuO1xuICAgIC8vIERlLWR1cGxpY2F0ZSB1c2luZyB0aGUgYWN0aXZlIG1hdGNoIG1vZGUncyBrZXkgc28gbm9ybWFsaXplZCBtb2RlIGNhbid0IGtlZXBcbiAgICAvLyBib3RoIFwiVHVtb3JcIiBhbmQgXCJ0dW1vclwiICh3aGljaCB3b3VsZCBjb2xsaWRlIGF0IHJ1bnRpbWUgaW4gZmluZFByZXNldCgpKS5cbiAgICBjb25zdCBub3JtID0gdGhpcy5wcmVzZXREcmFmdC5tYXRjaE1vZGUgPT09ICdub3JtYWxpemVkJztcbiAgICBjb25zdCBzZWVuID0gbmV3IFNldDxzdHJpbmc+KCk7XG4gICAgY29uc3QgY2xhc3NlczogQ2xhc3NQcmVzZXRbXSA9IFtdO1xuICAgIGZvciAoY29uc3QgYyBvZiB0aGlzLnByZXNldERyYWZ0LmNsYXNzZXMpIHtcbiAgICAgIGNvbnN0IG5hbWUgPSAoYy5uYW1lID8/ICcnKS50cmltKCk7XG4gICAgICBjb25zdCBrZXkgPSBub3JtID8gbmFtZS50b0xvd2VyQ2FzZSgpIDogbmFtZTtcbiAgICAgIGlmICghbmFtZSB8fCBzZWVuLmhhcyhrZXkpKSBjb250aW51ZTtcbiAgICAgIHNlZW4uYWRkKGtleSk7XG4gICAgICBjbGFzc2VzLnB1c2goeyAuLi5jLCBuYW1lIH0pO1xuICAgIH1cbiAgICB0aGlzLnByZXNldERyYWZ0LmNsYXNzZXMgPSBjbGFzc2VzO1xuICAgIC8vIENsYXNzZXMgZHJvcHBlZCBpbiB0aGUgZGlhbG9nOiB0aGVpciByZWdpb25zIHJldmVydCB0byB0aGUgZGVmYXVsdCBcIlJlZ2lvblwiXG4gICAgLy8gY2xhc3MgKGVsc2Ugc3luY0NsYXNzZXNGcm9tUmVnaW9ucyB3b3VsZCBqdXN0IHJlLWFkZCB0aGVtKSAoaml0LXVpIzcwKS5cbiAgICBjb25zdCBrZXlPZiA9IChzOiBzdHJpbmcpID0+IChub3JtID8gcy50cmltKCkudG9Mb3dlckNhc2UoKSA6IHMpO1xuICAgIGNvbnN0IGtlcHQgPSBuZXcgU2V0KGNsYXNzZXMubWFwKChjKSA9PiBrZXlPZihjLm5hbWUpKSk7XG4gICAgY29uc3QgcmVtb3ZlZCA9IHRoaXMucHJlc2V0U2V0LmNsYXNzZXNcbiAgICAgIC5tYXAoKGMpID0+IGMubmFtZSlcbiAgICAgIC5maWx0ZXIoKG4pID0+ICFrZXB0LmhhcyhrZXlPZihuKSkpO1xuICAgIHRoaXMucmVhc3NpZ25SZWdpb25zVG9EZWZhdWx0Q2xhc3MocmVtb3ZlZCk7XG4gICAgdGhpcy5yZWdpb25BcGkuc2V0UHJlc2V0U2V0KHRoaXMucHJlc2V0RHJhZnQpO1xuICAgIHRoaXMuc2V0UmVnaW9uc0Zyb21FZGl0b3IoKTsgLy8gcmVjb2xvdXIgZXhpc3RpbmcgKG5vbi1vdmVycmlkZGVuKSByZWdpb25zIGZyb20gdGhlIG5ldyBwcmVzZXRzXG4gICAgaWYgKGNsb3NlKSB0aGlzLnNob3dNYW5hZ2VEaWFsb2cgPSBmYWxzZTtcbiAgfVxuICByZXNldFByZXNldHNUb0RlZmF1bHRzKCk6IHZvaWQge1xuICAgIHRoaXMucmVnaW9uQXBpLnJlc2V0UHJlc2V0cygpO1xuICAgIHRoaXMucHJlc2V0RHJhZnQgPSB0aGlzLmNsb25lUHJlc2V0U2V0KHRoaXMucmVnaW9uQXBpLmdldFByZXNldFNldCgpKTtcbiAgICB0aGlzLnNldFJlZ2lvbnNGcm9tRWRpdG9yKCk7XG4gIH1cbiAgZXhwb3J0UHJlc2V0cygpOiB2b2lkIHtcbiAgICBjb25zdCBqc29uID0gSlNPTi5zdHJpbmdpZnkodGhpcy5yZWdpb25BcGkuZ2V0UHJlc2V0U2V0KCksIG51bGwsIDIpO1xuICAgIHNhdmVBcyhuZXcgQmxvYihbanNvbl0sIHsgdHlwZTogJ2FwcGxpY2F0aW9uL2pzb24nIH0pLCAnYW5ub3RhdGlvbi1jbGFzc2VzLmpzb24nKTtcbiAgfVxuICBpbXBvcnRQcmVzZXRzKGV2ZW50OiBFdmVudCk6IHZvaWQge1xuICAgIGNvbnN0IGlucHV0ID0gZXZlbnQudGFyZ2V0IGFzIEhUTUxJbnB1dEVsZW1lbnQ7XG4gICAgY29uc3QgZmlsZSA9IGlucHV0LmZpbGVzPy5bMF07XG4gICAgaWYgKCFmaWxlKSByZXR1cm47XG4gICAgY29uc3QgcmVhZGVyID0gbmV3IEZpbGVSZWFkZXIoKTtcbiAgICByZWFkZXIub25sb2FkID0gKGU6IGFueSkgPT4ge1xuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3Qgc2V0ID0gSlNPTi5wYXJzZShlLnRhcmdldC5yZXN1bHQpIGFzIFByZXNldFNldDtcbiAgICAgICAgdGhpcy5yZWdpb25BcGkuc2V0UHJlc2V0U2V0KHNldCk7XG4gICAgICAgIHRoaXMucHJlc2V0RHJhZnQgPSB0aGlzLmNsb25lUHJlc2V0U2V0KHRoaXMucmVnaW9uQXBpLmdldFByZXNldFNldCgpKTtcbiAgICAgICAgdGhpcy5zZXRSZWdpb25zRnJvbUVkaXRvcigpO1xuICAgICAgICB0aGlzLm1lc3NhZ2VTZXJ2aWNlLmFkZCh7IGtleTogJ2FwcC10b2FzdCcsIHNldmVyaXR5OiAnc3VjY2VzcycsXG4gICAgICAgICAgc3VtbWFyeTogJ0NsYXNzZXMgaW1wb3J0ZWQnLCBkZXRhaWw6ICdBbm5vdGF0aW9uIGNsYXNzZXMgbG9hZGVkLicgfSk7XG4gICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgdGhpcy5tZXNzYWdlU2VydmljZS5hZGQoeyBrZXk6ICdhcHAtdG9hc3QnLCBzZXZlcml0eTogJ2Vycm9yJyxcbiAgICAgICAgICBzdW1tYXJ5OiAnSW1wb3J0IGZhaWxlZCcsIGRldGFpbDogYCR7ZXJyfWAgfSk7XG4gICAgICB9XG4gICAgfTtcbiAgICByZWFkZXIucmVhZEFzVGV4dChmaWxlKTtcbiAgfVxuXG4gIGltcG9ydFJvaXMoZXZlbnQ6IEV2ZW50KSB7XG4gICAgY29uc3QgaW5wdXQgPSBldmVudC50YXJnZXQgYXMgSFRNTElucHV0RWxlbWVudDtcbiAgICBjb25zdCBmaWxlID0gaW5wdXQuZmlsZXM/LlswXTtcbiAgICBpZiAoIWZpbGUpIHJldHVybjtcbiAgICBjb25zdCByZWFkZXIgPSBuZXcgRmlsZVJlYWRlcigpO1xuICAgIHJlYWRlci5vbmxvYWQgPSAoZTogYW55KSA9PiB7XG4gICAgICBjb25zdCBmaWxlQ29udGVudCA9IGUudGFyZ2V0LnJlc3VsdDtcbiAgICAgIHRyeSB7XG4gICAgICAgIHRoaXMucmVnaW9ucyA9IHRoaXMucmVnaW9uQXBpLmltcG9ydFJlZ2lvbnMoZmlsZUNvbnRlbnQpO1xuICAgICAgICAvLyBzZXQgbGFiZWwgY29sb3JzXG4gICAgICAgIGZvciAoY29uc3QgcmVnaW9uIG9mIHRoaXMucmVnaW9ucykge1xuICAgICAgICAgIHRoaXMubGFiZWxSZWdpb25VcGRhdGUocmVnaW9uLCBmYWxzZSk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5zeW5jQ2xhc3Nlc0Zyb21SZWdpb25zKHRoaXMucmVnaW9ucyk7XG4gICAgICAgIHRoaXMuc2V0UmVnaW9uc0Zyb21FZGl0b3IoKTtcbiAgICAgIH0gY2F0Y2ggKGV2ZW50KSB7XG4gICAgICAgIHRoaXMubWVzc2FnZVNlcnZpY2UuYWRkKHtcbiAgICAgICAgICBzZXZlcml0eTogJ2Vycm9yJyxcbiAgICAgICAgICBzdW1tYXJ5OiAnRXJyb3IgaW1wb3J0aW5nIHRoZSBmaWxlJyxcbiAgICAgICAgICBkZXRhaWw6IGAke2V2ZW50fWAsXG4gICAgICAgIH0pO1xuICAgICAgICBjb25zb2xlLmVycm9yKCdFcnJvciByZWFkaW5nIHRoZSBmaWxlOiAnICsgZXZlbnQpO1xuICAgICAgfVxuICAgIH07XG4gICAgcmVhZGVyLnJlYWRBc1RleHQoZmlsZSk7XG4gIH1cblxuICAvKipcbiAgICogUmVnaW9ucyB0byBzZXJpYWxpemUgb24gc2F2ZS9leHBvcnQuIEZvciBhIHNpbmdsZS1maWxlIHotc3RhY2sgdGhlIHN0b3JlXG4gICAqIGtlZXBzIG9ubHkgdGhlIGN1cnJlbnQgc2xpY2UgbGl2ZSwgc28gcHVsbCBFVkVSWSBzbGljZSdzIGFubm90YXRpb25zIChlYWNoXG4gICAqIHRhZ2dlZCB3aXRoIGl0cyB6ZXJvLWJhc2VkIFJlZ2lvbi56KSB0byB3cml0ZSBvbmUgY29tYmluZWQgei1pbmRleGVkXG4gICAqIGdlb2pzb24gKGppdC11aSM5MykuIE90aGVyd2lzZSAoc2luZ2xlLXBsYW5lIGltYWdlLCBvciBhIGZvbGRlciBzdGFjayB3aG9zZVxuICAgKiBzbGljZXMgc2F2ZSB0byB0aGVpciBvd24gcGVyLXNsaWNlIGZpbGVzKSB0aGUgY3VycmVudCBzZXQuIChqaXQtdWkjOTMpXG4gICAqL1xuICBwcml2YXRlIHJlZ2lvbnNGb3JTYXZlKCk6IFJlZ2lvbltdIHtcbiAgICBpZiAodGhpcy5yZWdpb25BcGkuaXNTdGFja01vZGUoKSAmJiB0aGlzLnJlZ2lvbkFwaS5nZXRTdGFja1NhdmVMYXlvdXQoKSA9PT0gJ2NvbWJpbmVkJykge1xuICAgICAgcmV0dXJuIHRoaXMucmVnaW9uQXBpLmdldFNsaWNlQW5ub3RhdGlvblJlZ2lvbnMoKTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXMucmVnaW9ucztcbiAgfVxuXG4gIGV4cG9ydFJvaXMoKSB7XG4gICAgaWYgKCF0aGlzLnJlZ2lvbnNGb3JTYXZlKCkubGVuZ3RoKSByZXR1cm47XG4gICAgY29uc3QgbmFtZSA9IHRoaXMucmVnaW9uSW8uZ2V0U2VsZWN0ZWRGaWxlTmFtZSgpO1xuICAgIGNvbnN0IHN0ZW0gPSBuYW1lID8gbmFtZS5zdWJzdHJpbmcoMCwgbmFtZS5sYXN0SW5kZXhPZignLicpKSA6ICdyb2lzJztcbiAgICB0aGlzLmV4cG9ydEZpbGVuYW1lID0gYCR7c3RlbX0uZ2VvanNvbmA7XG4gICAgdGhpcy5zaG93RXhwb3J0RGlhbG9nID0gdHJ1ZTtcbiAgfVxuXG4gIGNvbmZpcm1FeHBvcnQoKSB7XG4gICAgY29uc3QgZmlsZW5hbWUgPSB0aGlzLmV4cG9ydEZpbGVuYW1lLnRyaW0oKTtcbiAgICBjb25zdCByZWdpb25zID0gdGhpcy5yZWdpb25zRm9yU2F2ZSgpO1xuICAgIGlmICghZmlsZW5hbWUgfHwgIXJlZ2lvbnMubGVuZ3RoKSByZXR1cm47XG4gICAgdGhpcy5zaG93RXhwb3J0RGlhbG9nID0gZmFsc2U7XG4gICAgY29uc3QganNvblN0cmluZyA9IHRoaXMucmVnaW9uQXBpLmdldEdlb0pzb25TdHJpbmcocmVnaW9ucyk7XG4gICAgY29uc3QgYmxvYiA9IG5ldyBCbG9iKFtqc29uU3RyaW5nXSwgeyB0eXBlOiAnYXBwbGljYXRpb24vanNvbicgfSk7XG4gICAgc2F2ZUFzKGJsb2IsIGZpbGVuYW1lKTtcbiAgfVxuXG4gIC8qKiBPcGVuIHRoZSBcIlNhdmUgbWFza1wiIGRpYWxvZywgc2VlZGVkIHdpdGggYDxpbWFnZS1zdGVtPl9tYXNrLnBuZ2AuICovXG4gIG9wZW5TYXZlTWFza0RpYWxvZygpIHtcbiAgICBpZiAoIXRoaXMucmVnaW9ucy5sZW5ndGgpIHJldHVybjtcbiAgICBjb25zdCBuYW1lID0gdGhpcy5yZWdpb25Jby5nZXRTZWxlY3RlZEZpbGVOYW1lKCk7XG4gICAgLy8gU3RyaXAgdGhlIGV4dGVuc2lvbiBvbmx5IHdoZW4gdGhlcmUgaXMgb25lOyBhbiBleHRlbnNpb24tbGVzcyBuYW1lIGtlZXBzXG4gICAgLy8gaXRzIHdob2xlIHN0ZW0gKGEgbGVhZGluZy1kb3QgZG90ZmlsZSBpcyB0cmVhdGVkIGFzIGhhdmluZyBubyBleHRlbnNpb24pXG4gICAgLy8gc28gd2UgbmV2ZXIgcHJvZHVjZSBhIGJhcmUgXCJfbWFzay5wbmdcIi5cbiAgICBjb25zdCBkb3QgPSBuYW1lID8gbmFtZS5sYXN0SW5kZXhPZignLicpIDogLTE7XG4gICAgY29uc3Qgc3RlbSA9IG5hbWUgPyAoZG90ID4gMCA/IG5hbWUuc3Vic3RyaW5nKDAsIGRvdCkgOiBuYW1lKSA6ICdyZWdpb25zJztcbiAgICB0aGlzLnNhdmVNYXNrRmlsZW5hbWUgPSBgJHtzdGVtfV9tYXNrLnBuZ2A7XG4gICAgdGhpcy5tYXNrTW9kZSA9ICdiaW5hcnknO1xuICAgIHRoaXMuc2hvd1NhdmVNYXNrRGlhbG9nID0gdHJ1ZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSYXN0ZXJpemUgdGhlIHJlZ2lvbnMgdG8gdGhlIGNob3NlbiBtYXNrIHR5cGUgYW5kIGRvd25sb2FkIGFzIGEgUE5HLiBUaGVcbiAgICogaGVhdnkgd29yayAoZnVsbC1yZXMgcmFzdGVyaXplICsgUE5HIGVuY29kZSkgcnVucyBpbiBhIFdlYiBXb3JrZXIgc28gdGhlIFVJXG4gICAqIG5ldmVyIGZyZWV6ZXMgb24gbGFyZ2Uvd2hvbGUtc2xpZGUgaW1hZ2VzIGFuZCB0aGUgam9iIGNhbiBiZSBjYW5jZWxsZWRcbiAgICogKGppdC11aSM5NSkuIFRoZSBkaWFsb2cgc3RheXMgb3BlbiBzaG93aW5nIGEgcHJvZ3Jlc3MgYmFyIHVudGlsIGRvbmUuXG4gICAqL1xuICBjb25maXJtU2F2ZU1hc2soKSB7XG4gICAgY29uc3QgZmlsZW5hbWUgPSB0aGlzLnNhdmVNYXNrRmlsZW5hbWUudHJpbSgpO1xuICAgIGlmICghZmlsZW5hbWUgfHwgIXRoaXMucmVnaW9ucy5sZW5ndGggfHwgdGhpcy5tYXNrQnVzeSkgcmV0dXJuO1xuXG4gICAgY29uc3Qgc2l6ZSA9IHRoaXMucmVnaW9uQXBpLmdldE1hc2tJbWFnZVNpemUoKTtcbiAgICBpZiAoIXNpemUpIHtcbiAgICAgIHRoaXMubWFza0Vycm9yKCdObyBpbWFnZSBzaXplIGlzIGF2YWlsYWJsZSwgc28gdGhlIG1hc2sgY2Fubm90IGJlIHNpemVkLicpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIFdob2xlLXNsaWRlIGltYWdlcyBleGNlZWQgdGhlIGJyb3dzZXIncyB0eXBlZC1hcnJheS9tZW1vcnkgbGltaXRzLCBzbyBjYXBcbiAgICAvLyB0aGUgbWFzayB0byBhIHNhZmUgcGl4ZWwgYnVkZ2V0IGFuZCBzY2FsZSB0aGUgZ2VvbWV0cnkgdG8gbWF0Y2guXG4gICAgY29uc3Qgc2NhbGUgPSBtYXNrU2NhbGVGb3Ioc2l6ZS53aWR0aCwgc2l6ZS5oZWlnaHQpO1xuICAgIGNvbnN0IHdpZHRoID0gTWF0aC5tYXgoMSwgTWF0aC5yb3VuZChzaXplLndpZHRoICogc2NhbGUpKTtcbiAgICBjb25zdCBoZWlnaHQgPSBNYXRoLm1heCgxLCBNYXRoLnJvdW5kKHNpemUuaGVpZ2h0ICogc2NhbGUpKTtcbiAgICBpZiAoc2NhbGUgPCAxKSB7XG4gICAgICB0aGlzLm1lc3NhZ2VTZXJ2aWNlLmFkZCh7XG4gICAgICAgIGtleTogJ2FwcC10b2FzdCcsXG4gICAgICAgIHNldmVyaXR5OiAnaW5mbycsXG4gICAgICAgIHN1bW1hcnk6ICdNYXNrIGRvd25zY2FsZWQnLFxuICAgICAgICBkZXRhaWw6IGBJbWFnZSB0b28gbGFyZ2UgZm9yIGEgZnVsbC1yZXNvbHV0aW9uIG1hc2s7IHNhdmluZyBhdCAke3dpZHRofcOXJHtoZWlnaHR9LmAsXG4gICAgICB9KTtcbiAgICB9XG4gICAgY29uc3QgcmVnaW9ucyA9IHRoaXMucmVnaW9ucy5tYXAoKHIpID0+IHNjYWxlUGFydHMocmVnaW9uVG9QYXJ0cyhyKSwgc2NhbGUpKTtcblxuICAgIHRoaXMucGVuZGluZ01hc2tGaWxlbmFtZSA9IGZpbGVuYW1lO1xuICAgIHRoaXMubWFza0J1c3kgPSB0cnVlO1xuICAgIHRoaXMubWFza0VuY29kaW5nID0gZmFsc2U7XG4gICAgdGhpcy5tYXNrUHJvZ3Jlc3MgPSAwO1xuXG4gICAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICAgIHdpZHRoLCBoZWlnaHQsXG4gICAgICBvcmlnaW5hbFdpZHRoOiBzaXplLndpZHRoLCBvcmlnaW5hbEhlaWdodDogc2l6ZS5oZWlnaHQsIHNjYWxlLFxuICAgICAgbW9kZTogdGhpcy5tYXNrTW9kZSxcbiAgICAgIHNvdXJjZU5hbWU6IHRoaXMucmVnaW9uSW8uZ2V0U2VsZWN0ZWRGaWxlTmFtZSgpLFxuICAgICAgcmVnaW9ucyxcbiAgICB9O1xuICAgIC8vIGNyZWF0ZU1hc2tXb3JrZXIoKSBpcyBhc3luYyAodGhlIHdvcmtlciBtb2R1bGUgaXMgaW1wb3J0ZWQgZHluYW1pY2FsbHkgc29cbiAgICAvLyBpdHMgaW1wb3J0Lm1ldGEgbmV2ZXIgcmVhY2hlcyB0aGUgQ29tbW9uSlMgdGVzdCBjb21waWxlKS4gV2lyZSB1cCBvbmNlIGl0XG4gICAgLy8gcmVzb2x2ZXMg4oCUIHVubGVzcyB0aGUgdXNlciBjYW5jZWxsZWQgd2hpbGUgaXQgd2FzIGxvYWRpbmcuXG4gICAgdGhpcy5jcmVhdGVNYXNrV29ya2VyKCkudGhlbigod29ya2VyKSA9PiB7XG4gICAgICBpZiAoIXRoaXMubWFza0J1c3kpIHsgd29ya2VyLnRlcm1pbmF0ZSgpOyByZXR1cm47IH1cbiAgICAgIHRoaXMubWFza1dvcmtlciA9IHdvcmtlcjtcbiAgICAgIHdvcmtlci5vbm1lc3NhZ2UgPSAoeyBkYXRhIH06IE1lc3NhZ2VFdmVudCkgPT4ge1xuICAgICAgICBzd2l0Y2ggKGRhdGE/LnR5cGUpIHtcbiAgICAgICAgICBjYXNlICdwcm9ncmVzcyc6XG4gICAgICAgICAgICB0aGlzLm1hc2tQcm9ncmVzcyA9IGRhdGEudG90YWwgPyBNYXRoLnJvdW5kKChkYXRhLmRvbmUgLyBkYXRhLnRvdGFsKSAqIDEwMCkgOiAwO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgY2FzZSAnZW5jb2RpbmcnOlxuICAgICAgICAgICAgdGhpcy5tYXNrRW5jb2RpbmcgPSB0cnVlO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgY2FzZSAnZG9uZSc6XG4gICAgICAgICAgICB0aGlzLmZpbmlzaE1hc2sobmV3IEJsb2IoW2RhdGEucG5nXSwgeyB0eXBlOiAnaW1hZ2UvcG5nJyB9KSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBjYXNlICdlcnJvcic6XG4gICAgICAgICAgICB0aGlzLm1hc2tFcnJvcihkYXRhLmVycm9yIHx8ICdUaGUgbWFzayBjb3VsZCBub3QgYmUgZ2VuZXJhdGVkLicpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgIH07XG4gICAgICB3b3JrZXIub25lcnJvciA9ICgpID0+IHRoaXMubWFza0Vycm9yKCdUaGUgbWFzayB3b3JrZXIgZmFpbGVkLicpO1xuICAgICAgd29ya2VyLnBvc3RNZXNzYWdlKHBheWxvYWQpO1xuICAgIH0pLmNhdGNoKCgpID0+IHRoaXMubWFza0Vycm9yKCdUaGUgbWFzayB3b3JrZXIgZmFpbGVkIHRvIHN0YXJ0LicpKTtcbiAgfVxuXG4gIC8qKiBDYW5jZWwgYW4gaW4tcHJvZ3Jlc3MgbWFzayBleHBvcnQ6IHRlcm1pbmF0ZSB0aGUgd29ya2VyIGFuZCByZXNldCBzdGF0ZS4gKi9cbiAgY2FuY2VsU2F2ZU1hc2soKSB7XG4gICAgdGhpcy50ZWFyZG93bk1hc2tXb3JrZXIoKTtcbiAgICB0aGlzLm1hc2tCdXN5ID0gZmFsc2U7XG4gICAgdGhpcy5tYXNrRW5jb2RpbmcgPSBmYWxzZTtcbiAgICB0aGlzLm1hc2tQcm9ncmVzcyA9IDA7XG4gIH1cblxuICAvKiogV29ya2VyIGZhY3Rvcnkg4oCUIGFzeW5jIHNvIHRoZSB3b3JrZXIgbW9kdWxlIChhbmQgaXRzIGBpbXBvcnQubWV0YS51cmxgLFxuICAgKiAgd2hpY2ggdGhlIENvbW1vbkpTIHRlc3QgY29tcGlsZSByZWplY3RzKSBpcyBsb2FkZWQgdmlhIGEgZHluYW1pYyBpbXBvcnQsXG4gICAqICBleGFjdGx5IGxpa2UgdGhlIHNlZ21lbnRhdGlvbiB3b3JrZXIuIE92ZXJyaWRhYmxlIGluIHRlc3RzLiAqL1xuICBwcm90ZWN0ZWQgYXN5bmMgY3JlYXRlTWFza1dvcmtlcigpOiBQcm9taXNlPFdvcmtlcj4ge1xuICAgIGNvbnN0IHsgY3JlYXRlTWFza1dvcmtlciB9ID0gYXdhaXQgaW1wb3J0KCcuL21hc2std29ya2VyJyk7XG4gICAgcmV0dXJuIGNyZWF0ZU1hc2tXb3JrZXIoKTtcbiAgfVxuXG4gIHByaXZhdGUgZmluaXNoTWFzayhibG9iOiBCbG9iKSB7XG4gICAgdGhpcy50ZWFyZG93bk1hc2tXb3JrZXIoKTtcbiAgICB0aGlzLm1hc2tCdXN5ID0gZmFsc2U7XG4gICAgdGhpcy5tYXNrRW5jb2RpbmcgPSBmYWxzZTtcbiAgICB0aGlzLnNob3dTYXZlTWFza0RpYWxvZyA9IGZhbHNlO1xuICAgIHNhdmVBcyhibG9iLCB0aGlzLnBlbmRpbmdNYXNrRmlsZW5hbWUpO1xuICB9XG5cbiAgcHJpdmF0ZSBtYXNrRXJyb3IoZGV0YWlsOiBzdHJpbmcpIHtcbiAgICB0aGlzLnRlYXJkb3duTWFza1dvcmtlcigpO1xuICAgIHRoaXMubWFza0J1c3kgPSBmYWxzZTtcbiAgICB0aGlzLm1hc2tFbmNvZGluZyA9IGZhbHNlO1xuICAgIHRoaXMubWVzc2FnZVNlcnZpY2UuYWRkKHtcbiAgICAgIGtleTogJ2FwcC10b2FzdCcsXG4gICAgICBzZXZlcml0eTogJ2Vycm9yJyxcbiAgICAgIHN1bW1hcnk6ICdDb3VsZCBub3QgY3JlYXRlIG1hc2snLFxuICAgICAgZGV0YWlsLFxuICAgIH0pO1xuICB9XG5cbiAgcHJpdmF0ZSB0ZWFyZG93bk1hc2tXb3JrZXIoKSB7XG4gICAgdGhpcy5tYXNrV29ya2VyPy50ZXJtaW5hdGUoKTtcbiAgICB0aGlzLm1hc2tXb3JrZXIgPSB1bmRlZmluZWQ7XG4gIH1cblxuICBwZXJzaXN0UmVnaW9ucygpIHtcbiAgICAvLyBGb2xkZXIgc3RhY2s6IHdyaXRlIGVhY2ggc2xpY2UncyByZWdpb25zIGJhY2sgdG8gaXRzIG93biBzbGljZS1maWxlJ3NcbiAgICAvLyBzaWJsaW5nIGdlb2pzb24g4oCUIG5vIHNpbmdsZS1maWxlbmFtZSBwcm9tcHQgKHRoZSBmaWxlbmFtZXMgYXJlIHRoZSBzbGljZVxuICAgIC8vIGZpbGVzJykuIChqaXQtdWkjOTMpXG4gICAgaWYgKHRoaXMucmVnaW9uQXBpLmlzU3RhY2tNb2RlKCkgJiYgdGhpcy5yZWdpb25BcGkuZ2V0U3RhY2tTYXZlTGF5b3V0KCkgPT09ICdwZXItc2xpY2UtZmlsZScpIHtcbiAgICAgIHRoaXMuc2F2ZVN0YWNrU2xpY2VzKCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IG5hbWUgPSB0aGlzLnJlZ2lvbklvLmdldFNlbGVjdGVkRmlsZU5hbWUoKTtcbiAgICBpZiAoIW5hbWUgfHwgIXRoaXMucmVnaW9uc0ZvclNhdmUoKS5sZW5ndGgpIHJldHVybjtcblxuICAgIHRoaXMuc2F2ZUFzRmlsZW5hbWUgPSBuYW1lLnN1YnN0cmluZygwLCBuYW1lLmxhc3RJbmRleE9mKCcuJykpICsgJy5nZW9qc29uJztcbiAgICB0aGlzLnNhdmVBc0ZpbGVFeGlzdHMgPSBmYWxzZTtcbiAgICB0aGlzLnNob3dTYXZlQXNEaWFsb2cgPSB0cnVlO1xuICAgIHRoaXMuX3NhdmVBc0NoZWNrJC5uZXh0KHRoaXMuc2F2ZUFzRmlsZW5hbWUpO1xuICB9XG5cbiAgLyoqXG4gICAqIFNhdmUgYSBmb2xkZXIgc3RhY2sncyByZWdpb25zIGFzIG9uZSBnZW9qc29uIHBlciBzbGljZS1maWxlIChqaXQtdWkjOTMpLlxuICAgKiBHcm91cHMgdGhlIHN0b3JlJ3MgcGVyLXNsaWNlIHJlZ2lvbnMgYnkgc2xpY2UgaW5kZXggYW5kIHNlcmlhbGl6ZXMgZWFjaCBvblxuICAgKiB0aGUgZGVmYXVsdCBwbGFuZSAoej0wKSDigJQgZWFjaCBzbGljZS1maWxlIGlzIGl0c2VsZiBvbmUgcGxhbmUsIGFuZCB0aGVcbiAgICogbG9hZGVyIHJlLWRlcml2ZXMgdGhlIHNsaWNlIGluZGV4IGZyb20gdGhlIGZpbGUncyBwb3NpdGlvbiBpbiB0aGUgc2VyaWVzLlxuICAgKiBTbGljZXMgY2xlYXJlZCBzaW5jZSBsb2FkIGFyZSBpbmNsdWRlZCAoZW1wdHkpIHNvIHRoZWlyIGZpbGUgaXMgb3ZlcndyaXR0ZW4uXG4gICAqL1xuICBwcml2YXRlIHNhdmVTdGFja1NsaWNlcygpIHtcbiAgICBjb25zdCBieVNsaWNlID0gdGhpcy5yZWdpb25BcGkuZ2V0U3RhY2tTYXZlQW5ub3RhdGlvblNsaWNlcygpO1xuICAgIGlmICghYnlTbGljZS5zaXplKSByZXR1cm47XG4gICAgY29uc3Qgc2xpY2VzOiB7IHo6IG51bWJlcjsgZ2VvSnNvblN0cjogc3RyaW5nIH1bXSA9IFtdO1xuICAgIGZvciAoY29uc3QgW3osIHJlZ3NdIG9mIGJ5U2xpY2UpIHtcbiAgICAgIGNvbnN0IGZsYXQgPSByZWdzLm1hcCgocikgPT4gT2JqZWN0LmFzc2lnbihuZXcgUmVnaW9uKCksIHIsIHsgejogMCB9KSk7XG4gICAgICBzbGljZXMucHVzaCh7IHosIGdlb0pzb25TdHI6IHRoaXMucmVnaW9uQXBpLmdldEdlb0pzb25TdHJpbmcoZmxhdCkgfSk7XG4gICAgfVxuICAgIHRoaXMuc2F2ZUFzQnVzeSA9IHRydWU7XG4gICAgdGhpcy5fc2F2ZUFzU3ViID0gdGhpcy5yZWdpb25Jby5zYXZlU2xpY2VHZW9Kc29ucyhzbGljZXMpLnN1YnNjcmliZSh7XG4gICAgICBuZXh0OiAoKSA9PiB7XG4gICAgICAgIHRoaXMuc2F2ZUFzQnVzeSA9IGZhbHNlO1xuICAgICAgICB0aGlzLm1lc3NhZ2VTZXJ2aWNlLmFkZCh7XG4gICAgICAgICAga2V5OiAnYXBwLXRvYXN0JyxcbiAgICAgICAgICBzZXZlcml0eTogJ3N1Y2Nlc3MnLFxuICAgICAgICAgIHN1bW1hcnk6ICdSZWdpb25zIHNhdmVkJyxcbiAgICAgICAgICBkZXRhaWw6IGBTYXZlZCBST0lzIGZvciAke3NsaWNlcy5sZW5ndGh9IHNsaWNlJHtzbGljZXMubGVuZ3RoID09PSAxID8gJycgOiAncyd9YCxcbiAgICAgICAgfSk7XG4gICAgICB9LFxuICAgICAgZXJyb3I6IChlcnIpID0+IHtcbiAgICAgICAgdGhpcy5zYXZlQXNCdXN5ID0gZmFsc2U7XG4gICAgICAgIHRoaXMubWVzc2FnZVNlcnZpY2UuYWRkKHtcbiAgICAgICAgICBrZXk6ICdhcHAtdG9hc3QnLFxuICAgICAgICAgIHNldmVyaXR5OiAnZXJyb3InLFxuICAgICAgICAgIHN1bW1hcnk6ICdFcnJvciBzYXZpbmcgcmVnaW9ucycsXG4gICAgICAgICAgZGV0YWlsOiBgJHsoZXJyIGFzIEVycm9yKT8ubWVzc2FnZSA/PyBlcnJ9YCxcbiAgICAgICAgfSk7XG4gICAgICB9LFxuICAgIH0pO1xuICB9XG5cbiAgY2hlY2tTYXZlQXNGaWxlRXhpc3RzKCkge1xuICAgIHRoaXMuX3NhdmVBc0NoZWNrJC5uZXh0KHRoaXMuc2F2ZUFzRmlsZW5hbWUpO1xuICB9XG5cbiAgY29uZmlybVNhdmVBcygpIHtcbiAgICBpZiAoIXRoaXMucmVnaW9uSW8uZ2V0U2VsZWN0ZWRGaWxlTmFtZSgpKSByZXR1cm47XG5cbiAgICBjb25zdCBmaWxlbmFtZSA9IHRoaXMuc2F2ZUFzRmlsZW5hbWUudHJpbSgpO1xuICAgIGlmICghZmlsZW5hbWUpIHJldHVybjtcblxuICAgIGNvbnN0IGRvU2F2ZSA9ICgpID0+IHtcbiAgICAgIC8vIEtlZXAgdGhlIGRpYWxvZyBvcGVuIHNob3dpbmcgYW4gKGluZGV0ZXJtaW5hdGUpIHByb2dyZXNzIGJhciB3aGlsZSB0aGVcbiAgICAgIC8vIHJlZ2lvbnMgc2VyaWFsaXplIGFuZCB1cGxvYWQsIHRoZW4gY2xvc2Ugb24gc3VjY2Vzcy4gRGVmZXIgb25lIHRpY2sgc29cbiAgICAgIC8vIHRoZSBiYXIgcGFpbnRzIGJlZm9yZSBhIGxhcmdlIHN5bmNocm9ub3VzIEdlb0pTT04gc2VyaWFsaXplLlxuICAgICAgdGhpcy5zYXZlQXNCdXN5ID0gdHJ1ZTtcbiAgICAgIHRoaXMuX3NhdmVBc1RpbWVyID0gc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgIHRoaXMuX3NhdmVBc1RpbWVyID0gdW5kZWZpbmVkO1xuICAgICAgICBsZXQgZ2VvSnNvblN0cjogc3RyaW5nO1xuICAgICAgICB0cnkge1xuICAgICAgICAgIGdlb0pzb25TdHIgPSB0aGlzLnJlZ2lvbkFwaS5nZXRHZW9Kc29uU3RyaW5nKHRoaXMucmVnaW9uc0ZvclNhdmUoKSk7XG4gICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgIHRoaXMuc2F2ZUFzQnVzeSA9IGZhbHNlO1xuICAgICAgICAgIHRoaXMubWVzc2FnZVNlcnZpY2UuYWRkKHtcbiAgICAgICAgICAgIGtleTogJ2FwcC10b2FzdCcsXG4gICAgICAgICAgICBzZXZlcml0eTogJ2Vycm9yJyxcbiAgICAgICAgICAgIHN1bW1hcnk6ICdFcnJvciBzYXZpbmcgcmVnaW9ucycsXG4gICAgICAgICAgICBkZXRhaWw6IGAkeyhlcnIgYXMgRXJyb3IpPy5tZXNzYWdlID8/IGVycn1gLFxuICAgICAgICAgIH0pO1xuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLl9zYXZlQXNTdWIgPSB0aGlzLnJlZ2lvbklvLnNhdmVHZW9Kc29uKGdlb0pzb25TdHIsIGZpbGVuYW1lKS5zdWJzY3JpYmUoe1xuICAgICAgICAgIG5leHQ6ICgpID0+IHtcbiAgICAgICAgICAgIHRoaXMuc2F2ZUFzQnVzeSA9IGZhbHNlO1xuICAgICAgICAgICAgdGhpcy5zaG93U2F2ZUFzRGlhbG9nID0gZmFsc2U7XG4gICAgICAgICAgICB0aGlzLm1lc3NhZ2VTZXJ2aWNlLmFkZCh7XG4gICAgICAgICAgICAgIGtleTogJ2FwcC10b2FzdCcsXG4gICAgICAgICAgICAgIHNldmVyaXR5OiAnc3VjY2VzcycsXG4gICAgICAgICAgICAgIHN1bW1hcnk6ICdSZWdpb25zIHNhdmVkJyxcbiAgICAgICAgICAgICAgZGV0YWlsOiBgU2F2ZWQgYXMgJHtmaWxlbmFtZX1gLFxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgfSxcbiAgICAgICAgICBlcnJvcjogKGVycikgPT4ge1xuICAgICAgICAgICAgdGhpcy5zYXZlQXNCdXN5ID0gZmFsc2U7XG4gICAgICAgICAgICB0aGlzLm1lc3NhZ2VTZXJ2aWNlLmFkZCh7XG4gICAgICAgICAgICAgIGtleTogJ2FwcC10b2FzdCcsXG4gICAgICAgICAgICAgIHNldmVyaXR5OiAnZXJyb3InLFxuICAgICAgICAgICAgICBzdW1tYXJ5OiAnRXJyb3Igc2F2aW5nIHJlZ2lvbnMnLFxuICAgICAgICAgICAgICBkZXRhaWw6IGAke2Vyci5tZXNzYWdlIHx8IGVycn1gLFxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgfSxcbiAgICAgICAgfSk7XG4gICAgICB9KTtcbiAgICB9O1xuXG4gICAgaWYgKHRoaXMuc2F2ZUFzRmlsZUV4aXN0cykge1xuICAgICAgdGhpcy5jb25maXJtYXRpb25TZXJ2aWNlLmNvbmZpcm0oe1xuICAgICAgICBtZXNzYWdlOiBgXCIke2ZpbGVuYW1lfVwiIGFscmVhZHkgZXhpc3RzLiBEbyB5b3Ugd2FudCB0byBvdmVyd3JpdGUgaXQ/YCxcbiAgICAgICAgaGVhZGVyOiAnQ29uZmlybSBPdmVyd3JpdGUnLFxuICAgICAgICBpY29uOiAncGkgcGktZXhjbGFtYXRpb24tdHJpYW5nbGUnLFxuICAgICAgICBhY2NlcHQ6IGRvU2F2ZSxcbiAgICAgIH0pO1xuICAgIH0gZWxzZSB7XG4gICAgICBkb1NhdmUoKTtcbiAgICB9XG4gIH1cblxuICAvKiogQ2FuY2VsIGFuIGluLXByb2dyZXNzIEdlb0pTT04gcGVyc2lzdDogYWJvcnQgdGhlIGRlZmVycmVkIHNlcmlhbGl6ZSBhbmQvb3JcbiAgICogIHRoZSBpbi1mbGlnaHQgdXBsb2FkLCBhbmQgcmVzZXQgc3RhdGUuICovXG4gIGNhbmNlbFNhdmVBcygpIHtcbiAgICBpZiAodGhpcy5fc2F2ZUFzVGltZXIgIT09IHVuZGVmaW5lZCkge1xuICAgICAgY2xlYXJUaW1lb3V0KHRoaXMuX3NhdmVBc1RpbWVyKTtcbiAgICAgIHRoaXMuX3NhdmVBc1RpbWVyID0gdW5kZWZpbmVkO1xuICAgIH1cbiAgICB0aGlzLl9zYXZlQXNTdWI/LnVuc3Vic2NyaWJlKCk7XG4gICAgdGhpcy5fc2F2ZUFzU3ViID0gdW5kZWZpbmVkO1xuICAgIHRoaXMuc2F2ZUFzQnVzeSA9IGZhbHNlO1xuICB9XG59XG4iLCJcbjxwLXRvb2xiYXIgc3R5bGVDbGFzcz1cInJlZ2lvbi10b29sYmFyXCI+XG4gIDxkaXYgY2xhc3M9XCJwLXRvb2xiYXItZ3JvdXAtc3RhcnQgYWxpZ24taXRlbXMtY2VudGVyIGZsZXgtd3JhcCBnYXAtMVwiPlxuICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktdGFnXCIgc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgW3RleHRdPVwiIXNob3dTaGFwZUxhYmVsXCJcbiAgICAgICAgICAgICAgcFRvb2x0aXA9XCJTaG93IHJlZ2lvbiBsYWJlbHMuXCJcbiAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgKG9uQ2xpY2spPVwic2hvd1NoYXBlTGFiZWwgPSAhc2hvd1NoYXBlTGFiZWw7IGNoYW5nZVNob3dTaGFwZUxhYmVsKHNob3dTaGFwZUxhYmVsKVwiPlxuICAgIDwvcC1idXR0b24+XG4gICAgPHNwYW4gY2xhc3M9XCJ0b29sYmFyLXNlcGFyYXRvclwiPjwvc3Bhbj5cbiAgICA8IS0tIE5ldyByZWdpb25zIGFyZSBub3cgY3JlYXRlZCBmcm9tIHRoZSBkaWFncmFtIHRvb2xiYXJcbiAgICAgICAgIChyZWN0YW5nbGUsIHBvbHlsaW5lLCBmcmVlZm9ybSwgd2FuZCkuIC0tPlxuICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktdXBsb2FkXCIgc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiIHN0eWxlQ2xhc3M9XCJtci0yXCJcbiAgICAgICAgICAgICAgcFRvb2x0aXA9XCJVcGxvYWQgYW5kIGRpc3BsYXkgcmVnaW9ucyBmcm9tIGdlb2pzb24gZmlsZS5cIlxuICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAoY2xpY2spPVwicm9pRmlsZUlucHV0LmNsaWNrKClcIj5cbiAgICA8L3AtYnV0dG9uPlxuICAgIDxpbnB1dCAjcm9pRmlsZUlucHV0IHR5cGU9XCJmaWxlXCIgYWNjZXB0PVwiLmdlb2pzb24sLmpzb25cIiBoaWRkZW5cbiAgICAgICAgICAgKGNoYW5nZSk9XCJpbXBvcnRSb2lzKCRldmVudCk7IHJvaUZpbGVJbnB1dC52YWx1ZSA9ICcnXCIvPlxuICAgIDxwLWJ1dHRvbiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCIgc3R5bGVDbGFzcz1cIm1yLTJcIlxuICAgICAgICAgICAgICBwVG9vbHRpcD1cIkRvd25sb2FkIHJlZ2lvbnMgYXMgZ2VvanNvbiBmaWxlLlwiXG4gICAgICAgICAgICAgIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiIGljb249XCJwaSBwaS1kb3dubG9hZFwiXG4gICAgICAgICAgICAgIChjbGljayk9XCJleHBvcnRSb2lzKClcIj5cbiAgICA8L3AtYnV0dG9uPlxuICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktc2F2ZVwiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIiBzdHlsZUNsYXNzPVwibXItMlwiXG4gICAgICAgICAgICAgIHBUb29sdGlwPVwiU2F2ZSByZWdpb25zIHRvIHNlcnZlci5cIlxuICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAoY2xpY2spPVwicGVyc2lzdFJlZ2lvbnMoKVwiXG4gICAgICAgICAgICAgIFtkaXNhYmxlZF09XCIhcmVnaW9ucyB8fCAhcmVnaW9ucy5sZW5ndGhcIj5cbiAgICA8L3AtYnV0dG9uPlxuICAgIDxwLWJ1dHRvbiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCIgc3R5bGVDbGFzcz1cIm1yLTJcIlxuICAgICAgICAgICAgICBwVG9vbHRpcD1cIkRvd25sb2FkIG1hc2sgb2YgdGhlIHJlZ2lvbnMgYXMgUE5HXCJcbiAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgKGNsaWNrKT1cIm9wZW5TYXZlTWFza0RpYWxvZygpXCJcbiAgICAgICAgICAgICAgW2Rpc2FibGVkXT1cIiFyZWdpb25zIHx8ICFyZWdpb25zLmxlbmd0aFwiPlxuICAgICAgPCEtLSBSZWZlcmVuY2VzIHRoZSBmaWxlIHNvIElua3NjYXBlIGVkaXRzIHRvIG1hc2suc3ZnIHNob3cgdXAgZGlyZWN0bHlcbiAgICAgICAgICAgKHRoZSBsaWIncyBhc3NldHMvKiogYXJlIHNlcnZlZCBhdCBhc3NldHMvcGxvdHRpbmcvKS4gVGhlXG4gICAgICAgICAgIHRvb2xiYXItaWNvbi1kYXJrIGZpbHRlciByZWNvbG91cnMgdGhlIGJsYWNrIFNWRyB0byB0aGUgdGhlbWUgYmx1ZSxcbiAgICAgICAgICAgc2FtZSBhcyByZWdpb24tZWRpdG9yLnN2ZyBhbmQgdGhlIG90aGVyIFNWRyB0b29sYmFyIGljb25zLiAtLT5cbiAgICAgIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL21hc2suc3ZnXCIgY2xhc3M9XCJ0b29sYmFyLWljb24tZGFya1wiXG4gICAgICAgICAgIHN0eWxlPVwid2lkdGg6IDFyZW07IGhlaWdodDogMXJlbTsgdmVydGljYWwtYWxpZ246IG1pZGRsZTtcIlxuICAgICAgICAgICBhbHQ9XCJEb3dubG9hZCBtYXNrXCIvPlxuICAgIDwvcC1idXR0b24+XG4gICAgPHAtYnV0dG9uIGxhYmVsPVwieDUxMlwiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIiBzdHlsZUNsYXNzPVwibXItMlwiXG4gICAgICAgICAgICAgIHBUb29sdGlwPVwiUm91bmQgYWxsIHJlY3RhbmdsZSByZWdpb25zXG4gICAgICAgICAgICAgICAgICAgIHRvIHdpZHRoIGFuZCBoZWlnaHQgdGhhdCBhcmUgbXVsdGlwbGUgb2YgNTEyXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgKGNsaWNrKT1cInJvdW5kUmVjdGFuZ2xlTGVuZ3RocygpXCI+XG4gICAgPC9wLWJ1dHRvbj5cbiAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLWNoZWNrLXNxdWFyZVwiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIiBzdHlsZUNsYXNzPVwibXItMlwiXG4gICAgICAgICAgICAgIHBUb29sdGlwPVwiU2VsZWN0IGFsbCByZWdpb25zXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgKGNsaWNrKT1cInNlbGVjdEFsbFJlZ2lvbnMoKVwiXG4gICAgICAgICAgICAgIFtkaXNhYmxlZF09XCIhcmVnaW9ucyB8fCAhcmVnaW9ucy5sZW5ndGhcIj5cbiAgICA8L3AtYnV0dG9uPlxuICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktdGFnc1wiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIiBzdHlsZUNsYXNzPVwibXItMlwiXG4gICAgICAgICAgICAgIHBUb29sdGlwPVwiRWRpdCBjbGFzcyBvbiBzZWxlY3RlZCByb3dzXCJcbiAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgKGNsaWNrKT1cIighc2VsZWN0ZWRSZWdpb25zIHx8ICFzZWxlY3RlZFJlZ2lvbnMubGVuZ3RoKT8gb3AuaGlkZSgpIDogb3AudG9nZ2xlKCRldmVudClcIlxuICAgICAgICAgICAgICBbZGlzYWJsZWRdPVwiIXNlbGVjdGVkUmVnaW9ucyB8fCAhc2VsZWN0ZWRSZWdpb25zLmxlbmd0aFwiPlxuICAgIDwvcC1idXR0b24+XG4gICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1wYWxldHRlXCIgc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiIHN0eWxlQ2xhc3M9XCJtci0yXCJcbiAgICAgICAgICAgICAgcFRvb2x0aXA9XCJFZGl0IGNvbG91ciBvZiBzZWxlY3RlZCByZWdpb25zXCJcbiAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgKGNsaWNrKT1cIm9wZW5Db2xvckRpYWxvZygpXCJcbiAgICAgICAgICAgICAgW2Rpc2FibGVkXT1cIiFzZWxlY3RlZFJlZ2lvbnMgfHwgIXNlbGVjdGVkUmVnaW9ucy5sZW5ndGhcIj5cbiAgICA8L3AtYnV0dG9uPlxuICAgIDxwLW92ZXJsYXlQYW5lbCAjb3AgKG9uSGlkZSk9XCJidWxrQ2xhc3M9Jyc7IG5ld0J1bGtDbGFzcz0nJ1wiPlxuICAgICAgPGRpdiBjbGFzcz1cImZsZXggZmxleC1jb2x1bW4gZ2FwLTJcIiBzdHlsZT1cIndpZHRoOiAxOHJlbVwiPlxuICAgICAgICA8c3BhbiBjbGFzcz1cImZvbnQtbWVkaXVtIHRleHQtOTAwXCI+U2V0IGNsYXNzIG9uIHt7IHNlbGVjdGVkUmVnaW9ucz8ubGVuZ3RoIHx8IDAgfX0gc2VsZWN0ZWQgcmVnaW9uKHMpPC9zcGFuPlxuICAgICAgICA8IS0tIFNlbGVjdGluZyBhIGNsYXNzIGFwcGxpZXMgaXQgaW1tZWRpYXRlbHkgKGNob29zaW5nIGFuIG9wdGlvbiBjbG9zZXMgdGhlXG4gICAgICAgICAgICAgb3ZlcmxheSwgc28gdGhlcmUncyBubyBzZXBhcmF0ZSBBcHBseSBidXR0b24pLiAtLT5cbiAgICAgICAgPHAtZHJvcGRvd24gc3R5bGVDbGFzcz1cInctZnVsbFwiIGFwcGVuZFRvPVwiYm9keVwiIFtvcHRpb25zXT1cInByZXNldFNldC5jbGFzc2VzXCJcbiAgICAgICAgICAgICAgICAgICAgb3B0aW9uTGFiZWw9XCJuYW1lXCIgb3B0aW9uVmFsdWU9XCJuYW1lXCIgWyhuZ01vZGVsKV09XCJidWxrQ2xhc3NcIlxuICAgICAgICAgICAgICAgICAgICBwbGFjZWhvbGRlcj1cIkNob29zZSBhIGNsYXNz4oCmXCJcbiAgICAgICAgICAgICAgICAgICAgW2ZpbHRlcl09XCJwcmVzZXRTZXQuY2xhc3Nlcy5sZW5ndGggPiA4XCIgZmlsdGVyQnk9XCJuYW1lXCJcbiAgICAgICAgICAgICAgICAgICAgKG9uQ2hhbmdlKT1cImFwcGx5QnVsa0NsYXNzKClcIj5cbiAgICAgICAgICA8bmctdGVtcGxhdGUgbGV0LWMgcFRlbXBsYXRlPVwiaXRlbVwiPlxuICAgICAgICAgICAgPHNwYW4gY2xhc3M9XCJkZC12YWx1ZVwiPjxzcGFuIGNsYXNzPVwiY2hpcC1zd2F0Y2hcIiBbc3R5bGUuYmFja2dyb3VuZF09XCJjLmNvbG9yXCI+PC9zcGFuPnt7IGMubmFtZSB9fTwvc3Bhbj5cbiAgICAgICAgICA8L25nLXRlbXBsYXRlPlxuICAgICAgICA8L3AtZHJvcGRvd24+XG4gICAgICAgIDxkaXYgY2xhc3M9XCJwLWlucHV0Z3JvdXBcIj5cbiAgICAgICAgICA8aW5wdXQgdHlwZT1cInRleHRcIiBwSW5wdXRUZXh0IHBsYWNlaG9sZGVyPVwi4oCmb3IgYSBuZXcgY2xhc3NcIiBbKG5nTW9kZWwpXT1cIm5ld0J1bGtDbGFzc1wiXG4gICAgICAgICAgICAgICAgIChrZXlkb3duLmVudGVyKT1cImFkZEFuZEFwcGx5QnVsa0NsYXNzKClcIi8+XG4gICAgICAgICAgPGJ1dHRvbiBwQnV0dG9uIHR5cGU9XCJidXR0b25cIiBpY29uPVwicGkgcGktcGx1c1wiIHBUb29sdGlwPVwiQWRkIGNsYXNzICZhbXA7IHNldCBpdFwiXG4gICAgICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIiBbZGlzYWJsZWRdPVwiIW5ld0J1bGtDbGFzcy50cmltKClcIlxuICAgICAgICAgICAgICAgICAgKGNsaWNrKT1cImFkZEFuZEFwcGx5QnVsa0NsYXNzKClcIj48L2J1dHRvbj5cbiAgICAgICAgPC9kaXY+XG4gICAgICA8L2Rpdj5cbiAgICA8L3Atb3ZlcmxheVBhbmVsPlxuICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktdHJhc2hcIiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCIgc3R5bGVDbGFzcz1cIm1yLTJcIlxuICAgICAgICAgICAgICAoY2xpY2spPVwiZGVsZXRlU2VsZWN0ZWRSZWdpb25zKClcIlxuICAgICAgICAgICAgICBwVG9vbHRpcD1cIkRlbGV0ZSBzZWxlY3RlZCByZWdpb25zXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgW2Rpc2FibGVkXT1cIiFzZWxlY3RlZFJlZ2lvbnMgfHwgIXNlbGVjdGVkUmVnaW9ucy5sZW5ndGhcIj48L3AtYnV0dG9uPlxuICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktdGltZXMtY2lyY2xlXCIgc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiIHN0eWxlQ2xhc3M9XCJtci0yXCJcbiAgICAgICAgICAgICAgKGNsaWNrKT1cImNsZWFyQWxsUmVnaW9ucygpXCJcbiAgICAgICAgICAgICAgcFRvb2x0aXA9XCJEZWxldGUgYWxsIHJlZ2lvbnNcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICBbZGlzYWJsZWRdPVwiIXJlZ2lvbnMgfHwgIXJlZ2lvbnMubGVuZ3RoXCI+PC9wLWJ1dHRvbj5cbiAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLXF1ZXN0aW9uXCIgc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgIChjbGljayk9XCJzaG93SGVscCgpXCIgcFRvb2x0aXA9XCJIZWxwXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCI+PC9wLWJ1dHRvbj5cbiAgPC9kaXY+XG48L3AtdG9vbGJhcj5cblxuPCEtLSBEb2NrZWQgXCJDbGFzc2VzXCIgcGFuZWwgKGppdC11aSM3MCk6IHRoZSBhbHdheXMtdmlzaWJsZSBsaXN0IG9mIGFubm90YXRpb25cbiAgICAgY2xhc3NlcyBhYm92ZSB0aGUgcmVnaW9ucyB0YWJsZS4gQ2xpY2sgYSByb3cgdG8gbWFrZSBpdCB0aGUgYWN0aXZlIGNsYXNzXG4gICAgIChuZXcgcmVnaW9ucyBpbmhlcml0IGl0OyB3aXRoIHJvd3Mgc2VsZWN0ZWQgaXQgcmUtY2xhc3NpZmllcyB0aGVtKTsgcmVjb2xvdXJcbiAgICAgdmlhIHRoZSBzd2F0Y2g7IHRoZSBiYWRnZSBzaG93cyBob3cgbWFueSByZWdpb25zIHVzZSB0aGUgY2xhc3MuIFwiTWFuYWdlXCJcbiAgICAgb3BlbnMgdGhlIGZ1bGwgZWRpdG9yIChmYWxsYmFjayBwYWxldHRlLCBhdXRvLWFkZCwgbWF0Y2hpbmcsIGltcG9ydC9leHBvcnQpLiAtLT5cbjxkaXYgY2xhc3M9XCJjbGFzc2VzLXBhbmVsXCI+XG4gIDxkaXYgY2xhc3M9XCJjbGFzc2VzLXBhbmVsLWhlYWRlclwiPlxuICAgIDxzcGFuIGNsYXNzPVwiY2xhc3Nlcy1wYW5lbC10aXRsZVwiPkNsYXNzZXM8L3NwYW4+XG4gICAgPGJ1dHRvbiBwQnV0dG9uIHR5cGU9XCJidXR0b25cIiBpY29uPVwicGkgcGktY29nXCIgbGFiZWw9XCJNYW5hZ2VcIlxuICAgICAgICAgICAgY2xhc3M9XCJwLWJ1dHRvbi1zbSBwLWJ1dHRvbi10ZXh0XCJcbiAgICAgICAgICAgIHBUb29sdGlwPVwiTWFuYWdlIGNsYXNzZXMg4oCUIGFkZCAvIHJlbW92ZSwgcGFsZXR0ZSwgbWF0Y2hpbmcsIGltcG9ydC9leHBvcnRcIlxuICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCIgKGNsaWNrKT1cIm9wZW5NYW5hZ2VEaWFsb2coKVwiPjwvYnV0dG9uPlxuICA8L2Rpdj5cbiAgPGRpdiBjbGFzcz1cImNsYXNzZXMtcGFuZWwtYm9keVwiPlxuICAgIDxkaXYgKm5nRm9yPVwibGV0IGMgb2YgZGlzcGxheUNsYXNzZXM7IHRyYWNrQnk6IHRyYWNrQnlDbGFzc05hbWVcIlxuICAgICAgICAgY2xhc3M9XCJjbGFzcy1yb3dcIiBbY2xhc3MuYWN0aXZlXT1cImFjdGl2ZUNsYXNzID09PSBjLm5hbWVcIlxuICAgICAgICAgKGNsaWNrKT1cInNlbGVjdEFjdGl2ZUNsYXNzKGMubmFtZSlcIlxuICAgICAgICAgW3BUb29sdGlwXT1cInNlbGVjdGVkUmVnaW9ucz8ubGVuZ3RoXG4gICAgICAgICAgID8gJ0FwcGx5IOKAnCcgKyBjLm5hbWUgKyAn4oCdIHRvIHRoZSBzZWxlY3RlZCByZWdpb24ocyknXG4gICAgICAgICAgIDogJ1NldCDigJwnICsgYy5uYW1lICsgJ+KAnSBhY3RpdmUg4oCUIG5ldyByZWdpb25zIHVzZSBpdCdcIlxuICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCI+XG4gICAgICA8c3BhbiBjbGFzcz1cInJvdy1zd2F0Y2hcIiAoY2xpY2spPVwiJGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpXCI+XG4gICAgICAgIDxoZXgtY29sb3ItcGlja2VyIFtjb2xvcl09XCJjLmNvbG9yXCIgKGNvbG9yQ2hhbmdlKT1cInNldENsYXNzQ29sb3IoYy5uYW1lLCAkZXZlbnQpXCI+PC9oZXgtY29sb3ItcGlja2VyPlxuICAgICAgPC9zcGFuPlxuICAgICAgPHNwYW4gY2xhc3M9XCJyb3ctbmFtZVwiPnt7IGMubmFtZSB9fTwvc3Bhbj5cbiAgICAgIDxzcGFuIGNsYXNzPVwicm93LWNvdW50XCIgW2NsYXNzLnplcm9dPVwiY2xhc3NDb3VudChjLm5hbWUpID09PSAwXCI+e3sgY2xhc3NDb3VudChjLm5hbWUpIH19PC9zcGFuPlxuICAgICAgPGJ1dHRvbiBwQnV0dG9uIHR5cGU9XCJidXR0b25cIiBpY29uPVwicGkgcGktdHJhc2hcIlxuICAgICAgICAgICAgICBjbGFzcz1cInAtYnV0dG9uLXNtIHAtYnV0dG9uLXRleHQgcC1idXR0b24td2FybmluZyByb3ctZGVsXCJcbiAgICAgICAgICAgICAgW2Rpc2FibGVkXT1cImMubmFtZSA9PT0gZGVmYXVsdENsYXNzTmFtZSAmJiBjbGFzc0NvdW50KGMubmFtZSkgPiAwXCJcbiAgICAgICAgICAgICAgW3BUb29sdGlwXT1cImRlbGV0ZUNsYXNzVG9vbHRpcChjLm5hbWUpXCJcbiAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgKGNsaWNrKT1cIiRldmVudC5zdG9wUHJvcGFnYXRpb24oKTsgZGVsZXRlQ2xhc3MoYy5uYW1lKVwiPjwvYnV0dG9uPlxuICAgIDwvZGl2PlxuICAgIDxkaXYgKm5nSWY9XCIhZGlzcGxheUNsYXNzZXMubGVuZ3RoXCIgY2xhc3M9XCJjbGFzc2VzLWVtcHR5XCI+XG4gICAgICBObyBjbGFzc2VzIHlldCDigJQgYWRkIG9uZSBmcm9tIE1hbmFnZS5cbiAgICA8L2Rpdj5cbiAgPC9kaXY+XG48L2Rpdj5cblxuPGRpdiBjbGFzcz1cInJlZ2lvbi1lZGl0b3ItY29udGVudFwiPlxuICAgIDxwLXRhYmxlICNkdCBbdmFsdWVdPVwicmVnaW9ucy5zbGljZShwYWdpbmF0b3JGaXJzdCwgcGFnaW5hdG9yRmlyc3QgKyBwYWdpbmF0b3JSb3dzKVwiXG4gICAgICAgICAgICAgW3RhYmxlU3R5bGVdPVwieyd3aWR0aCc6ICcxMDAlJ31cIiBbKHNlbGVjdGlvbildPVwic2VsZWN0ZWRSZWdpb25zXCJcbiAgICAgICAgICAgICBzZWxlY3Rpb25Nb2RlPVwibXVsdGlwbGVcIiBbbWV0YUtleVNlbGVjdGlvbl09XCJtZXRhS2V5XCJcbiAgICAgICAgICAgICAob25Sb3dTZWxlY3QpPVwib25TZWxlY3Rpb25DaGFuZ2VkKClcIiAob25Sb3dVbnNlbGVjdCk9XCJvblNlbGVjdGlvbkNoYW5nZWQoKVwiXG4gICAgICAgICAgICAgW3Jlc2l6YWJsZUNvbHVtbnNdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgW3Jvd0hvdmVyXT1cInRydWVcIiBkYXRhS2V5PVwiaWRcIiBzdHlsZUNsYXNzPVwicC1kYXRhdGFibGUtc20gcmVnaW9uLWVkaXRvci10YWJsZVwiXG4gICAgICAgICAgICAgW3Njcm9sbGFibGVdPVwidHJ1ZVwiIHNjcm9sbEhlaWdodD1cIjEwMCVcIj5cbiAgICAgIDxuZy10ZW1wbGF0ZSBwVGVtcGxhdGU9XCJoZWFkZXJcIj5cbiAgICAgICAgPCEtLSBIaWRlIHRoZSBjb2x1bW4gaGVhZGVyIHdoZW4gdGhlcmUgYXJlIG5vIHJlZ2lvbnMgdG8gc2hvdy4gLS0+XG4gICAgICAgIDx0ciAqbmdJZj1cInJlZ2lvbnMubGVuZ3RoID4gMFwiPlxuICAgICAgICAgIDwhLS0gQ2xhc3MgY29sdW1uOiBzaHJpbmtzIHRvIGZpdCBcIlJlZ2lvblwiIGJ5IGRlZmF1bHQsIGdyb3dzIHdpdGggbG9uZ2VyIGxhYmVscy4gLS0+XG4gICAgICAgICAgPHRoIGNsYXNzPVwiY2xhc3MtY29sLWhlYWRlclwiIHBSZXNpemFibGVDb2x1bW4+Q2xhc3MgPHAtc29ydEljb24gZmllbGQ9XCJsYWJlbFwiPjwvcC1zb3J0SWNvbj48L3RoPlxuICAgICAgICAgIDx0aCBjbGFzcz1cInN1cmZhY2UtY29sLWhlYWRlclwiIHBSZXNpemFibGVDb2x1bW4+U3VyZmFjZTwvdGg+XG4gICAgICAgICAgPCEtLSBUcmFzaCBjb2x1bW46IGp1c3QgZW5vdWdoIGZvciB0aGUgaWNvbi4gLS0+XG4gICAgICAgICAgPHRoIHN0eWxlPVwid2lkdGg6MnJlbVwiPjwvdGg+XG4gICAgICAgIDwvdHI+XG4gICAgICA8L25nLXRlbXBsYXRlPlxuICAgICAgPG5nLXRlbXBsYXRlIHBUZW1wbGF0ZT1cImJvZHlcIiBsZXQtcmVnaW9uIGxldC1zaGFwZUlkeD1cInJvd0luZGV4XCI+XG4gICAgICAgIDx0ciBbcFNlbGVjdGFibGVSb3ddPVwicmVnaW9uXCIgW3BTZWxlY3RhYmxlUm93SW5kZXhdPVwic2hhcGVJZHhcIj5cbiAgICAgICAgICA8dGQgY2xhc3M9XCJjbGFzcy1jZWxsXCI+XG4gICAgICAgICAgICA8bmctY29udGFpbmVyICpuZ0lmPVwiIWlzRWRpdGluZ0xhYmVsKHJlZ2lvbik7IGVsc2UgZWRpdExhYmVsVHBsXCI+XG4gICAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwiY2xhc3MtY2VsbC1kaXNwbGF5XCI+XG4gICAgICAgICAgICAgICAgPCEtLSBRdWljay1waWNrIGEgcHJlc2V0IGNsYXNzIChpdHMgY29sb3VyIGZvbGxvd3MpOyB0aGUgcGVuY2lsIGlzIGZvclxuICAgICAgICAgICAgICAgICAgICAgYSBmcmVlLWZvcm0gbmFtZSAvIHJlbmFtZSAoaml0LXVpIzcwKS4gLS0+XG4gICAgICAgICAgICAgICAgPHAtZHJvcGRvd24gY2xhc3M9XCJjbGFzcy1kcm9wZG93blwiIGFwcGVuZFRvPVwiYm9keVwiXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgW29wdGlvbnNdPVwicHJlc2V0U2V0LmNsYXNzZXNcIiBvcHRpb25MYWJlbD1cIm5hbWVcIiBvcHRpb25WYWx1ZT1cIm5hbWVcIlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIFtuZ01vZGVsXT1cInJlZ2lvbi5sYWJlbFwiIFthdXRvRGlzcGxheUZpcnN0XT1cImZhbHNlXCJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAob25DaGFuZ2UpPVwiYXBwbHlQcmVzZXRUb1JlZ2lvbihyZWdpb24sICRldmVudC52YWx1ZSlcIj5cbiAgICAgICAgICAgICAgICAgIDxuZy10ZW1wbGF0ZSBwVGVtcGxhdGU9XCJzZWxlY3RlZEl0ZW1cIj5cbiAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9XCJkZC12YWx1ZVwiPlxuICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwiY2hpcC1zd2F0Y2hcIiBbc3R5bGUuYmFja2dyb3VuZF09XCJjb2xvckZvck5hbWUocmVnaW9uLmxhYmVsKVwiPjwvc3Bhbj5cbiAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz1cImNsYXNzLW5hbWVcIiBbcFRvb2x0aXBdPVwicmVnaW9uLmxhYmVsXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCI+e3sgcmVnaW9uLmxhYmVsIHx8ICfigJQnIH19PC9zcGFuPlxuICAgICAgICAgICAgICAgICAgICA8L3NwYW4+XG4gICAgICAgICAgICAgICAgICA8L25nLXRlbXBsYXRlPlxuICAgICAgICAgICAgICAgICAgPG5nLXRlbXBsYXRlIGxldC1jIHBUZW1wbGF0ZT1cIml0ZW1cIj5cbiAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9XCJkZC12YWx1ZVwiPlxuICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwiY2hpcC1zd2F0Y2hcIiBbc3R5bGUuYmFja2dyb3VuZF09XCJjLmNvbG9yXCI+PC9zcGFuPlxuICAgICAgICAgICAgICAgICAgICAgIDxzcGFuPnt7IGMubmFtZSB9fTwvc3Bhbj5cbiAgICAgICAgICAgICAgICAgICAgPC9zcGFuPlxuICAgICAgICAgICAgICAgICAgPC9uZy10ZW1wbGF0ZT5cbiAgICAgICAgICAgICAgICA8L3AtZHJvcGRvd24+XG4gICAgICAgICAgICAgICAgPGkgY2xhc3M9XCJwaSBwaS1wZW5jaWwgaWNvbi1ob3ZlciBtbC0yXCJcbiAgICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlJlbmFtZSAvIGZyZWUtZm9ybSBsYWJlbFwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgICAgKGNsaWNrKT1cInN0YXJ0RWRpdExhYmVsKHJlZ2lvbiwgJGV2ZW50KVwiPjwvaT5cbiAgICAgICAgICAgICAgPC9zcGFuPlxuICAgICAgICAgICAgPC9uZy1jb250YWluZXI+XG4gICAgICAgICAgICA8bmctdGVtcGxhdGUgI2VkaXRMYWJlbFRwbD5cbiAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9XCJwLWlucHV0LWljb24tcmlnaHQgY2xhc3MtY2VsbC1lZGl0XCI+XG4gICAgICAgICAgICAgICAgPGkgY2xhc3M9XCJwaSBwaS10aW1lcyBpY29uLWhvdmVyXCJcbiAgICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIkNsb3NlXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAgICAoY2xpY2spPVwic3RvcEVkaXRMYWJlbChyZWdpb24sIGZhbHNlLCAkZXZlbnQpXCI+PC9pPlxuICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPVwidGV4dFwiIHBJbnB1dFRleHQgY2xhc3M9XCJwLWlucHV0dGV4dC1zbSB3LWZ1bGxcIlxuICAgICAgICAgICAgICAgICAgICAgICBbKG5nTW9kZWwpXT1cInJlZ2lvbi5sYWJlbFwiXG4gICAgICAgICAgICAgICAgICAgICAgIChjbGljayk9XCIkZXZlbnQuc3RvcFByb3BhZ2F0aW9uKClcIlxuICAgICAgICAgICAgICAgICAgICAgICAoa2V5ZG93bi5lbnRlcik9XCJzdG9wRWRpdExhYmVsKHJlZ2lvbiwgdHJ1ZSwgJGV2ZW50KVwiXG4gICAgICAgICAgICAgICAgICAgICAgIChrZXlkb3duLmVzY2FwZSk9XCJzdG9wRWRpdExhYmVsKHJlZ2lvbiwgZmFsc2UsICRldmVudClcIi8+XG4gICAgICAgICAgICAgIDwvc3Bhbj5cbiAgICAgICAgICAgIDwvbmctdGVtcGxhdGU+XG4gICAgICAgICAgPC90ZD5cbiAgICAgICAgICA8IS0tIFN1cmZhY2UgYXJlYSAowrVtwrIvbW3CsiB3aGVuIHRoZSBpbWFnZSBpcyBzY2FsZWQsIGVsc2UgcHjCsikuIC0tPlxuICAgICAgICAgIDx0ZCBjbGFzcz1cInN1cmZhY2UtY2VsbFwiPnt7IHJlZ2lvbkFyZWEocmVnaW9uKSB8fCAn4oCUJyB9fTwvdGQ+XG4gICAgICAgICAgPHRkPlxuICAgICAgICAgICAgPGJ1dHRvbiBwQnV0dG9uIHBSaXBwbGUgaWNvbj1cInBpIHBpLXRyYXNoXCJcblx0XHRcdFx0XHRcdFx0Y2xhc3M9XCJwLWJ1dHRvbi1zbSBwLWJ1dHRvbi1yb3VuZGVkIHAtYnV0dG9uLXRleHQgcC1idXR0b24td2FybmluZ1wiXG4gICAgICAgICAgICAgICAgICAgIChjbGljayk9XCJkZWxldGVSZWdpb24ocGFnaW5hdG9yRmlyc3QgKyBzaGFwZUlkeClcIj48L2J1dHRvbj5cbiAgICAgICAgICA8L3RkPlxuICAgICAgICA8L3RyPlxuICAgICAgPC9uZy10ZW1wbGF0ZT5cbiAgICA8L3AtdGFibGU+XG48L2Rpdj5cblxuPHAtcGFnaW5hdG9yICpuZ0lmPVwicmVnaW9ucy5sZW5ndGggPiAwXCJcbiAgICAgICAgICAgICBbcm93c109XCJwYWdpbmF0b3JSb3dzXCIgW3RvdGFsUmVjb3Jkc109XCJyZWdpb25zLmxlbmd0aFwiXG4gICAgICAgICAgICAgW2ZpcnN0XT1cInBhZ2luYXRvckZpcnN0XCJcbiAgICAgICAgICAgICBbcm93c1BlclBhZ2VPcHRpb25zXT1cInJvd3NQZXJQYWdlT3B0aW9uc1wiXG4gICAgICAgICAgICAgW3Nob3dDdXJyZW50UGFnZVJlcG9ydF09XCJ0cnVlXCJcbiAgICAgICAgICAgICBjdXJyZW50UGFnZVJlcG9ydFRlbXBsYXRlPVwie2ZpcnN0fSB0byB7bGFzdH0gb2Yge3RvdGFsUmVjb3Jkc31cIlxuICAgICAgICAgICAgIChvblBhZ2VDaGFuZ2UpPVwib25QYWdlQ2hhbmdlKCRldmVudClcIlxuICAgICAgICAgICAgIHN0eWxlQ2xhc3M9XCJyZWdpb24tZWRpdG9yLXBhZ2luYXRvclwiPlxuPC9wLXBhZ2luYXRvcj5cblxuPCEtLSBMaXZlLWVkaXQgbW9kZTogbm8gU2F2ZS9DYW5jZWwg4oCUIGV2ZXJ5IGVkaXQgY29tbWl0cyBpbW1lZGlhdGVseSB0byB0aGVcbiAgICAgYWN0aXZlIGltYWdlJ3MgcmVnaW9uIGNhY2hlLiAtLT5cblxuXG48cC1jb25maXJtRGlhbG9nICNjZCBbc3R5bGVdPVwie3dpZHRoOiAnMjRyZW0nLCBoZWlnaHQ6ICcxNHJlbSd9XCIga2V5PVwicG9zaXRpb25EaWFsb2dcIlxuICAgICAgICAgICAgICAgICBbYmFzZVpJbmRleF09XCIxMDAwMFwiIHJlamVjdEJ1dHRvblN0eWxlQ2xhc3M9XCJwLWJ1dHRvbi1vdXRsaW5lZFwiPlxuICA8bmctdGVtcGxhdGUgcFRlbXBsYXRlPVwiZm9vdGVyXCI+XG4gICAgPGJ1dHRvbiB0eXBlPVwiYnV0dG9uXCIgcEJ1dHRvbiBpY29uPVwicGkgcGktY2hlY2tcIiBsYWJlbD1cIlllc1wiIChjbGljayk9XCJjZC5hY2NlcHQoKVwiPjwvYnV0dG9uPlxuICAgIDxidXR0b24gdHlwZT1cImJ1dHRvblwiIGNsYXNzPVwicC1idXR0b24tb3V0bGluZWRcIiBwQnV0dG9uIGljb249XCJwaSBwaS10aW1lc1wiIGxhYmVsPVwiTm9cIlxuICAgICAgICAgICAgKGNsaWNrKT1cImNkLnJlamVjdCgpXCI+PC9idXR0b24+XG4gIDwvbmctdGVtcGxhdGU+XG48L3AtY29uZmlybURpYWxvZz5cblxuPHAtZGlhbG9nIGhlYWRlcj1cIlNhdmUgUmVnaW9ucyBBc1wiIFsodmlzaWJsZSldPVwic2hvd1NhdmVBc0RpYWxvZ1wiIFttb2RhbF09XCJ0cnVlXCIgYXBwZW5kVG89XCJib2R5XCJcbiAgICAgICAgICBbc3R5bGVdPVwie3dpZHRoOiAnMzByZW0nfVwiIFtkcmFnZ2FibGVdPVwiZmFsc2VcIiBbcmVzaXphYmxlXT1cImZhbHNlXCJcbiAgICAgICAgICBbY2xvc2FibGVdPVwiIXNhdmVBc0J1c3lcIiBbY2xvc2VPbkVzY2FwZV09XCIhc2F2ZUFzQnVzeVwiPlxuICA8IS0tIElkbGU6IGZpbGVuYW1lICsgb3ZlcndyaXRlIHdhcm5pbmcuIC0tPlxuICA8ZGl2ICpuZ0lmPVwiIXNhdmVBc0J1c3lcIiBjbGFzcz1cImZsZXggZmxleC1jb2x1bW4gZ2FwLTMgcHQtMlwiPlxuICAgIDxsYWJlbCBmb3I9XCJnZW9qc29uLWZpbGVuYW1lXCIgY2xhc3M9XCJmb250LW1lZGl1bVwiPkdlb0pTT04gZmlsZW5hbWU8L2xhYmVsPlxuICAgIDxpbnB1dCBpZD1cImdlb2pzb24tZmlsZW5hbWVcIiB0eXBlPVwidGV4dFwiIHBJbnB1dFRleHRcbiAgICAgICAgICAgWyhuZ01vZGVsKV09XCJzYXZlQXNGaWxlbmFtZVwiXG4gICAgICAgICAgIChuZ01vZGVsQ2hhbmdlKT1cImNoZWNrU2F2ZUFzRmlsZUV4aXN0cygpXCJcbiAgICAgICAgICAgY2xhc3M9XCJ3LWZ1bGxcIiAvPlxuICAgIDxzbWFsbCAqbmdJZj1cInNhdmVBc0ZpbGVFeGlzdHNcIiBjbGFzcz1cInAtZXJyb3JcIj5cbiAgICAgIDxpIGNsYXNzPVwicGkgcGktZXhjbGFtYXRpb24tdHJpYW5nbGUgbXItMVwiPjwvaT5cbiAgICAgIFRoaXMgZmlsZSBhbHJlYWR5IGV4aXN0cyBhbmQgd2lsbCBiZSBvdmVyd3JpdHRlbi5cbiAgICA8L3NtYWxsPlxuICA8L2Rpdj5cblxuICA8IS0tIEJ1c3k6IHByb2dyZXNzIHdoaWxlIHJlZ2lvbnMgc2VyaWFsaXplICsgdXBsb2FkLiAtLT5cbiAgPGRpdiAqbmdJZj1cInNhdmVBc0J1c3lcIiBjbGFzcz1cImZsZXggZmxleC1jb2x1bW4gZ2FwLTMgcHQtMlwiPlxuICAgIDxzcGFuPlNhdmluZyByZWdpb25z4oCmPC9zcGFuPlxuICAgIDxwLXByb2dyZXNzQmFyIG1vZGU9XCJpbmRldGVybWluYXRlXCIgW3N0eWxlXT1cIntoZWlnaHQ6ICcwLjc1cmVtJ31cIj48L3AtcHJvZ3Jlc3NCYXI+XG4gIDwvZGl2PlxuXG4gIDxuZy10ZW1wbGF0ZSBwVGVtcGxhdGU9XCJmb290ZXJcIj5cbiAgICA8cC1idXR0b24gKm5nSWY9XCIhc2F2ZUFzQnVzeVwiIGxhYmVsPVwiQ2FuY2VsXCIgaWNvbj1cInBpIHBpLXRpbWVzXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgIChvbkNsaWNrKT1cInNob3dTYXZlQXNEaWFsb2cgPSBmYWxzZVwiPjwvcC1idXR0b24+XG4gICAgPHAtYnV0dG9uICpuZ0lmPVwiIXNhdmVBc0J1c3lcIiBsYWJlbD1cIlNhdmVcIiBpY29uPVwicGkgcGktc2F2ZVwiXG4gICAgICAgICAgICAgIFtkaXNhYmxlZF09XCIhc2F2ZUFzRmlsZW5hbWUgfHwgIXNhdmVBc0ZpbGVuYW1lLnRyaW0oKVwiXG4gICAgICAgICAgICAgIChvbkNsaWNrKT1cImNvbmZpcm1TYXZlQXMoKVwiPjwvcC1idXR0b24+XG4gICAgPHAtYnV0dG9uICpuZ0lmPVwic2F2ZUFzQnVzeVwiIGxhYmVsPVwiQ2FuY2VsXCIgaWNvbj1cInBpIHBpLXRpbWVzXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgIChvbkNsaWNrKT1cImNhbmNlbFNhdmVBcygpXCI+PC9wLWJ1dHRvbj5cbiAgPC9uZy10ZW1wbGF0ZT5cbjwvcC1kaWFsb2c+XG5cbjxwLWRpYWxvZyBoZWFkZXI9XCJFeHBvcnQgUmVnaW9uc1wiIFsodmlzaWJsZSldPVwic2hvd0V4cG9ydERpYWxvZ1wiIFttb2RhbF09XCJ0cnVlXCIgYXBwZW5kVG89XCJib2R5XCJcbiAgICAgICAgICBbc3R5bGVdPVwie3dpZHRoOiAnMzByZW0nfVwiIFtkcmFnZ2FibGVdPVwiZmFsc2VcIiBbcmVzaXphYmxlXT1cImZhbHNlXCI+XG4gIDxkaXYgY2xhc3M9XCJmbGV4IGZsZXgtY29sdW1uIGdhcC0zIHB0LTJcIj5cbiAgICA8bGFiZWwgZm9yPVwiZXhwb3J0LWZpbGVuYW1lXCIgY2xhc3M9XCJmb250LW1lZGl1bVwiPkdlb0pTT04gZmlsZW5hbWU8L2xhYmVsPlxuICAgIDxpbnB1dCBpZD1cImV4cG9ydC1maWxlbmFtZVwiIHR5cGU9XCJ0ZXh0XCIgcElucHV0VGV4dFxuICAgICAgICAgICBbKG5nTW9kZWwpXT1cImV4cG9ydEZpbGVuYW1lXCJcbiAgICAgICAgICAgY2xhc3M9XCJ3LWZ1bGxcIiAvPlxuICA8L2Rpdj5cbiAgPG5nLXRlbXBsYXRlIHBUZW1wbGF0ZT1cImZvb3RlclwiPlxuICAgIDxwLWJ1dHRvbiBsYWJlbD1cIkNhbmNlbFwiIGljb249XCJwaSBwaS10aW1lc1wiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAob25DbGljayk9XCJzaG93RXhwb3J0RGlhbG9nID0gZmFsc2VcIj48L3AtYnV0dG9uPlxuICAgIDxwLWJ1dHRvbiBsYWJlbD1cIkV4cG9ydFwiIGljb249XCJwaSBwaS1kb3dubG9hZFwiXG4gICAgICAgICAgICAgIFtkaXNhYmxlZF09XCIhZXhwb3J0RmlsZW5hbWUgfHwgIWV4cG9ydEZpbGVuYW1lLnRyaW0oKVwiXG4gICAgICAgICAgICAgIChvbkNsaWNrKT1cImNvbmZpcm1FeHBvcnQoKVwiPjwvcC1idXR0b24+XG4gIDwvbmctdGVtcGxhdGU+XG48L3AtZGlhbG9nPlxuXG48cC1kaWFsb2cgaGVhZGVyPVwiU2F2ZSBNYXNrXCIgWyh2aXNpYmxlKV09XCJzaG93U2F2ZU1hc2tEaWFsb2dcIiBbbW9kYWxdPVwidHJ1ZVwiIGFwcGVuZFRvPVwiYm9keVwiXG4gICAgICAgICAgW3N0eWxlXT1cInt3aWR0aDogJzMwcmVtJ31cIiBbZHJhZ2dhYmxlXT1cImZhbHNlXCIgW3Jlc2l6YWJsZV09XCJmYWxzZVwiXG4gICAgICAgICAgW2Nsb3NhYmxlXT1cIiFtYXNrQnVzeVwiIFtjbG9zZU9uRXNjYXBlXT1cIiFtYXNrQnVzeVwiPlxuICA8IS0tIElkbGU6IGZpbGVuYW1lICsgbWFzay10eXBlIGZvcm0uIC0tPlxuICA8ZGl2ICpuZ0lmPVwiIW1hc2tCdXN5XCIgY2xhc3M9XCJmbGV4IGZsZXgtY29sdW1uIGdhcC0zIHB0LTJcIj5cbiAgICA8bGFiZWwgZm9yPVwibWFzay1maWxlbmFtZVwiIGNsYXNzPVwiZm9udC1tZWRpdW1cIj5NYXNrIGZpbGVuYW1lPC9sYWJlbD5cbiAgICA8aW5wdXQgaWQ9XCJtYXNrLWZpbGVuYW1lXCIgdHlwZT1cInRleHRcIiBwSW5wdXRUZXh0XG4gICAgICAgICAgIFsobmdNb2RlbCldPVwic2F2ZU1hc2tGaWxlbmFtZVwiXG4gICAgICAgICAgIGNsYXNzPVwidy1mdWxsXCIgLz5cbiAgICA8c3BhbiBjbGFzcz1cImZvbnQtbWVkaXVtXCI+TWFzayB0eXBlPC9zcGFuPlxuICAgIDxkaXYgY2xhc3M9XCJmbGV4IGFsaWduLWl0ZW1zLWNlbnRlciBnYXAtMlwiPlxuICAgICAgPHAtcmFkaW9CdXR0b24gaW5wdXRJZD1cIm1hc2stYmluYXJ5XCIgbmFtZT1cIm1hc2tNb2RlXCIgdmFsdWU9XCJiaW5hcnlcIlxuICAgICAgICAgICAgICAgICAgICAgWyhuZ01vZGVsKV09XCJtYXNrTW9kZVwiPjwvcC1yYWRpb0J1dHRvbj5cbiAgICAgIDxsYWJlbCBmb3I9XCJtYXNrLWJpbmFyeVwiPkJpbmFyeSA8c3BhbiBjbGFzcz1cInRleHQtY29sb3Itc2Vjb25kYXJ5XCI+KHJlZ2lvbnMgMSwgYmFja2dyb3VuZCAwKTwvc3Bhbj48L2xhYmVsPlxuICAgIDwvZGl2PlxuICAgIDxkaXYgY2xhc3M9XCJmbGV4IGFsaWduLWl0ZW1zLWNlbnRlciBnYXAtMlwiPlxuICAgICAgPHAtcmFkaW9CdXR0b24gaW5wdXRJZD1cIm1hc2stbXVsdGljbGFzc1wiIG5hbWU9XCJtYXNrTW9kZVwiIHZhbHVlPVwibXVsdGljbGFzc1wiXG4gICAgICAgICAgICAgICAgICAgICBbKG5nTW9kZWwpXT1cIm1hc2tNb2RlXCI+PC9wLXJhZGlvQnV0dG9uPlxuICAgICAgPGxhYmVsIGZvcj1cIm1hc2stbXVsdGljbGFzc1wiPk11bHRpLWNsYXNzIDxzcGFuIGNsYXNzPVwidGV4dC1jb2xvci1zZWNvbmRhcnlcIj4odW5pcXVlIGlkIHBlciByZWdpb24sIGJhY2tncm91bmQgMCk8L3NwYW4+PC9sYWJlbD5cbiAgICA8L2Rpdj5cbiAgPC9kaXY+XG5cbiAgPCEtLSBCdXN5OiBwcm9ncmVzcyB3aGlsZSB0aGUgd29ya2VyIHJhc3Rlcml6ZXMgKyBlbmNvZGVzIHRoZSBQTkcuIC0tPlxuICA8ZGl2ICpuZ0lmPVwibWFza0J1c3lcIiBjbGFzcz1cImZsZXggZmxleC1jb2x1bW4gZ2FwLTMgcHQtMlwiPlxuICAgIDxzcGFuPnt7IG1hc2tFbmNvZGluZyA/ICdFbmNvZGluZyBQTkfigKYnIDogJ1Jhc3Rlcml6aW5nIHJlZ2lvbnPigKYnIH19PC9zcGFuPlxuICAgIDxwLXByb2dyZXNzQmFyIFttb2RlXT1cIm1hc2tFbmNvZGluZyA/ICdpbmRldGVybWluYXRlJyA6ICdkZXRlcm1pbmF0ZSdcIlxuICAgICAgICAgICAgICAgICAgIFt2YWx1ZV09XCJtYXNrUHJvZ3Jlc3NcIlxuICAgICAgICAgICAgICAgICAgIFtzdHlsZV09XCJ7aGVpZ2h0OiAnMC43NXJlbSd9XCI+PC9wLXByb2dyZXNzQmFyPlxuICA8L2Rpdj5cblxuICA8bmctdGVtcGxhdGUgcFRlbXBsYXRlPVwiZm9vdGVyXCI+XG4gICAgPHAtYnV0dG9uICpuZ0lmPVwiIW1hc2tCdXN5XCIgbGFiZWw9XCJDYW5jZWxcIiBpY29uPVwicGkgcGktdGltZXNcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgKG9uQ2xpY2spPVwic2hvd1NhdmVNYXNrRGlhbG9nID0gZmFsc2VcIj48L3AtYnV0dG9uPlxuICAgIDxwLWJ1dHRvbiAqbmdJZj1cIiFtYXNrQnVzeVwiIGxhYmVsPVwiRG93bmxvYWRcIiBpY29uPVwicGkgcGktZG93bmxvYWRcIlxuICAgICAgICAgICAgICBbZGlzYWJsZWRdPVwiIXNhdmVNYXNrRmlsZW5hbWUgfHwgIXNhdmVNYXNrRmlsZW5hbWUudHJpbSgpXCJcbiAgICAgICAgICAgICAgKG9uQ2xpY2spPVwiY29uZmlybVNhdmVNYXNrKClcIj48L3AtYnV0dG9uPlxuICAgIDxwLWJ1dHRvbiAqbmdJZj1cIm1hc2tCdXN5XCIgbGFiZWw9XCJDYW5jZWxcIiBpY29uPVwicGkgcGktdGltZXNcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgKG9uQ2xpY2spPVwiY2FuY2VsU2F2ZU1hc2soKVwiPjwvcC1idXR0b24+XG4gIDwvbmctdGVtcGxhdGU+XG48L3AtZGlhbG9nPlxuXG48cC1kaWFsb2cgaGVhZGVyPVwiUmVnaW9uIGNvbG91clwiIFsodmlzaWJsZSldPVwic2hvd0NvbG9yRGlhbG9nXCIgW21vZGFsXT1cImZhbHNlXCIgYXBwZW5kVG89XCJib2R5XCJcbiAgICAgICAgICBbc3R5bGVdPVwie3dpZHRoOiAnMjRyZW0nfVwiIFtkcmFnZ2FibGVdPVwidHJ1ZVwiIFtyZXNpemFibGVdPVwiZmFsc2VcIj5cbiAgPGRpdiBjbGFzcz1cImZsZXggZmxleC1jb2x1bW4gZ2FwLTMgcHQtMlwiPlxuICAgIDxzcGFuPk91dGxpbmUgY29sb3VyIHBlciBjbGFzcyBmb3IgdGhlXG4gICAgICA8Yj57eyBzZWxlY3RlZFJlZ2lvbnM/Lmxlbmd0aCB8fCAwIH19PC9iPiBzZWxlY3RlZCByZWdpb24ocyk6PC9zcGFuPlxuICAgIDxkaXYgKm5nRm9yPVwibGV0IGVkaXQgb2YgY2xhc3NDb2xvckVkaXRzOyBsZXQgaSA9IGluZGV4XCJcbiAgICAgICAgIGNsYXNzPVwiZmxleCBhbGlnbi1pdGVtcy1jZW50ZXIgZ2FwLTNcIj5cbiAgICAgIDxzcGFuIGNsYXNzPVwiZm9udC1tZWRpdW0gZmxleC0xXCI+e3sgZWRpdC5sYWJlbCB8fCAnVW5jbGFzc2lmaWVkJyB9fTwvc3Bhbj5cbiAgICAgIDxoZXgtY29sb3ItcGlja2VyIFtjb2xvcl09XCJlZGl0LmNvbG9yXCJcbiAgICAgICAgICAgICAgICAgICAgICAgIChjb2xvckNoYW5nZSk9XCJjbGFzc0NvbG9yRWRpdHNbaV0uY29sb3IgPSAkZXZlbnRcIj48L2hleC1jb2xvci1waWNrZXI+XG4gICAgPC9kaXY+XG4gIDwvZGl2PlxuICA8bmctdGVtcGxhdGUgcFRlbXBsYXRlPVwiZm9vdGVyXCI+XG4gICAgPHAtYnV0dG9uIGxhYmVsPVwiQ2FuY2VsXCIgaWNvbj1cInBpIHBpLXRpbWVzXCIgW3RleHRdPVwidHJ1ZVwiIChvbkNsaWNrKT1cInNob3dDb2xvckRpYWxvZyA9IGZhbHNlXCI+XG4gICAgPC9wLWJ1dHRvbj5cbiAgICA8cC1idXR0b24gbGFiZWw9XCJBcHBseVwiIGljb249XCJwaSBwaS1jaGVja1wiIChvbkNsaWNrKT1cImFwcGx5Q29sb3JUb1NlbGVjdGVkKClcIj5cbiAgICA8L3AtYnV0dG9uPlxuICA8L25nLXRlbXBsYXRlPlxuPC9wLWRpYWxvZz5cblxuPCEtLSBNYW5hZ2UgYW5ub3RhdGlvbiBjbGFzc2VzIChqaXQtdWkjNzApOiBDUlVEICsgZmFsbGJhY2sgcGFsZXR0ZSArIG1hdGNoaW5nL2F1dG8tYWRkXG4gICAgIHNldHRpbmdzICsgcmVzZXQvaW1wb3J0L2V4cG9ydC4gRWRpdHMgYSB3b3JraW5nIGNvcHksIGNvbW1pdHRlZCBvbiBBcHBseS9Eb25lLiAtLT5cbjxwLWRpYWxvZyBoZWFkZXI9XCJNYW5hZ2UgYW5ub3RhdGlvbiBjbGFzc2VzXCIgWyh2aXNpYmxlKV09XCJzaG93TWFuYWdlRGlhbG9nXCIgW21vZGFsXT1cInRydWVcIiBhcHBlbmRUbz1cImJvZHlcIlxuICAgICAgICAgIFtzdHlsZV09XCJ7d2lkdGg6ICczNHJlbSd9XCIgW2RyYWdnYWJsZV09XCJ0cnVlXCIgW3Jlc2l6YWJsZV09XCJmYWxzZVwiPlxuICA8ZGl2ICpuZ0lmPVwicHJlc2V0RHJhZnRcIiBjbGFzcz1cIm1hbmFnZS1kaWFsb2cgZmxleCBmbGV4LWNvbHVtbiBnYXAtMyBwdC0yXCI+XG4gICAgPGRpdiBjbGFzcz1cIm1hbmFnZS1jbGFzc2VzIGZsZXggZmxleC1jb2x1bW4gZ2FwLTJcIj5cbiAgICAgIDxkaXYgKm5nRm9yPVwibGV0IGMgb2YgcHJlc2V0RHJhZnQuY2xhc3NlczsgbGV0IGkgPSBpbmRleFwiXG4gICAgICAgICAgIGNsYXNzPVwibWFuYWdlLWNsYXNzLXJvdyBmbGV4IGFsaWduLWl0ZW1zLWNlbnRlciBnYXAtMlwiPlxuICAgICAgICA8aGV4LWNvbG9yLXBpY2tlciBbY29sb3JdPVwiYy5jb2xvclwiIChjb2xvckNoYW5nZSk9XCJjLmNvbG9yID0gJGV2ZW50XCI+PC9oZXgtY29sb3ItcGlja2VyPlxuICAgICAgICA8aW5wdXQgdHlwZT1cInRleHRcIiBwSW5wdXRUZXh0IGNsYXNzPVwicC1pbnB1dHRleHQtc20gZmxleC0xXCJcbiAgICAgICAgICAgICAgIFsobmdNb2RlbCldPVwiYy5uYW1lXCIgcGxhY2Vob2xkZXI9XCJDbGFzcyBuYW1lXCIvPlxuICAgICAgICA8YnV0dG9uIHBCdXR0b24gdHlwZT1cImJ1dHRvblwiIGljb249XCJwaSBwaS10cmFzaFwiXG4gICAgICAgICAgICAgICAgY2xhc3M9XCJwLWJ1dHRvbi1zbSBwLWJ1dHRvbi10ZXh0IHAtYnV0dG9uLXdhcm5pbmdcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiUmVtb3ZlIGNsYXNzXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAoY2xpY2spPVwicmVtb3ZlUHJlc2V0Q2xhc3MoaSlcIj48L2J1dHRvbj5cbiAgICAgIDwvZGl2PlxuICAgICAgPGJ1dHRvbiBwQnV0dG9uIHR5cGU9XCJidXR0b25cIiBpY29uPVwicGkgcGktcGx1c1wiIGxhYmVsPVwiQWRkIGNsYXNzXCJcbiAgICAgICAgICAgICAgY2xhc3M9XCJwLWJ1dHRvbi1zbSBwLWJ1dHRvbi10ZXh0IGFsaWduLXNlbGYtc3RhcnRcIiAoY2xpY2spPVwiYWRkUHJlc2V0Q2xhc3MoKVwiPjwvYnV0dG9uPlxuICAgIDwvZGl2PlxuXG4gICAgPGRpdiBjbGFzcz1cIm1hbmFnZS1zZWN0aW9uIGZsZXggZmxleC1jb2x1bW4gZ2FwLTJcIj5cbiAgICAgIDxzcGFuIGNsYXNzPVwic2VjdGlvbi10aXRsZSBmb250LW1lZGl1bVwiPkZhbGxiYWNrIHBhbGV0dGVcbiAgICAgICAgPHNtYWxsIGNsYXNzPVwidGV4dC1jb2xvci1zZWNvbmRhcnlcIj7igJQgY29sb3VycyBmb3IgY2xhc3NlcyBub3QgbGlzdGVkPC9zbWFsbD48L3NwYW4+XG4gICAgICA8ZGl2IGNsYXNzPVwiZmFsbGJhY2stcGFsZXR0ZSBmbGV4IGFsaWduLWl0ZW1zLWNlbnRlciBmbGV4LXdyYXAgZ2FwLTJcIj5cbiAgICAgICAgPHNwYW4gKm5nRm9yPVwibGV0IGNvbCBvZiBwcmVzZXREcmFmdC5mYWxsYmFja1BhbGV0dGU7IGxldCBpID0gaW5kZXhcIlxuICAgICAgICAgICAgICBjbGFzcz1cImZhbGxiYWNrLXN3YXRjaCBmbGV4IGFsaWduLWl0ZW1zLWNlbnRlclwiPlxuICAgICAgICAgIDxoZXgtY29sb3ItcGlja2VyIFtjb2xvcl09XCJjb2xcIiAoY29sb3JDaGFuZ2UpPVwicHJlc2V0RHJhZnQuZmFsbGJhY2tQYWxldHRlW2ldID0gJGV2ZW50XCI+PC9oZXgtY29sb3ItcGlja2VyPlxuICAgICAgICAgIDxpIGNsYXNzPVwicGkgcGktdGltZXMgaWNvbi1ob3ZlciBtbC0xXCIgcFRvb2x0aXA9XCJSZW1vdmVcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgIChjbGljayk9XCJyZW1vdmVGYWxsYmFja0NvbG9yKGkpXCI+PC9pPlxuICAgICAgICA8L3NwYW4+XG4gICAgICAgIDxidXR0b24gcEJ1dHRvbiB0eXBlPVwiYnV0dG9uXCIgaWNvbj1cInBpIHBpLXBsdXNcIlxuICAgICAgICAgICAgICAgIGNsYXNzPVwicC1idXR0b24tc20gcC1idXR0b24tdGV4dFwiIHBUb29sdGlwPVwiQWRkIGZhbGxiYWNrIGNvbG91clwiXG4gICAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCIgKGNsaWNrKT1cImFkZEZhbGxiYWNrQ29sb3IoKVwiPjwvYnV0dG9uPlxuICAgICAgPC9kaXY+XG4gICAgPC9kaXY+XG5cbiAgICA8ZGl2IGNsYXNzPVwiZmxleCBhbGlnbi1pdGVtcy1jZW50ZXIgZ2FwLTJcIj5cbiAgICAgIDxwLWNoZWNrYm94IFsobmdNb2RlbCldPVwicHJlc2V0RHJhZnQuYXV0b1Byb21vdGVcIiBbYmluYXJ5XT1cInRydWVcIiBpbnB1dElkPVwiYXV0b1Byb21vdGVcIj48L3AtY2hlY2tib3g+XG4gICAgICA8bGFiZWwgZm9yPVwiYXV0b1Byb21vdGVcIj5BdXRvLWFkZCBkZXRlY3RlZCBjbGFzc2VzIHRvIHRoaXMgbGlzdCBhcyB0aGV5IGFwcGVhcjwvbGFiZWw+XG4gICAgPC9kaXY+XG5cbiAgICA8ZGl2IGNsYXNzPVwiZmxleCBhbGlnbi1pdGVtcy1jZW50ZXIgZ2FwLTNcIj5cbiAgICAgIDxzcGFuIGNsYXNzPVwiZm9udC1tZWRpdW1cIj5MYWJlbCBtYXRjaGluZzwvc3Bhbj5cbiAgICAgIDxwLXNlbGVjdEJ1dHRvbiBbb3B0aW9uc109XCJtYXRjaE1vZGVPcHRpb25zXCIgWyhuZ01vZGVsKV09XCJwcmVzZXREcmFmdC5tYXRjaE1vZGVcIlxuICAgICAgICAgICAgICAgICAgICAgIG9wdGlvbkxhYmVsPVwibGFiZWxcIiBvcHRpb25WYWx1ZT1cInZhbHVlXCI+PC9wLXNlbGVjdEJ1dHRvbj5cbiAgICA8L2Rpdj5cbiAgPC9kaXY+XG5cbiAgPG5nLXRlbXBsYXRlIHBUZW1wbGF0ZT1cImZvb3RlclwiPlxuICAgIDxkaXYgY2xhc3M9XCJtYW5hZ2UtZm9vdGVyIGZsZXggYWxpZ24taXRlbXMtY2VudGVyIGZsZXgtd3JhcCBnYXAtMiB3LWZ1bGxcIj5cbiAgICAgIDxzcGFuIGNsYXNzPVwiZmxleCBhbGlnbi1pdGVtcy1jZW50ZXIgZmxleC13cmFwIGdhcC0xXCI+XG4gICAgICAgIDxwLWJ1dHRvbiBsYWJlbD1cIlJlc2V0XCIgaWNvbj1cInBpIHBpLXJlZnJlc2hcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInJlc2V0UHJlc2V0c1RvRGVmYXVsdHMoKVwiPjwvcC1idXR0b24+XG4gICAgICAgIDxwLWJ1dHRvbiBsYWJlbD1cIkltcG9ydFwiIGljb249XCJwaSBwaS11cGxvYWRcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInByZXNldEZpbGVJbnB1dC5jbGljaygpXCI+PC9wLWJ1dHRvbj5cbiAgICAgICAgPGlucHV0ICNwcmVzZXRGaWxlSW5wdXQgdHlwZT1cImZpbGVcIiBhY2NlcHQ9XCIuanNvbixhcHBsaWNhdGlvbi9qc29uXCIgaGlkZGVuXG4gICAgICAgICAgICAgICAoY2hhbmdlKT1cImltcG9ydFByZXNldHMoJGV2ZW50KTsgcHJlc2V0RmlsZUlucHV0LnZhbHVlID0gJydcIi8+XG4gICAgICAgIDxwLWJ1dHRvbiBsYWJlbD1cIkV4cG9ydFwiIGljb249XCJwaSBwaS1kb3dubG9hZFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwiZXhwb3J0UHJlc2V0cygpXCI+PC9wLWJ1dHRvbj5cbiAgICAgIDwvc3Bhbj5cbiAgICAgIDxzcGFuIGNsYXNzPVwiZmxleC0xXCI+PC9zcGFuPlxuICAgICAgPCEtLSBBcHBseSBwZXJzaXN0cyB0byB0aGUgc2VydmVyIGFuZCBjbG9zZXM7IGl0IHJlcGxhY2VzIHRoZSBvbGQgQXBwbHkrRG9uZSBwYWlyLiAtLT5cbiAgICAgIDxwLWJ1dHRvbiBsYWJlbD1cIkFwcGx5XCIgaWNvbj1cInBpIHBpLWNoZWNrXCIgKG9uQ2xpY2spPVwiYXBwbHlNYW5hZ2VEaWFsb2codHJ1ZSlcIj48L3AtYnV0dG9uPlxuICAgIDwvZGl2PlxuICA8L25nLXRlbXBsYXRlPlxuPC9wLWRpYWxvZz5cblxuPHAtZGlhbG9nIGhlYWRlcj1cIlJlZ2lvbiBlZGl0b3IgaGVscFwiIFsodmlzaWJsZSldPVwiZGlzcGxheUhlbHBEaWFsb2dcIiBbc3R5bGVdPVwie3dpZHRoOiAnNTB2dyd9XCJcbiAgICAgICAgICBbbWF4aW1pemFibGVdPVwidHJ1ZVwiIFtkcmFnZ2FibGVdPVwidHJ1ZVwiIGFwcGVuZFRvPVwiYm9keVwiPlxuICA8cD5cbiAgICBUaGUgUmVnaW9uIEVkaXRvciBsaXN0cyBldmVyeSByZWdpb24gZHJhd24gb24gdGhlIGN1cnJlbnRseSBkaXNwbGF5ZWQgaW1hZ2UuXG4gICAgT3BlbiBpdCB3aXRoIHRoZVxuICAgIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3JlZ2lvbi1lZGl0b3Iuc3ZnXCIgd2lkdGg9XCIxNFwiIGhlaWdodD1cIjE0XCIgYWx0PVwiUmVnaW9uIEVkaXRvclwiLz5cbiAgICBidXR0b24gaW4gdGhlIGRpYWdyYW0gdG9vbGJhcjogaXQgb3BlbnMgYXMgYSBmbG9hdGluZyBkaWFsb2cgeW91IGNhbiBkcmFnLFxuICAgIHJlc2l6ZSwgYW5kIGxlYXZlIG9wZW4gd2hpbGUgeW91IGRyYXcgb24gdGhlIGltYWdlLlxuICAgIFJlZ2lvbnMgYXJlIHRyYWNrZWQgPGI+cGVyIGltYWdlPC9iPjogc3dpdGNoaW5nIHRvIGFub3RoZXIgaW1hZ2UgaGlkZXNcbiAgICB0aGVzZSBhbmQgc2hvd3MgdGhhdCBpbWFnZSdzIHJlZ2lvbnM7IHN3aXRjaGluZyBiYWNrIHJlc3RvcmVzIHRoZW0uXG4gICAgRWRpdHMgaW4gdGhlIHRhYmxlIGFwcGx5IDxiPmxpdmU8L2I+IHRvIHRoZSBkaWFncmFtIOKAlCB0aGVyZSBpcyBubyBzYXZlIG9yXG4gICAgY2FuY2VsLlxuICA8L3A+XG5cbiAgPGgzPkNyZWF0aW5nIHJlZ2lvbnM8L2gzPlxuICA8cD5cbiAgICBVc2UgdGhlIGRpYWdyYW0gdG9vbGJhciB0b29scyB0byBkcmF3IG5ldyByZWdpb25zOlxuICAgIDxpIGNsYXNzPVwicGkgcGktc3RvcFwiPjwvaT4gcmVjdGFuZ2xlLFxuICAgIDxvYmplY3QgZGF0YT1cImFzc2V0cy9pY29ucy9wb2x5bGluZS5zdmdcIiB3aWR0aD1cIjE0XCIgaGVpZ2h0PVwiMTRcIj48L29iamVjdD4gcG9seWxpbmUsXG4gICAgPGkgY2xhc3M9XCJwaSBwaS1wZW5jaWxcIj48L2k+IGZyZWVmb3JtIHBhdGgsIGJydXNoLCBvclxuICAgIDxvYmplY3QgZGF0YT1cImFzc2V0cy9pY29ucy93YW5kLnN2Z1wiIHdpZHRoPVwiMTRcIiBoZWlnaHQ9XCIxNFwiPjwvb2JqZWN0PiB3YW5kLlxuICAgIEFueXRoaW5nIHlvdSBkcmF3IGFwcGVhcnMgaW1tZWRpYXRlbHkgaW4gdGhlIHRhYmxlLCB3aXRoIHRoZSBjbGFzcyBuYW1lXG4gICAgXCI8Yj5SZWdpb248L2I+XCIgYnkgZGVmYXVsdC5cbiAgPC9wPlxuXG4gIDxoMz5UaGUgdGFibGUgY29sdW1uczwvaDM+XG4gIDx1bD5cbiAgICA8bGk+PGI+Q2xhc3M8L2I+IOKAlCB0aGUgcmVnaW9uJ3MgY2xhc3MuIFBpY2sgYSBjbGFzcyBmcm9tIHRoZSBkcm9wZG93biAoaXRzXG4gICAgICBjb2xvdXIgZm9sbG93cyksIG9yIHVzZSB0aGUgPGkgY2xhc3M9XCJwaSBwaS1wZW5jaWxcIj48L2k+IGZvciBhIGZyZWUtZm9ybSBuYW1lLjwvbGk+XG4gICAgPGxpPjxiPlN1cmZhY2U8L2I+IOKAlCB0aGUgcmVnaW9uJ3MgYXJlYSwgaW4gwrVtwrIvbW3CsiB3aGVuIHRoZSBpbWFnZSBjYXJyaWVzIGFcbiAgICAgIHBoeXNpY2FsIHBpeGVsIHNpemUsIG90aGVyd2lzZSBpbiBweMKyLiBIb2xlcyAoZG9udXRzKSBhbmQgbXVsdGktcGFydFxuICAgICAgcmVnaW9ucyBhcmUgYWNjb3VudGVkIGZvci48L2xpPlxuICAgIDxsaT5UaGUgdHJhc2ggaWNvbiBhdCB0aGUgZW5kIG9mIGEgcm93IGRlbGV0ZXMgdGhhdCBvbmUgcmVnaW9uLjwvbGk+XG4gICAgPGxpPlVzZSBcIjxiPng1MTI8L2I+XCIgdG8gcm91bmQgYWxsIHJlY3RhbmdsZSB3aWR0aHMvaGVpZ2h0cyB0byB0aGVcbiAgICAgIG5lYXJlc3QgbXVsdGlwbGUgb2YgNTEyLjwvbGk+XG4gIDwvdWw+XG4gIDxwPlJlZ2lvbiBnZW9tZXRyeSBpcyBlZGl0ZWQgb24gdGhlIGRpYWdyYW0gaXRzZWxmIChkcmFnIHZlcnRpY2VzLCB0aGUgdmVydGV4XG4gICAgdG9vbHMsIGJydXNoL3dhbmQsIGFuZCB0aGUgcmlnaHQtY2xpY2sgcmVnaW9uIGFjdGlvbnMpLCBub3QgaW4gdGhpcyB0YWJsZS48L3A+XG5cbiAgPGgzPlNlbGVjdGluZyByb3dzPC9oMz5cbiAgPHVsPlxuICAgIDxsaT5DbGljayBhIHJvdyB0byBzZWxlY3QgaXQuIFRoZSBjb3JyZXNwb25kaW5nIHJlZ2lvbiBpcyBoaWdobGlnaHRlZCBpbiB0aGVcbiAgICAgIGRpYWdyYW0gKFBsb3RseSBzaG93cyBpdHMgZWRpdCBoYW5kbGVzOyB0aGUgT3BlblNlYWRyYWdvbiBpbWFnZSB2aWV3XG4gICAgICBoaWdobGlnaHRzIGl0IGFuZCBzaG93cyBpdHMgaGFuZGxlcyBpbiBzZWxlY3QgbW9kZSkuPC9saT5cbiAgICA8bGk+PGtiZD5DdHJsPC9rYmQ+LzxrYmQ+Q21kPC9rYmQ+K2NsaWNrIHRvIGFkZCBvciByZW1vdmUgYSByb3cgZnJvbSB0aGVcbiAgICAgIHNlbGVjdGlvbi4gPGtiZD5TaGlmdDwva2JkPitjbGljayB0byBleHRlbmQgdGhlIHNlbGVjdGlvbiBpbiBhIHJhbmdlLjwvbGk+XG4gICAgPGxpPlNlbGVjdGluZyBhIHJlZ2lvbiBpbiB0aGUgZGlhZ3JhbSBoaWdobGlnaHRzIHRoZSBtYXRjaGluZyByb3cgaW4gdGhlXG4gICAgICB0YWJsZS48L2xpPlxuICAgIDxsaT5DbGlja3Mgb24gdGhlIGNvbG91ciBwaWNrZXIsIHRoZSB0cmFzaCBpY29uLCBvciB0aGUgYWN0aXZlIGNsYXNzLWVkaXRcbiAgICAgIGlucHV0IGRvIG5vdCBjaGFuZ2UgdGhlIHNlbGVjdGlvbiDigJQgdGhhdCdzIHN0YW5kYXJkIFByaW1lTkcgYmVoYXZpb3VyLjwvbGk+XG4gIDwvdWw+XG5cbiAgPGgzPkJ1bGsgYWN0aW9ucyBvbiB0aGUgc2VsZWN0ZWQgcm93czwvaDM+XG4gIDx1bD5cbiAgICA8bGk+PGkgY2xhc3M9XCJwaSBwaS1jaGVjay1zcXVhcmVcIj48L2k+IFNlbGVjdCBldmVyeSByZWdpb24gaW4gdGhlIHRhYmxlIGF0IG9uY2UgKGFsc28gYVxuICAgICAgcmlnaHQtY2xpY2sgYWN0aW9uIG9uIHRoZSBpbWFnZSkuPC9saT5cbiAgICA8bGk+PGkgY2xhc3M9XCJwaSBwaS10YWdzXCI+PC9pPiBFZGl0IHRoZSBsYWJlbCBvbiBldmVyeSBzZWxlY3RlZCByb3cgYXQgb25jZS48L2xpPlxuICAgIDxsaT48aSBjbGFzcz1cInBpIHBpLXRyYXNoXCI+PC9pPiBEZWxldGUgZXZlcnkgc2VsZWN0ZWQgcmVnaW9uIOKAlCBib3RoIGZyb21cbiAgICAgIHRoZSB0YWJsZSBhbmQgdGhlIGRpYWdyYW0uPC9saT5cbiAgICA8bGk+PGkgY2xhc3M9XCJwaSBwaS10aW1lcy1jaXJjbGVcIj48L2k+IERlbGV0ZSBldmVyeSByZWdpb24gb24gdGhpcyBpbWFnZS48L2xpPlxuICA8L3VsPlxuXG4gIDxoMz5Bbm5vdGF0aW9uIGNsYXNzZXMgJmFtcDsgY29sb3VycyAocHJlc2V0cyk8L2gzPlxuICA8cD5cbiAgICBQcmVzZXQgYSBjbGFzcyBvbmNlIOKAlCBhIDxiPm5hbWU8L2I+IGFuZCBhIDxiPmNvbG91cjwvYj4g4oCUIGFuZCBldmVyeSByZWdpb24gd2l0aFxuICAgIHRoYXQgY2xhc3MgbmFtZSBpcyBkcmF3biBpbiB0aGF0IGNvbG91ciwgPGI+aW5jbHVkaW5nIHJlZ2lvbnMgbG9hZGVkIGZyb20gYVxuICAgIEdlb0pTT04gbGF0ZXI8L2I+LiBUaGUgcHJlc2V0IGNvbG91ciBpcyB0aGUgc291cmNlIG9mIHRydXRoOiBvbiBsb2FkIGl0XG4gICAgPGI+b3ZlcnJpZGVzIHRoZSBjb2xvdXIgc3RvcmVkIGluIHRoZSBmaWxlPC9iPiwgc28gYSBnaXZlbiBhbm5vdGF0aW9uIHR5cGUgYWx3YXlzXG4gICAgbG9va3MgdGhlIHNhbWUgYWNyb3NzIGltYWdlcyBhbmQgc2Vzc2lvbnMuXG4gIDwvcD5cbiAgPHA+U2V0IHVwIHlvdXIgY2xhc3NlcyBpbiB0aGUgYW5ub3RhdGlvbi1jbGFzcyBtYW5hZ2VyIOKAlFxuICAgIDxpIGNsYXNzPVwicGkgcGktY29nXCI+PC9pPiA8Yj5NYW5hZ2U8L2I+OjwvcD5cbiAgPG9sPlxuICAgIDxsaT48Yj5BZGQgY2xhc3M8L2I+LCB0aGVuIHR5cGUgdGhlIGNsYXNzIDxiPm5hbWUgZXhhY3RseSBhcyBpdCBhcHBlYXJzIGluIHlvdXJcbiAgICAgIGRhdGE8L2I+IChlLmcuIDxjb2RlPlR1bW9yPC9jb2RlPikgYW5kIHBpY2sgaXRzIDxiPmNvbG91cjwvYj4gZnJvbSB0aGUgc3dhdGNoLlxuICAgICAgUmVwZWF0IGZvciBlYWNoIGNsYXNzLjwvbGk+XG4gICAgPGxpPk9wdGlvbmFsbHk6IGVkaXQgdGhlIDxiPmZhbGxiYWNrIHBhbGV0dGU8L2I+IChjb2xvdXJzIGdpdmVuIHRvIGNsYXNzZXMgdGhhdFxuICAgICAgYXJlbid0IGluIHlvdXIgbGlzdCksIHRvZ2dsZSA8Yj5hdXRvLWFkZDwvYj4gKGFkZHMgdW5tYXRjaGVkIGNsYXNzZXMgZm91bmQgb25cbiAgICAgIGxvYWQpLCBhbmQgY2hvb3NlIDxiPmV4YWN0PC9iPiB2cy4gPGI+bm9ybWFsaXplZDwvYj4gbmFtZSBtYXRjaGluZyDigJQgbm9ybWFsaXplZFxuICAgICAgaWdub3JlcyBjYXNlIGFuZCBzdXJyb3VuZGluZyBzcGFjZXMsIHNvIDxjb2RlPnR1bW9yPC9jb2RlPiBtYXRjaGVzXG4gICAgICA8Y29kZT5UdW1vcjwvY29kZT4uPC9saT5cbiAgICA8bGk+PGI+QXBwbHkuPC9iPiBQcmVzZXRzIGFyZSBzYXZlZCA8Yj5wZXIgdXNlcjwvYj4gb24gdGhlIHNlcnZlciwgc28gdGhleSBmb2xsb3dcbiAgICAgIHlvdSBhY3Jvc3MgaW1hZ2VzIGFuZCBzZXNzaW9ucy4gWW91IGNhbiBhbHNvIDxiPnJlc2V0IHRvIGRlZmF1bHRzPC9iPiBvclxuICAgICAgPGI+aW1wb3J0IC8gZXhwb3J0PC9iPiB0aGUgc2V0IGFzIEpTT04gdG8gc2hhcmUgaXQuPC9saT5cbiAgPC9vbD5cbiAgPHA+XG4gICAgTmV4dCB0aW1lIHlvdSBvcGVuIG9yIDxpIGNsYXNzPVwicGkgcGktdXBsb2FkXCI+PC9pPiBpbXBvcnQgYSBHZW9KU09OIHdob3NlIHJlZ2lvbnNcbiAgICBjYXJyeSB0aG9zZSBjbGFzcyBuYW1lcywgZWFjaCByZWdpb24gaXMgc2hvd24gaW4gaXRzIHByZXNldCBjb2xvdXIgYXV0b21hdGljYWxseSDigJRcbiAgICBubyByZS1jb2xvdXJpbmcgbmVlZGVkLiBBIGNsYXNzIHRoYXQgaXNuJ3QgaW4geW91ciBsaXN0IGluc3RlYWQgZ2V0cyBhIHN0YWJsZVxuICAgIGNvbG91ciBmcm9tIHRoZSBmYWxsYmFjayBwYWxldHRlLlxuICA8L3A+XG4gIDxwPkRheSB0byBkYXk6PC9wPlxuICA8dWw+XG4gICAgPGxpPlRoZSA8Yj5jbGFzcyBzdHJpcDwvYj4gYWJvdmUgdGhlIHRhYmxlIGxpc3RzIHlvdXIgcHJlc2V0cyB3aXRoIHRoZWlyIGNvbG91cnMuXG4gICAgICBDbGljayBhIGNsYXNzIHRvIG1ha2UgaXQgPGI+YWN0aXZlPC9iPiAobmV3IHJlZ2lvbnMgaW5oZXJpdCBpdCk7IHdpdGggcm93c1xuICAgICAgc2VsZWN0ZWQsIGNsaWNraW5nIGEgY2xhc3MgcmUtY2xhc3NpZmllcyB0aGVtLjwvbGk+XG4gICAgPGxpPlRoZSA8Yj5DbGFzczwvYj4gY29sdW1uIGRyb3Bkb3duIHJlLWNsYXNzaWZpZXMgb25lIHJlZ2lvbiB0byBhIHByZXNldCAoaXRzXG4gICAgICBjb2xvdXIgZm9sbG93cyksIG9yIHVzZSB0aGUgPGkgY2xhc3M9XCJwaSBwaS1wZW5jaWxcIj48L2k+IGZvciBhIGZyZWUtZm9ybSBuYW1lLjwvbGk+XG4gICAgPGxpPlJlY29sb3VyIGEgY2xhc3MgZnJvbSBpdHMgc3dhdGNoIChjbGFzcyBzdHJpcCBvciBNYW5hZ2UpIGFuZCBldmVyeSByZWdpb24gb2ZcbiAgICAgIHRoYXQgY2xhc3MgdXBkYXRlcyBhdCBvbmNlLjwvbGk+XG4gICAgPGxpPkRlbGV0aW5nIGEgY2xhc3MgcmVhc3NpZ25zIGl0cyByZWdpb25zIHRvIHRoZSBkZWZhdWx0IDxiPlJlZ2lvbjwvYj4gY2xhc3MuPC9saT5cbiAgICA8bGk+QSBjb2xvdXIgeW91IHNldCBvbiBhIDxiPnNpbmdsZTwvYj4gcmVnaW9uICg8aSBjbGFzcz1cInBpIHBpLXBhbGV0dGVcIj48L2k+XG4gICAgICBTZWxlY3RlZCByZWdpb25zKSBpcyBrZXB0IGFuZCBpcyBub3Qgb3ZlcnJpZGRlbiBieSBpdHMgY2xhc3MuPC9saT5cbiAgPC91bD5cblxuICA8aDM+UmVnaW9uIGFjdGlvbnMgKHJpZ2h0LWNsaWNrIHRoZSBkaWFncmFtKTwvaDM+XG4gIDxwPlJpZ2h0LWNsaWNrIGEgcmVnaW9uIOKAlCBvciB0aGUgY3VycmVudCBtdWx0aS1zZWxlY3Rpb24g4oCUIG9uIHRoZSBkaWFncmFtIGZvclxuICAgIHRoZSBcIlNlbGVjdGVkIHJlZ2lvbihzKVwiIGFjdGlvbnM6PC9wPlxuICA8dWw+XG4gICAgPGxpPjxiPk1lcmdlIC8gZ3JvdXA8L2I+IOKAlCBjb21iaW5lIDIrIHNlbGVjdGVkIHJlZ2lvbnMgaW50byBvbmUgKGl0IG1heSBlbmRcbiAgICAgIHVwIG11bHRpLXBhcnQgaWYgdGhlIHBpZWNlcyBkb24ndCB0b3VjaCkuPC9saT5cbiAgICA8bGk+PGI+VW5ncm91cDwvYj4g4oCUIHNwbGl0IGEgbXVsdGktcGFydCByZWdpb24gYmFjayBpbnRvIHNlcGFyYXRlIHJlZ2lvbnMuPC9saT5cbiAgICA8bGk+PGI+SW52ZXJzZTwvYj4g4oCUIHJlcGxhY2UgdGhlIHNlbGVjdGlvbiB3aXRoIGV2ZXJ5dGhpbmcgb3V0c2lkZSBpdCAodGhlXG4gICAgICByZWdpb25zIGJlY29tZSBob2xlcyBpbiB0aGUgaW1hZ2UgcmVjdGFuZ2xlKS48L2xpPlxuICAgIDxsaT48Yj5TaW1wbGlmeTwvYj4g4oCUIHJlZHVjZSB0aGUgdmVydGV4IGNvdW50IChEb3VnbGFz4oCTUGV1Y2tlcikgYnkgYSBwaXhlbFxuICAgICAgdGhyZXNob2xkOyBwaWNrIGEgcHJlc2V0IG9yIGEgY3VzdG9tIHZhbHVlLjwvbGk+XG4gICAgPGxpPjxiPkNvbnZlcnQgdG8gQsOpemllciAvIHRvIHBvbHlnb248L2I+IGFuZCA8Yj5EZWxldGU8L2I+LjwvbGk+XG4gIDwvdWw+XG5cbiAgPGgzPkRpc3BsYXkgb3B0aW9uczwvaDM+XG4gIDx1bD5cbiAgICA8bGk+PGkgY2xhc3M9XCJwaSBwaS10YWdcIj48L2k+IFRvZ2dsZSBzaG93aW5nIGNsYXNzIGxhYmVscyBvbiBldmVyeVxuICAgICAgcmVnaW9uIGluIHRoZSBkaWFncmFtIChzbG93ZXIgd2l0aCBtYW55IHJlZ2lvbnMpLjwvbGk+XG4gIDwvdWw+XG5cbiAgPGgzPkltcG9ydCAvIGV4cG9ydDwvaDM+XG4gIDx1bD5cbiAgICA8bGk+PGkgY2xhc3M9XCJwaSBwaS11cGxvYWRcIj48L2k+IEltcG9ydCByZWdpb25zIGZyb20gYSBHZW9KU09OIGZpbGUuPC9saT5cbiAgICA8bGk+PGkgY2xhc3M9XCJwaSBwaS1kb3dubG9hZFwiPjwvaT4gRXhwb3J0IHRoZSBjdXJyZW50IGltYWdlJ3MgcmVnaW9ucyB0b1xuICAgICAgYSBHZW9KU09OIGZpbGUuIChUaGUgcGVyLWltYWdlIGNhY2hlIGlzIGluLW1lbW9yeSBvbmx5IOKAlCBleHBvcnRpbmcgaXNcbiAgICAgIGhvdyB5b3UgcGVyc2lzdCByZWdpb25zIGFjcm9zcyBzZXNzaW9ucy4pPC9saT5cbiAgPC91bD5cblxuICA8bmctdGVtcGxhdGUgcFRlbXBsYXRlPVwiZm9vdGVyXCI+XG4gICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1jaGVja1wiIChjbGljayk9XCJkaXNwbGF5SGVscERpYWxvZz1mYWxzZVwiIGxhYmVsPVwiT2tcIiBzdHlsZUNsYXNzPVwicC1idXR0b24tdGV4dFwiPlxuICAgIDwvcC1idXR0b24+XG4gIDwvbmctdGVtcGxhdGU+XG48L3AtZGlhbG9nPlxuXG4iXX0=
|