@sumaris-net/ngx-components 18.25.4 → 18.25.6

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 (34) hide show
  1. package/doc/changelog.md +12 -0
  2. package/esm2022/public_api.mjs +4 -1
  3. package/esm2022/src/app/core/form/properties/properties.form.mjs +1 -1
  4. package/esm2022/src/app/core/table/testing/nested-table.testing.mjs +1 -1
  5. package/esm2022/src/app/core/table/testing/table.testing.mjs +1 -1
  6. package/esm2022/src/app/core/table/testing/table2.testing.mjs +1 -1
  7. package/esm2022/src/app/shared/form/field.component.mjs +1 -1
  8. package/esm2022/src/app/shared/image/gallery/image-gallery.component.mjs +82 -82
  9. package/esm2022/src/app/shared/image/gallery/image-gallery.model.mjs +2 -0
  10. package/esm2022/src/app/shared/image/gallery/image-gallery.module.mjs +8 -3
  11. package/esm2022/src/app/shared/image/gallery/slideshow/image-gallery-slideshow.component.mjs +685 -0
  12. package/esm2022/src/app/shared/image/gallery/testing/gallery.testing.mjs +16 -9
  13. package/esm2022/src/app/shared/material/autocomplete/material.autocomplete.directive.mjs +25 -0
  14. package/esm2022/src/app/shared/material/autocomplete/material.autocomplete.mjs +20 -18
  15. package/esm2022/src/app/shared/material/autocomplete/material.autocomplete.module.mjs +6 -5
  16. package/esm2022/src/app/shared/material/autocomplete/testing/autocomplete.test.mjs +5 -4
  17. package/esm2022/src/app/shared/named-filter/named-filter-selector.component.mjs +1 -1
  18. package/fesm2022/sumaris-net.ngx-components.mjs +817 -111
  19. package/fesm2022/sumaris-net.ngx-components.mjs.map +1 -1
  20. package/package.json +1 -1
  21. package/public_api.d.ts +3 -0
  22. package/src/app/shared/image/gallery/image-gallery.component.d.ts +42 -21
  23. package/src/app/shared/image/gallery/image-gallery.model.d.ts +11 -0
  24. package/src/app/shared/image/gallery/image-gallery.module.d.ts +10 -9
  25. package/src/app/shared/image/gallery/slideshow/image-gallery-slideshow.component.d.ts +149 -0
  26. package/src/app/shared/image/gallery/testing/gallery.testing.d.ts +6 -1
  27. package/src/app/shared/material/autocomplete/material.autocomplete.d.ts +8 -9
  28. package/src/app/shared/material/autocomplete/material.autocomplete.directive.d.ts +9 -0
  29. package/src/app/shared/material/autocomplete/material.autocomplete.module.d.ts +18 -17
  30. package/src/assets/i18n/en-US.json +6 -0
  31. package/src/assets/i18n/en.json +6 -0
  32. package/src/assets/i18n/fr.json +6 -0
  33. package/src/assets/manifest.json +1 -1
  34. package/src/theme/_ngx-components.scss +15 -5
@@ -0,0 +1,685 @@
1
+ import { booleanAttribute, ChangeDetectionStrategy, Component, EventEmitter, HostListener, inject, Input, Output, ViewChild, } from '@angular/core';
2
+ import { IonicSlides } from '@ionic/angular';
3
+ import { isNilOrBlank } from '../../../functions';
4
+ import { createPromiseEventEmitter, emitPromiseEvent } from '../../../events';
5
+ import { Alerts } from '../../../alerts';
6
+ import { APP_SHOW_TOOLTIP } from '../../../constants';
7
+ import * as i0 from "@angular/core";
8
+ import * as i1 from "@ionic/angular";
9
+ import * as i2 from "@ngx-translate/core";
10
+ import * as i3 from "@angular/common";
11
+ import * as i4 from "@angular/material/icon";
12
+ import * as i5 from "@angular/material/tooltip";
13
+ import * as i6 from "../../../pipes/property.pipes";
14
+ import * as i7 from "../../../pipes/arrays.pipe";
15
+ import * as i8 from "../../../pipes/url.pipes";
16
+ /**
17
+ * The zoom/pan/rotate/crop slideshow shown when clicking an image in `AppImageGalleryComponent`.
18
+ * Extracted into its own component so the exact same slideshow can be presented either inside an
19
+ * `ion-modal` (default), or inline as an overlay directly within the gallery, without any duplication.
20
+ */
21
+ export class AppImageGallerySlideshowComponent {
22
+ alterCtrl;
23
+ translate;
24
+ cd;
25
+ activeSlideIndex = 0;
26
+ swiperModules = [IonicSlides];
27
+ _swiper;
28
+ // Mouse wheel zoom & rotation state
29
+ _zoomState = { scale: 1, tx: 0, ty: 0 };
30
+ _rotation = 0; // degrees: 0, 90, 180, 270
31
+ _dragState = null;
32
+ _wheelListener = null;
33
+ _wheelEl = null;
34
+ // Crop & rotate state. Rotation is always available; the crop grid/handles only show once the
35
+ // crop tool has been explicitly activated (fab button). Validate/cancel show up once something changed.
36
+ _cropToolActive = false;
37
+ _cropRect = { x: 0, y: 0, width: 1, height: 1 };
38
+ _cropOverlayRect = null;
39
+ _cropDragState = null;
40
+ _cropHandles = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
41
+ rows = [];
42
+ selectedIndex = 0;
43
+ mobile = false;
44
+ readOnly = false;
45
+ disabled = false;
46
+ debug = false;
47
+ mouseZoomEnabled = false;
48
+ wheelZoomStep = 0.3;
49
+ maxZoomRatio = 3;
50
+ rotationEnabled = true;
51
+ cropEnabled = true;
52
+ imageExportFormat = 'png';
53
+ imageExportQuality = 0.92;
54
+ imageSizeQueryParam = 'size';
55
+ modalImageSize;
56
+ /** Requests deletion of the given row. The caller (gallery) owns the confirmation/deletion logic and resolves the promise with whether it actually happened. */
57
+ deleteRequested = createPromiseEventEmitter();
58
+ closeRequested = new EventEmitter();
59
+ afterEditImage = new EventEmitter();
60
+ afterEditRow = new EventEmitter();
61
+ swiperRef;
62
+ showTooltip = inject(APP_SHOW_TOOLTIP, { optional: true });
63
+ get hasPendingEdit() {
64
+ const r = this._cropRect;
65
+ return this._rotation !== 0 || r.x !== 0 || r.y !== 0 || r.width !== 1 || r.height !== 1;
66
+ }
67
+ constructor(alterCtrl, translate, cd) {
68
+ this.alterCtrl = alterCtrl;
69
+ this.translate = translate;
70
+ this.cd = cd;
71
+ }
72
+ ngOnDestroy() {
73
+ this._cleanupWheelZoom();
74
+ }
75
+ getSwiper(swiperElement) {
76
+ this._swiper = (swiperElement || this.swiperRef?.nativeElement)?.swiper || this._swiper;
77
+ return this._swiper;
78
+ }
79
+ onSlideChange(swiperElement) {
80
+ const swiper = this.getSwiper(swiperElement);
81
+ this.activeSlideIndex = swiper.activeIndex;
82
+ this._resetZoom(swiperElement);
83
+ this._updateSwiperTouchMove(swiperElement);
84
+ this.markForCheck();
85
+ }
86
+ onSwiperInit(swiperElement) {
87
+ this._resetZoom();
88
+ if (this.selectedIndex > 0) {
89
+ if (this.debug)
90
+ console.debug(`[image-gallery-slideshow] Initializing slideshow, selectedIndex=${this.selectedIndex}`);
91
+ // Use a setTimeout otherwise the swiper cannot slide
92
+ setTimeout(() => {
93
+ const swiper = this.getSwiper(swiperElement);
94
+ swiper.slideTo(this.selectedIndex, 0);
95
+ });
96
+ }
97
+ this._setupWheelZoom(swiperElement);
98
+ this._updateSwiperTouchMove(swiperElement);
99
+ setTimeout(() => {
100
+ this._updateEditImageSize(swiperElement);
101
+ this._updateCropOverlayPosition(swiperElement);
102
+ });
103
+ }
104
+ /**
105
+ * Swiper's own touch/mouse-drag-to-swipe gesture is captured very early (before our crop overlay's
106
+ * own mousedown handler can stop it), so it must be disabled upfront whenever the crop tool is active
107
+ * -- otherwise dragging inside the crop selection ends up sliding to the next/previous image instead.
108
+ * Prev/next arrow buttons keep working, since they call the Swiper API directly.
109
+ */
110
+ _updateSwiperTouchMove(swiperElement) {
111
+ const swiper = this.getSwiper(swiperElement);
112
+ if (!swiper)
113
+ return;
114
+ swiper.allowTouchMove = !this._cropToolActive;
115
+ }
116
+ async zoom(swiperElement, zoomIn) {
117
+ if (this.debug)
118
+ console.debug('[image-gallery-slideshow] zoom ' + (zoomIn ? 'in' : 'out'), swiperElement);
119
+ if (this.mouseZoomEnabled) {
120
+ const delta = zoomIn ? this.wheelZoomStep : -this.wheelZoomStep;
121
+ const newScale = Math.max(1, Math.min(this._zoomState.scale + delta, this.maxZoomRatio));
122
+ if (newScale === this._zoomState.scale)
123
+ return;
124
+ if (newScale === 1) {
125
+ this._zoomState = { scale: 1, tx: 0, ty: 0 };
126
+ }
127
+ else {
128
+ this._zoomState.scale = newScale;
129
+ this._clampTranslations(swiperElement);
130
+ }
131
+ this._applyZoom(swiperElement);
132
+ }
133
+ else {
134
+ const swiper = this.getSwiper(swiperElement);
135
+ if (zoomIn)
136
+ swiper.zoom.in();
137
+ else
138
+ swiper.zoom.out();
139
+ }
140
+ }
141
+ async requestDelete(swiperElement) {
142
+ const swiper = this.getSwiper(swiperElement);
143
+ const currentRow = this.rows.find((row, index) => index === swiper.activeIndex);
144
+ if (!currentRow)
145
+ return false; // Row not found
146
+ let deleted = false;
147
+ try {
148
+ deleted = await emitPromiseEvent(this.deleteRequested, 'delete', { detail: { row: currentRow } });
149
+ }
150
+ catch (err) {
151
+ if (err === 'CANCELLED')
152
+ return false; // User cancel
153
+ throw err;
154
+ }
155
+ if (deleted) {
156
+ if (!this.rows?.length) {
157
+ this.closeRequested.emit();
158
+ }
159
+ else {
160
+ this._resetZoom(swiperElement);
161
+ swiper.update();
162
+ this.markForCheck();
163
+ }
164
+ }
165
+ return deleted;
166
+ }
167
+ async slidePrev(swiperElement) {
168
+ const swiper = this.getSwiper(swiperElement);
169
+ const activeIndex = swiper.activeIndex;
170
+ if (activeIndex === 0) {
171
+ // Loop to last
172
+ await swiper.slideTo(this.rows.length - 1, 0 /*no transition, to avoid a "revert" effect*/);
173
+ }
174
+ else {
175
+ await swiper.slidePrev();
176
+ }
177
+ }
178
+ async slideNext(swiperElement) {
179
+ const swiper = this.getSwiper(swiperElement);
180
+ const activeIndex = swiper.activeIndex;
181
+ if (activeIndex === this.rows.length - 1) {
182
+ // Loop to first
183
+ await swiper.slideTo(0, 0 /*no transition, to avoid a "revert" effect*/);
184
+ }
185
+ else {
186
+ await swiper.slideNext();
187
+ }
188
+ }
189
+ getImageSizeQueryParams() {
190
+ if (!this.imageSizeQueryParam || isNilOrBlank(this.modalImageSize))
191
+ return undefined;
192
+ return {
193
+ [this.imageSizeQueryParam]: this.modalImageSize,
194
+ };
195
+ }
196
+ // --- Crop & rotate tools: always available (no "enter edit mode" step) ---
197
+ // Validate/Cancel only appear once the user actually changed something (see `hasPendingEdit`).
198
+ discardEdit(swiperElement) {
199
+ this._resetEditState(swiperElement);
200
+ }
201
+ resetCrop(_swiperElement) {
202
+ this._cropRect = { x: 0, y: 0, width: 1, height: 1 };
203
+ this.markForCheck();
204
+ }
205
+ toggleCropTool(swiperElement) {
206
+ if (!this.cropEnabled || this.readOnly)
207
+ return;
208
+ this._cropToolActive = !this._cropToolActive;
209
+ // Discard any pending crop selection when hiding the tool
210
+ if (!this._cropToolActive) {
211
+ this._cropRect = { x: 0, y: 0, width: 1, height: 1 };
212
+ this._cropDragState = null;
213
+ }
214
+ this._updateSwiperTouchMove(swiperElement);
215
+ this.markForCheck();
216
+ if (this._cropToolActive) {
217
+ // Wait for the crop overlay to be rendered, then compute its position
218
+ setTimeout(() => this._updateCropOverlayPosition(swiperElement));
219
+ }
220
+ }
221
+ async validateEdit(swiperElement) {
222
+ const swiper = this.getSwiper(swiperElement);
223
+ const currentRow = this.rows.find((row, index) => index === swiper.activeIndex);
224
+ if (!currentRow)
225
+ return;
226
+ const image = currentRow.currentData;
227
+ const format = this.imageExportFormat;
228
+ const mimeType = format === 'jpeg' ? 'image/jpeg' : 'image/png';
229
+ const quality = format === 'jpeg' ? this.imageExportQuality : undefined;
230
+ try {
231
+ const canvas = await this._renderEditedImage(image, this._rotation, this._cropRect);
232
+ const dataUrl = canvas.toDataURL(mimeType, quality);
233
+ const blob = await new Promise((resolve, reject) => {
234
+ canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('canvas.toBlob() failed'))), mimeType, quality);
235
+ });
236
+ // Update the row, so the slideshow and thumbnail reflect the change immediately
237
+ image.dataUrl = dataUrl;
238
+ image.url = null;
239
+ this.afterEditImage.emit({ row: currentRow, image, dataUrl, blob, format });
240
+ this.afterEditRow.emit(currentRow);
241
+ this._resetEditState(swiperElement);
242
+ }
243
+ catch (err) {
244
+ console.error('[image-gallery-slideshow] Failed to export the edited image', err);
245
+ await Alerts.showError('IMAGE.GALLERY.ERROR_EDIT_IMAGE', this.alterCtrl, this.translate);
246
+ }
247
+ }
248
+ _resetEditState(swiperElement) {
249
+ this._rotation = 0;
250
+ this._cropRect = { x: 0, y: 0, width: 1, height: 1 };
251
+ this._cropDragState = null;
252
+ this._cropToolActive = false;
253
+ this._applyZoom(swiperElement);
254
+ this._updateEditImageSize(swiperElement);
255
+ this._updateSwiperTouchMove(swiperElement);
256
+ this.markForCheck();
257
+ }
258
+ onWindowResize() {
259
+ const swiperElement = this.swiperRef?.nativeElement;
260
+ if (!swiperElement)
261
+ return;
262
+ this._updateEditImageSize(swiperElement);
263
+ this._updateCropOverlayPosition(swiperElement);
264
+ }
265
+ rotate(swiperElement, direction) {
266
+ if (!this.rotationEnabled)
267
+ return;
268
+ const delta = direction === 'right' ? 90 : -90;
269
+ this._rotation = (((this._rotation + delta) % 360) + 360) % 360;
270
+ this._applyZoom(swiperElement);
271
+ this._updateEditImageSize(swiperElement);
272
+ this._updateCropOverlayPosition(swiperElement);
273
+ }
274
+ /**
275
+ * Constrains the displayed image so that, even once rotated 90°/270° via CSS transform,
276
+ * its visual bounding box always fits inside the slide container (no overflow).
277
+ */
278
+ _updateEditImageSize(swiperElement, retriesLeft = 60) {
279
+ const swiper = this.getSwiper(swiperElement);
280
+ const activeSlide = swiper?.slides?.[swiper.activeIndex];
281
+ if (!activeSlide)
282
+ return;
283
+ const containerEl = activeSlide.querySelector('.swiper-zoom-container');
284
+ const imgEl = activeSlide.querySelector('.swiper-zoom-target, .swiper-zoom-container > img');
285
+ if (!containerEl || !imgEl)
286
+ return;
287
+ if (!this.cropEnabled && !this.rotationEnabled) {
288
+ imgEl.style.width = '';
289
+ imgEl.style.height = '';
290
+ return;
291
+ }
292
+ const containerRect = containerEl.getBoundingClientRect();
293
+ const naturalSize = this._getImageNaturalSize(imgEl);
294
+ // The container/image may not be laid out or loaded yet: skip and retry on the next frame
295
+ if (!containerRect.width || !containerRect.height || !naturalSize) {
296
+ if (retriesLeft > 0) {
297
+ requestAnimationFrame(() => this._updateEditImageSize(swiperElement, retriesLeft - 1));
298
+ }
299
+ return;
300
+ }
301
+ // Compute the exact box (preserving the image's aspect ratio) that fits the available space,
302
+ // swapping width/height when rotated 90°/270° so the rotated visual box never overflows.
303
+ const swapped = this._rotation % 180 !== 0;
304
+ const availWidth = swapped ? containerRect.height : containerRect.width;
305
+ const availHeight = swapped ? containerRect.width : containerRect.height;
306
+ const ratio = Math.min(availWidth / naturalSize.width, availHeight / naturalSize.height);
307
+ imgEl.style.width = `${naturalSize.width * ratio}px`;
308
+ imgEl.style.height = `${naturalSize.height * ratio}px`;
309
+ }
310
+ _getImageNaturalSize(imgEl) {
311
+ const inner = (imgEl.tagName === 'ION-IMG' ? imgEl.shadowRoot?.querySelector('img') : imgEl);
312
+ if (!inner?.naturalWidth || !inner?.naturalHeight)
313
+ return null;
314
+ return { width: inner.naturalWidth, height: inner.naturalHeight };
315
+ }
316
+ // --- Crop overlay: drag to move/resize the crop rectangle ---
317
+ onCropPointerDown(event, swiperElement, handle) {
318
+ event.preventDefault();
319
+ event.stopPropagation();
320
+ const pt = this._getEventPoint(event);
321
+ this._cropOverlayRect = this._cropOverlayRect ?? this._updateCropOverlayPosition(swiperElement);
322
+ this._cropDragState = {
323
+ handle,
324
+ startX: pt.x,
325
+ startY: pt.y,
326
+ startRect: { ...this._cropRect },
327
+ };
328
+ // Also disable programmatic slide navigation while dragging (allowTouchMove is already
329
+ // permanently off whenever crop is enabled, see `_updateSwiperTouchMove`)
330
+ const swiper = this.getSwiper(swiperElement);
331
+ if (swiper) {
332
+ swiper.allowSlideNext = false;
333
+ swiper.allowSlidePrev = false;
334
+ }
335
+ }
336
+ onCropPointerMove(event, _swiperElement) {
337
+ if (!this._cropDragState)
338
+ return;
339
+ event.preventDefault();
340
+ const rect = this._cropOverlayRect;
341
+ if (!rect?.width || !rect?.height)
342
+ return;
343
+ const pt = this._getEventPoint(event);
344
+ const dx = (pt.x - this._cropDragState.startX) / rect.width;
345
+ const dy = (pt.y - this._cropDragState.startY) / rect.height;
346
+ this._cropRect = this._computeCropRect(this._cropDragState.handle, this._cropDragState.startRect, dx, dy);
347
+ this.markForCheck();
348
+ }
349
+ onCropPointerUp(_event, swiperElement) {
350
+ if (!this._cropDragState)
351
+ return;
352
+ this._cropDragState = null;
353
+ const swiper = swiperElement ? this.getSwiper(swiperElement) : null;
354
+ if (swiper) {
355
+ swiper.allowSlideNext = true;
356
+ swiper.allowSlidePrev = true;
357
+ }
358
+ if (swiperElement)
359
+ this._updateSwiperTouchMove(swiperElement);
360
+ }
361
+ _computeCropRect(handle, start, dx, dy) {
362
+ const MIN_SIZE = 0.08;
363
+ const clamp01 = (v) => Math.max(0, Math.min(1, v));
364
+ if (handle === 'move') {
365
+ let x = clamp01(start.x + dx);
366
+ let y = clamp01(start.y + dy);
367
+ x = Math.min(x, 1 - start.width);
368
+ y = Math.min(y, 1 - start.height);
369
+ return { x, y, width: start.width, height: start.height };
370
+ }
371
+ let left = start.x;
372
+ let top = start.y;
373
+ let right = start.x + start.width;
374
+ let bottom = start.y + start.height;
375
+ if (handle.includes('w'))
376
+ left = clamp01(start.x + dx);
377
+ if (handle.includes('e'))
378
+ right = clamp01(start.x + start.width + dx);
379
+ if (handle.includes('n'))
380
+ top = clamp01(start.y + dy);
381
+ if (handle.includes('s'))
382
+ bottom = clamp01(start.y + start.height + dy);
383
+ if (right - left < MIN_SIZE) {
384
+ if (handle.includes('w'))
385
+ left = right - MIN_SIZE;
386
+ else
387
+ right = left + MIN_SIZE;
388
+ }
389
+ if (bottom - top < MIN_SIZE) {
390
+ if (handle.includes('n'))
391
+ top = bottom - MIN_SIZE;
392
+ else
393
+ bottom = top + MIN_SIZE;
394
+ }
395
+ left = clamp01(left);
396
+ right = clamp01(right);
397
+ top = clamp01(top);
398
+ bottom = clamp01(bottom);
399
+ return { x: left, y: top, width: right - left, height: bottom - top };
400
+ }
401
+ _getEventPoint(event) {
402
+ if ('touches' in event && event.touches.length) {
403
+ return { x: event.touches[0].clientX, y: event.touches[0].clientY };
404
+ }
405
+ if ('changedTouches' in event && event.changedTouches.length) {
406
+ return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
407
+ }
408
+ const mouseEvent = event;
409
+ return { x: mouseEvent.clientX, y: mouseEvent.clientY };
410
+ }
411
+ _updateCropOverlayPosition(swiperElement, retriesLeft = 60) {
412
+ const swiper = this.getSwiper(swiperElement);
413
+ const activeSlide = swiper?.slides?.[swiper.activeIndex];
414
+ if (!activeSlide)
415
+ return null;
416
+ const containerEl = activeSlide.querySelector('.swiper-zoom-container');
417
+ const imgEl = activeSlide.querySelector('.swiper-zoom-target, .swiper-zoom-container > img');
418
+ const overlayEl = activeSlide.querySelector('.crop-overlay');
419
+ if (!containerEl || !imgEl || !overlayEl)
420
+ return null;
421
+ const containerRect = containerEl.getBoundingClientRect();
422
+ const imgRect = imgEl.getBoundingClientRect();
423
+ // The image may not be laid out yet: keep any previous (non-zero) value and retry on the next frame
424
+ if (!imgRect.width || !imgRect.height) {
425
+ if (retriesLeft > 0) {
426
+ requestAnimationFrame(() => this._updateCropOverlayPosition(swiperElement, retriesLeft - 1));
427
+ }
428
+ return this._cropOverlayRect;
429
+ }
430
+ overlayEl.style.left = `${imgRect.left - containerRect.left}px`;
431
+ overlayEl.style.top = `${imgRect.top - containerRect.top}px`;
432
+ overlayEl.style.width = `${imgRect.width}px`;
433
+ overlayEl.style.height = `${imgRect.height}px`;
434
+ this._cropOverlayRect = { width: imgRect.width, height: imgRect.height };
435
+ return this._cropOverlayRect;
436
+ }
437
+ // --- Canvas rendering, used to export the cropped/rotated image ---
438
+ async _renderEditedImage(image, rotation, cropRect) {
439
+ const src = image.dataUrl || image.url;
440
+ const img = await this._loadImageElement(src);
441
+ const sw = img.naturalWidth;
442
+ const sh = img.naturalHeight;
443
+ const swapped = rotation % 180 !== 0;
444
+ const rw = swapped ? sh : sw;
445
+ const rh = swapped ? sw : sh;
446
+ // 1) Draw the full (uncropped) image, rotated
447
+ const rotCanvas = document.createElement('canvas');
448
+ rotCanvas.width = rw;
449
+ rotCanvas.height = rh;
450
+ const rctx = rotCanvas.getContext('2d');
451
+ rctx.translate(rw / 2, rh / 2);
452
+ rctx.rotate((rotation * Math.PI) / 180);
453
+ rctx.drawImage(img, -sw / 2, -sh / 2);
454
+ // 2) Crop the rotated image
455
+ const cropX = Math.round(cropRect.x * rw);
456
+ const cropY = Math.round(cropRect.y * rh);
457
+ const cropW = Math.max(1, Math.round(cropRect.width * rw));
458
+ const cropH = Math.max(1, Math.round(cropRect.height * rh));
459
+ const outCanvas = document.createElement('canvas');
460
+ outCanvas.width = cropW;
461
+ outCanvas.height = cropH;
462
+ outCanvas.getContext('2d').drawImage(rotCanvas, cropX, cropY, cropW, cropH, 0, 0, cropW, cropH);
463
+ return outCanvas;
464
+ }
465
+ _loadImageElement(src) {
466
+ return new Promise((resolve, reject) => {
467
+ const img = new Image();
468
+ if (!src.startsWith('data:'))
469
+ img.crossOrigin = 'anonymous';
470
+ img.onload = () => resolve(img);
471
+ img.onerror = () => reject(new Error(`[image-gallery-slideshow] Cannot load image: ${src}`));
472
+ img.src = src;
473
+ });
474
+ }
475
+ // --- Mouse-wheel zoom & mouse-drag pan ---
476
+ onPointerDown(event, swiperElement) {
477
+ if (!this.mouseZoomEnabled || this._zoomState.scale <= 1)
478
+ return;
479
+ // Don't intercept clicks on buttons/controls
480
+ if (event.target.closest('ion-fab-button, ion-button, button'))
481
+ return;
482
+ event.preventDefault();
483
+ this._dragState = {
484
+ startX: event.clientX,
485
+ startY: event.clientY,
486
+ startTx: this._zoomState.tx,
487
+ startTy: this._zoomState.ty,
488
+ };
489
+ const wrapEl = this._getActiveZoomWrapEl(swiperElement);
490
+ if (wrapEl)
491
+ wrapEl.style.cursor = 'grabbing';
492
+ // Disable Swiper slide-change while dragging
493
+ const swiper = this.getSwiper(swiperElement);
494
+ if (swiper) {
495
+ swiper.allowSlideNext = false;
496
+ swiper.allowSlidePrev = false;
497
+ }
498
+ }
499
+ onPointerMove(event, swiperElement) {
500
+ if (this._cropDragState) {
501
+ this.onCropPointerMove(event, swiperElement);
502
+ return;
503
+ }
504
+ if (!this._dragState)
505
+ return;
506
+ event.preventDefault();
507
+ this._zoomState.tx = this._dragState.startTx + event.clientX - this._dragState.startX;
508
+ this._zoomState.ty = this._dragState.startTy + event.clientY - this._dragState.startY;
509
+ this._clampTranslations(swiperElement);
510
+ this._applyZoom(swiperElement);
511
+ }
512
+ onPointerUp(event, swiperElement) {
513
+ if (this._cropDragState) {
514
+ this.onCropPointerUp(event, swiperElement);
515
+ return;
516
+ }
517
+ if (!this._dragState)
518
+ return;
519
+ this._dragState = null;
520
+ const wrapEl = this._getActiveZoomWrapEl(swiperElement);
521
+ if (wrapEl)
522
+ wrapEl.style.cursor = this._zoomState.scale > 1 ? 'grab' : '';
523
+ // Re-enable Swiper navigation
524
+ const swiper = swiperElement ? this.getSwiper(swiperElement) : null;
525
+ if (swiper) {
526
+ swiper.allowSlideNext = true;
527
+ swiper.allowSlidePrev = true;
528
+ }
529
+ }
530
+ _resetZoom(swiperElement) {
531
+ this._zoomState = { scale: 1, tx: 0, ty: 0 };
532
+ this._dragState = null;
533
+ this._rotation = 0;
534
+ this._cropRect = { x: 0, y: 0, width: 1, height: 1 };
535
+ this._cropDragState = null;
536
+ this._cropToolActive = false;
537
+ if (swiperElement) {
538
+ this._applyZoom(swiperElement);
539
+ this._updateEditImageSize(swiperElement);
540
+ }
541
+ }
542
+ _setupWheelZoom(swiperElement) {
543
+ this._cleanupWheelZoom();
544
+ if (!this.mouseZoomEnabled)
545
+ return;
546
+ const el = swiperElement?.nativeElement ?? swiperElement;
547
+ if (!el)
548
+ return;
549
+ this._wheelListener = (event) => {
550
+ event.preventDefault();
551
+ const step = this.wheelZoomStep;
552
+ const oldScale = this._zoomState.scale;
553
+ const newScale = Math.max(1, Math.min(oldScale + (event.deltaY < 0 ? step : -step), this.maxZoomRatio));
554
+ if (newScale === oldScale)
555
+ return;
556
+ if (newScale === 1) {
557
+ this._zoomState = { scale: 1, tx: 0, ty: 0 };
558
+ }
559
+ else {
560
+ // Zoom towards cursor position
561
+ const swiper = this.getSwiper(el);
562
+ const activeSlide = swiper?.slides?.[swiper.activeIndex];
563
+ if (activeSlide) {
564
+ const rect = activeSlide.getBoundingClientRect();
565
+ const cx = event.clientX - rect.left - rect.width / 2;
566
+ const cy = event.clientY - rect.top - rect.height / 2;
567
+ this._zoomState.tx = cx - ((cx - this._zoomState.tx) * newScale) / oldScale;
568
+ this._zoomState.ty = cy - ((cy - this._zoomState.ty) * newScale) / oldScale;
569
+ }
570
+ this._zoomState.scale = newScale;
571
+ this._clampTranslations(el);
572
+ }
573
+ this._applyZoom(el);
574
+ };
575
+ el.addEventListener('wheel', this._wheelListener, { passive: false });
576
+ this._wheelEl = el;
577
+ }
578
+ _cleanupWheelZoom() {
579
+ if (this._wheelEl && this._wheelListener) {
580
+ this._wheelEl.removeEventListener('wheel', this._wheelListener);
581
+ }
582
+ this._wheelEl = null;
583
+ this._wheelListener = null;
584
+ }
585
+ _applyZoom(swiperElement) {
586
+ const swiper = this.getSwiper(swiperElement);
587
+ const activeSlide = swiper?.slides?.[swiper.activeIndex];
588
+ if (!activeSlide)
589
+ return;
590
+ const wrapEl = activeSlide.querySelector('.swiper-zoom-container');
591
+ const imgEl = activeSlide.querySelector('.swiper-zoom-target, .swiper-zoom-container > img');
592
+ if (!wrapEl || !imgEl)
593
+ return;
594
+ const { scale, tx, ty } = this._zoomState;
595
+ wrapEl.style.transitionDuration = '0ms';
596
+ wrapEl.style.transform = `translate3d(${tx}px, ${ty}px, 0)`;
597
+ imgEl.style.transitionDuration = '0ms';
598
+ imgEl.style.transform = `translate3d(0, 0, 0) scale(${scale}) rotate(${this._rotation}deg)`;
599
+ wrapEl.style.cursor = scale > 1 ? 'grab' : '';
600
+ }
601
+ _clampTranslations(swiperElement) {
602
+ const swiper = this.getSwiper(swiperElement);
603
+ const activeSlide = swiper?.slides?.[swiper.activeIndex];
604
+ if (!activeSlide)
605
+ return;
606
+ const imgEl = activeSlide.querySelector('.swiper-zoom-target, .swiper-zoom-container > img');
607
+ if (!imgEl)
608
+ return;
609
+ const imgW = imgEl.offsetWidth || imgEl.clientWidth;
610
+ const imgH = imgEl.offsetHeight || imgEl.clientHeight;
611
+ if (!imgW || !imgH)
612
+ return;
613
+ const { scale } = this._zoomState;
614
+ const maxTx = Math.max(0, (imgW * scale - activeSlide.offsetWidth) / 2);
615
+ const maxTy = Math.max(0, (imgH * scale - activeSlide.offsetHeight) / 2);
616
+ this._zoomState.tx = Math.max(-maxTx, Math.min(maxTx, this._zoomState.tx));
617
+ this._zoomState.ty = Math.max(-maxTy, Math.min(maxTy, this._zoomState.ty));
618
+ }
619
+ _getActiveZoomWrapEl(swiperElement) {
620
+ const swiper = this.getSwiper(swiperElement);
621
+ const activeSlide = swiper?.slides?.[swiper.activeIndex];
622
+ return activeSlide?.querySelector('.swiper-zoom-container') ?? null;
623
+ }
624
+ markForCheck() {
625
+ this.cd.markForCheck();
626
+ }
627
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppImageGallerySlideshowComponent, deps: [{ token: i1.AlertController }, { token: i2.TranslateService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
628
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AppImageGallerySlideshowComponent, selector: "app-image-gallery-slideshow", inputs: { rows: "rows", selectedIndex: "selectedIndex", mobile: ["mobile", "mobile", booleanAttribute], readOnly: ["readOnly", "readOnly", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], debug: ["debug", "debug", booleanAttribute], mouseZoomEnabled: ["mouseZoomEnabled", "mouseZoomEnabled", booleanAttribute], wheelZoomStep: "wheelZoomStep", maxZoomRatio: "maxZoomRatio", rotationEnabled: ["rotationEnabled", "rotationEnabled", booleanAttribute], cropEnabled: ["cropEnabled", "cropEnabled", booleanAttribute], imageExportFormat: "imageExportFormat", imageExportQuality: "imageExportQuality", imageSizeQueryParam: "imageSizeQueryParam", modalImageSize: "modalImageSize" }, outputs: { deleteRequested: "deleteRequested", closeRequested: "closeRequested", afterEditImage: "afterEditImage", afterEditRow: "afterEditRow" }, host: { listeners: { "window:resize": "onWindowResize()" } }, viewQueries: [{ propertyName: "swiperRef", first: true, predicate: ["slideshowSwiper"], descendants: true }], ngImport: i0, template: "<ion-content\n class=\"ion-no-padding ion-no-margin\"\n (mousedown)=\"onPointerDown($event, slideshowSwiper)\"\n (mousemove)=\"onPointerMove($event, slideshowSwiper)\"\n (mouseup)=\"onPointerUp($event, slideshowSwiper)\"\n (mouseleave)=\"onPointerUp($event, slideshowSwiper)\"\n (touchmove)=\"onCropPointerMove($event, slideshowSwiper)\"\n (touchend)=\"onCropPointerUp($event, slideshowSwiper)\"\n (touchcancel)=\"onCropPointerUp($event, slideshowSwiper)\"\n>\n @let count = rows?.length || 0;\n <!-- Counter -->\n <div class=\"top-left\">\n <ion-label color=\"light\">{{ activeSlideIndex + 1 }} / {{ count }}</ion-label>\n </div>\n\n <!-- zoom / rotation / crop buttons: all shown together, no \"enter edit mode\" step needed -->\n <div class=\"top-right\">\n @if (!mobile) {\n <ion-fab-button size=\"small\" (click)=\"zoom(slideshowSwiper, true)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"add\"></ion-icon>\n </ion-fab-button>\n <ion-fab-button size=\"small\" (click)=\"zoom(slideshowSwiper, false)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"remove\"></ion-icon>\n </ion-fab-button>\n }\n @if (!readOnly && rotationEnabled) {\n <ion-fab-button\n size=\"small\"\n (click)=\"rotate(slideshowSwiper, 'left')\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_LEFT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_LEFT' | translate) : ''\"\n >\n <mat-icon>rotate_left</mat-icon>\n </ion-fab-button>\n <ion-fab-button\n size=\"small\"\n (click)=\"rotate(slideshowSwiper, 'right')\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_RIGHT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_RIGHT' | translate) : ''\"\n >\n <mat-icon>rotate_right</mat-icon>\n </ion-fab-button>\n }\n @if (!readOnly && cropEnabled) {\n <ion-fab-button\n size=\"small\"\n (click)=\"toggleCropTool(slideshowSwiper)\"\n [color]=\"_cropToolActive ? 'tertiary' : 'light'\"\n [title]=\"\n !showTooltip\n ? ((_cropToolActive ? 'IMAGE.GALLERY.BTN_CROP_DEACTIVATE' : 'IMAGE.GALLERY.BTN_CROP_ACTIVATE') | translate)\n : ''\n \"\n [matTooltip]=\"\n showTooltip\n ? ((_cropToolActive ? 'IMAGE.GALLERY.BTN_CROP_DEACTIVATE' : 'IMAGE.GALLERY.BTN_CROP_ACTIVATE') | translate)\n : ''\n \"\n >\n <mat-icon>crop</mat-icon>\n </ion-fab-button>\n @if (_cropToolActive) {\n <ion-fab-button\n size=\"small\"\n (click)=\"resetCrop(slideshowSwiper)\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_CROP_RESET' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_CROP_RESET' | translate) : ''\"\n >\n <mat-icon>crop_free</mat-icon>\n </ion-fab-button>\n }\n }\n </div>\n\n <!-- navigation side buttons next/back -->\n <ng-container *ngIf=\"!mobile && (rows | isArrayLength: { greaterThan: 1 })\">\n <div class=\"side-buttons prev\" (click)=\"slidePrev(slideshowSwiper)\">\n <ion-icon name=\"chevron-back\"></ion-icon>\n </div>\n <div class=\"side-buttons next\" (click)=\"slideNext(slideshowSwiper)\">\n <ion-icon name=\"chevron-forward\"></ion-icon>\n </div>\n </ng-container>\n\n <!-- image slides -->\n <swiper-container\n #slideshowSwiper\n [modules]=\"swiperModules\"\n [zoom]=\"!mouseZoomEnabled\"\n (swiperslidechange)=\"onSlideChange(slideshowSwiper)\"\n (swiperinit)=\"onSwiperInit(slideshowSwiper)\"\n >\n @for (row of rows; track row.id; let i = $index) {\n <swiper-slide>\n @let image = row | propertyGet: 'currentData';\n <div class=\"swiper-zoom-container\">\n @if (image.url) {\n <ion-img class=\"swiper-zoom-target\" [src]=\"image.url | appendQueryParams: getImageSizeQueryParams()\" />\n } @else {\n <img [src]=\"image.dataUrl\" [alt]=\"image.title\" />\n }\n\n <!-- crop overlay: only rendered for the active slide -->\n @if (!readOnly && cropEnabled && _cropToolActive && i === activeSlideIndex) {\n <div\n class=\"crop-overlay\"\n (mousedown)=\"onCropPointerDown($event, slideshowSwiper, 'move')\"\n (touchstart)=\"onCropPointerDown($event, slideshowSwiper, 'move')\"\n >\n <!-- darkened mask around the selection: clipped to the image bounds -->\n <div class=\"crop-mask\">\n <div\n class=\"crop-mask-rect\"\n [style.left.%]=\"_cropRect.x * 100\"\n [style.top.%]=\"_cropRect.y * 100\"\n [style.width.%]=\"_cropRect.width * 100\"\n [style.height.%]=\"_cropRect.height * 100\"\n ></div>\n </div>\n\n <!-- interactive selection border, grid & drag handles: never clipped, so corner/edge handles\n remain fully grabbable even when the selection touches the image bounds -->\n <div\n class=\"crop-rect\"\n [style.left.%]=\"_cropRect.x * 100\"\n [style.top.%]=\"_cropRect.y * 100\"\n [style.width.%]=\"_cropRect.width * 100\"\n [style.height.%]=\"_cropRect.height * 100\"\n >\n <div class=\"crop-grid\">\n <span class=\"line v\" style=\"left: 33.333%\"></span>\n <span class=\"line v\" style=\"left: 66.666%\"></span>\n <span class=\"line h\" style=\"top: 33.333%\"></span>\n <span class=\"line h\" style=\"top: 66.666%\"></span>\n </div>\n @for (handle of _cropHandles; track handle) {\n <div\n class=\"crop-handle {{ handle }}\"\n (mousedown)=\"onCropPointerDown($event, slideshowSwiper, handle)\"\n (touchstart)=\"onCropPointerDown($event, slideshowSwiper, handle)\"\n ></div>\n }\n </div>\n </div>\n }\n </div>\n </swiper-slide>\n }\n </swiper-container>\n</ion-content>\n\n<ion-footer>\n <ion-toolbar color=\"light\">\n @if (!hasPendingEdit) {\n <ion-row>\n <ion-col size=\"4\" class=\"ion-text-start\">\n <ion-button\n *ngIf=\"!readOnly\"\n (click)=\"requestDelete(slideshowSwiper)\"\n [disabled]=\"disabled\"\n fill=\"clear\"\n color=\"danger\"\n >\n <mat-icon slot=\"start\">delete</mat-icon>\n {{ 'COMMON.BTN_DELETE' | translate }}\n </ion-button>\n </ion-col>\n <ion-col size=\"4\"></ion-col>\n <ion-col size=\"4\" class=\"ion-text-end\">\n <ion-button (click)=\"closeRequested.emit()\" color=\"tertiary\" fill=\"clear\" *ngIf=\"mobile; else desktop\">\n <ion-icon slot=\"start\" name=\"close\"></ion-icon>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n <ng-template #desktop>\n <ion-button (click)=\"closeRequested.emit()\" color=\"tertiary\">\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ng-template>\n </ion-col>\n </ion-row>\n } @else {\n <!-- A crop or rotation change is pending: ask for an explicit validation before exporting the image -->\n <ion-row>\n <ion-col size=\"6\" class=\"ion-text-start\">\n <ion-button (click)=\"discardEdit(slideshowSwiper)\" color=\"medium\" fill=\"clear\">\n <ion-icon slot=\"start\" name=\"close\"></ion-icon>\n {{ 'COMMON.BTN_CANCEL' | translate }}\n </ion-button>\n </ion-col>\n <ion-col size=\"6\" class=\"ion-text-end\">\n <ion-button (click)=\"validateEdit(slideshowSwiper)\" color=\"tertiary\">\n <ion-icon slot=\"start\" name=\"checkmark\"></ion-icon>\n {{ 'COMMON.BTN_VALIDATE' | translate }}\n </ion-button>\n </ion-col>\n </ion-row>\n }\n </ion-toolbar>\n</ion-footer>\n", styles: [":host{display:flex;flex-direction:column;height:100%;-webkit-user-select:none;user-select:none}:host ion-content{flex:1 1 auto}:host .top-left{z-index:14;position:absolute;display:inline-grid;left:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}:host .top-right{z-index:14;position:absolute;display:inline-grid;right:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}:host .side-buttons{--side-buttons-width: 120px;--side-buttons-margin: calc(var(--side-buttons-width) / 3);z-index:13;position:absolute;top:0;bottom:0;width:var(--side-buttons-width)}:host .side-buttons ion-icon{top:50%;color:#fff;font-size:2.5rem;position:absolute;opacity:0;transition-duration:.15s;transition-timing-function:ease-in}:host .side-buttons:hover{cursor:pointer}:host .side-buttons:hover ion-icon{opacity:1}:host .side-buttons.prev{left:0}:host .side-buttons.prev ion-icon{left:var(--side-buttons-margin)}:host .side-buttons.next{right:0}:host .side-buttons.next ion-icon{right:var(--side-buttons-margin)}:host swiper-container{height:100%;background:#000}:host .swiper-zoom-container{position:relative}:host ion-fab-button .icon-secondary{top:11px;left:6px;height:14px}:host .crop-overlay{position:absolute;z-index:15;cursor:move;touch-action:none;animation:crop-overlay-fade-in .15s ease-in}:host .crop-overlay .crop-mask{position:absolute;inset:0;overflow:hidden;pointer-events:none}:host .crop-overlay .crop-mask .crop-mask-rect{position:absolute;box-shadow:0 0 0 2000px #0009}:host .crop-overlay .crop-rect{position:absolute;box-sizing:border-box;border:1px solid rgba(255,255,255,.95);cursor:move;touch-action:none}:host .crop-overlay .crop-rect .crop-grid{position:absolute;inset:0;pointer-events:none}:host .crop-overlay .crop-rect .crop-grid .line{position:absolute;background:#ffffff73}:host .crop-overlay .crop-rect .crop-grid .line.v{top:0;bottom:0;width:1px}:host .crop-overlay .crop-rect .crop-grid .line.h{left:0;right:0;height:1px}:host .crop-overlay .crop-rect .crop-handle{position:absolute;width:16px;height:16px;margin:-8px;background:#fff;border:1px solid rgba(0,0,0,.45);border-radius:50%;box-shadow:0 1px 4px #00000080;transition:transform .12s ease;touch-action:none}:host .crop-overlay .crop-rect .crop-handle:hover{transform:scale(1.3)}:host .crop-overlay .crop-rect .crop-handle.nw{left:0;top:0;cursor:nwse-resize}:host .crop-overlay .crop-rect .crop-handle.n{left:50%;top:0;cursor:ns-resize}:host .crop-overlay .crop-rect .crop-handle.ne{left:100%;top:0;cursor:nesw-resize}:host .crop-overlay .crop-rect .crop-handle.e{left:100%;top:50%;cursor:ew-resize}:host .crop-overlay .crop-rect .crop-handle.se{left:100%;top:100%;cursor:nwse-resize}:host .crop-overlay .crop-rect .crop-handle.s{left:50%;top:100%;cursor:ns-resize}:host .crop-overlay .crop-rect .crop-handle.sw{left:0;top:100%;cursor:nesw-resize}:host .crop-overlay .crop-rect .crop-handle.w{left:0;top:50%;cursor:ew-resize}@keyframes crop-overlay-fade-in{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i1.IonFabButton, selector: "ion-fab-button", inputs: ["activated", "closeIcon", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "show", "size", "target", "translucent", "type"] }, { kind: "component", type: i1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i1.IonRow, selector: "ion-row" }, { kind: "component", type: i1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: i6.PropertyGetPipe, name: "propertyGet" }, { kind: "pipe", type: i7.ArrayLengthPipe, name: "isArrayLength" }, { kind: "pipe", type: i8.AppendQueryParamsPipePipe, name: "appendQueryParams" }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
629
+ }
630
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppImageGallerySlideshowComponent, decorators: [{
631
+ type: Component,
632
+ args: [{ selector: 'app-image-gallery-slideshow', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-content\n class=\"ion-no-padding ion-no-margin\"\n (mousedown)=\"onPointerDown($event, slideshowSwiper)\"\n (mousemove)=\"onPointerMove($event, slideshowSwiper)\"\n (mouseup)=\"onPointerUp($event, slideshowSwiper)\"\n (mouseleave)=\"onPointerUp($event, slideshowSwiper)\"\n (touchmove)=\"onCropPointerMove($event, slideshowSwiper)\"\n (touchend)=\"onCropPointerUp($event, slideshowSwiper)\"\n (touchcancel)=\"onCropPointerUp($event, slideshowSwiper)\"\n>\n @let count = rows?.length || 0;\n <!-- Counter -->\n <div class=\"top-left\">\n <ion-label color=\"light\">{{ activeSlideIndex + 1 }} / {{ count }}</ion-label>\n </div>\n\n <!-- zoom / rotation / crop buttons: all shown together, no \"enter edit mode\" step needed -->\n <div class=\"top-right\">\n @if (!mobile) {\n <ion-fab-button size=\"small\" (click)=\"zoom(slideshowSwiper, true)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"add\"></ion-icon>\n </ion-fab-button>\n <ion-fab-button size=\"small\" (click)=\"zoom(slideshowSwiper, false)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"remove\"></ion-icon>\n </ion-fab-button>\n }\n @if (!readOnly && rotationEnabled) {\n <ion-fab-button\n size=\"small\"\n (click)=\"rotate(slideshowSwiper, 'left')\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_LEFT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_LEFT' | translate) : ''\"\n >\n <mat-icon>rotate_left</mat-icon>\n </ion-fab-button>\n <ion-fab-button\n size=\"small\"\n (click)=\"rotate(slideshowSwiper, 'right')\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_RIGHT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_RIGHT' | translate) : ''\"\n >\n <mat-icon>rotate_right</mat-icon>\n </ion-fab-button>\n }\n @if (!readOnly && cropEnabled) {\n <ion-fab-button\n size=\"small\"\n (click)=\"toggleCropTool(slideshowSwiper)\"\n [color]=\"_cropToolActive ? 'tertiary' : 'light'\"\n [title]=\"\n !showTooltip\n ? ((_cropToolActive ? 'IMAGE.GALLERY.BTN_CROP_DEACTIVATE' : 'IMAGE.GALLERY.BTN_CROP_ACTIVATE') | translate)\n : ''\n \"\n [matTooltip]=\"\n showTooltip\n ? ((_cropToolActive ? 'IMAGE.GALLERY.BTN_CROP_DEACTIVATE' : 'IMAGE.GALLERY.BTN_CROP_ACTIVATE') | translate)\n : ''\n \"\n >\n <mat-icon>crop</mat-icon>\n </ion-fab-button>\n @if (_cropToolActive) {\n <ion-fab-button\n size=\"small\"\n (click)=\"resetCrop(slideshowSwiper)\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_CROP_RESET' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_CROP_RESET' | translate) : ''\"\n >\n <mat-icon>crop_free</mat-icon>\n </ion-fab-button>\n }\n }\n </div>\n\n <!-- navigation side buttons next/back -->\n <ng-container *ngIf=\"!mobile && (rows | isArrayLength: { greaterThan: 1 })\">\n <div class=\"side-buttons prev\" (click)=\"slidePrev(slideshowSwiper)\">\n <ion-icon name=\"chevron-back\"></ion-icon>\n </div>\n <div class=\"side-buttons next\" (click)=\"slideNext(slideshowSwiper)\">\n <ion-icon name=\"chevron-forward\"></ion-icon>\n </div>\n </ng-container>\n\n <!-- image slides -->\n <swiper-container\n #slideshowSwiper\n [modules]=\"swiperModules\"\n [zoom]=\"!mouseZoomEnabled\"\n (swiperslidechange)=\"onSlideChange(slideshowSwiper)\"\n (swiperinit)=\"onSwiperInit(slideshowSwiper)\"\n >\n @for (row of rows; track row.id; let i = $index) {\n <swiper-slide>\n @let image = row | propertyGet: 'currentData';\n <div class=\"swiper-zoom-container\">\n @if (image.url) {\n <ion-img class=\"swiper-zoom-target\" [src]=\"image.url | appendQueryParams: getImageSizeQueryParams()\" />\n } @else {\n <img [src]=\"image.dataUrl\" [alt]=\"image.title\" />\n }\n\n <!-- crop overlay: only rendered for the active slide -->\n @if (!readOnly && cropEnabled && _cropToolActive && i === activeSlideIndex) {\n <div\n class=\"crop-overlay\"\n (mousedown)=\"onCropPointerDown($event, slideshowSwiper, 'move')\"\n (touchstart)=\"onCropPointerDown($event, slideshowSwiper, 'move')\"\n >\n <!-- darkened mask around the selection: clipped to the image bounds -->\n <div class=\"crop-mask\">\n <div\n class=\"crop-mask-rect\"\n [style.left.%]=\"_cropRect.x * 100\"\n [style.top.%]=\"_cropRect.y * 100\"\n [style.width.%]=\"_cropRect.width * 100\"\n [style.height.%]=\"_cropRect.height * 100\"\n ></div>\n </div>\n\n <!-- interactive selection border, grid & drag handles: never clipped, so corner/edge handles\n remain fully grabbable even when the selection touches the image bounds -->\n <div\n class=\"crop-rect\"\n [style.left.%]=\"_cropRect.x * 100\"\n [style.top.%]=\"_cropRect.y * 100\"\n [style.width.%]=\"_cropRect.width * 100\"\n [style.height.%]=\"_cropRect.height * 100\"\n >\n <div class=\"crop-grid\">\n <span class=\"line v\" style=\"left: 33.333%\"></span>\n <span class=\"line v\" style=\"left: 66.666%\"></span>\n <span class=\"line h\" style=\"top: 33.333%\"></span>\n <span class=\"line h\" style=\"top: 66.666%\"></span>\n </div>\n @for (handle of _cropHandles; track handle) {\n <div\n class=\"crop-handle {{ handle }}\"\n (mousedown)=\"onCropPointerDown($event, slideshowSwiper, handle)\"\n (touchstart)=\"onCropPointerDown($event, slideshowSwiper, handle)\"\n ></div>\n }\n </div>\n </div>\n }\n </div>\n </swiper-slide>\n }\n </swiper-container>\n</ion-content>\n\n<ion-footer>\n <ion-toolbar color=\"light\">\n @if (!hasPendingEdit) {\n <ion-row>\n <ion-col size=\"4\" class=\"ion-text-start\">\n <ion-button\n *ngIf=\"!readOnly\"\n (click)=\"requestDelete(slideshowSwiper)\"\n [disabled]=\"disabled\"\n fill=\"clear\"\n color=\"danger\"\n >\n <mat-icon slot=\"start\">delete</mat-icon>\n {{ 'COMMON.BTN_DELETE' | translate }}\n </ion-button>\n </ion-col>\n <ion-col size=\"4\"></ion-col>\n <ion-col size=\"4\" class=\"ion-text-end\">\n <ion-button (click)=\"closeRequested.emit()\" color=\"tertiary\" fill=\"clear\" *ngIf=\"mobile; else desktop\">\n <ion-icon slot=\"start\" name=\"close\"></ion-icon>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n <ng-template #desktop>\n <ion-button (click)=\"closeRequested.emit()\" color=\"tertiary\">\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ng-template>\n </ion-col>\n </ion-row>\n } @else {\n <!-- A crop or rotation change is pending: ask for an explicit validation before exporting the image -->\n <ion-row>\n <ion-col size=\"6\" class=\"ion-text-start\">\n <ion-button (click)=\"discardEdit(slideshowSwiper)\" color=\"medium\" fill=\"clear\">\n <ion-icon slot=\"start\" name=\"close\"></ion-icon>\n {{ 'COMMON.BTN_CANCEL' | translate }}\n </ion-button>\n </ion-col>\n <ion-col size=\"6\" class=\"ion-text-end\">\n <ion-button (click)=\"validateEdit(slideshowSwiper)\" color=\"tertiary\">\n <ion-icon slot=\"start\" name=\"checkmark\"></ion-icon>\n {{ 'COMMON.BTN_VALIDATE' | translate }}\n </ion-button>\n </ion-col>\n </ion-row>\n }\n </ion-toolbar>\n</ion-footer>\n", styles: [":host{display:flex;flex-direction:column;height:100%;-webkit-user-select:none;user-select:none}:host ion-content{flex:1 1 auto}:host .top-left{z-index:14;position:absolute;display:inline-grid;left:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}:host .top-right{z-index:14;position:absolute;display:inline-grid;right:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}:host .side-buttons{--side-buttons-width: 120px;--side-buttons-margin: calc(var(--side-buttons-width) / 3);z-index:13;position:absolute;top:0;bottom:0;width:var(--side-buttons-width)}:host .side-buttons ion-icon{top:50%;color:#fff;font-size:2.5rem;position:absolute;opacity:0;transition-duration:.15s;transition-timing-function:ease-in}:host .side-buttons:hover{cursor:pointer}:host .side-buttons:hover ion-icon{opacity:1}:host .side-buttons.prev{left:0}:host .side-buttons.prev ion-icon{left:var(--side-buttons-margin)}:host .side-buttons.next{right:0}:host .side-buttons.next ion-icon{right:var(--side-buttons-margin)}:host swiper-container{height:100%;background:#000}:host .swiper-zoom-container{position:relative}:host ion-fab-button .icon-secondary{top:11px;left:6px;height:14px}:host .crop-overlay{position:absolute;z-index:15;cursor:move;touch-action:none;animation:crop-overlay-fade-in .15s ease-in}:host .crop-overlay .crop-mask{position:absolute;inset:0;overflow:hidden;pointer-events:none}:host .crop-overlay .crop-mask .crop-mask-rect{position:absolute;box-shadow:0 0 0 2000px #0009}:host .crop-overlay .crop-rect{position:absolute;box-sizing:border-box;border:1px solid rgba(255,255,255,.95);cursor:move;touch-action:none}:host .crop-overlay .crop-rect .crop-grid{position:absolute;inset:0;pointer-events:none}:host .crop-overlay .crop-rect .crop-grid .line{position:absolute;background:#ffffff73}:host .crop-overlay .crop-rect .crop-grid .line.v{top:0;bottom:0;width:1px}:host .crop-overlay .crop-rect .crop-grid .line.h{left:0;right:0;height:1px}:host .crop-overlay .crop-rect .crop-handle{position:absolute;width:16px;height:16px;margin:-8px;background:#fff;border:1px solid rgba(0,0,0,.45);border-radius:50%;box-shadow:0 1px 4px #00000080;transition:transform .12s ease;touch-action:none}:host .crop-overlay .crop-rect .crop-handle:hover{transform:scale(1.3)}:host .crop-overlay .crop-rect .crop-handle.nw{left:0;top:0;cursor:nwse-resize}:host .crop-overlay .crop-rect .crop-handle.n{left:50%;top:0;cursor:ns-resize}:host .crop-overlay .crop-rect .crop-handle.ne{left:100%;top:0;cursor:nesw-resize}:host .crop-overlay .crop-rect .crop-handle.e{left:100%;top:50%;cursor:ew-resize}:host .crop-overlay .crop-rect .crop-handle.se{left:100%;top:100%;cursor:nwse-resize}:host .crop-overlay .crop-rect .crop-handle.s{left:50%;top:100%;cursor:ns-resize}:host .crop-overlay .crop-rect .crop-handle.sw{left:0;top:100%;cursor:nesw-resize}:host .crop-overlay .crop-rect .crop-handle.w{left:0;top:50%;cursor:ew-resize}@keyframes crop-overlay-fade-in{0%{opacity:0}to{opacity:1}}\n"] }]
633
+ }], ctorParameters: () => [{ type: i1.AlertController }, { type: i2.TranslateService }, { type: i0.ChangeDetectorRef }], propDecorators: { rows: [{
634
+ type: Input
635
+ }], selectedIndex: [{
636
+ type: Input
637
+ }], mobile: [{
638
+ type: Input,
639
+ args: [{ transform: booleanAttribute }]
640
+ }], readOnly: [{
641
+ type: Input,
642
+ args: [{ transform: booleanAttribute }]
643
+ }], disabled: [{
644
+ type: Input,
645
+ args: [{ transform: booleanAttribute }]
646
+ }], debug: [{
647
+ type: Input,
648
+ args: [{ transform: booleanAttribute }]
649
+ }], mouseZoomEnabled: [{
650
+ type: Input,
651
+ args: [{ transform: booleanAttribute }]
652
+ }], wheelZoomStep: [{
653
+ type: Input
654
+ }], maxZoomRatio: [{
655
+ type: Input
656
+ }], rotationEnabled: [{
657
+ type: Input,
658
+ args: [{ transform: booleanAttribute }]
659
+ }], cropEnabled: [{
660
+ type: Input,
661
+ args: [{ transform: booleanAttribute }]
662
+ }], imageExportFormat: [{
663
+ type: Input
664
+ }], imageExportQuality: [{
665
+ type: Input
666
+ }], imageSizeQueryParam: [{
667
+ type: Input
668
+ }], modalImageSize: [{
669
+ type: Input
670
+ }], deleteRequested: [{
671
+ type: Output
672
+ }], closeRequested: [{
673
+ type: Output
674
+ }], afterEditImage: [{
675
+ type: Output
676
+ }], afterEditRow: [{
677
+ type: Output
678
+ }], swiperRef: [{
679
+ type: ViewChild,
680
+ args: ['slideshowSwiper']
681
+ }], onWindowResize: [{
682
+ type: HostListener,
683
+ args: ['window:resize']
684
+ }] } });
685
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1hZ2UtZ2FsbGVyeS1zbGlkZXNob3cuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vc3JjL2FwcC9zaGFyZWQvaW1hZ2UvZ2FsbGVyeS9zbGlkZXNob3cvaW1hZ2UtZ2FsbGVyeS1zbGlkZXNob3cuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vc3JjL2FwcC9zaGFyZWQvaW1hZ2UvZ2FsbGVyeS9zbGlkZXNob3cvaW1hZ2UtZ2FsbGVyeS1zbGlkZXNob3cuY29tcG9uZW50Lmh0bWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUNMLGdCQUFnQixFQUNoQix1QkFBdUIsRUFFdkIsU0FBUyxFQUVULFlBQVksRUFDWixZQUFZLEVBQ1osTUFBTSxFQUNOLEtBQUssRUFFTCxNQUFNLEVBQ04sU0FBUyxHQUNWLE1BQU0sZUFBZSxDQUFDO0FBRXZCLE9BQU8sRUFBbUIsV0FBVyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFJOUQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ2xELE9BQU8sRUFBRSx5QkFBeUIsRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQzlFLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN6QyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQzs7Ozs7Ozs7OztBQVl0RDs7OztHQUlHO0FBT0gsTUFBTSxPQUFPLGlDQUFpQztJQW9EaEM7SUFDQTtJQUNBO0lBckRGLGdCQUFnQixHQUFHLENBQUMsQ0FBQztJQUNyQixhQUFhLEdBQUcsQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUM5QixPQUFPLENBQVM7SUFFMUIsb0NBQW9DO0lBQzFCLFVBQVUsR0FBRyxFQUFFLEtBQUssRUFBRSxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsRUFBRSxFQUFFLEVBQUUs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QUF2QixNQUFNO2dCQUNHLGNBQWM7c0JBQXZCLE1BQU07Z0JBQ0csWUFBWTtzQkFBckIsTUFBTTtnQkFFdUIsU0FBUztzQkFBdEMsU0FBUzt1QkFBQyxpQkFBaUI7Z0JBK01sQixjQUFjO3NCQUR2QixZQUFZO3VCQUFDLGVBQWUiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge1xuICBib29sZWFuQXR0cmlidXRlLFxuICBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneSxcbiAgQ2hhbmdlRGV0ZWN0b3JSZWYsXG4gIENvbXBvbmVudCxcbiAgRWxlbWVudFJlZixcbiAgRXZlbnRFbWl0dGVyLFxuICBIb3N0TGlzdGVuZXIsXG4gIGluamVjdCxcbiAgSW5wdXQsXG4gIE9uRGVzdHJveSxcbiAgT3V0cHV0LFxuICBWaWV3Q2hpbGQsXG59IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgVGFibGVFbGVtZW50IH0gZnJvbSAnQGUtaXMvbmd4LW1hdGVyaWFsLXRhYmxlJztcbmltcG9ydCB7IEFsZXJ0Q29udHJvbGxlciwgSW9uaWNTbGlkZXMgfSBmcm9tICdAaW9uaWMvYW5ndWxhcic7XG5pbXBvcnQgeyBUcmFuc2xhdGVTZXJ2aWNlIH0gZnJvbSAnQG5neC10cmFuc2xhdGUvY29yZSc7XG5pbXBvcnQgeyBTd2lwZXIgfSBmcm9tICdzd2lwZXIvdHlwZXMnO1xuaW1wb3J0IHsgSW1hZ2UsIEltYWdlU2l6ZSB9IGZyb20gJy4uLy4uL2ltYWdlLm1vZGVsJztcbmltcG9ydCB7IGlzTmlsT3JCbGFuayB9IGZyb20gJy4uLy4uLy4uL2Z1bmN0aW9ucyc7XG5pbXBvcnQgeyBjcmVhdGVQcm9taXNlRXZlbnRFbWl0dGVyLCBlbWl0UHJvbWlzZUV2ZW50IH0gZnJvbSAnLi4vLi4vLi4vZXZlbnRzJztcbmltcG9ydCB7IEFsZXJ0cyB9IGZyb20gJy4uLy4uLy4uL2FsZXJ0cyc7XG5pbXBvcnQgeyBBUFBfU0hPV19UT09MVElQIH0gZnJvbSAnLi4vLi4vLi4vY29uc3RhbnRzJztcbmltcG9ydCB7SW1hZ2VFZGl0RXZlbnQsIEltYWdlRXhwb3J0Rm9ybWF0fSBmcm9tICcuLi9pbWFnZS1nYWxsZXJ5Lm1vZGVsJztcblxuaW50ZXJmYWNlIENyb3BSZWN0IHtcbiAgeDogbnVtYmVyO1xuICB5OiBudW1iZXI7XG4gIHdpZHRoOiBudW1iZXI7XG4gIGhlaWdodDogbnVtYmVyO1xufVxuXG50eXBlIENyb3BIYW5kbGUgPSAnbW92ZScgfCAnbncnIHwgJ24nIHwgJ25lJyB8ICdlJyB8ICdzZScgfCAncycgfCAnc3cnIHwgJ3cnO1xuXG4vKipcbiAqIFRoZSB6b29tL3Bhbi9yb3RhdGUvY3JvcCBzbGlkZXNob3cgc2hvd24gd2hlbiBjbGlja2luZyBhbiBpbWFnZSBpbiBgQXBwSW1hZ2VHYWxsZXJ5Q29tcG9uZW50YC5cbiAqIEV4dHJhY3RlZCBpbnRvIGl0cyBvd24gY29tcG9uZW50IHNvIHRoZSBleGFjdCBzYW1lIHNsaWRlc2hvdyBjYW4gYmUgcHJlc2VudGVkIGVpdGhlciBpbnNpZGUgYW5cbiAqIGBpb24tbW9kYWxgIChkZWZhdWx0KSwgb3IgaW5saW5lIGFzIGFuIG92ZXJsYXkgZGlyZWN0bHkgd2l0aGluIHRoZSBnYWxsZXJ5LCB3aXRob3V0IGFueSBkdXBsaWNhdGlvbi5cbiAqL1xuQENvbXBvbmVudCh7XG4gIHNlbGVjdG9yOiAnYXBwLWltYWdlLWdhbGxlcnktc2xpZGVzaG93JyxcbiAgdGVtcGxhdGVVcmw6ICcuL2ltYWdlLWdhbGxlcnktc2xpZGVzaG93LmNvbXBvbmVudC5odG1sJyxcbiAgc3R5bGVVcmxzOiBbJy4vaW1hZ2UtZ2FsbGVyeS1zbGlkZXNob3cuY29tcG9uZW50LnNjc3MnXSxcbiAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2gsXG59KVxuZXhwb3J0IGNsYXNzIEFwcEltYWdlR2FsbGVyeVNsaWRlc2hvd0NvbXBvbmVudDxUIGV4dGVuZHMgSW1hZ2UgPSBJbWFnZT4gaW1wbGVtZW50cyBPbkRlc3Ryb3kge1xuICBwcm90ZWN0ZWQgYWN0aXZlU2xpZGVJbmRleCA9IDA7XG4gIHByb3RlY3RlZCBzd2lwZXJNb2R1bGVzID0gW0lvbmljU2xpZGVzXTtcbiAgcHJvdGVjdGVkIF9zd2lwZXI6IFN3aXBlcjtcblxuICAvLyBNb3VzZSB3aGVlbCB6b29tICYgcm90YXRpb24gc3RhdGVcbiAgcHJvdGVjdGVkIF96b29tU3RhdGUgPSB7IHNjYWxlOiAxLCB0eDogMCwgdHk6IDAgfTtcbiAgcHJvdGVjdGVkIF9yb3RhdGlvbiA9IDA7IC8vIGRlZ3JlZXM6IDAsIDkwLCAxODAsIDI3MFxuICBwcm90ZWN0ZWQgX2RyYWdTdGF0ZTogeyBzdGFydFg6IG51bWJlcjsgc3RhcnRZOiBudW1iZXI7IHN0YXJ0VHg6IG51bWJlcjsgc3RhcnRUeTogbnVtYmVyIH0gfCBudWxsID0gbnVsbDtcbiAgcHJvdGVjdGVkIF93aGVlbExpc3RlbmVyOiAoKGU6IFdoZWVsRXZlbnQpID0+IHZvaWQpIHwgbnVsbCA9IG51bGw7XG4gIHByb3RlY3RlZCBfd2hlZWxFbDogSFRNTEVsZW1lbnQgfCBudWxsID0gbnVsbDtcblxuICAvLyBDcm9wICYgcm90YXRlIHN0YXRlLiBSb3RhdGlvbiBpcyBhbHdheXMgYXZhaWxhYmxlOyB0aGUgY3JvcCBncmlkL2hhbmRsZXMgb25seSBzaG93IG9uY2UgdGhlXG4gIC8vIGNyb3AgdG9vbCBoYXMgYmVlbiBleHBsaWNpdGx5IGFjdGl2YXRlZCAoZmFiIGJ1dHRvbikuIFZhbGlkYXRlL2NhbmNlbCBzaG93IHVwIG9uY2Ugc29tZXRoaW5nIGNoYW5nZWQuXG4gIHByb3RlY3RlZCBfY3JvcFRvb2xBY3RpdmUgPSBmYWxzZTtcbiAgcHJvdGVjdGVkIF9jcm9wUmVjdDogQ3JvcFJlY3QgPSB7IHg6IDAsIHk6IDAsIHdpZHRoOiAxLCBoZWlnaHQ6IDEgfTtcbiAgcHJvdGVjdGVkIF9jcm9wT3ZlcmxheVJlY3Q6IHsgd2lkdGg6IG51bWJlcjsgaGVpZ2h0OiBudW1iZXIgfSB8IG51bGwgPSBudWxsO1xuICBwcm90ZWN0ZWQgX2Nyb3BEcmFnU3RhdGU6IHsgaGFuZGxlOiBDcm9wSGFuZGxlOyBzdGFydFg6IG51bWJlcjsgc3RhcnRZOiBudW1iZXI7IHN0YXJ0UmVjdDogQ3JvcFJlY3QgfSB8IG51bGwgPSBudWxsO1xuICBwcm90ZWN0ZWQgcmVhZG9ubHkgX2Nyb3BIYW5kbGVzOiBDcm9wSGFuZGxlW10gPSBbJ253JywgJ24nLCAnbmUnLCAnZScsICdzZScsICdzJywgJ3N3JywgJ3cnXTtcblxuICBASW5wdXQoKSByb3dzOiBUYWJsZUVsZW1lbnQ8VD5bXSB8IFJlYWRvbmx5QXJyYXk8VGFibGVFbGVtZW50PFQ+PiA9IFtdO1xuICBASW5wdXQoKSBzZWxlY3RlZEluZGV4ID0gMDtcbiAgQElucHV0KHsgdHJhbnNmb3JtOiBib29sZWFuQXR0cmlidXRlIH0pIG1vYmlsZSA9IGZhbHNlO1xuICBASW5wdXQoeyB0cmFuc2Zvcm06IGJvb2xlYW5BdHRyaWJ1dGUgfSkgcmVhZE9ubHkgPSBmYWxzZTtcbiAgQElucHV0KHsgdHJhbnNmb3JtOiBib29sZWFuQXR0cmlidXRlIH0pIGRpc2FibGVkID0gZmFsc2U7XG4gIEBJbnB1dCh7IHRyYW5zZm9ybTogYm9vbGVhbkF0dHJpYnV0ZSB9KSBkZWJ1ZyA9IGZhbHNlO1xuICBASW5wdXQoeyB0cmFuc2Zvcm06IGJvb2xlYW5BdHRyaWJ1dGUgfSkgbW91c2Vab29tRW5hYmxlZCA9IGZhbHNlO1xuICBASW5wdXQoKSB3aGVlbFpvb21TdGVwID0gMC4zO1xuICBASW5wdXQoKSBtYXhab29tUmF0aW8gPSAzO1xuICBASW5wdXQoeyB0cmFuc2Zvcm06IGJvb2xlYW5BdHRyaWJ1dGUgfSkgcm90YXRpb25FbmFibGVkID0gdHJ1ZTtcbiAgQElucHV0KHsgdHJhbnNmb3JtOiBib29sZWFuQXR0cmlidXRlIH0pIGNyb3BFbmFibGVkID0gdHJ1ZTtcbiAgQElucHV0KCkgaW1hZ2VFeHBvcnRGb3JtYXQ6IEltYWdlRXhwb3J0Rm9ybWF0ID0gJ3BuZyc7XG4gIEBJbnB1dCgpIGltYWdlRXhwb3J0UXVhbGl0eSA9IDAuOTI7XG4gIEBJbnB1dCgpIGltYWdlU2l6ZVF1ZXJ5UGFyYW0gPSAnc2l6ZSc7XG4gIEBJbnB1dCgpIG1vZGFsSW1hZ2VTaXplOiBJbWFnZVNpemUgfCBzdHJpbmc7XG5cbiAgLyoqIFJlcXVlc3RzIGRlbGV0aW9uIG9mIHRoZSBnaXZlbiByb3cuIFRoZSBjYWxsZXIgKGdhbGxlcnkpIG93bnMgdGhlIGNvbmZpcm1hdGlvbi9kZWxldGlvbiBsb2dpYyBhbmQgcmVzb2x2ZXMgdGhlIHByb21pc2Ugd2l0aCB3aGV0aGVyIGl0IGFjdHVhbGx5IGhhcHBlbmVkLiAqL1xuICBAT3V0cHV0KCkgZGVsZXRlUmVxdWVzdGVkID0gY3JlYXRlUHJvbWlzZUV2ZW50RW1pdHRlcjxib29sZWFuLCB7IHJvdzogVGFibGVFbGVtZW50PFQ+IH0+KCk7XG4gIEBPdXRwdXQoKSBjbG9zZVJlcXVlc3RlZCA9IG5ldyBFdmVudEVtaXR0ZXI8dm9pZD4oKTtcbiAgQE91dHB1dCgpIGFmdGVyRWRpdEltYWdlID0gbmV3IEV2ZW50RW1pdHRlcjxJbWFnZUVkaXRFdmVudDxUPj4oKTtcbiAgQE91dHB1dCgpIGFmdGVyRWRpdFJvdyA9IG5ldyBFdmVudEVtaXR0ZXI8VGFibGVFbGVtZW50PFQ+PigpO1xuXG4gIEBWaWV3Q2hpbGQoJ3NsaWRlc2hvd1N3aXBlcicpIHN3aXBlclJlZjogRWxlbWVudFJlZjtcblxuICBzaG93VG9vbHRpcCA9IGluamVjdChBUFBfU0hPV19UT09MVElQLCB7IG9wdGlvbmFsOiB0cnVlIH0pO1xuXG4gIHByb3RlY3RlZCBnZXQgaGFzUGVuZGluZ0VkaXQoKTogYm9vbGVhbiB7XG4gICAgY29uc3QgciA9IHRoaXMuX2Nyb3BSZWN0O1xuICAgIHJldHVybiB0aGlzLl9yb3RhdGlvbiAhPT0gMCB8fCByLnggIT09IDAgfHwgci55ICE9PSAwIHx8IHIud2lkdGggIT09IDEgfHwgci5oZWlnaHQgIT09IDE7XG4gIH1cblxuICBjb25zdHJ1Y3RvcihcbiAgICBwcm90ZWN0ZWQgYWx0ZXJDdHJsOiBBbGVydENvbnRyb2xsZXIsXG4gICAgcHJvdGVjdGVkIHRyYW5zbGF0ZTogVHJhbnNsYXRlU2VydmljZSxcbiAgICBwcm90ZWN0ZWQgY2Q6IENoYW5nZURldGVjdG9yUmVmXG4gICkge31cblxuICBuZ09uRGVzdHJveSgpIHtcbiAgICB0aGlzLl9jbGVhbnVwV2hlZWxab29tKCk7XG4gIH1cblxuICBwcm90ZWN0ZWQgZ2V0U3dpcGVyKHN3aXBlckVsZW1lbnQ/OiBhbnkpOiBTd2lwZXIge1xuICAgIHRoaXMuX3N3aXBlciA9IChzd2lwZXJFbGVtZW50IHx8IHRoaXMuc3dpcGVyUmVmPy5uYXRpdmVFbGVtZW50KT8uc3dpcGVyIHx8IHRoaXMuX3N3aXBlcjtcbiAgICByZXR1cm4gdGhpcy5fc3dpcGVyO1xuICB9XG5cbiAgcHJvdGVjdGVkIG9uU2xpZGVDaGFuZ2Uoc3dpcGVyRWxlbWVudDogYW55KSB7XG4gICAgY29uc3Qgc3dpcGVyID0gdGhpcy5nZXRTd2lwZXIoc3dpcGVyRWxlbWVudCk7XG4gICAgdGhpcy5hY3RpdmVTbGlkZUluZGV4ID0gc3dpcGVyLmFjdGl2ZUluZGV4O1xuICAgIHRoaXMuX3Jlc2V0Wm9vbShzd2lwZXJFbGVtZW50KTtcbiAgICB0aGlzLl91cGRhdGVTd2lwZXJUb3VjaE1vdmUoc3dpcGVyRWxlbWVudCk7XG4gICAgdGhpcy5tYXJrRm9yQ2hlY2soKTtcbiAgfVxuXG4gIHByb3RlY3RlZCBvblN3aXBlckluaXQoc3dpcGVyRWxlbWVudDogYW55KSB7XG4gICAgdGhpcy5fcmVzZXRab29tKCk7XG4gICAgaWYgKHRoaXMuc2VsZWN0ZWRJbmRleCA+IDApIHtcbiAgICAgIGlmICh0aGlzLmRlYnVnKSBjb25zb2xlLmRlYnVnKGBbaW1hZ2UtZ2FsbGVyeS1zbGlkZXNob3ddIEluaXRpYWxpemluZyBzbGlkZXNob3csIHNlbGVjdGVkSW5kZXg9JHt0aGlzLnNlbGVjdGVkSW5kZXh9YCk7XG4gICAgICAvLyBVc2UgYSBzZXRUaW1lb3V0IG90aGVyd2lzZSB0aGUgc3dpcGVyIGNhbm5vdCBzbGlkZVxuICAgICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgIGNvbnN0IHN3aXBlciA9IHRoaXMuZ2V0U3dpcGVyKHN3aXBlckVsZW1lbnQpO1xuICAgICAgICBzd2lwZXIuc2xpZGVUbyh0aGlzLnNlbGVjdGVkSW5kZXgsIDApO1xuICAgICAgfSk7XG4gICAgfVxuICAgIHRoaXMuX3NldHVwV2hlZWxab29tKHN3aXBlckVsZW1lbnQpO1xuICAgIHRoaXMuX3VwZGF0ZVN3aXBlclRvdWNoTW92ZShzd2lwZXJFbGVtZW50KTtcbiAgICBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgIHRoaXMuX3VwZGF0ZUVkaXRJbWFnZVNpemUoc3dpcGVyRWxlbWVudCk7XG4gICAgICB0aGlzLl91cGRhdGVDcm9wT3ZlcmxheVBvc2l0aW9uKHN3aXBlckVsZW1lbnQpO1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFN3aXBlcidzIG93biB0b3VjaC9tb3VzZS1kcmFnLXRvLXN3aXBlIGdlc3R1cmUgaXMgY2FwdHVyZWQgdmVyeSBlYXJseSAoYmVmb3JlIG91ciBjcm9wIG92ZXJsYXknc1xuICAgKiBvd24gbW91c2Vkb3duIGhhbmRsZXIgY2FuIHN0b3AgaXQpLCBzbyBpdCBtdXN0IGJlIGRpc2FibGVkIHVwZnJvbnQgd2hlbmV2ZXIgdGhlIGNyb3AgdG9vbCBpcyBhY3RpdmVcbiAgICogLS0gb3RoZXJ3aXNlIGRyYWdnaW5nIGluc2lkZSB0aGUgY3JvcCBzZWxlY3Rpb24gZW5kcyB1cCBzbGlkaW5nIHRvIHRoZSBuZXh0L3ByZXZpb3VzIGltYWdlIGluc3RlYWQuXG4gICAqIFByZXYvbmV4dCBhcnJvdyBidXR0b25zIGtlZXAgd29ya2luZywgc2luY2UgdGhleSBjYWxsIHRoZSBTd2lwZXIgQVBJIGRpcmVjdGx5LlxuICAgKi9cbiAgcHJvdGVjdGVkIF91cGRhdGVTd2lwZXJUb3VjaE1vdmUoc3dpcGVyRWxlbWVudDogYW55KSB7XG4gICAgY29uc3Qgc3dpcGVyID0gdGhpcy5nZXRTd2lwZXIoc3dpcGVyRWxlbWVudCk7XG4gICAgaWYgKCFzd2lwZXIpIHJldHVybjtcbiAgICBzd2lwZXIuYWxsb3dUb3VjaE1vdmUgPSAhdGhpcy5fY3JvcFRvb2xBY3RpdmU7XG4gIH1cblxuICBwcm90ZWN0ZWQgYXN5bmMgem9vbShzd2lwZXJFbGVtZW50OiBhbnksIHpvb21JbjogYm9vbGVhbikge1xuICAgIGlmICh0aGlzLmRlYnVnKSBjb25zb2xlLmRlYnVnKCdbaW1hZ2UtZ2FsbGVyeS1zbGlkZXNob3ddIHpvb20gJyArICh6b29tSW4gPyAnaW4nIDogJ291dCcpLCBzd2lwZXJFbGVtZW50KTtcbiAgICBpZiAodGhpcy5tb3VzZVpvb21FbmFibGVkKSB7XG4gICAgICBjb25zdCBkZWx0YSA9IHpvb21JbiA/IHRoaXMud2hlZWxab29tU3RlcCA6IC10aGlzLndoZWVsWm9vbVN0ZXA7XG4gICAgICBjb25zdCBuZXdTY2FsZSA9IE1hdGgubWF4KDEsIE1hdGgubWluKHRoaXMuX3pvb21TdGF0ZS5zY2FsZSArIGRlbHRhLCB0aGlzLm1heFpvb21SYXRpbykpO1xuICAgICAgaWYgKG5ld1NjYWxlID09PSB0aGlzLl96b29tU3RhdGUuc2NhbGUpIHJldHVybjtcbiAgICAgIGlmIChuZXdTY2FsZSA9PT0gMSkge1xuICAgICAgICB0aGlzLl96b29tU3RhdGUgPSB7IHNjYWxlOiAxLCB0eDogMCwgdHk6IDAgfTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMuX3pvb21TdGF0ZS5zY2FsZSA9IG5ld1NjYWxlO1xuICAgICAgICB0aGlzLl9jbGFtcFRyYW5zbGF0aW9ucyhzd2lwZXJFbGVtZW50KTtcbiAgICAgIH1cbiAgICAgIHRoaXMuX2FwcGx5Wm9vbShzd2lwZXJFbGVtZW50KTtcbiAgICB9IGVsc2Uge1xuICAgICAgY29uc3Qgc3dpcGVyID0gdGhpcy5nZXRTd2lwZXIoc3dpcGVyRWxlbWVudCk7XG4gICAgICBpZiAoem9vbUluKSBzd2lwZXIuem9vbS5pbigpO1xuICAgICAgZWxzZSBzd2lwZXIuem9vbS5vdXQoKTtcbiAgICB9XG4gIH1cblxuICBwcm90ZWN0ZWQgYXN5bmMgcmVxdWVzdERlbGV0ZShzd2lwZXJFbGVtZW50OiBhbnkpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgICBjb25zdCBzd2lwZXIgPSB0aGlzLmdldFN3aXBlcihzd2lwZXJFbGVtZW50KTtcbiAgICBjb25zdCBjdXJyZW50Um93ID0gdGhpcy5yb3dzLmZpbmQoKHJvdywgaW5kZXgpID0+IGluZGV4ID09PSBzd2lwZXIuYWN0aXZlSW5kZXgpO1xuICAgIGlmICghY3VycmVudFJvdykgcmV0dXJuIGZhbHNlOyAvLyBSb3cgbm90IGZvdW5kXG5cbiAgICBsZXQgZGVsZXRlZCA9IGZhbHNlO1xuICAgIHRyeSB7XG4gICAgICBkZWxldGVkID0gYXdhaXQgZW1pdFByb21pc2VFdmVudCh0aGlzLmRlbGV0ZVJlcXVlc3RlZCwgJ2RlbGV0ZScsIHsgZGV0YWlsOiB7IHJvdzogY3VycmVudFJvdyB9IH0pO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgaWYgKGVyciA9PT0gJ0NBTkNFTExFRCcpIHJldHVybiBmYWxzZTsgLy8gVXNlciBjYW5jZWxcbiAgICAgIHRocm93IGVycjtcbiAgICB9XG5cbiAgICBpZiAoZGVsZXRlZCkge1xuICAgICAgaWYgKCF0aGlzLnJvd3M/Lmxlbmd0aCkge1xuICAgICAgICB0aGlzLmNsb3NlUmVxdWVzdGVkLmVtaXQoKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMuX3Jlc2V0Wm9vbShzd2lwZXJFbGVtZW50KTtcbiAgICAgICAgc3dpcGVyLnVwZGF0ZSgpO1xuICAgICAgICB0aGlzLm1hcmtGb3JDaGVjaygpO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gZGVsZXRlZDtcbiAgfVxuXG4gIHByb3RlY3RlZCBhc3luYyBzbGlkZVByZXYoc3dpcGVyRWxlbWVudDogYW55KSB7XG4gICAgY29uc3Qgc3dpcGVyID0gdGhpcy5nZXRTd2lwZXIoc3dpcGVyRWxlbWVudCk7XG4gICAgY29uc3QgYWN0aXZlSW5kZXggPSBzd2lwZXIuYWN0aXZlSW5kZXg7XG4gICAgaWYgKGFjdGl2ZUluZGV4ID09PSAwKSB7XG4gICAgICAvLyBMb29wIHRvIGxhc3RcbiAgICAgIGF3YWl0IHN3aXBlci5zbGlkZVRvKHRoaXMucm93cy5sZW5ndGggLSAxLCAwIC8qbm8gdHJhbnNpdGlvbiwgdG8gYXZvaWQgYSBcInJldmVydFwiIGVmZmVjdCovKTtcbiAgICB9IGVsc2Uge1xuICAgICAgYXdhaXQgc3dpcGVyLnNsaWRlUHJldigpO1xuICAgIH1cbiAgfVxuXG4gIHByb3RlY3RlZCBhc3luYyBzbGlkZU5leHQoc3dpcGVyRWxlbWVudDogYW55KSB7XG4gICAgY29uc3Qgc3dpcGVyID0gdGhpcy5nZXRTd2lwZXIoc3dpcGVyRWxlbWVudCk7XG4gICAgY29uc3QgYWN0aXZlSW5kZXggPSBzd2lwZXIuYWN0aXZlSW5kZXg7XG4gICAgaWYgKGFjdGl2ZUluZGV4ID09PSB0aGlzLnJvd3MubGVuZ3RoIC0gMSkge1xuICAgICAgLy8gTG9vcCB0byBmaXJzdFxuICAgICAgYXdhaXQgc3dpcGVyLnNsaWRlVG8oMCwgMCAvKm5vIHRyYW5zaXRpb24sIHRvIGF2b2lkIGEgXCJyZXZlcnRcIiBlZmZlY3QqLyk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGF3YWl0IHN3aXBlci5zbGlkZU5leHQoKTtcbiAgICB9XG4gIH1cblxuICBwcm90ZWN0ZWQgZ2V0SW1hZ2VTaXplUXVlcnlQYXJhbXMoKTogeyBba2V5OiBzdHJpbmddOiBhbnkgfSB7XG4gICAgaWYgKCF0aGlzLmltYWdlU2l6ZVF1ZXJ5UGFyYW0gfHwgaXNOaWxPckJsYW5rKHRoaXMubW9kYWxJbWFnZVNpemUpKSByZXR1cm4gdW5kZWZpbmVkO1xuICAgIHJldHVybiB7XG4gICAgICBbdGhpcy5pbWFnZVNpemVRdWVyeVBhcmFtXTogdGhpcy5tb2RhbEltYWdlU2l6ZSxcbiAgICB9O1xuICB9XG5cbiAgLy8gLS0tIENyb3AgJiByb3RhdGUgdG9vbHM6IGFsd2F5cyBhdmFpbGFibGUgKG5vIFwiZW50ZXIgZWRpdCBtb2RlXCIgc3RlcCkgLS0tXG4gIC8vIFZhbGlkYXRlL0NhbmNlbCBvbmx5IGFwcGVhciBvbmNlIHRoZSB1c2VyIGFjdHVhbGx5IGNoYW5nZWQgc29tZXRoaW5nIChzZWUgYGhhc1BlbmRpbmdFZGl0YCkuXG5cbiAgcHJvdGVjdGVkIGRpc2NhcmRFZGl0KHN3aXBlckVsZW1lbnQ6IGFueSkge1xuICAgIHRoaXMuX3Jlc2V0RWRpdFN0YXRlKHN3aXBlckVsZW1lbnQpO1xuICB9XG5cbiAgcHJvdGVjdGVkIHJlc2V0Q3JvcChfc3dpcGVyRWxlbWVudDogYW55KSB7XG4gICAgdGhpcy5fY3JvcFJlY3QgPSB7IHg6IDAsIHk6IDAsIHdpZHRoOiAxLCBoZWlnaHQ6IDEgfTtcbiAgICB0aGlzLm1hcmtGb3JDaGVjaygpO1xuICB9XG5cbiAgcHJvdGVjdGVkIHRvZ2dsZUNyb3BUb29sKHN3aXBlckVsZW1lbnQ6IGFueSkge1xuICAgIGlmICghdGhpcy5jcm9wRW5hYmxlZCB8fCB0aGlzLnJlYWRPbmx5KSByZXR1cm47XG4gICAgdGhpcy5fY3JvcFRvb2xBY3RpdmUgPSAhdGhpcy5fY3JvcFRvb2xBY3RpdmU7XG4gICAgLy8gRGlzY2FyZCBhbnkgcGVuZGluZyBjcm9wIHNlbGVjdGlvbiB3aGVuIGhpZGluZyB0aGUgdG9vbFxuICAgIGlmICghdGhpcy5fY3JvcFRvb2xBY3RpdmUpIHtcbiAgICAgIHRoaXMuX2Nyb3BSZWN0ID0geyB4OiAwLCB5OiAwLCB3aWR0aDogMSwgaGVpZ2h0OiAxIH07XG4gICAgICB0aGlzLl9jcm9wRHJhZ1N0YXRlID0gbnVsbDtcbiAgICB9XG4gICAgdGhpcy5fdXBkYXRlU3dpcGVyVG91Y2hNb3ZlKHN3aXBlckVsZW1lbnQpO1xuICAgIHRoaXMubWFya0ZvckNoZWNrKCk7XG4gICAgaWYgKHRoaXMuX2Nyb3BUb29sQWN0aXZlKSB7XG4gICAgICAvLyBXYWl0IGZvciB0aGUgY3JvcCBvdmVybGF5IHRvIGJlIHJlbmRlcmVkLCB0aGVuIGNvbXB1dGUgaXRzIHBvc2l0aW9uXG4gICAgICBzZXRUaW1lb3V0KCgpID0+IHRoaXMuX3VwZGF0ZUNyb3BPdmVybGF5UG9zaXRpb24oc3dpcGVyRWxlbWVudCkpO1xuICAgIH1cbiAgfVxuXG4gIHByb3RlY3RlZCBhc3luYyB2YWxpZGF0ZUVkaXQoc3dpcGVyRWxlbWVudDogYW55KSB7XG4gICAgY29uc3Qgc3dpcGVyID0gdGhpcy5nZXRTd2lwZXIoc3dpcGVyRWxlbWVudCk7XG4gICAgY29uc3QgY3VycmVudFJvdyA9IHRoaXMucm93cy5maW5kKChyb3csIGluZGV4KSA9PiBpbmRleCA9PT0gc3dpcGVyLmFjdGl2ZUluZGV4KTtcbiAgICBpZiAoIWN1cnJlbnRSb3cpIHJldHVybjtcblxuICAgIGNvbnN0IGltYWdlID0gY3VycmVudFJvdy5jdXJyZW50RGF0YTtcbiAgICBjb25zdCBmb3JtYXQgPSB0aGlzLmltYWdlRXhwb3J0Rm9ybWF0O1xuICAgIGNvbnN0IG1pbWVUeXBlID0gZm9ybWF0ID09PSAnanBlZycgPyAnaW1hZ2UvanBlZycgOiAnaW1hZ2UvcG5nJztcbiAgICBjb25zdCBxdWFsaXR5ID0gZm9ybWF0ID09PSAnanBlZycgPyB0aGlzLmltYWdlRXhwb3J0UXVhbGl0eSA6IHVuZGVmaW5lZDtcblxuICAgIHRyeSB7XG4gICAgICBjb25zdCBjYW52YXMgPSBhd2FpdCB0aGlzLl9yZW5kZXJFZGl0ZWRJbWFnZShpbWFnZSwgdGhpcy5fcm90YXRpb24sIHRoaXMuX2Nyb3BSZWN0KTtcbiAgICAgIGNvbnN0IGRhdGFVcmwgPSBjYW52YXMudG9EYXRhVVJMKG1pbWVUeXBlLCBxdWFsaXR5KTtcbiAgICAgIGNvbnN0IGJsb2IgPSBhd2FpdCBuZXcgUHJvbWlzZTxCbG9iPigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgIGNhbnZhcy50b0Jsb2IoKGIpID0+IChiID8gcmVzb2x2ZShiKSA6IHJlamVjdChuZXcgRXJyb3IoJ2NhbnZhcy50b0Jsb2IoKSBmYWlsZWQnKSkpLCBtaW1lVHlwZSwgcXVhbGl0eSk7XG4gICAgICB9KTtcblxuICAgICAgLy8gVXBkYXRlIHRoZSByb3csIHNvIHRoZSBzbGlkZXNob3cgYW5kIHRodW1ibmFpbCByZWZsZWN0IHRoZSBjaGFuZ2UgaW1tZWRpYXRlbHlcbiAgICAgIGltYWdlLmRhdGFVcmwgPSBkYXRhVXJsO1xuICAgICAgaW1hZ2UudXJsID0gbnVsbDtcblxuICAgICAgdGhpcy5hZnRlckVkaXRJbWFnZS5lbWl0KHsgcm93OiBjdXJyZW50Um93LCBpbWFnZSwgZGF0YVVybCwgYmxvYiwgZm9ybWF0IH0pO1xuICAgICAgdGhpcy5hZnRlckVkaXRSb3cuZW1pdChjdXJyZW50Um93KTtcblxuICAgICAgdGhpcy5fcmVzZXRFZGl0U3RhdGUoc3dpcGVyRWxlbWVudCk7XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBjb25zb2xlLmVycm9yKCdbaW1hZ2UtZ2FsbGVyeS1zbGlkZXNob3ddIEZhaWxlZCB0byBleHBvcnQgdGhlIGVkaXRlZCBpbWFnZScsIGVycik7XG4gICAgICBhd2FpdCBBbGVydHMuc2hvd0Vycm9yKCdJTUFHRS5HQUxMRVJZLkVSUk9SX0VESVRfSU1BR0UnLCB0aGlzLmFsdGVyQ3RybCwgdGhpcy50cmFuc2xhdGUpO1xuICAgIH1cbiAgfVxuXG4gIHByb3RlY3RlZCBfcmVzZXRFZGl0U3RhdGUoc3dpcGVyRWxlbWVudDogYW55KSB7XG4gICAgdGhpcy5fcm90YXRpb24gPSAwO1xuICAgIHRoaXMuX2Nyb3BSZWN0ID0geyB4OiAwLCB5OiAwLCB3aWR0aDogMSwgaGVpZ2h0OiAxIH07XG4gICAgdGhpcy5fY3JvcERyYWdTdGF0ZSA9IG51bGw7XG4gICAgdGhpcy5fY3JvcFRvb2xBY3RpdmUgPSBmYWxzZTtcbiAgICB0aGlzLl9hcHBseVpvb20oc3dpcGVyRWxlbWVudCk7XG4gICAgdGhpcy5fdXBkYXRlRWRpdEltYWdlU2l6ZShzd2lwZXJFbGVtZW50KTtcbiAgICB0aGlzLl91cGRhdGVTd2lwZXJUb3VjaE1vdmUoc3dpcGVyRWxlbWVudCk7XG4gICAgdGhpcy5tYXJrRm9yQ2hlY2soKTtcbiAgfVxuXG4gIEBIb3N0TGlzdGVuZXIoJ3dpbmRvdzpyZXNpemUnKVxuICBwcm90ZWN0ZWQgb25XaW5kb3dSZXNpemUoKSB7XG4gICAgY29uc3Qgc3dpcGVyRWxlbWVudCA9IHRoaXMuc3dpcGVyUmVmPy5uYXRpdmVFbGVtZW50O1xuICAgIGlmICghc3dpcGVyRWxlbWVudCkgcmV0dXJuO1xuICAgIHRoaXMuX3VwZGF0ZUVkaXRJbWFnZVNpemUoc3dpcGVyRWxlbWVudCk7XG4gICAgdGhpcy5fdXBkYXRlQ3JvcE92ZXJsYXlQb3NpdGlvbihzd2lwZXJFbGVtZW50KTtcbiAgfVxuXG4gIHByb3RlY3RlZCByb3RhdGUoc3dpcGVyRWxlbWVudDogYW55LCBkaXJlY3Rpb246ICdsZWZ0JyB8ICdyaWdodCcpIHtcbiAgICBpZiAoIXRoaXMucm90YXRpb25FbmFibGVkKSByZXR1cm47XG4gICAgY29uc3QgZGVsdGEgPSBkaXJlY3Rpb24gPT09ICdyaWdodCcgPyA5MCA6IC05MDtcbiAgICB0aGlzLl9yb3RhdGlvbiA9ICgoKHRoaXMuX3JvdGF0aW9uICsgZGVsdGEpICUgMzYwKSArIDM2MCkgJSAzNjA7XG4gICAgdGhpcy5fYXBwbHlab29tKHN3aXBlckVsZW1lbnQpO1xuICAgIHRoaXMuX3VwZGF0ZUVkaXRJbWFnZVNpemUoc3dpcGVyRWxlbWVudCk7XG4gICAgdGhpcy5fdXBkYXRlQ3JvcE92ZXJsYXlQb3NpdGlvbihzd2lwZXJFbGVtZW50KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDb25zdHJhaW5zIHRoZSBkaXNwbGF5ZWQgaW1hZ2Ugc28gdGhhdCwgZXZlbiBvbmNlIHJvdGF0ZWQgOTDCsC8yNzDCsCB2aWEgQ1NTIHRyYW5zZm9ybSxcbiAgICogaXRzIHZpc3VhbCBib3VuZGluZyBib3ggYWx3YXlzIGZpdHMgaW5zaWRlIHRoZSBzbGlkZSBjb250YWluZXIgKG5vIG92ZXJmbG93KS5cbiAgICovXG4gIHByb3RlY3RlZCBfdXBkYXRlRWRpdEltYWdlU2l6ZShzd2lwZXJFbGVtZW50OiBhbnksIHJldHJpZXNMZWZ0ID0gNjApIHtcbiAgICBjb25zdCBzd2lwZXIgPSB0aGlzLmdldFN3aXBlcihzd2lwZXJFbGVtZW50KTtcbiAgICBjb25zdCBhY3RpdmVTbGlkZSA9IHN3aXBlcj8uc2xpZGVzPy5bc3dpcGVyLmFjdGl2ZUluZGV4XSBhcyBIVE1MRWxlbWVudDtcbiAgICBpZiAoIWFjdGl2ZVNsaWRlKSByZXR1cm47XG4gICAgY29uc3QgY29udGFpbmVyRWwgPSBhY3RpdmVTbGlkZS5xdWVyeVNlbGVjdG9yKCcuc3dpcGVyLXpvb20tY29udGFpbmVyJykgYXMgSFRNTEVsZW1lbnQ7XG4gICAgY29uc3QgaW1nRWwgPSBhY3RpdmVTbGlkZS5xdWVyeVNlbGVjdG9yKCcuc3dpcGVyLXpvb20tdGFyZ2V0LCAuc3dpcGVyLXpvb20tY29udGFpbmVyID4gaW1nJykgYXMgSFRNTEVsZW1lbnQ7XG4gICAgaWYgKCFjb250YWluZXJFbCB8fCAhaW1nRWwpIHJldHVybjtcbiAgICBpZiAoIXRoaXMuY3JvcEVuYWJsZWQgJiYgIXRoaXMucm90YXRpb25FbmFibGVkKSB7XG4gICAgICBpbWdFbC5zdHlsZS53aWR0aCA9ICcnO1xuICAgICAgaW1nRWwuc3R5bGUuaGVpZ2h0ID0gJyc7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGNvbnRhaW5lclJlY3QgPSBjb250YWluZXJFbC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcbiAgICBjb25zdCBuYXR1cmFsU2l6ZSA9IHRoaXMuX2dldEltYWdlTmF0dXJhbFNpemUoaW1nRWwpO1xuICAgIC8vIFRoZSBjb250YWluZXIvaW1hZ2UgbWF5IG5vdCBiZSBsYWlkIG91dCBvciBsb2FkZWQgeWV0OiBza2lwIGFuZCByZXRyeSBvbiB0aGUgbmV4dCBmcmFtZVxuICAgIGlmICghY29udGFpbmVyUmVjdC53aWR0aCB8fCAhY29udGFpbmVyUmVjdC5oZWlnaHQgfHwgIW5hdHVyYWxTaXplKSB7XG4gICAgICBpZiAocmV0cmllc0xlZnQgPiAwKSB7XG4gICAgICAgIHJlcXVlc3RBbmltYXRpb25GcmFtZSgoKSA9PiB0aGlzLl91cGRhdGVFZGl0SW1hZ2VTaXplKHN3aXBlckVsZW1lbnQsIHJldHJpZXNMZWZ0IC0gMSkpO1xuICAgICAgfVxuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICAvLyBDb21wdXRlIHRoZSBleGFjdCBib3ggKHByZXNlcnZpbmcgdGhlIGltYWdlJ3MgYXNwZWN0IHJhdGlvKSB0aGF0IGZpdHMgdGhlIGF2YWlsYWJsZSBzcGFjZSxcbiAgICAvLyBzd2FwcGluZyB3aWR0aC9oZWlnaHQgd2hlbiByb3RhdGVkIDkwwrAvMjcwwrAgc28gdGhlIHJvdGF0ZWQgdmlzdWFsIGJveCBuZXZlciBvdmVyZmxvd3MuXG4gICAgY29uc3Qgc3dhcHBlZCA9IHRoaXMuX3JvdGF0aW9uICUgMTgwICE9PSAwO1xuICAgIGNvbnN0IGF2YWlsV2lkdGggPSBzd2FwcGVkID8gY29udGFpbmVyUmVjdC5oZWlnaHQgOiBjb250YWluZXJSZWN0LndpZHRoO1xuICAgIGNvbnN0IGF2YWlsSGVpZ2h0ID0gc3dhcHBlZCA/IGNvbnRhaW5lclJlY3Qud2lkdGggOiBjb250YWluZXJSZWN0LmhlaWdodDtcbiAgICBjb25zdCByYXRpbyA9IE1hdGgubWluKGF2YWlsV2lkdGggLyBuYXR1cmFsU2l6ZS53aWR0aCwgYXZhaWxIZWlnaHQgLyBuYXR1cmFsU2l6ZS5oZWlnaHQpO1xuICAgIGltZ0VsLnN0eWxlLndpZHRoID0gYCR7bmF0dXJhbFNpemUud2lkdGggKiByYXRpb31weGA7XG4gICAgaW1nRWwuc3R5bGUuaGVpZ2h0ID0gYCR7bmF0dXJhbFNpemUuaGVpZ2h0ICogcmF0aW99cHhgO1xuICB9XG5cbiAgcHJvdGVjdGVkIF9nZXRJbWFnZU5hdHVyYWxTaXplKGltZ0VsOiBIVE1MRWxlbWVudCk6IHsgd2lkdGg6IG51bWJlcjsgaGVpZ2h0OiBudW1iZXIgfSB8IG51bGwge1xuICAgIGNvbnN0IGlubmVyID0gKGltZ0VsLnRhZ05hbWUgPT09ICdJT04tSU1HJyA/IGltZ0VsLnNoYWRvd1Jvb3Q/LnF1ZXJ5U2VsZWN0b3IoJ2ltZycpIDogaW1nRWwpIGFzIEhUTUxJbWFnZUVsZW1lbnQ7XG4gICAgaWYgKCFpbm5lcj8ubmF0dXJhbFdpZHRoIHx8ICFpbm5lcj8ubmF0dXJhbEhlaWdodCkgcmV0dXJuIG51bGw7XG4gICAgcmV0dXJuIHsgd2lkdGg6IGlubmVyLm5hdHVyYWxXaWR0aCwgaGVpZ2h0OiBpbm5lci5uYXR1cmFsSGVpZ2h0IH07XG4gIH1cblxuICAvLyAtLS0gQ3JvcCBvdmVybGF5OiBkcmFnIHRvIG1vdmUvcmVzaXplIHRoZSBjcm9wIHJlY3RhbmdsZSAtLS1cblxuICBwcm90ZWN0ZWQgb25Dcm9wUG9pbnRlckRvd24oZXZlbnQ6IE1vdXNlRXZlbnQgfCBUb3VjaEV2ZW50LCBzd2lwZXJFbGVtZW50OiBhbnksIGhhbmRsZTogQ3JvcEhhbmRsZSkge1xuICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgY29uc3QgcHQgPSB0aGlzLl9nZXRFdmVudFBvaW50KGV2ZW50KTtcbiAgICB0aGlzLl9jcm9wT3ZlcmxheVJlY3QgPSB0aGlzLl9jcm9wT3ZlcmxheVJlY3QgPz8gdGhpcy5fdXBkYXRlQ3JvcE92ZXJsYXlQb3NpdGlvbihzd2lwZXJFbGVtZW50KTtcbiAgICB0aGlzLl9jcm9wRHJhZ1N0YXRlID0ge1xuICAgICAgaGFuZGxlLFxuICAgICAgc3RhcnRYOiBwdC54LFxuICAgICAgc3RhcnRZOiBwdC55LFxuICAgICAgc3RhcnRSZWN0OiB7IC4uLnRoaXMuX2Nyb3BSZWN0IH0sXG4gICAgfTtcbiAgICAvLyBBbHNvIGRpc2FibGUgcHJvZ3JhbW1hdGljIHNsaWRlIG5hdmlnYXRpb24gd2hpbGUgZHJhZ2dpbmcgKGFsbG93VG91Y2hNb3ZlIGlzIGFscmVhZHlcbiAgICAvLyBwZXJtYW5lbnRseSBvZmYgd2hlbmV2ZXIgY3JvcCBpcyBlbmFibGVkLCBzZWUgYF91cGRhdGVTd2lwZXJUb3VjaE1vdmVgKVxuICAgIGNvbnN0IHN3aXBlciA9IHRoaXMuZ2V0U3dpcGVyKHN3aXBlckVsZW1lbnQpO1xuICAgIGlmIChzd2lwZXIpIHtcbiAgICAgIHN3aXBlci5hbGxvd1NsaWRlTmV4dCA9IGZhbHNlO1xuICAgICAgc3dpcGVyLmFsbG93U2xpZGVQcmV2ID0gZmFsc2U7XG4gICAgfVxuICB9XG5cbiAgcHJvdGVjdGVkIG9uQ3JvcFBvaW50ZXJNb3ZlKGV2ZW50OiBNb3VzZUV2ZW50IHwgVG91Y2hFdmVudCwgX3N3aXBlckVsZW1lbnQ6IGFueSkge1xuICAgIGlmICghdGhpcy5fY3JvcERyYWdTdGF0ZSkgcmV0dXJuO1xuICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgY29uc3QgcmVjdCA9IHRoaXMuX2Nyb3BPdmVybGF5UmVjdDtcbiAgICBpZiAoIXJlY3Q/LndpZHRoIHx8ICFyZWN0Py5oZWlnaHQpIHJldHVybjtcbiAgICBjb25zdCBwdCA9IHRoaXMuX2dldEV2ZW50UG9pbnQoZXZlbnQpO1xuICAgIGNvbnN0IGR4ID0gKHB0LnggLSB0aGlzLl9jcm9wRHJhZ1N0YXRlLnN0YXJ0WCkgLyByZWN0LndpZHRoO1xuICAgIGNvbnN0IGR5ID0gKHB0LnkgLSB0aGlzLl9jcm9wRHJhZ1N0YXRlLnN0YXJ0WSkgLyByZWN0LmhlaWdodDtcbiAgICB0aGlzLl9jcm9wUmVjdCA9IHRoaXMuX2NvbXB1dGVDcm9wUmVjdCh0aGlzLl9jcm9wRHJhZ1N0YXRlLmhhbmRsZSwgdGhpcy5fY3JvcERyYWdTdGF0ZS5zdGFydFJlY3QsIGR4LCBkeSk7XG4gICAgdGhpcy5tYXJrRm9yQ2hlY2soKTtcbiAgfVxuXG4gIHByb3RlY3RlZCBvbkNyb3BQb2ludGVyVXAoX2V2ZW50OiBNb3VzZUV2ZW50IHwgVG91Y2hFdmVudCwgc3dpcGVyRWxlbWVudD86IGFueSkge1xuICAgIGlmICghdGhpcy5fY3JvcERyYWdTdGF0ZSkgcmV0dXJuO1xuICAgIHRoaXMuX2Nyb3BEcmFnU3RhdGUgPSBudWxsO1xuICAgIGNvbnN0IHN3aXBlciA9IHN3aXBlckVsZW1lbnQgPyB0aGlzLmdldFN3aXBlcihzd2lwZXJFbGVtZW50KSA6IG51bGw7XG4gICAgaWYgKHN3aXBlcikge1xuICAgICAgc3dpcGVyLmFsbG93U2xpZGVOZXh0ID0gdHJ1ZTtcbiAgICAgIHN3aXBlci5hbGxvd1NsaWRlUHJldiA9IHRydWU7XG4gICAgfVxuICAgIGlmIChzd2lwZXJFbGVtZW50KSB0aGlzLl91cGRhdGVTd2lwZXJUb3VjaE1vdmUoc3dpcGVyRWxlbWVudCk7XG4gIH1cblxuICBwcm90ZWN0ZWQgX2NvbXB1dGVDcm9wUmVjdChoYW5kbGU6IENyb3BIYW5kbGUsIHN0YXJ0OiBDcm9wUmVjdCwgZHg6IG51bWJlciwgZHk6IG51bWJlcik6IENyb3BSZWN0IHtcbiAgICBjb25zdCBNSU5fU0laRSA9IDAuMDg7XG4gICAgY29uc3QgY2xhbXAwMSA9ICh2OiBudW1iZXIpID0+IE1hdGgubWF4KDAsIE1hdGgubWluKDEsIHYpKTtcblxuICAgIGlmIChoYW5kbGUgPT09ICdtb3ZlJykge1xuICAgICAgbGV0IHggPSBjbGFtcDAxKHN0YXJ0LnggKyBkeCk7XG4gICAgICBsZXQgeSA9IGNsYW1wMDEoc3RhcnQueSArIGR5KTtcbiAgICAgIHggPSBNYXRoLm1pbih4LCAxIC0gc3RhcnQud2lkdGgpO1xuICAgICAgeSA9IE1hdGgubWluKHksIDEgLSBzdGFydC5oZWlnaHQpO1xuICAgICAgcmV0dXJuIHsgeCwgeSwgd2lkdGg6IHN0YXJ0LndpZHRoLCBoZWlnaHQ6IHN0YXJ0LmhlaWdodCB9O1xuICAgIH1cblxuICAgIGxldCBsZWZ0ID0gc3RhcnQueDtcbiAgICBsZXQgdG9wID0gc3RhcnQueTtcbiAgICBsZXQgcmlnaHQgPSBzdGFydC54ICsgc3RhcnQud2lkdGg7XG4gICAgbGV0IGJvdHRvbSA9IHN0YXJ0LnkgKyBzdGFydC5oZWlnaHQ7XG5cbiAgICBpZiAoaGFuZGxlLmluY2x1ZGVzKCd3JykpIGxlZnQgPSBjbGFtcDAxKHN0YXJ0LnggKyBkeCk7XG4gICAgaWYgKGhhbmRsZS5pbmNsdWRlcygnZScpKSByaWdodCA9IGNsYW1wMDEoc3RhcnQueCArIHN0YXJ0LndpZHRoICsgZHgpO1xuICAgIGlmIChoYW5kbGUuaW5jbHVkZXMoJ24nKSkgdG9wID0gY2xhbXAwMShzdGFydC55ICsgZHkpO1xuICAgIGlmIChoYW5kbGUuaW5jbHVkZXMoJ3MnKSkgYm90dG9tID0gY2xhbXAwMShzdGFydC55ICsgc3RhcnQuaGVpZ2h0ICsgZHkpO1xuXG4gICAgaWYgKHJpZ2h0IC0gbGVmdCA8IE1JTl9TSVpFKSB7XG4gICAgICBpZiAoaGFuZGxlLmluY2x1ZGVzKCd3JykpIGxlZnQgPSByaWdodCAtIE1JTl9TSVpFO1xuICAgICAgZWxzZSByaWdodCA9IGxlZnQgKyBNSU5fU0laRTtcbiAgICB9XG4gICAgaWYgKGJvdHRvbSAtIHRvcCA8IE1JTl9TSVpFKSB7XG4gICAgICBpZiAoaGFuZGxlLmluY2x1ZGVzKCduJykpIHRvcCA9IGJvdHRvbSAtIE1JTl9TSVpFO1xuICAgICAgZWxzZSBib3R0b20gPSB0b3AgKyBNSU5fU0laRTtcbiAgICB9XG5cbiAgICBsZWZ0ID0gY2xhbXAwMShsZWZ0KTtcbiAgICByaWdodCA9IGNsYW1wMDEocmlnaHQpO1xuICAgIHRvcCA9IGNsYW1wMDEodG9wKTtcbiAgICBib3R0b20gPSBjbGFtcDAxKGJvdHRvbSk7XG5cbiAgICByZXR1cm4geyB4OiBsZWZ0LCB5OiB0b3AsIHdpZHRoOiByaWdodCAtIGxlZnQsIGhlaWdodDogYm90dG9tIC0gdG9wIH07XG4gIH1cblxuICBwcm90ZWN0ZWQgX2dldEV2ZW50UG9pbnQoZXZlbnQ6IE1vdXNlRXZlbnQgfCBUb3VjaEV2ZW50KTogeyB4OiBudW1iZXI7IHk6IG51bWJlciB9IHtcbiAgICBpZiAoJ3RvdWNoZXMnIGluIGV2ZW50ICYmIGV2ZW50LnRvdWNoZXMubGVuZ3RoKSB7XG4gICAgICByZXR1cm4geyB4OiBldmVudC50b3VjaGVzWzBdLmNsaWVudFgsIHk6IGV2ZW50LnRvdWNoZXNbMF0uY2xpZW50WSB9O1xuICAgIH1cbiAgICBpZiAoJ2NoYW5nZWRUb3VjaGVzJyBpbiBldmVudCAmJiBldmVudC5jaGFuZ2VkVG91Y2hlcy5sZW5ndGgpIHtcbiAgICAgIHJldHVybiB7IHg6IGV2ZW50LmNoYW5nZWRUb3VjaGVzWzBdLmNsaWVudFgsIHk6IGV2ZW50LmNoYW5nZWRUb3VjaGVzWzBdLmNsaWVudFkgfTtcbiAgICB9XG4gICAgY29uc3QgbW91c2VFdmVudCA9IGV2ZW50IGFzIE1vdXNlRXZlbnQ7XG4gICAgcmV0dXJuIHsgeDogbW91c2VFdmVudC5jbGllbnRYLCB5OiBtb3VzZUV2ZW50LmNsaWVudFkgfTtcbiAgfVxuXG4gIHByb3RlY3RlZCBfdXBkYXRlQ3JvcE92ZXJsYXlQb3NpdGlvbihzd2lwZXJFbGVtZW50OiBhbnksIHJldHJpZXNMZWZ0ID0gNjApOiB7IHdpZHRoOiBudW1iZXI7IGhlaWdodDogbnVtYmVyIH0gfCBudWxsIHtcbiAgICBjb25zdCBzd2lwZXIgPSB0aGlzLmdldFN3aXBlcihzd2lwZXJFbGVtZW50KTtcbiAgICBjb25zdCBhY3RpdmVTbGlkZSA9IHN3aXBlcj8uc2xpZGVzPy5bc3dpcGVyLmFjdGl2ZUluZGV4XSBhcyBIVE1MRWxlbWVudDtcbiAgICBpZiAoIWFjdGl2ZVNsaWRlKSByZXR1cm4gbnVsbDtcbiAgICBjb25zdCBjb250YWluZXJFbCA9IGFjdGl2ZVNsaWRlLnF1ZXJ5U2VsZWN0b3IoJy5zd2lwZXItem9vbS1jb250YWluZXInKSBhcyBIVE1MRWxlbWVudDtcbiAgICBjb25zdCBpbWdFbCA9IGFjdGl2ZVNsaWRlLnF1ZXJ5U2VsZWN0b3IoJy5zd2lwZXItem9vbS10YXJnZXQsIC5zd2lwZXItem9vbS1jb250YWluZXIgPiBpbWcnKSBhcyBIVE1MRWxlbWVudDtcbiAgICBjb25zdCBvdmVybGF5RWwgPSBhY3RpdmVTbGlkZS5xdWVyeVNlbGVjdG9yKCcuY3JvcC1vdmVybGF5JykgYXMgSFRNTEVsZW1lbnQ7XG4gICAgaWYgKCFjb250YWluZXJFbCB8fCAhaW1nRWwgfHwgIW92ZXJsYXlFbCkgcmV0dXJuIG51bGw7XG4gICAgY29uc3QgY29udGFpbmVyUmVjdCA9IGNvbnRhaW5lckVsLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgIGNvbnN0IGltZ1JlY3QgPSBpbWdFbC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcbiAgICAvLyBUaGUgaW1hZ2UgbWF5IG5vdCBiZSBsYWlkIG91dCB5ZXQ6IGtlZXAgYW55IHByZXZpb3VzIChub24temVybykgdmFsdWUgYW5kIHJldHJ5IG9uIHRoZSBuZXh0IGZyYW1lXG4gICAgaWYgKCFpbWdSZWN0LndpZHRoIHx8ICFpbWdSZWN0LmhlaWdodCkge1xuICAgICAgaWYgKHJldHJpZXNMZWZ0ID4gMCkge1xuICAgICAgICByZXF1ZXN0QW5pbWF0aW9uRnJhbWUoKCkgPT4gdGhpcy5fdXBkYXRlQ3JvcE92ZXJsYXlQb3NpdGlvbihzd2lwZXJFbGVtZW50LCByZXRyaWVzTGVmdCAtIDEpKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiB0aGlzLl9jcm9wT3ZlcmxheVJlY3Q7XG4gICAgfVxuICAgIG92ZXJsYXlFbC5zdHlsZS5sZWZ0ID0gYCR7aW1nUmVjdC5sZWZ0IC0gY29udGFpbmVyUmVjdC5sZWZ0fXB4YDtcbiAgICBvdmVybGF5RWwuc3R5bGUudG9wID0gYCR7aW1nUmVjdC50b3AgLSBjb250YWluZXJSZWN0LnRvcH1weGA7XG4gICAgb3ZlcmxheUVsLnN0eWxlLndpZHRoID0gYCR7aW1nUmVjdC53aWR0aH1weGA7XG4gICAgb3ZlcmxheUVsLnN0eWxlLmhlaWdodCA9IGAke2ltZ1JlY3QuaGVpZ2h0fXB4YDtcbiAgICB0aGlzLl9jcm9wT3ZlcmxheVJlY3QgPSB7IHdpZHRoOiBpbWdSZWN0LndpZHRoLCBoZWlnaHQ6IGltZ1JlY3QuaGVpZ2h0IH07XG4gICAgcmV0dXJuIHRoaXMuX2Nyb3BPdmVybGF5UmVjdDtcbiAgfVxuXG4gIC8vIC0tLSBDYW52YXMgcmVuZGVyaW5nLCB1c2VkIHRvIGV4cG9ydCB0aGUgY3JvcHBlZC9yb3RhdGVkIGltYWdlIC0tLVxuXG4gIHByb3RlY3RlZCBhc3luYyBfcmVuZGVyRWRpdGVkSW1hZ2UoaW1hZ2U6IEltYWdlLCByb3RhdGlvbjogbnVtYmVyLCBjcm9wUmVjdDogQ3JvcFJlY3QpOiBQcm9taXNlPEhUTUxDYW52YXNFbGVtZW50PiB7XG4gICAgY29uc3Qgc3JjID0gaW1hZ2UuZGF0YVVybCB8fCBpbWFnZS51cmw7XG4gICAgY29uc3QgaW1nID0gYXdhaXQgdGhpcy5fbG9hZEltYWdlRWxlbWVudChzcmMpO1xuICAgIGNvbnN0IHN3ID0gaW1nLm5hdHVyYWxXaWR0aDtcbiAgICBjb25zdCBzaCA9IGltZy5uYXR1cmFsSGVpZ2h0O1xuICAgIGNvbnN0IHN3YXBwZWQgPSByb3RhdGlvbiAlIDE4MCAhPT0gMDtcbiAgICBjb25zdCBydyA9IHN3YXBwZWQgPyBzaCA6IHN3O1xuICAgIGNvbnN0IHJoID0gc3dhcHBlZCA/IHN3IDogc2g7XG5cbiAgICAvLyAxKSBEcmF3IHRoZSBmdWxsICh1bmNyb3BwZWQpIGltYWdlLCByb3RhdGVkXG4gICAgY29uc3Qgcm90Q2FudmFzID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnY2FudmFzJyk7XG4gICAgcm90Q2FudmFzLndpZHRoID0gcnc7XG4gICAgcm90Q2FudmFzLmhlaWdodCA9IHJoO1xuICAgIGNvbnN0IHJjdHggPSByb3RDYW52YXMuZ2V0Q29udGV4dCgnMmQnKTtcbiAgICByY3R4LnRyYW5zbGF0ZShydyAvIDIsIHJoIC8gMik7XG4gICAgcmN0eC5yb3RhdGUoKHJvdGF0aW9uICogTWF0aC5QSSkgLyAxODApO1xuICAgIHJjdHguZHJhd0ltYWdlKGltZywgLXN3IC8gMiwgLXNoIC8gMik7XG5cbiAgICAvLyAyKSBDcm9wIHRoZSByb3RhdGVkIGltYWdlXG4gICAgY29uc3QgY3JvcFggPSBNYXRoLnJvdW5kKGNyb3BSZWN0LnggKiBydyk7XG4gICAgY29uc3QgY3JvcFkgPSBNYXRoLnJvdW5kKGNyb3BSZWN0LnkgKiByaCk7XG4gICAgY29uc3QgY3JvcFcgPSBNYXRoLm1heCgxLCBNYXRoLnJvdW5kKGNyb3BSZWN0LndpZHRoICogcncpKTtcbiAgICBjb25zdCBjcm9wSCA9IE1hdGgubWF4KDEsIE1hdGgucm91bmQoY3JvcFJlY3QuaGVpZ2h0ICogcmgpKTtcblxuICAgIGNvbnN0IG91dENhbnZhcyA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2NhbnZhcycpO1xuICAgIG91dENhbnZhcy53aWR0aCA9IGNyb3BXO1xuICAgIG91dENhbnZhcy5oZWlnaHQgPSBjcm9wSDtcbiAgICBvdXRDYW52YXMuZ2V0Q29udGV4dCgnMmQnKS5kcmF3SW1hZ2Uocm90Q2FudmFzLCBjcm9wWCwgY3JvcFksIGNyb3BXLCBjcm9wSCwgMCwgMCwgY3JvcFcsIGNyb3BIKTtcblxuICAgIHJldHVybiBvdXRDYW52YXM7XG4gIH1cblxuICBwcm90ZWN0ZWQgX2xvYWRJbWFnZUVsZW1lbnQoc3JjOiBzdHJpbmcpOiBQcm9taXNlPEhUTUxJbWFnZUVsZW1lbnQ+IHtcbiAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgY29uc3QgaW1nID0gbmV3IEltYWdlKCk7XG4gICAgICBpZiAoIXNyYy5zdGFydHNXaXRoKCdkYXRhOicpKSBpbWcuY3Jvc3NPcmlnaW4gPSAnYW5vbnltb3VzJztcbiAgICAgIGltZy5vbmxvYWQgPSAoKSA9PiByZXNvbHZlKGltZyk7XG4gICAgICBpbWcub25lcnJvciA9ICgpID0+IHJlamVjdChuZXcgRXJyb3IoYFtpbWFnZS1nYWxsZXJ5LXNsaWRlc2hvd10gQ2Fubm90IGxvYWQgaW1hZ2U6ICR7c3JjfWApKTtcbiAgICAgIGltZy5zcmMgPSBzcmM7XG4gICAgfSk7XG4gIH1cblxuICAvLyAtLS0gTW91c2Utd2hlZWwgem9vbSAmIG1vdXNlLWRyYWcgcGFuIC0tLVxuXG4gIHByb3RlY3RlZCBvblBvaW50ZXJEb3duKGV2ZW50OiBNb3VzZUV2ZW50LCBzd2lwZXJFbGVtZW50OiBhbnkpIHtcbiAgICBpZiAoIXRoaXMubW91c2Vab29tRW5hYmxlZCB8fCB0aGlzLl96b29tU3RhdGUuc2NhbGUgPD0gMSkgcmV0dXJuO1xuICAgIC8vIERvbid0IGludGVyY2VwdCBjbGlja3Mgb24gYnV0dG9ucy9jb250cm9sc1xuICAgIGlmICgoZXZlbnQudGFyZ2V0IGFzIEhUTUxFbGVtZW50KS5jbG9zZXN0KCdpb24tZmFiLWJ1dHRvbiwgaW9uLWJ1dHRvbiwgYnV0dG9uJykpIHJldHVybjtcbiAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xuICAgIHRoaXMuX2RyYWdTdGF0ZSA9IHtcbiAgICAgIHN0YXJ0WDogZXZlbnQuY2xpZW50WCxcbiAgICAgIHN0YXJ0WTogZXZlbnQuY2xpZW50WSxcbiAgICAgIHN0YXJ0VHg6IHRoaXMuX3pvb21TdGF0ZS50eCxcbiAgICAgIHN0YXJ0VHk6IHRoaXMuX3pvb21TdGF0ZS50eSxcbiAgICB9O1xuICAgIGNvbnN0IHdyYXBFbCA9IHRoaXMuX2dldEFjdGl2ZVpvb21XcmFwRWwoc3dpcGVyRWxlbWVudCk7XG4gICAgaWYgKHdyYXBFbCkgd3JhcEVsLnN0eWxlLmN1cnNvciA9ICdncmFiYmluZyc7XG5cbiAgICAvLyBEaXNhYmxlIFN3aXBlciBzbGlkZS1jaGFuZ2Ugd2hpbGUgZHJhZ2dpbmdcbiAgICBjb25zdCBzd2lwZXIgPSB0aGlzLmdldFN3aXBlcihzd2lwZXJFbGVtZW50KTtcbiAgICBpZiAoc3dpcGVyKSB7XG4gICAgICBzd2lwZXIuYWxsb3dTbGlkZU5leHQgPSBmYWxzZTtcbiAgICAgIHN3aXBlci5hbGxvd1NsaWRlUHJldiA9IGZhbHNlO1xuICAgIH1cbiAgfVxuXG4gIHByb3RlY3RlZCBvblBvaW50ZXJNb3ZlKGV2ZW50OiBNb3VzZUV2ZW50LCBzd2lwZXJFbGVtZW50OiBhbnkpIHtcbiAgICBpZiAodGhpcy5fY3JvcERyYWdTdGF0ZSkge1xuICAgICAgdGhpcy5vbkNyb3BQb2ludGVyTW92ZShldmVudCwgc3dpcGVyRWxlbWVudCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmICghdGhpcy5fZHJhZ1N0YXRlKSByZXR1cm47XG4gICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICB0aGlzLl96b29tU3RhdGUudHggPSB0aGlzLl9kcmFnU3RhdGUuc3RhcnRUeCArIGV2ZW50LmNsaWVudFggLSB0aGlzLl9kcmFnU3RhdGUuc3RhcnRYO1xuICAgIHRoaXMuX3pvb21TdGF0ZS50eSA9IHRoaXMuX2RyYWdTdGF0ZS5zdGFydFR5ICsgZXZlbnQuY2xpZW50WSAtIHRoaXMuX2RyYWdTdGF0ZS5zdGFydFk7XG4gICAgdGhpcy5fY2xhbXBUcmFuc2xhdGlvbnMoc3dpcGVyRWxlbWVudCk7XG4gICAgdGhpcy5fYXBwbHlab29tKHN3aXBlckVsZW1lbnQpO1xuICB9XG5cbiAgcHJvdGVjdGVkIG9uUG9pbnRlclVwKGV2ZW50OiBNb3VzZUV2ZW50LCBzd2lwZXJFbGVtZW50PzogYW55KSB7XG4gICAgaWYgKHRoaXMuX2Nyb3BEcmFnU3RhdGUpIHtcbiAgICAgIHRoaXMub25Dcm9wUG9pbnRlclVwKGV2ZW50LCBzd2lwZXJFbGVtZW50KTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKCF0aGlzLl9kcmFnU3RhdGUpIHJldHVybjtcbiAgICB0aGlzLl9kcmFnU3RhdGUgPSBudWxsO1xuICAgIGNvbnN0IHdyYXBFbCA9IHRoaXMuX2dldEFjdGl2ZVpvb21XcmFwRWwoc3dpcGVyRWxlbWVudCk7XG4gICAgaWYgKHdyYXBFbCkgd3JhcEVsLnN0eWxlLmN1cnNvciA9IHRoaXMuX3pvb21TdGF0ZS5zY2FsZSA+IDEgPyAnZ3JhYicgOiAnJztcbiAgICAvLyBSZS1lbmFibGUgU3dpcGVyIG5hdmlnYXRpb25cbiAgICBjb25zdCBzd2lwZXIgPSBzd2lwZXJFbGVtZW50ID8gdGhpcy5nZXRTd2lwZXIoc3dpcGVyRWxlbWVudCkgOiBudWxsO1xuICAgIGlmIChzd2lwZXIpIHtcbiAgICAgIHN3aXBlci5hbGxvd1NsaWRlTmV4dCA9IHRydWU7XG4gICAgICBzd2lwZXIuYWxsb3dTbGlkZVByZXYgPSB0cnVlO1xuICAgIH1cbiAgfVxuXG4gIHByb3RlY3RlZCBfcmVzZXRab29tKHN3aXBlckVsZW1lbnQ/OiBhbnkpIHtcbiAgICB0aGlzLl96b29tU3RhdGUgPSB7IHNjYWxlOiAxLCB0eDogMCwgdHk6IDAgfTtcbiAgICB0aGlzLl9kcmFnU3RhdGUgPSBudWxsO1xuICAgIHRoaXMuX3JvdGF0aW9uID0gMDtcbiAgICB0aGlzLl9jcm9wUmVjdCA9IHsgeDogMCwgeTogMCwgd2lkdGg6IDEsIGhlaWdodDogMSB9O1xuICAgIHRoaXMuX2Nyb3BEcmFnU3RhdGUgPSBudWxsO1xuICAgIHRoaXMuX2Nyb3BUb29sQWN0aXZlID0gZmFsc2U7XG4gICAgaWYgKHN3aXBlckVsZW1lbnQpIHtcbiAgICAgIHRoaXMuX2FwcGx5Wm9vbShzd2lwZXJFbGVtZW50KTtcbiAgICAgIHRoaXMuX3VwZGF0ZUVkaXRJbWFnZVNpemUoc3dpcGVyRWxlbWVudCk7XG4gICAgfVxuICB9XG5cbiAgcHJvdGVjdGVkIF9zZXR1cFdoZWVsWm9vbShzd2lwZXJFbGVtZW50OiBhbnkpIHtcbiAgICB0aGlzLl9jbGVhbnVwV2hlZWxab29tKCk7XG4gICAgaWYgKCF0aGlzLm1vdXNlWm9vbUVuYWJsZWQpIHJldHVybjtcbiAgICBjb25zdCBlbDogSFRNTEVsZW1lbnQgPSBzd2lwZXJFbGVtZW50Py5uYXRpdmVFbGVtZW50ID8/IHN3aXBlckVsZW1lbnQ7XG4gICAgaWYgKCFlbCkgcmV0dXJuO1xuXG4gICAgdGhpcy5fd2hlZWxMaXN0ZW5lciA9IChldmVudDogV2hlZWxFdmVudCkgPT4ge1xuICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgIGNvbnN0IHN0ZXAgPSB0aGlzLndoZWVsWm9vbVN0ZXA7XG4gICAgICBjb25zdCBvbGRTY2FsZSA9IHRoaXMuX3pvb21TdGF0ZS5zY2FsZTtcbiAgICAgIGNvbnN0IG5ld1NjYWxlID0gTWF0aC5tYXgoMSwgTWF0aC5taW4ob2xkU2NhbGUgKyAoZXZlbnQuZGVsdGFZIDwgMCA/IHN0ZXAgOiAtc3RlcCksIHRoaXMubWF4Wm9vbVJhdGlvKSk7XG4gICAgICBpZiAobmV3U2NhbGUgPT09IG9sZFNjYWxlKSByZXR1cm47XG5cbiAgICAgIGlmIChuZXdTY2FsZSA9PT0gMSkge1xuICAgICAgICB0aGlzLl96b29tU3RhdGUgPSB7IHNjYWxlOiAxLCB0eDogMCwgdHk6IDAgfTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIFpvb20gdG93YXJkcyBjdXJzb3IgcG9zaXRpb25cbiAgICAgICAgY29uc3Qgc3dpcGVyID0gdGhpcy5nZXRTd2lwZXIoZWwpO1xuICAgICAgICBjb25zdCBhY3RpdmVTbGlkZSA9IHN3aXBlcj8uc2xpZGVzPy5bc3dpcGVyLmFjdGl2ZUluZGV4XSBhcyBIVE1MRWxlbWVudDtcbiAgICAgICAgaWYgKGFjdGl2ZVNsaWRlKSB7XG4gICAgICAgICAgY29uc3QgcmVjdCA9IGFjdGl2ZVNsaWRlLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgICAgICAgIGNvbnN0IGN4ID0gZXZlbnQuY2xpZW50WCAtIHJlY3QubGVmdCAtIHJlY3Qud2lkdGggLyAyO1xuICAgICAgICAgIGNvbnN0IGN5ID0gZXZlbnQuY2xpZW50WSAtIHJlY3QudG9wIC0gcmVjdC5oZWlnaHQgLyAyO1xuICAgICAgICAgIHRoaXMuX3pvb21TdGF0ZS50eCA9IGN4IC0gKChjeCAtIHRoaXMuX3pvb21TdGF0ZS50eCkgKiBuZXdTY2FsZSkgLyBvbGRTY2FsZTtcbiAgICAgICAgICB0aGlzLl96b29tU3RhdGUudHkgPSBjeSAtICgoY3kgLSB0aGlzLl96b29tU3RhdGUudHkpICogbmV3U2NhbGUpIC8gb2xkU2NhbGU7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5fem9vbVN0YXRlLnNjYWxlID0gbmV3U2NhbGU7XG4gICAgICAgIHRoaXMuX2NsYW1wVHJhbnNsYXRpb25zKGVsKTtcbiAgICAgIH1cbiAgICAgIHRoaXMuX2FwcGx5Wm9vbShlbCk7XG4gICAgfTtcblxuICAgIGVsLmFkZEV2ZW50TGlzdGVuZXIoJ3doZWVsJywgdGhpcy5fd2hlZWxMaXN0ZW5lciwgeyBwYXNzaXZlOiBmYWxzZSB9KTtcbiAgICB0aGlzLl93aGVlbEVsID0gZWw7XG4gIH1cblxuICBwcm90ZWN0ZWQgX2NsZWFudXBXaGVlbFpvb20oKSB7XG4gICAgaWYgKHRoaXMuX3doZWVsRWwgJiYgdGhpcy5fd2hlZWxMaXN0ZW5lcikge1xuICAgICAgdGhpcy5fd2hlZWxFbC5yZW1vdmVFdmVudExpc3RlbmVyKCd3aGVlbCcsIHRoaXMuX3doZWVsTGlzdGVuZXIpO1xuICAgIH1cbiAgICB0aGlzLl93aGVlbEVsID0gbnVsbDtcbiAgICB0aGlzLl93aGVlbExpc3RlbmVyID0gbnVsbDtcbiAgfVxuXG4gIHByb3RlY3RlZCBfYXBwbHlab29tKHN3aXBlckVsZW1lbnQ6IGFueSkge1xuICAgIGNvbnN0IHN3aXBlciA9IHRoaXMuZ2V0U3dpcGVyKHN3aXBlckVsZW1lbnQpO1xuICAgIGNvbnN0IGFjdGl2ZVNsaWRlID0gc3dpcGVyPy5zbGlkZXM/Lltzd2lwZXIuYWN0aXZlSW5kZXhdIGFzIEhUTUxFbGVtZW50O1xuICAgIGlmICghYWN0aXZlU2xpZGUpIHJldHVybjtcbiAgICBjb25zdCB3cmFwRWwgPSBhY3RpdmVTbGlkZS5xdWVyeVNlbGVjdG9yKCcuc3dpcGVyLXpvb20tY29udGFpbmVyJykgYXMgSFRNTEVsZW1lbnQ7XG4gICAgY29uc3QgaW1nRWwgPSBhY3RpdmVTbGlkZS5xdWVyeVNlbGVjdG9yKCcuc3dpcGVyLXpvb20tdGFyZ2V0LCAuc3dpcGVyLXpvb20tY29udGFpbmVyID4gaW1nJykgYXMgSFRNTEVsZW1lbnQ7XG4gICAgaWYgKCF3cmFwRWwgfHwgIWltZ0VsKSByZXR1cm47XG4gICAgY29uc3QgeyBzY2FsZSwgdHgsIHR5IH0gPSB0aGlzLl96b29tU3RhdGU7XG4gICAgd3JhcEVsLnN0eWxlLnRyYW5zaXRpb25EdXJhdGlvbiA9ICcwbXMnO1xuICAgIHdyYXBFbC5zdHlsZS50cmFuc2Zvcm0gPSBgdHJhbnNsYXRlM2QoJHt0eH1weCwgJHt0eX1weCwgMClgO1xuICAgIGltZ0VsLnN0eWxlLnRyYW5zaXRpb25EdXJhdGlvbiA9ICcwbXMnO1xuICAgIGltZ0VsLnN0eWxlLnRyYW5zZm9ybSA9IGB0cmFuc2xhdGUzZCgwLCAwLCAwKSBzY2FsZSgke3NjYWxlfSkgcm90YXRlKCR7dGhpcy5fcm90YXRpb259ZGVnKWA7XG4gICAgd3JhcEVsLnN0eWxlLmN1cnNvciA9IHNjYWxlID4gMSA/ICdncmFiJyA6ICcnO1xuICB9XG5cbiAgcHJvdGVjdGVkIF9jbGFtcFRyYW5zbGF0aW9ucyhzd2lwZXJFbGVtZW50OiBhbnkpIHtcbiAgICBjb25zdCBzd2lwZXIgPSB0aGlzLmdldFN3aXBlcihzd2lwZXJFbGVtZW50KTtcbiAgICBjb25zdCBhY3RpdmVTbGlkZSA9IHN3aXBlcj8uc2xpZGVzPy5bc3dpcGVyLmFjdGl2ZUluZGV4XSBhcyBIVE1MRWxlbWVudDtcbiAgICBpZiAoIWFjdGl2ZVNsaWRlKSByZXR1cm47XG4gICAgY29uc3QgaW1nRWwgPSBhY3RpdmVTbGlkZS5xdWVyeVNlbGVjdG9yKCcuc3dpcGVyLXpvb20tdGFyZ2V0LCAuc3dpcGVyLXpvb20tY29udGFpbmVyID4gaW1nJykgYXMgSFRNTEVsZW1lbnQ7XG4gICAgaWYgKCFpbWdFbCkgcmV0dXJuO1xuICAgIGNvbnN0IGltZ1cgPSBpbWdFbC5vZmZzZXRXaWR0aCB8fCBpbWdFbC5jbGllbnRXaWR0aDtcbiAgICBjb25zdCBpbWdIID0gaW1nRWwub2Zmc2V0SGVpZ2h0IHx8IGltZ0VsLmNsaWVudEhlaWdodDtcbiAgICBpZiAoIWltZ1cgfHwgIWltZ0gpIHJldHVybjtcbiAgICBjb25zdCB7IHNjYWxlIH0gPSB0aGlzLl96b29tU3RhdGU7XG4gICAgY29uc3QgbWF4VHggPSBNYXRoLm1heCgwLCAoaW1nVyAqIHNjYWxlIC0gYWN0aXZlU2xpZGUub2Zmc2V0V2lkdGgpIC8gMik7XG4gICAgY29uc3QgbWF4VHkgPSBNYXRoLm1heCgwLCAoaW1nSCAqIHNjYWxlIC0gYWN0aXZlU2xpZGUub2Zmc2V0SGVpZ2h0KSAvIDIpO1xuICAgIHRoaXMuX3pvb21TdGF0ZS50eCA9IE1hdGgubWF4KC1tYXhUeCwgTWF0aC5taW4obWF4VHgsIHRoaXMuX3pvb21TdGF0ZS50eCkpO1xuICAgIHRoaXMuX3pvb21TdGF0ZS50eSA9IE1hdGgubWF4KC1tYXhUeSwgTWF0aC5taW4obWF4VHksIHRoaXMuX3pvb21TdGF0ZS50eSkpO1xuICB9XG5cbiAgcHJvdGVjdGVkIF9nZXRBY3RpdmVab29tV3JhcEVsKHN3aXBlckVsZW1lbnQ6IGFueSk6IEhUTUxFbGVtZW50IHwgbnVsbCB7XG4gICAgY29uc3Qgc3dpcGVyID0gdGhpcy5nZXRTd2lwZXIoc3dpcGVyRWxlbWVudCk7XG4gICAgY29uc3QgYWN0aXZlU2xpZGUgPSBzd2lwZXI/LnNsaWRlcz8uW3N3aXBlci5hY3RpdmVJbmRleF0gYXMgSFRNTEVsZW1lbnQ7XG4gICAgcmV0dXJuIGFjdGl2ZVNsaWRlPy5xdWVyeVNlbGVjdG9yKCcuc3dpcGVyLXpvb20tY29udGFpbmVyJykgPz8gbnVsbDtcbiAgfVxuXG4gIHByb3RlY3RlZCBtYXJrRm9yQ2hlY2soKSB7XG4gICAgdGhpcy5jZC5tYXJrRm9yQ2hlY2soKTtcbiAgfVxufVxuIiwiPGlvbi1jb250ZW50XG4gIGNsYXNzPVwiaW9uLW5vLXBhZGRpbmcgaW9uLW5vLW1hcmdpblwiXG4gIChtb3VzZWRvd24pPVwib25Qb2ludGVyRG93bigkZXZlbnQsIHNsaWRlc2hvd1N3aXBlcilcIlxuICAobW91c2Vtb3ZlKT1cIm9uUG9pbnRlck1vdmUoJGV2ZW50LCBzbGlkZXNob3dTd2lwZXIpXCJcbiAgKG1vdXNldXApPVwib25Qb2ludGVyVXAoJGV2ZW50LCBzbGlkZXNob3dTd2lwZXIpXCJcbiAgKG1vdXNlbGVhdmUpPVwib25Qb2ludGVyVXAoJGV2ZW50LCBzbGlkZXNob3dTd2lwZXIpXCJcbiAgKHRvdWNobW92ZSk9XCJvbkNyb3BQb2ludGVyTW92ZSgkZXZlbnQsIHNsaWRlc2hvd1N3aXBlcilcIlxuICAodG91Y2hlbmQpPVwib25Dcm9wUG9pbnRlclVwKCRldmVudCwgc2xpZGVzaG93U3dpcGVyKVwiXG4gICh0b3VjaGNhbmNlbCk9XCJvbkNyb3BQb2ludGVyVXAoJGV2ZW50LCBzbGlkZXNob3dTd2lwZXIpXCJcbj5cbiAgQGxldCBjb3VudCA9IHJvd3M/Lmxlbmd0aCB8fCAwO1xuICA8IS0tIENvdW50ZXIgIC0tPlxuICA8ZGl2IGNsYXNzPVwidG9wLWxlZnRcIj5cbiAgICA8aW9uLWxhYmVsIGNvbG9yPVwibGlnaHRcIj57eyBhY3RpdmVTbGlkZUluZGV4ICsgMSB9fSAvIHt7IGNvdW50IH19PC9pb24tbGFiZWw+XG4gIDwvZGl2PlxuXG4gIDwhLS0gem9vbSAvIHJvdGF0aW9uIC8gY3JvcCBidXR0b25zOiBhbGwgc2hvd24gdG9nZXRoZXIsIG5vIFwiZW50ZXIgZWRpdCBtb2RlXCIgc3RlcCBuZWVkZWQgLS0+XG4gIDxkaXYgY2xhc3M9XCJ0b3AtcmlnaHRcIj5cbiAgICBAaWYgKCFtb2JpbGUpIHtcbiAgICAgIDxpb24tZmFiLWJ1dHRvbiBzaXplPVwic21hbGxcIiAoY2xpY2spPVwiem9vbShzbGlkZXNob3dTd2lwZXIsIHRydWUpXCIgY29sb3I9XCJsaWdodFwiPlxuICAgICAgICA8aW9uLWljb24gbmFtZT1cInNlYXJjaFwiPjwvaW9uLWljb24+XG4gICAgICAgIDxpb24taWNvbiBjbGFzcz1cImljb24tc2Vjb25kYXJ5XCIgbmFtZT1cImFkZFwiPjwvaW9uLWljb24+XG4gICAgICA8L2lvbi1mYWItYnV0dG9uPlxuICAgICAgPGlvbi1mYWItYnV0dG9uIHNpemU9XCJzbWFsbFwiIChjbGljayk9XCJ6b29tKHNsaWRlc2hvd1N3aXBlciwgZmFsc2UpXCIgY29sb3I9XCJsaWdodFwiPlxuICAgICAgICA8aW9uLWljb24gbmFtZT1cInNlYXJjaFwiPjwvaW9uLWljb24+XG4gICAgICAgIDxpb24taWNvbiBjbGFzcz1cImljb24tc2Vjb25kYXJ5XCIgbmFtZT1cInJlbW92ZVwiPjwvaW9uLWljb24+XG4gICAgICA8L2lvbi1mYWItYnV0dG9uPlxuICAgIH1cbiAgICBAaWYgKCFyZWFkT25seSAmJiByb3RhdGlvbkVuYWJsZWQpIHtcbiAgICAgIDxpb24tZmFiLWJ1dHRvblxuICAgICAgICBzaXplPVwic21hbGxcIlxuICAgICAgICAoY2xpY2spPVwicm90YXRlKHNsaWRlc2hvd1N3aXBlciwgJ2xlZnQnKVwiXG4gICAgICAgIGNvbG9yPVwibGlnaHRcIlxuICAgICAgICBbdGl0bGVdPVwiIXNob3dUb29sdGlwID8gKCdJTUFHRS5HQUxMRVJZLkJUTl9ST1RBVEVfTEVGVCcgfCB0cmFuc2xhdGUpIDogJydcIlxuICAgICAgICBbbWF0VG9vbHRpcF09XCJzaG93VG9vbHRpcCA/ICgnSU1BR0UuR0FMTEVSWS5CVE5fUk9UQVRFX0xFRlQnIHwgdHJhbnNsYXRlKSA6ICcnXCJcbiAgICAgID5cbiAgICAgICAgPG1hdC1pY29uPnJvdGF0ZV9sZWZ0PC9tYXQtaWNvbj5cbiAgICAgIDwvaW9uLWZhYi1idXR0b24+XG4gICAgICA8aW9uLWZhYi1idXR0b25cbiAgICAgICAgc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgKGNsaWNrKT1cInJvdGF0ZShzbGlkZXNob3dTd2lwZXIsICdyaWdodCcpXCJcbiAgICAgICAgY29sb3I9XCJsaWdodFwiXG4gICAgICAgIFt0aXRsZV09XCIhc2hvd1Rvb2x0aXAgPyAoJ0lNQUdFLkdBTExFUlkuQlROX1JPVEFURV9SSUdIVCcgfCB0cmFuc2xhdGUpIDogJydcIlxuICAgICAgICBbbWF0VG9vbHRpcF09XCJzaG93VG9vbHRpcCA/ICgnSU1BR0UuR0FMTEVSWS5CVE5fUk9UQVRFX1JJR0hUJyB8IHRyYW5zbGF0ZSkgOiAnJ1wiXG4gICAgICA+XG4gICAgICAgIDxtYXQtaWNvbj5yb3RhdGVfcmlnaHQ8L21hdC1pY29uPlxuICAgICAgPC9pb24tZmFiLWJ1dHRvbj5cbiAgICB9XG4gICAgQGlmICghcmVhZE9ubHkgJiYgY3JvcEVuYWJsZWQpIHtcbiAgICAgIDxpb24tZmFiLWJ1dHRvblxuICAgICAgICBzaXplPVwic21hbGxcIlxuICAgICAgICAoY2xpY2spPVwidG9nZ2xlQ3JvcFRvb2woc2xpZGVzaG93U3dpcGVyKVwiXG4gICAgICAgIFtjb2xvcl09XCJfY3JvcFRvb2xBY3RpdmUgPyAndGVydGlhcnknIDogJ2xpZ2h0J1wiXG4gICAgICAgIFt0aXRsZV09XCJcbiAgICAgICAgICAhc2hvd1Rvb2x0aXBcbiAgICAgICAgICAgID8gKChfY3JvcFRvb2xBY3RpdmUgPyAnSU1BR0UuR0FMTEVSWS5CVE5fQ1JPUF9ERUFDVElWQVRFJyA6ICdJTUFHRS5HQUxMRVJZLkJUTl9DUk9QX0FDVElWQVRFJykgfCB0cmFuc2xhdGUpXG4gICAgICAgICAgICA6ICcnXG4gICAgICAgIFwiXG4gICAgICAgIFttYXRUb29sdGlwXT1cIlxuICAgICAgICAgIHNob3dUb29sdGlwXG4gICAgICAgICAgICA/ICgoX2Nyb3BUb29sQWN0aXZlID8gJ0lNQUdFLkdBTExFUlkuQlROX0NST1BfREVBQ1RJVkFURScgOiAnSU1BR0UuR0FMTEVSWS5CVE5fQ1JPUF9BQ1RJVkFURScpIHwgdHJhbnNsYXRlKVxuICAgICAgICAgICAgOiAnJ1xuICAgICAgICBcIlxuICAgICAgPlxuICAgICAgICA8bWF0LWljb24+Y3JvcDwvbWF0LWljb24+XG4gICAgICA8L2lvbi1mYWItYnV0dG9uPlxuICAgICAgQGlmIChfY3JvcFRvb2xBY3RpdmUpIHtcbiAgICAgICAgPGlvbi1mYWItYnV0dG9uXG4gICAgICAgICAgc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAoY2xpY2spPVwicmVzZXRDcm9wKHNsaWRlc2hvd1N3aXBlcilcIlxuICAgICAgICAgIGNvbG9yPVwibGlnaHRcIlxuICAgICAgICAgIFt0aXRsZV09XCIhc2hvd1Rvb2x0aXAgPyAoJ0lNQUdFLkdBTExFUlkuQlROX0NST1BfUkVTRVQnIHwgdHJhbnNsYXRlKSA6ICcnXCJcbiAgICAgICAgICBbbWF0VG9vbHRpcF09XCJzaG93VG9vbHRpcCA/ICgnSU1BR0UuR0FMTEVSWS5CVE5fQ1JPUF9SRVNFVCcgfCB0cmFuc2xhdGUpIDogJydcIlxuICAgICAgICA+XG4gICAgICAgICAgPG1hdC1pY29uPmNyb3BfZnJlZTwvbWF0LWljb24+XG4gICAgICAgIDwvaW9uLWZhYi1idXR0b24+XG4gICAgICB9XG4gICAgfVxuICA8L2Rpdj5cblxuICA8IS0tIG5hdmlnYXRpb24gc2lkZSBidXR0b25zIG5leHQvYmFjayAtLT5cbiAgPG5nLWNvbnRhaW5lciAqbmdJZj1cIiFtb2JpbGUgJiYgKHJvd3MgfCBpc0FycmF5TGVuZ3RoOiB7IGdyZWF0ZXJUaGFuOiAxIH0pXCI+XG4gICAgPGRpdiBjbGFzcz1cInNpZGUtYnV0dG9ucyBwcmV2XCIgKGNsaWNrKT1cInNsaWRlUHJldihzbGlkZXNob3dTd2lwZXIpXCI+XG4gICAgICA8aW9uLWljb24gbmFtZT1cImNoZXZyb24tYmFja1wiPjwvaW9uLWljb24+XG4gICAgPC9kaXY+XG4gICAgPGRpdiBjbGFzcz1cInNpZGUtYnV0dG9ucyBuZXh0XCIgKGNsaWNrKT1cInNsaWRlTmV4dChzbGlkZXNob3dTd2lwZXIpXCI+XG4gICAgICA8aW9uLWljb24gbmFtZT1cImNoZXZyb24tZm9yd2FyZFwiPjwvaW9uLWljb24+XG4gICAgPC9kaXY+XG4gIDwvbmctY29udGFpbmVyPlxuXG4gIDwhLS0gaW1hZ2Ugc2xpZGVzIC0tPlxuICA8c3dpcGVyLWNvbnRhaW5lclxuICAgICNzbGlkZXNob3dTd2lwZXJcbiAgICBbbW9kdWxlc109XCJzd2lwZXJNb2R1bGVzXCJcbiAgICBbem9vbV09XCIhbW91c2Vab29tRW5hYmxlZFwiXG4gICAgKHN3aXBlcnNsaWRlY2hhbmdlKT1cIm9uU2xpZGVDaGFuZ2Uoc2xpZGVzaG93U3dpcGVyKVwiXG4gICAgKHN3aXBlcmluaXQpPVwib25Td2lwZXJJbml0KHNsaWRlc2hvd1N3aXBlcilcIlxuICA+XG4gICAgQGZvciAocm93IG9mIHJvd3M7IHRyYWNrIHJvdy5pZDsgbGV0IGkgPSAkaW5kZXgpIHtcbiAgICAgIDxzd2lwZXItc2xpZGU+XG4gICAgICAgIEBsZXQgaW1hZ2UgPSByb3cgfCBwcm9wZXJ0eUdldDogJ2N1cnJlbnREYXRhJztcbiAgICAgICAgPGRpdiBjbGFzcz1cInN3aXBlci16b29tLWNvbnRhaW5lclwiPlxuICAgICAgICAgIEBpZiAoaW1hZ2UudXJsKSB7XG4gICAgICAgICAgICA8aW9uLWltZyBjbGFzcz1cInN3aXBlci16b29tLXRhcmdldFwiIFtzcmNdPVwiaW1hZ2UudXJsIHwgYXBwZW5kUXVlcnlQYXJhbXM6IGdldEltYWdlU2l6ZVF1ZXJ5UGFyYW1zKClcIiAvPlxuICAgICAgICAgIH0gQGVsc2Uge1xuICAgICAgICAgICAgPGltZyBbc3JjXT1cImltYWdlLmRhdGFVcmxcIiBbYWx0XT1cImltYWdlLnRpdGxlXCIgLz5cbiAgICAgICAgICB9XG5cbiAgICAgICAgICA8IS0tIGNyb3Agb3ZlcmxheTogb25seSByZW5kZXJlZCBmb3IgdGhlIGFjdGl2ZSBzbGlkZSAtLT5cbiAgICAgICAgICBAaWYgKCFyZWFkT25seSAmJiBjcm9wRW5hYmxlZCAmJiBfY3JvcFRvb2xBY3RpdmUgJiYgaSA9PT0gYWN0aXZlU2xpZGVJbmRleCkge1xuICAgICAgICAgICAgPGRpdlxuICAgICAgICAgICAgICBjbGFzcz1cImNyb3Atb3ZlcmxheVwiXG4gICAgICAgICAgICAgIChtb3VzZWRvd24pPVwib25Dcm9wUG9pbnRlckRvd24oJGV2ZW50LCBzbGlkZXNob3dTd2lwZXIsICdtb3ZlJylcIlxuICAgICAgICAgICAgICAodG91Y2hzdGFydCk9XCJvbkNyb3BQb2ludGVyRG93bigkZXZlbnQsIHNsaWRlc2hvd1N3aXBlciwgJ21vdmUnKVwiXG4gICAgICAgICAgICA+XG4gICAgICAgICAgICAgIDwhLS0gZGFya2VuZWQgbWFzayBhcm91bmQgdGhlIHNlbGVjdGlvbjogY2xpcHBlZCB0byB0aGUgaW1hZ2UgYm91bmRzIC0tPlxuICAgICAgICAgICAgICA8ZGl2IGNsYXNzPVwiY3JvcC1tYXNrXCI+XG4gICAgICAgICAgICAgICAgPGRpdlxuICAgICAgICAgICAgICAgICAgY2xhc3M9XCJjcm9wLW1hc2stcmVjdFwiXG4gICAgICAgICAgICAgICAgICBbc3R5bGUubGVmdC4lXT1cIl9jcm9wUmVjdC54ICogMTAwXCJcbiAgICAgICAgICAgICAgICAgIFtzdHlsZS50b3AuJV09XCJfY3JvcFJlY3QueSAqIDEwMFwiXG4gICAgICAgICAgICAgICAgICBbc3R5bGUud2lkdGguJV09XCJfY3JvcFJlY3Qud2lkdGggKiAxMDBcIlxuICAgICAgICAgICAgICAgICAgW3N0eWxlLmhlaWdodC4lXT1cIl9jcm9wUmVjdC5oZWlnaHQgKiAxMDBcIlxuICAgICAgICAgICAgICAgID48L2Rpdj5cbiAgICAgICAgICAgICAgPC9kaXY+XG5cbiAgICAgICAgICAgICAgPCEtLSBpbnRlcmFjdGl2ZSBzZWxlY3Rpb24gYm9yZGVyLCBncmlkICYgZHJhZyBoYW5kbGVzOiBuZXZlciBjbGlwcGVkLCBzbyBjb3JuZXIvZWRnZSBoYW5kbGVzXG4gICAgICAgICAgICAgICAgICAgcmVtYWluIGZ1bGx5IGdyYWJiYWJsZSBldmVuIHdoZW4gdGhlIHNlbGVjdGlvbiB0b3VjaGVzIHRoZSBpbWFnZSBib3VuZHMgLS0+XG4gICAgICAgICAgICAgIDxkaXZcbiAgICAgICAgICAgICAgICBjbGFzcz1cImNyb3AtcmVjdFwiXG4gICAgICAgICAgICAgICAgW3N0eWxlLmxlZnQuJV09XCJfY3JvcFJlY3QueCAqIDEwMFwiXG4gICAgICAgICAgICAgICAgW3N0eWxlLnRvcC4lXT1cIl9jcm9wUmVjdC55ICogMTAwXCJcbiAgICAgICAgICAgICAgICBbc3R5bGUud2lkdGguJV09XCJfY3JvcFJlY3Qud2lkdGggKiAxMDBcIlxuICAgICAgICAgICAgICAgIFtzdHlsZS5oZWlnaHQuJV09XCJfY3JvcFJlY3QuaGVpZ2h0ICogMTAwXCJcbiAgICAgICAgICAgICAgPlxuICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJjcm9wLWdyaWRcIj5cbiAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwibGluZSB2XCIgc3R5bGU9XCJsZWZ0OiAzMy4zMzMlXCI+PC9zcGFuPlxuICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9XCJsaW5lIHZcIiBzdHlsZT1cImxlZnQ6IDY2LjY2NiVcIj48L3NwYW4+XG4gICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz1cImxpbmUgaFwiIHN0eWxlPVwidG9wOiAzMy4zMzMlXCI+PC9zcGFuPlxuICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9XCJsaW5lIGhcIiBzdHlsZT1cInRvcDogNjYuNjY2JVwiPjwvc3Bhbj5cbiAgICAgICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICAgICAgICBAZm9yIChoYW5kbGUgb2YgX2Nyb3BIYW5kbGVzOyB0cmFjayBoYW5kbGUpIHtcbiAgICAgICAgICAgICAgICAgIDxkaXZcbiAgICAgICAgICAgICAgICAgICAgY2xhc3M9XCJjcm9wLWhhbmRsZSB7eyBoYW5kbGUgfX1cIlxuICAgICAgICAgICAgICAgICAgICAobW91c2Vkb3duKT1cIm9uQ3JvcFBvaW50ZXJEb3duKCRldmVudCwgc2xpZGVzaG93U3dpcGVyLCBoYW5kbGUpXCJcbiAgICAgICAgICAgICAgICAgICAgKHRvdWNoc3RhcnQpPVwib25Dcm9wUG9pbnRlckRvd24oJGV2ZW50LCBzbGlkZXNob3dTd2lwZXIsIGhhbmRsZSlcIlxuICAgICAgICAgICAgICAgICAgPjwvZGl2PlxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgPC9kaXY+XG4gICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICB9XG4gICAgICAgIDwvZGl2PlxuICAgICAgPC9zd2lwZXItc2xpZGU+XG4gICAgfVxuICA8L3N3aXBlci1jb250YWluZXI+XG48L2lvbi1jb250ZW50PlxuXG48aW9uLWZvb3Rlcj5cbiAgPGlvbi10b29sYmFyIGNvbG9yPVwibGlnaHRcIj5cbiAgICBAaWYgKCFoYXNQZW5kaW5nRWRpdCkge1xuICAgICAgPGlvbi1yb3c+XG4gICAgICAgIDxpb24tY29sIHNpemU9XCI0XCIgY2xhc3M9XCJpb24tdGV4dC1zdGFydFwiPlxuICAgICAgICAgIDxpb24tYnV0dG9uXG4gICAgICAgICAgICAqbmdJZj1cIiFyZWFkT25seVwiXG4gICAgICAgICAgICAoY2xpY2spPVwicmVxdWVzdERlbGV0ZShzbGlkZXNob3dTd2lwZXIpXCJcbiAgICAgICAgICAgIFtkaXNhYmxlZF09XCJkaXNhYmxlZFwiXG4gICAgICAgICAgICBmaWxsPVwiY2xlYXJcIlxuICAgICAgICAgICAgY29sb3I9XCJkYW5nZXJcIlxuICAgICAgICAgID5cbiAgICAgICAgICAgIDxtYXQtaWNvbiBzbG90PVwic3RhcnRcIj5kZWxldGU8L21hdC1pY29uPlxuICAgICAgICAgICAge3sgJ0NPTU1PTi5CVE5fREVMRVRFJyB8IHRyYW5zbGF0ZSB9fVxuICAgICAgICAgIDwvaW9uLWJ1dHRvbj5cbiAgICAgICAgPC9pb24tY29sPlxuICAgICAgICA8aW9uLWNvbCBzaXplPVwiNFwiPjwvaW9uLWNvbD5cbiAgICAgICAgPGlvbi1jb2wgc2l6ZT1cIjRcIiBjbGFzcz1cImlvbi10ZXh0LWVuZFwiPlxuICAgICAgICAgIDxpb24tYnV0dG9uIChjbGljayk9XCJjbG9zZVJlcXVlc3RlZC5lbWl0KClcIiBjb2xvcj1cInRlcnRpYXJ5XCIgZmlsbD1cImNsZWFyXCIgKm5nSWY9XCJtb2JpbGU7IGVsc2UgZGVza3RvcFwiPlxuICAgICAgICAgICAgPGlvbi1pY29uIHNsb3Q9XCJzdGFydFwiIG5hbWU9XCJjbG9zZVwiPjwvaW9uLWljb24+XG4gICAgICAgICAgICB7eyAnQ09NTU9OLkJUTl9DTE9TRScgfCB0cmFuc2xhdGUgfX1cbiAgICAgICAgICA8L2lvbi1idXR0b24+XG4gICAgICAgICAgPG5nLXRlbXBsYXRlICNkZXNrdG9wPlxuICAgICAgICAgICAgPGlvbi1idXR0b24gKGNsaWNrKT1cImNsb3NlUmVxdWVzdGVkLmVtaXQoKVwiIGNvbG9yPVwidGVydGlhcnlcIj5cbiAgICAgICAgICAgICAge3sgJ0NPTU1PTi5CVE5fQ0xPU0UnIHwgdHJhbnNsYXRlIH19XG4gICAgICAgICAgICA8L2lvbi1idXR0b24+XG4gICAgICAgICAgPC9uZy10ZW1wbGF0ZT5cbiAgICAgICAgPC9pb24tY29sPlxuICAgICAgPC9pb24tcm93PlxuICAgIH0gQGVsc2Uge1xuICAgICAgPCEtLSBBIGNyb3Agb3Igcm90YXRpb24gY2hhbmdlIGlzIHBlbmRpbmc6IGFzayBmb3IgYW4gZXhwbGljaXQgdmFsaWRhdGlvbiBiZWZvcmUgZXhwb3J0aW5nIHRoZSBpbWFnZSAtLT5cbiAgICAgIDxpb24tcm93PlxuICAgICAgICA8aW9uLWNvbCBzaXplPVwiNlwiIGNsYXNzPVwiaW9uLXRleHQtc3RhcnRcIj5cbiAgICAgICAgICA8aW9uLWJ1dHRvbiAoY2xpY2spPVwiZGlzY2FyZEVkaXQoc2xpZGVzaG93U3dpcGVyKVwiIGNvbG9yPVwibWVkaXVtXCIgZmlsbD1cImNsZWFyXCI+XG4gICAgICAgICAgICA8aW9uLWljb24gc2xvdD1cInN0YXJ0XCIgbmFtZT1cImNsb3NlXCI+PC9pb24taWNvbj5cbiAgICAgICAgICAgIHt7ICdDT01NT04uQlROX0NBTkNFTCcgfCB0cmFuc2xhdGUgfX1cbiAgICAgICAgICA8L2lvbi1idXR0b24+XG4gICAgICAgIDwvaW9uLWNvbD5cbiAgICAgICAgPGlvbi1jb2wgc2l6ZT1cIjZcIiBjbGFzcz1cImlvbi10ZXh0LWVuZFwiPlxuICAgICAgICAgIDxpb24tYnV0dG9uIChjbGljayk9XCJ2YWxpZGF0ZUVkaXQoc2xpZGVzaG93U3dpcGVyKVwiIGNvbG9yPVwidGVydGlhcnlcIj5cbiAgICAgICAgICAgIDxpb24taWNvbiBzbG90PVwic3RhcnRcIiBuYW1lPVwiY2hlY2ttYXJrXCI+PC9pb24taWNvbj5cbiAgICAgICAgICAgIHt7ICdDT01NT04uQlROX1ZBTElEQVRFJyB8IHRyYW5zbGF0ZSB9fVxuICAgICAgICAgIDwvaW9uLWJ1dHRvbj5cbiAgICAgICAgPC9pb24tY29sPlxuICAgICAgPC9pb24tcm93PlxuICAgIH1cbiAgPC9pb24tdG9vbGJhcj5cbjwvaW9uLWZvb3Rlcj5cbiJdfQ==