@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.
Files changed (311) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +404 -0
  3. package/esm2022/index.mjs +52 -0
  4. package/esm2022/jax-data-science-sci-image-visualizer.mjs +5 -0
  5. package/esm2022/lib/channel-histogram/channel-histogram.component.mjs +398 -0
  6. package/esm2022/lib/contracts/capabilities.contract.mjs +32 -0
  7. package/esm2022/lib/contracts/cell-segmenter.contract.mjs +12 -0
  8. package/esm2022/lib/contracts/channel-histogram-api.contract.mjs +14 -0
  9. package/esm2022/lib/contracts/colormap-lut.mjs +95 -0
  10. package/esm2022/lib/contracts/coordinate-transform.contract.mjs +2 -0
  11. package/esm2022/lib/contracts/display-types.mjs +8 -0
  12. package/esm2022/lib/contracts/image.contract.mjs +11 -0
  13. package/esm2022/lib/contracts/intensity.mjs +28 -0
  14. package/esm2022/lib/contracts/plot-type.mjs +112 -0
  15. package/esm2022/lib/contracts/ports/image-state.port.mjs +3 -0
  16. package/esm2022/lib/contracts/ports/preferences.port.mjs +3 -0
  17. package/esm2022/lib/contracts/ports/region-io.port.mjs +3 -0
  18. package/esm2022/lib/contracts/ports/tile-access.port.mjs +3 -0
  19. package/esm2022/lib/contracts/region-editor-api.contract.mjs +4 -0
  20. package/esm2022/lib/contracts/region-overlay.contract.mjs +2 -0
  21. package/esm2022/lib/contracts/region-store.contract.mjs +2 -0
  22. package/esm2022/lib/contracts/sam.contract.mjs +12 -0
  23. package/esm2022/lib/contracts/toolbar-config.mjs +8 -0
  24. package/esm2022/lib/contracts/visualizer.contract.mjs +10 -0
  25. package/esm2022/lib/contracts/viz-config.mjs +3 -0
  26. package/esm2022/lib/hex-color-picker/hex-color-picker.component.mjs +260 -0
  27. package/esm2022/lib/implementations/base-store-visualizer.mjs +76 -0
  28. package/esm2022/lib/implementations/napari-js/napari-axes-labels.mjs +106 -0
  29. package/esm2022/lib/implementations/napari-js/napari-region-overlay.mjs +847 -0
  30. package/esm2022/lib/implementations/napari-js/napari-scale-bar.mjs +92 -0
  31. package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +2283 -0
  32. package/esm2022/lib/implementations/napari-js/napari-volume-z-handle.mjs +126 -0
  33. package/esm2022/lib/implementations/osd/display-pipeline.mjs +143 -0
  34. package/esm2022/lib/implementations/osd/histogram-sampler.mjs +217 -0
  35. package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +1780 -0
  36. package/esm2022/lib/implementations/osd/osd-coordinate-transform.mjs +25 -0
  37. package/esm2022/lib/implementations/osd/osd-coords.mjs +53 -0
  38. package/esm2022/lib/implementations/osd/osd-lib.mjs +20 -0
  39. package/esm2022/lib/implementations/osd/osd-region-overlay.mjs +1237 -0
  40. package/esm2022/lib/implementations/osd/osd-scale-bar.mjs +88 -0
  41. package/esm2022/lib/implementations/osd/slice-cache.mjs +488 -0
  42. package/esm2022/lib/implementations/osd/tile-client.mjs +29 -0
  43. package/esm2022/lib/implementations/plotly/plotly-coordinate-transform.mjs +37 -0
  44. package/esm2022/lib/implementations/plotly/plotly-region-overlay.mjs +31 -0
  45. package/esm2022/lib/implementations/plotly/plotly-trace-builders.mjs +183 -0
  46. package/esm2022/lib/implementations/plotly/plotly.service.mjs +2137 -0
  47. package/esm2022/lib/implementations/simple-slice-access.service.mjs +99 -0
  48. package/esm2022/lib/models/bezier.mjs +120 -0
  49. package/esm2022/lib/models/class-preset.mjs +46 -0
  50. package/esm2022/lib/models/geometry.mjs +198 -0
  51. package/esm2022/lib/models/region.mjs +166 -0
  52. package/esm2022/lib/models/shape.mjs +53 -0
  53. package/esm2022/lib/plot.utilities.mjs +693 -0
  54. package/esm2022/lib/processing/image-converter.service.mjs +90 -0
  55. package/esm2022/lib/processing/image-crop.mjs +62 -0
  56. package/esm2022/lib/processing/processing-image.mjs +50 -0
  57. package/esm2022/lib/provide-visualization.mjs +63 -0
  58. package/esm2022/lib/region-editor/mask-raster.mjs +95 -0
  59. package/esm2022/lib/region-editor/mask-worker.mjs +10 -0
  60. package/esm2022/lib/region-editor/region-editor.component.mjs +1190 -0
  61. package/esm2022/lib/region-ops.service.mjs +301 -0
  62. package/esm2022/lib/render-orchestrator.mjs +87 -0
  63. package/esm2022/lib/routing-visualizer.service.mjs +533 -0
  64. package/esm2022/lib/store/class-color.util.mjs +52 -0
  65. package/esm2022/lib/store/region-store.service.mjs +1029 -0
  66. package/esm2022/lib/store/visualizer-store.service.mjs +352 -0
  67. package/esm2022/lib/toolbar/brush/brush-tool.service.mjs +546 -0
  68. package/esm2022/lib/toolbar/crop/slide-crop.mjs +57 -0
  69. package/esm2022/lib/toolbar/segmentation/cell-segment-tool.service.mjs +115 -0
  70. package/esm2022/lib/toolbar/segmentation/cellpose-segmenter.service.mjs +84 -0
  71. package/esm2022/lib/toolbar/segmentation/onnx-sam-session.mjs +133 -0
  72. package/esm2022/lib/toolbar/segmentation/ort-runtime-config.mjs +31 -0
  73. package/esm2022/lib/toolbar/segmentation/sam-model-registry.mjs +108 -0
  74. package/esm2022/lib/toolbar/segmentation/sam-onnx-core.mjs +159 -0
  75. package/esm2022/lib/toolbar/segmentation/sam-point-tool.service.mjs +255 -0
  76. package/esm2022/lib/toolbar/segmentation/sam-prompt.mjs +94 -0
  77. package/esm2022/lib/toolbar/segmentation/sam-tool.service.mjs +188 -0
  78. package/esm2022/lib/toolbar/toolbar.component.mjs +311 -0
  79. package/esm2022/lib/toolbar/vertex-eraser/vertex-eraser-tool.service.mjs +234 -0
  80. package/esm2022/lib/toolbar/wand/wand-tool.service.mjs +499 -0
  81. package/esm2022/lib/toolbar/wand/wand.service.mjs +921 -0
  82. package/esm2022/lib/toolbar/zoom-to-box/zoom-to-box-tool.service.mjs +157 -0
  83. package/esm2022/lib/visualization.module.mjs +162 -0
  84. package/esm2022/lib/visualizer.component.mjs +1597 -0
  85. package/fesm2022/jax-data-science-sci-image-visualizer-mask-worker-uhepXUhM.mjs +12 -0
  86. package/fesm2022/jax-data-science-sci-image-visualizer-mask-worker-uhepXUhM.mjs.map +1 -0
  87. package/fesm2022/jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs +327 -0
  88. package/fesm2022/jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs.map +1 -0
  89. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +21314 -0
  90. package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -0
  91. package/fesm2022/mask.worker.js +989 -0
  92. package/fesm2022/onnx-sam.worker.js +202 -0
  93. package/index.d.ts +39 -0
  94. package/lib/channel-histogram/channel-histogram.component.d.ts +112 -0
  95. package/lib/contracts/capabilities.contract.d.ts +30 -0
  96. package/lib/contracts/cell-segmenter.contract.d.ts +40 -0
  97. package/lib/contracts/channel-histogram-api.contract.d.ts +98 -0
  98. package/lib/contracts/colormap-lut.d.ts +21 -0
  99. package/lib/contracts/coordinate-transform.contract.d.ts +43 -0
  100. package/lib/contracts/display-types.d.ts +41 -0
  101. package/lib/contracts/image.contract.d.ts +72 -0
  102. package/lib/contracts/intensity.d.ts +18 -0
  103. package/lib/contracts/plot-type.d.ts +108 -0
  104. package/lib/contracts/ports/image-state.port.d.ts +29 -0
  105. package/lib/contracts/ports/preferences.port.d.ts +19 -0
  106. package/lib/contracts/ports/region-io.port.d.ts +29 -0
  107. package/lib/contracts/ports/tile-access.port.d.ts +27 -0
  108. package/lib/contracts/region-editor-api.contract.d.ts +83 -0
  109. package/lib/contracts/region-overlay.contract.d.ts +47 -0
  110. package/lib/contracts/region-store.contract.d.ts +74 -0
  111. package/lib/contracts/sam.contract.d.ts +98 -0
  112. package/lib/contracts/toolbar-config.d.ts +24 -0
  113. package/lib/contracts/visualizer.contract.d.ts +346 -0
  114. package/lib/contracts/viz-config.d.ts +25 -0
  115. package/lib/hex-color-picker/hex-color-picker.component.d.ts +47 -0
  116. package/lib/implementations/base-store-visualizer.d.ts +71 -0
  117. package/lib/implementations/napari-js/napari-axes-labels.d.ts +47 -0
  118. package/lib/implementations/napari-js/napari-region-overlay.d.ts +115 -0
  119. package/lib/implementations/napari-js/napari-scale-bar.d.ts +31 -0
  120. package/lib/implementations/napari-js/napari-visualizer.service.d.ts +455 -0
  121. package/lib/implementations/napari-js/napari-volume-z-handle.d.ts +46 -0
  122. package/lib/implementations/osd/display-pipeline.d.ts +47 -0
  123. package/lib/implementations/osd/histogram-sampler.d.ts +85 -0
  124. package/lib/implementations/osd/openseadragon-visualizer.service.d.ts +476 -0
  125. package/lib/implementations/osd/osd-coordinate-transform.d.ts +17 -0
  126. package/lib/implementations/osd/osd-coords.d.ts +16 -0
  127. package/lib/implementations/osd/osd-lib.d.ts +19 -0
  128. package/lib/implementations/osd/osd-region-overlay.d.ts +191 -0
  129. package/lib/implementations/osd/osd-scale-bar.d.ts +17 -0
  130. package/lib/implementations/osd/slice-cache.d.ts +161 -0
  131. package/lib/implementations/osd/tile-client.d.ts +32 -0
  132. package/lib/implementations/plotly/plotly-coordinate-transform.d.ts +19 -0
  133. package/lib/implementations/plotly/plotly-region-overlay.d.ts +23 -0
  134. package/lib/implementations/plotly/plotly-trace-builders.d.ts +60 -0
  135. package/lib/implementations/plotly/plotly.service.d.ts +594 -0
  136. package/lib/implementations/simple-slice-access.service.d.ts +64 -0
  137. package/lib/models/bezier.d.ts +59 -0
  138. package/lib/models/class-preset.d.ts +39 -0
  139. package/lib/models/geometry.d.ts +66 -0
  140. package/lib/models/region.d.ts +132 -0
  141. package/lib/models/shape.d.ts +44 -0
  142. package/lib/plot.utilities.d.ts +107 -0
  143. package/lib/processing/image-converter.service.d.ts +28 -0
  144. package/lib/processing/image-crop.d.ts +18 -0
  145. package/lib/processing/processing-image.d.ts +17 -0
  146. package/lib/provide-visualization.d.ts +34 -0
  147. package/lib/region-editor/mask-raster.d.ts +61 -0
  148. package/lib/region-editor/mask-worker.d.ts +7 -0
  149. package/lib/region-editor/region-editor.component.d.ts +305 -0
  150. package/lib/region-ops.service.d.ts +92 -0
  151. package/lib/render-orchestrator.d.ts +59 -0
  152. package/lib/routing-visualizer.service.d.ts +227 -0
  153. package/lib/store/class-color.util.d.ts +26 -0
  154. package/lib/store/region-store.service.d.ts +315 -0
  155. package/lib/store/visualizer-store.service.d.ts +123 -0
  156. package/lib/toolbar/brush/brush-tool.service.d.ts +143 -0
  157. package/lib/toolbar/crop/slide-crop.d.ts +30 -0
  158. package/lib/toolbar/segmentation/cell-segment-tool.service.d.ts +32 -0
  159. package/lib/toolbar/segmentation/cellpose-segmenter.service.d.ts +39 -0
  160. package/lib/toolbar/segmentation/onnx-sam-session.d.ts +25 -0
  161. package/lib/toolbar/segmentation/ort-runtime-config.d.ts +4 -0
  162. package/lib/toolbar/segmentation/sam-model-registry.d.ts +36 -0
  163. package/lib/toolbar/segmentation/sam-onnx-core.d.ts +48 -0
  164. package/lib/toolbar/segmentation/sam-point-tool.service.d.ts +59 -0
  165. package/lib/toolbar/segmentation/sam-prompt.d.ts +34 -0
  166. package/lib/toolbar/segmentation/sam-tool.service.d.ts +50 -0
  167. package/lib/toolbar/toolbar.component.d.ts +167 -0
  168. package/lib/toolbar/vertex-eraser/vertex-eraser-tool.service.d.ts +64 -0
  169. package/lib/toolbar/wand/wand-tool.service.d.ts +149 -0
  170. package/lib/toolbar/wand/wand.service.d.ts +133 -0
  171. package/lib/toolbar/zoom-to-box/zoom-to-box-tool.service.d.ts +49 -0
  172. package/lib/visualization.module.d.ts +45 -0
  173. package/lib/visualizer.component.d.ts +378 -0
  174. package/package.json +73 -0
  175. package/src/lib/assets/3d-coordinates.svg +49 -0
  176. package/src/lib/assets/cells.svg +1 -0
  177. package/src/lib/assets/click.svg +1 -0
  178. package/src/lib/assets/colormap-luts.json +1 -0
  179. package/src/lib/assets/contour.svg +48 -0
  180. package/src/lib/assets/cube-3d.svg +1 -0
  181. package/src/lib/assets/edit-selection-white.svg +47 -0
  182. package/src/lib/assets/heatmap.svg +1 -0
  183. package/src/lib/assets/icons/colormap-Pastel1.png +0 -0
  184. package/src/lib/assets/icons/colormap-accent.png +0 -0
  185. package/src/lib/assets/icons/colormap-afmhot.png +0 -0
  186. package/src/lib/assets/icons/colormap-autumn.png +0 -0
  187. package/src/lib/assets/icons/colormap-berlin.png +0 -0
  188. package/src/lib/assets/icons/colormap-binary.png +0 -0
  189. package/src/lib/assets/icons/colormap-blackbody.png +0 -0
  190. package/src/lib/assets/icons/colormap-bluered.png +0 -0
  191. package/src/lib/assets/icons/colormap-blues.png +0 -0
  192. package/src/lib/assets/icons/colormap-bone.png +0 -0
  193. package/src/lib/assets/icons/colormap-brbg.png +0 -0
  194. package/src/lib/assets/icons/colormap-brg.png +0 -0
  195. package/src/lib/assets/icons/colormap-bugn.png +0 -0
  196. package/src/lib/assets/icons/colormap-bupu.png +0 -0
  197. package/src/lib/assets/icons/colormap-bwr.png +0 -0
  198. package/src/lib/assets/icons/colormap-cividis.png +0 -0
  199. package/src/lib/assets/icons/colormap-cmrmap.png +0 -0
  200. package/src/lib/assets/icons/colormap-cool.png +0 -0
  201. package/src/lib/assets/icons/colormap-coolwarm.png +0 -0
  202. package/src/lib/assets/icons/colormap-copper.png +0 -0
  203. package/src/lib/assets/icons/colormap-cubehelix.png +0 -0
  204. package/src/lib/assets/icons/colormap-dark2.png +0 -0
  205. package/src/lib/assets/icons/colormap-earth.png +0 -0
  206. package/src/lib/assets/icons/colormap-electric.png +0 -0
  207. package/src/lib/assets/icons/colormap-flag.png +0 -0
  208. package/src/lib/assets/icons/colormap-gist_earth.png +0 -0
  209. package/src/lib/assets/icons/colormap-gist_gray.png +0 -0
  210. package/src/lib/assets/icons/colormap-gist_heat.png +0 -0
  211. package/src/lib/assets/icons/colormap-gist_ncar.png +0 -0
  212. package/src/lib/assets/icons/colormap-gist_rainbow.png +0 -0
  213. package/src/lib/assets/icons/colormap-gist_stern.png +0 -0
  214. package/src/lib/assets/icons/colormap-gist_yarg.png +0 -0
  215. package/src/lib/assets/icons/colormap-gnbu.png +0 -0
  216. package/src/lib/assets/icons/colormap-gnuplot.png +0 -0
  217. package/src/lib/assets/icons/colormap-gnuplot2.png +0 -0
  218. package/src/lib/assets/icons/colormap-gray.png +0 -0
  219. package/src/lib/assets/icons/colormap-greens-inv.png +0 -0
  220. package/src/lib/assets/icons/colormap-greens.png +0 -0
  221. package/src/lib/assets/icons/colormap-greys-inv.png +0 -0
  222. package/src/lib/assets/icons/colormap-greys.png +0 -0
  223. package/src/lib/assets/icons/colormap-hot.png +0 -0
  224. package/src/lib/assets/icons/colormap-hsv.png +0 -0
  225. package/src/lib/assets/icons/colormap-inferno.png +0 -0
  226. package/src/lib/assets/icons/colormap-jet.png +0 -0
  227. package/src/lib/assets/icons/colormap-magma.png +0 -0
  228. package/src/lib/assets/icons/colormap-managua.png +0 -0
  229. package/src/lib/assets/icons/colormap-nipy_spectral.png +0 -0
  230. package/src/lib/assets/icons/colormap-ocean.png +0 -0
  231. package/src/lib/assets/icons/colormap-oranges.png +0 -0
  232. package/src/lib/assets/icons/colormap-orrd.png +0 -0
  233. package/src/lib/assets/icons/colormap-pastel2.png +0 -0
  234. package/src/lib/assets/icons/colormap-picnic.png +0 -0
  235. package/src/lib/assets/icons/colormap-pink.png +0 -0
  236. package/src/lib/assets/icons/colormap-piyg.png +0 -0
  237. package/src/lib/assets/icons/colormap-placeholder.png +0 -0
  238. package/src/lib/assets/icons/colormap-plasma.png +0 -0
  239. package/src/lib/assets/icons/colormap-portland.png +0 -0
  240. package/src/lib/assets/icons/colormap-prgn.png +0 -0
  241. package/src/lib/assets/icons/colormap-prism.png +0 -0
  242. package/src/lib/assets/icons/colormap-pubu.png +0 -0
  243. package/src/lib/assets/icons/colormap-pubugn.png +0 -0
  244. package/src/lib/assets/icons/colormap-puor.png +0 -0
  245. package/src/lib/assets/icons/colormap-purd.png +0 -0
  246. package/src/lib/assets/icons/colormap-purples.png +0 -0
  247. package/src/lib/assets/icons/colormap-rainbow.png +0 -0
  248. package/src/lib/assets/icons/colormap-rdbu.png +0 -0
  249. package/src/lib/assets/icons/colormap-rdgy.png +0 -0
  250. package/src/lib/assets/icons/colormap-rdpu.png +0 -0
  251. package/src/lib/assets/icons/colormap-rdylbu.png +0 -0
  252. package/src/lib/assets/icons/colormap-rdylgn.png +0 -0
  253. package/src/lib/assets/icons/colormap-reds.png +0 -0
  254. package/src/lib/assets/icons/colormap-seismic.png +0 -0
  255. package/src/lib/assets/icons/colormap-set1.png +0 -0
  256. package/src/lib/assets/icons/colormap-set2.png +0 -0
  257. package/src/lib/assets/icons/colormap-set3.png +0 -0
  258. package/src/lib/assets/icons/colormap-spectral.png +0 -0
  259. package/src/lib/assets/icons/colormap-spring.png +0 -0
  260. package/src/lib/assets/icons/colormap-summer.png +0 -0
  261. package/src/lib/assets/icons/colormap-tab10.png +0 -0
  262. package/src/lib/assets/icons/colormap-tab20.png +0 -0
  263. package/src/lib/assets/icons/colormap-tab20b.png +0 -0
  264. package/src/lib/assets/icons/colormap-tab20c.png +0 -0
  265. package/src/lib/assets/icons/colormap-terrain.png +0 -0
  266. package/src/lib/assets/icons/colormap-turbo.png +0 -0
  267. package/src/lib/assets/icons/colormap-twilight.png +0 -0
  268. package/src/lib/assets/icons/colormap-twilight_shifted.png +0 -0
  269. package/src/lib/assets/icons/colormap-vanimo.png +0 -0
  270. package/src/lib/assets/icons/colormap-viridis.png +0 -0
  271. package/src/lib/assets/icons/colormap-winter.png +0 -0
  272. package/src/lib/assets/icons/colormap-wistia.png +0 -0
  273. package/src/lib/assets/icons/colormap-ylgn.png +0 -0
  274. package/src/lib/assets/icons/colormap-ylgnbu-inv.png +0 -0
  275. package/src/lib/assets/icons/colormap-ylgnbu.png +0 -0
  276. package/src/lib/assets/icons/colormap-ylorbr.png +0 -0
  277. package/src/lib/assets/icons/colormap-ylorrd-inv.png +0 -0
  278. package/src/lib/assets/icons/colormap-ylorrd.png +0 -0
  279. package/src/lib/assets/interactive-segmentation-cursor.svg +1 -0
  280. package/src/lib/assets/invert.svg +1 -0
  281. package/src/lib/assets/isosurface.svg +7 -0
  282. package/src/lib/assets/mask.svg +1 -0
  283. package/src/lib/assets/paintbrush.svg +1 -0
  284. package/src/lib/assets/picture-segmentation.svg +43 -0
  285. package/src/lib/assets/polygon-vertices.svg +13 -0
  286. package/src/lib/assets/polyline.svg +3 -0
  287. package/src/lib/assets/redo.svg +39 -0
  288. package/src/lib/assets/region-editor.svg +37 -0
  289. package/src/lib/assets/region-move.svg +7 -0
  290. package/src/lib/assets/smooth.svg +56 -0
  291. package/src/lib/assets/spline-chart.svg +7 -0
  292. package/src/lib/assets/surface.svg +37 -0
  293. package/src/lib/assets/test/download-379.svg +1 -0
  294. package/src/lib/assets/test/download-98.svg +1 -0
  295. package/src/lib/assets/test/json-1.svg +1 -0
  296. package/src/lib/assets/test/load-53.svg +1 -0
  297. package/src/lib/assets/test/mask-44.svg +1 -0
  298. package/src/lib/assets/test/mask-download-44.svg +15 -0
  299. package/src/lib/assets/test/mask-save-44.svg +11 -0
  300. package/src/lib/assets/test/overlay-1.svg +1 -0
  301. package/src/lib/assets/test/overlay-4.svg +1 -0
  302. package/src/lib/assets/test/region-26.svg +1 -0
  303. package/src/lib/assets/test/save-56.svg +1 -0
  304. package/src/lib/assets/test/upload-43.svg +1 -0
  305. package/src/lib/assets/to-bezier.svg +15 -0
  306. package/src/lib/assets/to-polygon.svg +11 -0
  307. package/src/lib/assets/undo.svg +38 -0
  308. package/src/lib/assets/vertex-add.svg +12 -0
  309. package/src/lib/assets/vertex-delete.svg +12 -0
  310. package/src/lib/assets/wand.svg +54 -0
  311. 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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=