UNPKG

matrix-react-sdk

Version:
482 lines (470 loc) 79.9 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = _interopRequireWildcard(require("react")); var _reactFocusLock = _interopRequireDefault(require("react-focus-lock")); var _languageHandler = require("../../../languageHandler"); var _MemberAvatar = _interopRequireDefault(require("../avatars/MemberAvatar")); var _ContextMenuTooltipButton = require("../../../accessibility/context_menu/ContextMenuTooltipButton"); var _MessageContextMenu = _interopRequireDefault(require("../context_menus/MessageContextMenu")); var _ContextMenu = require("../../structures/ContextMenu"); var _MessageTimestamp = _interopRequireDefault(require("../messages/MessageTimestamp")); var _SettingsStore = _interopRequireDefault(require("../../../settings/SettingsStore")); var _DateUtils = require("../../../DateUtils"); var _dispatcher = _interopRequireDefault(require("../../../dispatcher/dispatcher")); var _actions = require("../../../dispatcher/actions"); var _Mouse = require("../../../utils/Mouse"); var _UIStore = _interopRequireDefault(require("../../../stores/UIStore")); var _KeyboardShortcuts = require("../../../accessibility/KeyboardShortcuts"); var _KeyBindingsManager = require("../../../KeyBindingsManager"); var _FileUtils = require("../../../utils/FileUtils"); var _AccessibleButton = _interopRequireDefault(require("./AccessibleButton")); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /* Copyright 2024 New Vector Ltd. Copyright 2020, 2021 Šimon Brandner <simon.bra.ag@gmail.com> Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> Copyright 2015, 2016 OpenMarket Ltd SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ // Max scale to keep gaps around the image const MAX_SCALE = 0.95; // This is used for the buttons const ZOOM_STEP = 0.1; // This is used for mouse wheel events const ZOOM_COEFFICIENT = 0.0025; // If we have moved only this much we can zoom const ZOOM_DISTANCE = 10; // Height of mx_ImageView_panel const getPanelHeight = () => { const value = getComputedStyle(document.documentElement).getPropertyValue("--image-view-panel-height"); // Return the value as a number without the unit return parseInt(value.slice(0, value.length - 2)); }; class ImageView extends _react.default.Component { constructor(props) { super(props); // XXX: Refs to functional components (0, _defineProperty2.default)(this, "contextMenuButton", /*#__PURE__*/(0, _react.createRef)()); (0, _defineProperty2.default)(this, "focusLock", /*#__PURE__*/(0, _react.createRef)()); (0, _defineProperty2.default)(this, "imageWrapper", /*#__PURE__*/(0, _react.createRef)()); (0, _defineProperty2.default)(this, "image", /*#__PURE__*/(0, _react.createRef)()); (0, _defineProperty2.default)(this, "initX", 0); (0, _defineProperty2.default)(this, "initY", 0); (0, _defineProperty2.default)(this, "previousX", 0); (0, _defineProperty2.default)(this, "previousY", 0); (0, _defineProperty2.default)(this, "animatingLoading", false); (0, _defineProperty2.default)(this, "imageIsLoaded", false); (0, _defineProperty2.default)(this, "imageLoaded", () => { if (!this.image.current) return; // First, we calculate the zoom, so that the image has the same size as // the thumbnail const { thumbnailInfo } = this.props; if (thumbnailInfo?.width) { this.setState({ zoom: thumbnailInfo.width / this.image.current.naturalWidth }); } // Once the zoom is set, we the image is considered loaded and we can // start animating it into the center of the screen this.imageIsLoaded = true; this.animatingLoading = true; this.setZoomAndRotation(); this.setState({ translationX: 0, translationY: 0 }); // Once the position is set, there is no need to animate anymore this.animatingLoading = false; }); (0, _defineProperty2.default)(this, "recalculateZoom", () => { this.setZoomAndRotation(); }); (0, _defineProperty2.default)(this, "setZoomAndRotation", inputRotation => { const image = this.image.current; const imageWrapper = this.imageWrapper.current; if (!image || !imageWrapper) return; const rotation = inputRotation ?? this.state.rotation; const imageIsNotFlipped = rotation % 180 === 0; // If the image is rotated take it into account const width = imageIsNotFlipped ? image.naturalWidth : image.naturalHeight; const height = imageIsNotFlipped ? image.naturalHeight : image.naturalWidth; const zoomX = imageWrapper.clientWidth / width; const zoomY = imageWrapper.clientHeight / height; // If the image is smaller in both dimensions set its the zoom to 1 to // display it in its original size if (zoomX >= 1 && zoomY >= 1) { this.setState({ zoom: 1, minZoom: 1, maxZoom: 1, rotation: rotation }); return; } // We set minZoom to the min of the zoomX and zoomY to avoid overflow in // any direction. We also multiply by MAX_SCALE to get a gap around the // image by default const minZoom = Math.min(zoomX, zoomY) * MAX_SCALE; // If zoom is smaller than minZoom don't go below that value const zoom = this.state.zoom <= this.state.minZoom ? minZoom : this.state.zoom; this.setState({ minZoom: minZoom, maxZoom: 1, rotation: rotation, zoom: zoom }); }); (0, _defineProperty2.default)(this, "onWheel", ev => { if (ev.target === this.image.current) { ev.stopPropagation(); ev.preventDefault(); const { deltaY } = (0, _Mouse.normalizeWheelEvent)(ev); // Zoom in on the point on the image targeted by the cursor this.zoomDelta(-deltaY * ZOOM_COEFFICIENT, ev.offsetX, ev.offsetY); } }); (0, _defineProperty2.default)(this, "onZoomInClick", () => { this.zoomDelta(ZOOM_STEP); }); (0, _defineProperty2.default)(this, "onZoomOutClick", () => { this.zoomDelta(-ZOOM_STEP); }); (0, _defineProperty2.default)(this, "onKeyDown", ev => { const action = (0, _KeyBindingsManager.getKeyBindingsManager)().getAccessibilityAction(ev); switch (action) { case _KeyboardShortcuts.KeyBindingAction.Escape: ev.stopPropagation(); ev.preventDefault(); this.props.onFinished(); break; } }); (0, _defineProperty2.default)(this, "onRotateCounterClockwiseClick", () => { const cur = this.state.rotation; this.setZoomAndRotation(cur - 90); }); (0, _defineProperty2.default)(this, "onRotateClockwiseClick", () => { const cur = this.state.rotation; this.setZoomAndRotation(cur + 90); }); (0, _defineProperty2.default)(this, "onDownloadClick", () => { const a = document.createElement("a"); a.href = this.props.src; if (this.props.name) a.download = this.props.name; a.target = "_blank"; a.rel = "noreferrer noopener"; a.click(); }); (0, _defineProperty2.default)(this, "onOpenContextMenu", () => { this.setState({ contextMenuDisplayed: true }); }); (0, _defineProperty2.default)(this, "onCloseContextMenu", () => { this.setState({ contextMenuDisplayed: false }); }); (0, _defineProperty2.default)(this, "onPermalinkClicked", ev => { // This allows the permalink to be opened in a new tab/window or copied as // matrix.to, but also for it to enable routing within Element when clicked. ev.preventDefault(); _dispatcher.default.dispatch({ action: _actions.Action.ViewRoom, event_id: this.props.mxEvent?.getId(), highlighted: true, room_id: this.props.mxEvent?.getRoomId(), metricsTrigger: undefined // room doesn't change }); this.props.onFinished(); }); (0, _defineProperty2.default)(this, "onStartMoving", ev => { ev.stopPropagation(); ev.preventDefault(); // Don't do anything if we pressed any // other button than the left one if (ev.button !== 0) return; // Zoom in if we are completely zoomed out and increase the zoom factor for images // smaller than the viewport size if (this.state.zoom === this.state.minZoom) { this.zoom(this.state.maxZoom === this.state.minZoom ? 2 * this.state.maxZoom : this.state.maxZoom, ev.nativeEvent.offsetX, ev.nativeEvent.offsetY); return; } this.setState({ moving: true }); this.previousX = this.state.translationX; this.previousY = this.state.translationY; this.initX = ev.pageX - this.state.translationX; this.initY = ev.pageY - this.state.translationY; }); (0, _defineProperty2.default)(this, "onMoving", ev => { ev.stopPropagation(); ev.preventDefault(); if (!this.state.moving) return; this.setState({ translationX: ev.pageX - this.initX, translationY: ev.pageY - this.initY }); }); (0, _defineProperty2.default)(this, "onEndMoving", () => { // Zoom out if we haven't moved much if (this.state.moving && Math.abs(this.state.translationX - this.previousX) < ZOOM_DISTANCE && Math.abs(this.state.translationY - this.previousY) < ZOOM_DISTANCE) { this.zoom(this.state.minZoom); this.initX = 0; this.initY = 0; } this.setState({ moving: false }); }); const { thumbnailInfo: _thumbnailInfo } = this.props; let translationX = 0; let translationY = 0; if (_thumbnailInfo) { translationX = _thumbnailInfo.positionX + _thumbnailInfo.width / 2 - _UIStore.default.instance.windowWidth / 2; translationY = _thumbnailInfo.positionY + _thumbnailInfo.height / 2 - _UIStore.default.instance.windowHeight / 2 - getPanelHeight() / 2; } this.state = { zoom: 0, // We default to 0 and override this in imageLoaded once we have naturalSize minZoom: MAX_SCALE, maxZoom: MAX_SCALE, rotation: 0, translationX, translationY, moving: false, contextMenuDisplayed: false }; } componentDidMount() { // We have to use addEventListener() because the listener // needs to be passive in order to work with Chromium this.focusLock.current.addEventListener("wheel", this.onWheel, { passive: false }); // We want to recalculate zoom whenever the window's size changes window.addEventListener("resize", this.recalculateZoom); // After the image loads for the first time we want to calculate the zoom this.image.current?.addEventListener("load", this.imageLoaded); } componentWillUnmount() { this.focusLock.current.removeEventListener("wheel", this.onWheel); window.removeEventListener("resize", this.recalculateZoom); this.image.current?.removeEventListener("load", this.imageLoaded); } zoomDelta(delta, anchorX, anchorY) { this.zoom(this.state.zoom + delta, anchorX, anchorY); } zoom(zoomLevel, anchorX, anchorY) { const oldZoom = this.state.zoom; const maxZoom = this.state.maxZoom === this.state.minZoom ? 2 * this.state.maxZoom : this.state.maxZoom; const newZoom = Math.min(zoomLevel, maxZoom); if (newZoom <= this.state.minZoom) { // Zoom out fully this.setState({ zoom: this.state.minZoom, translationX: 0, translationY: 0 }); } else if (typeof anchorX !== "number" || typeof anchorY !== "number") { // Zoom relative to the center of the view this.setState({ zoom: newZoom, translationX: this.state.translationX * newZoom / oldZoom, translationY: this.state.translationY * newZoom / oldZoom }); } else if (this.image.current) { // Zoom relative to the given point on the image. // First we need to figure out the offset of the anchor point // relative to the center of the image, accounting for rotation. let offsetX; let offsetY; // The modulo operator can return negative values for some // rotations, so we have to do some extra work to normalize it const rotation = (this.state.rotation % 360 + 360) % 360; switch (rotation) { case 0: offsetX = this.image.current.clientWidth / 2 - anchorX; offsetY = this.image.current.clientHeight / 2 - anchorY; break; case 90: offsetX = anchorY - this.image.current.clientHeight / 2; offsetY = this.image.current.clientWidth / 2 - anchorX; break; case 180: offsetX = anchorX - this.image.current.clientWidth / 2; offsetY = anchorY - this.image.current.clientHeight / 2; break; case 270: offsetX = this.image.current.clientHeight / 2 - anchorY; offsetY = anchorX - this.image.current.clientWidth / 2; } // Apply the zoom and offset this.setState({ zoom: newZoom, translationX: this.state.translationX + (newZoom - oldZoom) * offsetX, translationY: this.state.translationY + (newZoom - oldZoom) * offsetY }); } } renderContextMenu() { let contextMenu; if (this.state.contextMenuDisplayed && this.props.mxEvent) { contextMenu = /*#__PURE__*/_react.default.createElement(_MessageContextMenu.default, (0, _extends2.default)({}, (0, _ContextMenu.aboveLeftOf)(this.contextMenuButton.current.getBoundingClientRect()), { mxEvent: this.props.mxEvent, permalinkCreator: this.props.permalinkCreator, onFinished: this.onCloseContextMenu, onCloseDialog: this.props.onFinished })); } return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, contextMenu); } render() { const showEventMeta = !!this.props.mxEvent; let transitionClassName; if (this.animatingLoading) transitionClassName = "mx_ImageView_image_animatingLoading";else if (this.state.moving || !this.imageIsLoaded) transitionClassName = "";else transitionClassName = "mx_ImageView_image_animating"; const rotationDegrees = this.state.rotation + "deg"; const zoom = this.state.zoom; const translatePixelsX = this.state.translationX + "px"; const translatePixelsY = this.state.translationY + "px"; // The order of the values is important! // First, we translate and only then we rotate, otherwise // we would apply the translation to an already rotated // image causing it translate in the wrong direction. const style = { transform: `translateX(${translatePixelsX}) translateY(${translatePixelsY}) scale(${zoom}) rotate(${rotationDegrees})` }; if (this.state.moving) style.cursor = "grabbing";else if (this.state.zoom === this.state.minZoom) style.cursor = "zoom-in";else style.cursor = "zoom-out"; let info; if (showEventMeta) { const mxEvent = this.props.mxEvent; const showTwelveHour = _SettingsStore.default.getValue("showTwelveHourTimestamps"); let permalink = "#"; if (this.props.permalinkCreator) { permalink = this.props.permalinkCreator.forEvent(mxEvent.getId()); } const senderName = mxEvent.sender?.name ?? mxEvent.getSender(); const sender = /*#__PURE__*/_react.default.createElement("div", { className: "mx_ImageView_info_sender" }, senderName); const messageTimestamp = /*#__PURE__*/_react.default.createElement("a", { href: permalink, onClick: this.onPermalinkClicked, "aria-label": (0, _DateUtils.formatFullDate)(new Date(mxEvent.getTs()), showTwelveHour, false) }, /*#__PURE__*/_react.default.createElement(_MessageTimestamp.default, { showFullDate: true, showTwelveHour: showTwelveHour, ts: mxEvent.getTs(), showSeconds: false })); const avatar = /*#__PURE__*/_react.default.createElement(_MemberAvatar.default, { member: mxEvent.sender, fallbackUserId: mxEvent.getSender(), size: "32px", viewUserOnClick: true, className: "mx_Dialog_nonDialogButton" }); info = /*#__PURE__*/_react.default.createElement("div", { className: "mx_ImageView_info_wrapper" }, avatar, /*#__PURE__*/_react.default.createElement("div", { className: "mx_ImageView_info" }, sender, messageTimestamp)); } else { // If there is no event - we're viewing an avatar, we set // an empty div here, since the panel uses space-between // and we want the same placement of elements info = /*#__PURE__*/_react.default.createElement("div", null); } let contextMenuButton; if (this.props.mxEvent) { contextMenuButton = /*#__PURE__*/_react.default.createElement(_ContextMenuTooltipButton.ContextMenuTooltipButton, { className: "mx_ImageView_button mx_ImageView_button_more", title: (0, _languageHandler._t)("common|options"), onClick: this.onOpenContextMenu, ref: this.contextMenuButton, isExpanded: this.state.contextMenuDisplayed }); } const zoomOutButton = /*#__PURE__*/_react.default.createElement(_AccessibleButton.default, { className: "mx_ImageView_button mx_ImageView_button_zoomOut", title: (0, _languageHandler._t)("action|zoom_out"), onClick: this.onZoomOutClick }); const zoomInButton = /*#__PURE__*/_react.default.createElement(_AccessibleButton.default, { className: "mx_ImageView_button mx_ImageView_button_zoomIn", title: (0, _languageHandler._t)("action|zoom_in"), onClick: this.onZoomInClick }); let title; if (this.props.mxEvent?.getContent()) { title = /*#__PURE__*/_react.default.createElement("div", { className: "mx_ImageView_title" }, (0, _FileUtils.presentableTextForFile)(this.props.mxEvent?.getContent(), (0, _languageHandler._t)("common|image"), true)); } return /*#__PURE__*/_react.default.createElement(_reactFocusLock.default, { returnFocus: true, lockProps: { "onKeyDown": this.onKeyDown, "role": "dialog", "aria-label": (0, _languageHandler._t)("lightbox|title") }, className: "mx_ImageView", ref: this.focusLock }, /*#__PURE__*/_react.default.createElement("div", { className: "mx_ImageView_panel" }, info, title, /*#__PURE__*/_react.default.createElement("div", { className: "mx_ImageView_toolbar" }, zoomOutButton, zoomInButton, /*#__PURE__*/_react.default.createElement(_AccessibleButton.default, { className: "mx_ImageView_button mx_ImageView_button_rotateCCW", title: (0, _languageHandler._t)("lightbox|rotate_left"), onClick: this.onRotateCounterClockwiseClick }), /*#__PURE__*/_react.default.createElement(_AccessibleButton.default, { className: "mx_ImageView_button mx_ImageView_button_rotateCW", title: (0, _languageHandler._t)("lightbox|rotate_right"), onClick: this.onRotateClockwiseClick }), /*#__PURE__*/_react.default.createElement(_AccessibleButton.default, { className: "mx_ImageView_button mx_ImageView_button_download", title: (0, _languageHandler._t)("action|download"), onClick: this.onDownloadClick }), contextMenuButton, /*#__PURE__*/_react.default.createElement(_AccessibleButton.default, { className: "mx_ImageView_button mx_ImageView_button_close", title: (0, _languageHandler._t)("action|close"), onClick: this.props.onFinished }), this.renderContextMenu())), /*#__PURE__*/_react.default.createElement("div", { className: "mx_ImageView_image_wrapper", ref: this.imageWrapper, onMouseDown: this.props.onFinished, onMouseMove: this.onMoving, onMouseUp: this.onEndMoving, onMouseLeave: this.onEndMoving }, /*#__PURE__*/_react.default.createElement("img", { src: this.props.src, style: style, alt: this.props.name, ref: this.image, className: `mx_ImageView_image ${transitionClassName}`, draggable: true, onMouseDown: this.onStartMoving }))); } } exports.default = ImageView; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfcmVhY3QiLCJfaW50ZXJvcFJlcXVpcmVXaWxkY2FyZCIsInJlcXVpcmUiLCJfcmVhY3RGb2N1c0xvY2siLCJfaW50ZXJvcFJlcXVpcmVEZWZhdWx0IiwiX2xhbmd1YWdlSGFuZGxlciIsIl9NZW1iZXJBdmF0YXIiLCJfQ29udGV4dE1lbnVUb29sdGlwQnV0dG9uIiwiX01lc3NhZ2VDb250ZXh0TWVudSIsIl9Db250ZXh0TWVudSIsIl9NZXNzYWdlVGltZXN0YW1wIiwiX1NldHRpbmdzU3RvcmUiLCJfRGF0ZVV0aWxzIiwiX2Rpc3BhdGNoZXIiLCJfYWN0aW9ucyIsIl9Nb3VzZSIsIl9VSVN0b3JlIiwiX0tleWJvYXJkU2hvcnRjdXRzIiwiX0tleUJpbmRpbmdzTWFuYWdlciIsIl9GaWxlVXRpbHMiLCJfQWNjZXNzaWJsZUJ1dHRvbiIsIl9nZXRSZXF1aXJlV2lsZGNhcmRDYWNoZSIsImUiLCJXZWFrTWFwIiwiciIsInQiLCJfX2VzTW9kdWxlIiwiZGVmYXVsdCIsImhhcyIsImdldCIsIm4iLCJfX3Byb3RvX18iLCJhIiwiT2JqZWN0IiwiZGVmaW5lUHJvcGVydHkiLCJnZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IiLCJ1IiwiaGFzT3duUHJvcGVydHkiLCJjYWxsIiwiaSIsInNldCIsIk1BWF9TQ0FMRSIsIlpPT01fU1RFUCIsIlpPT01fQ09FRkZJQ0lFTlQiLCJaT09NX0RJU1RBTkNFIiwiZ2V0UGFuZWxIZWlnaHQiLCJ2YWx1ZSIsImdldENvbXB1dGVkU3R5bGUiLCJkb2N1bWVudCIsImRvY3VtZW50RWxlbWVudCIsImdldFByb3BlcnR5VmFsdWUiLCJwYXJzZUludCIsInNsaWNlIiwibGVuZ3RoIiwiSW1hZ2VWaWV3IiwiUmVhY3QiLCJDb21wb25lbnQiLCJjb25zdHJ1Y3RvciIsInByb3BzIiwiX2RlZmluZVByb3BlcnR5MiIsImNyZWF0ZVJlZiIsImltYWdlIiwiY3VycmVudCIsInRodW1ibmFpbEluZm8iLCJ3aWR0aCIsInNldFN0YXRlIiwiem9vbSIsIm5hdHVyYWxXaWR0aCIsImltYWdlSXNMb2FkZWQiLCJhbmltYXRpbmdMb2FkaW5nIiwic2V0Wm9vbUFuZFJvdGF0aW9uIiwidHJhbnNsYXRpb25YIiwidHJhbnNsYXRpb25ZIiwiaW5wdXRSb3RhdGlvbiIsImltYWdlV3JhcHBlciIsInJvdGF0aW9uIiwic3RhdGUiLCJpbWFnZUlzTm90RmxpcHBlZCIsIm5hdHVyYWxIZWlnaHQiLCJoZWlnaHQiLCJ6b29tWCIsImNsaWVudFdpZHRoIiwiem9vbVkiLCJjbGllbnRIZWlnaHQiLCJtaW5ab29tIiwibWF4Wm9vbSIsIk1hdGgiLCJtaW4iLCJldiIsInRhcmdldCIsInN0b3BQcm9wYWdhdGlvbiIsInByZXZlbnREZWZhdWx0IiwiZGVsdGFZIiwibm9ybWFsaXplV2hlZWxFdmVudCIsInpvb21EZWx0YSIsIm9mZnNldFgiLCJvZmZzZXRZIiwiYWN0aW9uIiwiZ2V0S2V5QmluZGluZ3NNYW5hZ2VyIiwiZ2V0QWNjZXNzaWJpbGl0eUFjdGlvbiIsIktleUJpbmRpbmdBY3Rpb24iLCJFc2NhcGUiLCJvbkZpbmlzaGVkIiwiY3VyIiwiY3JlYXRlRWxlbWVudCIsImhyZWYiLCJzcmMiLCJuYW1lIiwiZG93bmxvYWQiLCJyZWwiLCJjbGljayIsImNvbnRleHRNZW51RGlzcGxheWVkIiwiZGlzIiwiZGlzcGF0Y2giLCJBY3Rpb24iLCJWaWV3Um9vbSIsImV2ZW50X2lkIiwibXhFdmVudCIsImdldElkIiwiaGlnaGxpZ2h0ZWQiLCJyb29tX2lkIiwiZ2V0Um9vbUlkIiwibWV0cmljc1RyaWdnZXIiLCJ1bmRlZmluZWQiLCJidXR0b24iLCJuYXRpdmVFdmVudCIsIm1vdmluZyIsInByZXZpb3VzWCIsInByZXZpb3VzWSIsImluaXRYIiwicGFnZVgiLCJpbml0WSIsInBhZ2VZIiwiYWJzIiwicG9zaXRpb25YIiwiVUlTdG9yZSIsImluc3RhbmNlIiwid2luZG93V2lkdGgiLCJwb3NpdGlvblkiLCJ3aW5kb3dIZWlnaHQiLCJjb21wb25lbnREaWRNb3VudCIsImZvY3VzTG9jayIsImFkZEV2ZW50TGlzdGVuZXIiLCJvbldoZWVsIiwicGFzc2l2ZSIsIndpbmRvdyIsInJlY2FsY3VsYXRlWm9vbSIsImltYWdlTG9hZGVkIiwiY29tcG9uZW50V2lsbFVubW91bnQiLCJyZW1vdmVFdmVudExpc3RlbmVyIiwiZGVsdGEiLCJhbmNob3JYIiwiYW5jaG9yWSIsInpvb21MZXZlbCIsIm9sZFpvb20iLCJuZXdab29tIiwicmVuZGVyQ29udGV4dE1lbnUiLCJjb250ZXh0TWVudSIsIl9leHRlbmRzMiIsImFib3ZlTGVmdE9mIiwiY29udGV4dE1lbnVCdXR0b24iLCJnZXRCb3VuZGluZ0NsaWVudFJlY3QiLCJwZXJtYWxpbmtDcmVhdG9yIiwib25DbG9zZUNvbnRleHRNZW51Iiwib25DbG9zZURpYWxvZyIsIkZyYWdtZW50IiwicmVuZGVyIiwic2hvd0V2ZW50TWV0YSIsInRyYW5zaXRpb25DbGFzc05hbWUiLCJyb3RhdGlvbkRlZ3JlZXMiLCJ0cmFuc2xhdGVQaXhlbHNYIiwidHJhbnNsYXRlUGl4ZWxzWSIsInN0eWxlIiwidHJhbnNmb3JtIiwiY3Vyc29yIiwiaW5mbyIsInNob3dUd2VsdmVIb3VyIiwiU2V0dGluZ3NTdG9yZSIsImdldFZhbHVlIiwicGVybWFsaW5rIiwiZm9yRXZlbnQiLCJzZW5kZXJOYW1lIiwic2VuZGVyIiwiZ2V0U2VuZGVyIiwiY2xhc3NOYW1lIiwibWVzc2FnZVRpbWVzdGFtcCIsIm9uQ2xpY2siLCJvblBlcm1hbGlua0NsaWNrZWQiLCJmb3JtYXRGdWxsRGF0ZSIsIkRhdGUiLCJnZXRUcyIsInNob3dGdWxsRGF0ZSIsInRzIiwic2hvd1NlY29uZHMiLCJhdmF0YXIiLCJtZW1iZXIiLCJmYWxsYmFja1VzZXJJZCIsInNpemUiLCJ2aWV3VXNlck9uQ2xpY2siLCJDb250ZXh0TWVudVRvb2x0aXBCdXR0b24iLCJ0aXRsZSIsIl90Iiwib25PcGVuQ29udGV4dE1lbnUiLCJyZWYiLCJpc0V4cGFuZGVkIiwiem9vbU91dEJ1dHRvbiIsIm9uWm9vbU91dENsaWNrIiwiem9vbUluQnV0dG9uIiwib25ab29tSW5DbGljayIsImdldENvbnRlbnQiLCJwcmVzZW50YWJsZVRleHRGb3JGaWxlIiwicmV0dXJuRm9jdXMiLCJsb2NrUHJvcHMiLCJvbktleURvd24iLCJvblJvdGF0ZUNvdW50ZXJDbG9ja3dpc2VDbGljayIsIm9uUm90YXRlQ2xvY2t3aXNlQ2xpY2siLCJvbkRvd25sb2FkQ2xpY2siLCJvbk1vdXNlRG93biIsIm9uTW91c2VNb3ZlIiwib25Nb3ZpbmciLCJvbk1vdXNlVXAiLCJvbkVuZE1vdmluZyIsIm9uTW91c2VMZWF2ZSIsImFsdCIsImRyYWdnYWJsZSIsIm9uU3RhcnRNb3ZpbmciLCJleHBvcnRzIl0sInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvdmlld3MvZWxlbWVudHMvSW1hZ2VWaWV3LnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuQ29weXJpZ2h0IDIwMjQgTmV3IFZlY3RvciBMdGQuXG5Db3B5cmlnaHQgMjAyMCwgMjAyMSDFoGltb24gQnJhbmRuZXIgPHNpbW9uLmJyYS5hZ0BnbWFpbC5jb20+XG5Db3B5cmlnaHQgMjAxOSBNaWNoYWVsIFRlbGF0eW5za2kgPDd0M2NoZ3V5QGdtYWlsLmNvbT5cbkNvcHlyaWdodCAyMDE1LCAyMDE2IE9wZW5NYXJrZXQgTHRkXG5cblNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBR1BMLTMuMC1vbmx5IE9SIEdQTC0zLjAtb25seVxuUGxlYXNlIHNlZSBMSUNFTlNFIGZpbGVzIGluIHRoZSByZXBvc2l0b3J5IHJvb3QgZm9yIGZ1bGwgZGV0YWlscy5cbiovXG5cbmltcG9ydCBSZWFjdCwgeyBjcmVhdGVSZWYsIENTU1Byb3BlcnRpZXMgfSBmcm9tIFwicmVhY3RcIjtcbmltcG9ydCBGb2N1c0xvY2sgZnJvbSBcInJlYWN0LWZvY3VzLWxvY2tcIjtcbmltcG9ydCB7IE1hdHJpeEV2ZW50IH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL21hdHJpeFwiO1xuXG5pbXBvcnQgeyBfdCB9IGZyb20gXCIuLi8uLi8uLi9sYW5ndWFnZUhhbmRsZXJcIjtcbmltcG9ydCBNZW1iZXJBdmF0YXIgZnJvbSBcIi4uL2F2YXRhcnMvTWVtYmVyQXZhdGFyXCI7XG5pbXBvcnQgeyBDb250ZXh0TWVudVRvb2x0aXBCdXR0b24gfSBmcm9tIFwiLi4vLi4vLi4vYWNjZXNzaWJpbGl0eS9jb250ZXh0X21lbnUvQ29udGV4dE1lbnVUb29sdGlwQnV0dG9uXCI7XG5pbXBvcnQgTWVzc2FnZUNvbnRleHRNZW51IGZyb20gXCIuLi9jb250ZXh0X21lbnVzL01lc3NhZ2VDb250ZXh0TWVudVwiO1xuaW1wb3J0IHsgYWJvdmVMZWZ0T2YgfSBmcm9tIFwiLi4vLi4vc3RydWN0dXJlcy9Db250ZXh0TWVudVwiO1xuaW1wb3J0IE1lc3NhZ2VUaW1lc3RhbXAgZnJvbSBcIi4uL21lc3NhZ2VzL01lc3NhZ2VUaW1lc3RhbXBcIjtcbmltcG9ydCBTZXR0aW5nc1N0b3JlIGZyb20gXCIuLi8uLi8uLi9zZXR0aW5ncy9TZXR0aW5nc1N0b3JlXCI7XG5pbXBvcnQgeyBmb3JtYXRGdWxsRGF0ZSB9IGZyb20gXCIuLi8uLi8uLi9EYXRlVXRpbHNcIjtcbmltcG9ydCBkaXMgZnJvbSBcIi4uLy4uLy4uL2Rpc3BhdGNoZXIvZGlzcGF0Y2hlclwiO1xuaW1wb3J0IHsgQWN0aW9uIH0gZnJvbSBcIi4uLy4uLy4uL2Rpc3BhdGNoZXIvYWN0aW9uc1wiO1xuaW1wb3J0IHsgUm9vbVBlcm1hbGlua0NyZWF0b3IgfSBmcm9tIFwiLi4vLi4vLi4vdXRpbHMvcGVybWFsaW5rcy9QZXJtYWxpbmtzXCI7XG5pbXBvcnQgeyBub3JtYWxpemVXaGVlbEV2ZW50IH0gZnJvbSBcIi4uLy4uLy4uL3V0aWxzL01vdXNlXCI7XG5pbXBvcnQgVUlTdG9yZSBmcm9tIFwiLi4vLi4vLi4vc3RvcmVzL1VJU3RvcmVcIjtcbmltcG9ydCB7IFZpZXdSb29tUGF5bG9hZCB9IGZyb20gXCIuLi8uLi8uLi9kaXNwYXRjaGVyL3BheWxvYWRzL1ZpZXdSb29tUGF5bG9hZFwiO1xuaW1wb3J0IHsgS2V5QmluZGluZ0FjdGlvbiB9IGZyb20gXCIuLi8uLi8uLi9hY2Nlc3NpYmlsaXR5L0tleWJvYXJkU2hvcnRjdXRzXCI7XG5pbXBvcnQgeyBnZXRLZXlCaW5kaW5nc01hbmFnZXIgfSBmcm9tIFwiLi4vLi4vLi4vS2V5QmluZGluZ3NNYW5hZ2VyXCI7XG5pbXBvcnQgeyBwcmVzZW50YWJsZVRleHRGb3JGaWxlIH0gZnJvbSBcIi4uLy4uLy4uL3V0aWxzL0ZpbGVVdGlsc1wiO1xuaW1wb3J0IEFjY2Vzc2libGVCdXR0b24gZnJvbSBcIi4vQWNjZXNzaWJsZUJ1dHRvblwiO1xuXG4vLyBNYXggc2NhbGUgdG8ga2VlcCBnYXBzIGFyb3VuZCB0aGUgaW1hZ2VcbmNvbnN0IE1BWF9TQ0FMRSA9IDAuOTU7XG4vLyBUaGlzIGlzIHVzZWQgZm9yIHRoZSBidXR0b25zXG5jb25zdCBaT09NX1NURVAgPSAwLjE7XG4vLyBUaGlzIGlzIHVzZWQgZm9yIG1vdXNlIHdoZWVsIGV2ZW50c1xuY29uc3QgWk9PTV9DT0VGRklDSUVOVCA9IDAuMDAyNTtcbi8vIElmIHdlIGhhdmUgbW92ZWQgb25seSB0aGlzIG11Y2ggd2UgY2FuIHpvb21cbmNvbnN0IFpPT01fRElTVEFOQ0UgPSAxMDtcblxuLy8gSGVpZ2h0IG9mIG14X0ltYWdlVmlld19wYW5lbFxuY29uc3QgZ2V0UGFuZWxIZWlnaHQgPSAoKTogbnVtYmVyID0+IHtcbiAgICBjb25zdCB2YWx1ZSA9IGdldENvbXB1dGVkU3R5bGUoZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50KS5nZXRQcm9wZXJ0eVZhbHVlKFwiLS1pbWFnZS12aWV3LXBhbmVsLWhlaWdodFwiKTtcbiAgICAvLyBSZXR1cm4gdGhlIHZhbHVlIGFzIGEgbnVtYmVyIHdpdGhvdXQgdGhlIHVuaXRcbiAgICByZXR1cm4gcGFyc2VJbnQodmFsdWUuc2xpY2UoMCwgdmFsdWUubGVuZ3RoIC0gMikpO1xufTtcblxuaW50ZXJmYWNlIElQcm9wcyB7XG4gICAgc3JjOiBzdHJpbmc7IC8vIHRoZSBzb3VyY2Ugb2YgdGhlIGltYWdlIGJlaW5nIGRpc3BsYXllZFxuICAgIG5hbWU/OiBzdHJpbmc7IC8vIHRoZSBtYWluIHRpdGxlICgnbmFtZScpIGZvciB0aGUgaW1hZ2VcbiAgICBsaW5rPzogc3RyaW5nOyAvLyB0aGUgbGluayAoaWYgYW55KSBhcHBsaWVkIHRvIHRoZSBuYW1lIG9mIHRoZSBpbWFnZVxuICAgIHdpZHRoPzogbnVtYmVyOyAvLyB3aWR0aCBvZiB0aGUgaW1hZ2Ugc3JjIGluIHBpeGVsc1xuICAgIGhlaWdodD86IG51bWJlcjsgLy8gaGVpZ2h0IG9mIHRoZSBpbWFnZSBzcmMgaW4gcGl4ZWxzXG4gICAgZmlsZVNpemU/OiBudW1iZXI7IC8vIHNpemUgb2YgdGhlIGltYWdlIHNyYyBpbiBieXRlc1xuXG4gICAgLy8gdGhlIGV2ZW50IChpZiBhbnkpIHRoYXQgdGhlIEltYWdlIGlzIGRpc3BsYXlpbmcuIFVzZWQgZm9yIGV2ZW50LXNwZWNpZmljIHN0dWZmIGxpa2VcbiAgICAvLyByZWRhY3Rpb25zLCBzZW5kZXJzLCB0aW1lc3RhbXBzIGV0Yy4gIE90aGVyIGRlc2NyaXB0b3JzIGFyZSB0YWtlbiBmcm9tIHRoZSBleHBsaWNpdFxuICAgIC8vIHByb3BlcnRpZXMgYWJvdmUsIHdoaWNoIGxldCB1cyB1c2UgbGlnaHRib3hlcyB0byBkaXNwbGF5IGltYWdlcyB3aGljaCBhcmVuJ3QgYXNzb2NpYXRlZFxuICAgIC8vIHdpdGggZXZlbnRzLlxuICAgIG14RXZlbnQ/OiBNYXRyaXhFdmVudDtcbiAgICBwZXJtYWxpbmtDcmVhdG9yPzogUm9vbVBlcm1hbGlua0NyZWF0b3I7XG5cbiAgICB0aHVtYm5haWxJbmZvPzoge1xuICAgICAgICBwb3NpdGlvblg6IG51bWJlcjtcbiAgICAgICAgcG9zaXRpb25ZOiBudW1iZXI7XG4gICAgICAgIHdpZHRoOiBudW1iZXI7XG4gICAgICAgIGhlaWdodDogbnVtYmVyO1xuICAgIH07XG4gICAgb25GaW5pc2hlZCgpOiB2b2lkO1xufVxuXG5pbnRlcmZhY2UgSVN0YXRlIHtcbiAgICB6b29tOiBudW1iZXI7XG4gICAgbWluWm9vbTogbnVtYmVyO1xuICAgIG1heFpvb206IG51bWJlcjtcbiAgICByb3RhdGlvbjogbnVtYmVyO1xuICAgIHRyYW5zbGF0aW9uWDogbnVtYmVyO1xuICAgIHRyYW5zbGF0aW9uWTogbnVtYmVyO1xuICAgIG1vdmluZzogYm9vbGVhbjtcbiAgICBjb250ZXh0TWVudURpc3BsYXllZDogYm9vbGVhbjtcbn1cblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgSW1hZ2VWaWV3IGV4dGVuZHMgUmVhY3QuQ29tcG9uZW50PElQcm9wcywgSVN0YXRlPiB7XG4gICAgcHVibGljIGNvbnN0cnVjdG9yKHByb3BzOiBJUHJvcHMpIHtcbiAgICAgICAgc3VwZXIocHJvcHMpO1xuXG4gICAgICAgIGNvbnN0IHsgdGh1bWJuYWlsSW5mbyB9ID0gdGhpcy5wcm9wcztcblxuICAgICAgICBsZXQgdHJhbnNsYXRpb25YID0gMDtcbiAgICAgICAgbGV0IHRyYW5zbGF0aW9uWSA9IDA7XG4gICAgICAgIGlmICh0aHVtYm5haWxJbmZvKSB7XG4gICAgICAgICAgICB0cmFuc2xhdGlvblggPSB0aHVtYm5haWxJbmZvLnBvc2l0aW9uWCArIHRodW1ibmFpbEluZm8ud2lkdGggLyAyIC0gVUlTdG9yZS5pbnN0YW5jZS53aW5kb3dXaWR0aCAvIDI7XG4gICAgICAgICAgICB0cmFuc2xhdGlvblkgPVxuICAgICAgICAgICAgICAgIHRodW1ibmFpbEluZm8ucG9zaXRpb25ZICtcbiAgICAgICAgICAgICAgICB0aHVtYm5haWxJbmZvLmhlaWdodCAvIDIgLVxuICAgICAgICAgICAgICAgIFVJU3RvcmUuaW5zdGFuY2Uud2luZG93SGVpZ2h0IC8gMiAtXG4gICAgICAgICAgICAgICAgZ2V0UGFuZWxIZWlnaHQoKSAvIDI7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnN0YXRlID0ge1xuICAgICAgICAgICAgem9vbTogMCwgLy8gV2UgZGVmYXVsdCB0byAwIGFuZCBvdmVycmlkZSB0aGlzIGluIGltYWdlTG9hZGVkIG9uY2Ugd2UgaGF2ZSBuYXR1cmFsU2l6ZVxuICAgICAgICAgICAgbWluWm9vbTogTUFYX1NDQUxFLFxuICAgICAgICAgICAgbWF4Wm9vbTogTUFYX1NDQUxFLFxuICAgICAgICAgICAgcm90YXRpb246IDAsXG4gICAgICAgICAgICB0cmFuc2xhdGlvblgsXG4gICAgICAgICAgICB0cmFuc2xhdGlvblksXG4gICAgICAgICAgICBtb3Zpbmc6IGZhbHNlLFxuICAgICAgICAgICAgY29udGV4dE1lbnVEaXNwbGF5ZWQ6IGZhbHNlLFxuICAgICAgICB9O1xuICAgIH1cblxuICAgIC8vIFhYWDogUmVmcyB0byBmdW5jdGlvbmFsIGNvbXBvbmVudHNcbiAgICBwcml2YXRlIGNvbnRleHRNZW51QnV0dG9uID0gY3JlYXRlUmVmPGFueT4oKTtcbiAgICBwcml2YXRlIGZvY3VzTG9jayA9IGNyZWF0ZVJlZjxhbnk+KCk7XG4gICAgcHJpdmF0ZSBpbWFnZVdyYXBwZXIgPSBjcmVhdGVSZWY8SFRNTERpdkVsZW1lbnQ+KCk7XG4gICAgcHJpdmF0ZSBpbWFnZSA9IGNyZWF0ZVJlZjxIVE1MSW1hZ2VFbGVtZW50PigpO1xuXG4gICAgcHJpdmF0ZSBpbml0WCA9IDA7XG4gICAgcHJpdmF0ZSBpbml0WSA9IDA7XG4gICAgcHJpdmF0ZSBwcmV2aW91c1ggPSAwO1xuICAgIHByaXZhdGUgcHJldmlvdXNZID0gMDtcblxuICAgIHByaXZhdGUgYW5pbWF0aW5nTG9hZGluZyA9IGZhbHNlO1xuICAgIHByaXZhdGUgaW1hZ2VJc0xvYWRlZCA9IGZhbHNlO1xuXG4gICAgcHVibGljIGNvbXBvbmVudERpZE1vdW50KCk6IHZvaWQge1xuICAgICAgICAvLyBXZSBoYXZlIHRvIHVzZSBhZGRFdmVudExpc3RlbmVyKCkgYmVjYXVzZSB0aGUgbGlzdGVuZXJcbiAgICAgICAgLy8gbmVlZHMgdG8gYmUgcGFzc2l2ZSBpbiBvcmRlciB0byB3b3JrIHdpdGggQ2hyb21pdW1cbiAgICAgICAgdGhpcy5mb2N1c0xvY2suY3VycmVudC5hZGRFdmVudExpc3RlbmVyKFwid2hlZWxcIiwgdGhpcy5vbldoZWVsLCB7IHBhc3NpdmU6IGZhbHNlIH0pO1xuICAgICAgICAvLyBXZSB3YW50IHRvIHJlY2FsY3VsYXRlIHpvb20gd2hlbmV2ZXIgdGhlIHdpbmRvdydzIHNpemUgY2hhbmdlc1xuICAgICAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcihcInJlc2l6ZVwiLCB0aGlzLnJlY2FsY3VsYXRlWm9vbSk7XG4gICAgICAgIC8vIEFmdGVyIHRoZSBpbWFnZSBsb2FkcyBmb3IgdGhlIGZpcnN0IHRpbWUgd2Ugd2FudCB0byBjYWxjdWxhdGUgdGhlIHpvb21cbiAgICAgICAgdGhpcy5pbWFnZS5jdXJyZW50Py5hZGRFdmVudExpc3RlbmVyKFwibG9hZFwiLCB0aGlzLmltYWdlTG9hZGVkKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgY29tcG9uZW50V2lsbFVubW91bnQoKTogdm9pZCB7XG4gICAgICAgIHRoaXMuZm9jdXNMb2NrLmN1cnJlbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcihcIndoZWVsXCIsIHRoaXMub25XaGVlbCk7XG4gICAgICAgIHdpbmRvdy5yZW1vdmVFdmVudExpc3RlbmVyKFwicmVzaXplXCIsIHRoaXMucmVjYWxjdWxhdGVab29tKTtcbiAgICAgICAgdGhpcy5pbWFnZS5jdXJyZW50Py5yZW1vdmVFdmVudExpc3RlbmVyKFwibG9hZFwiLCB0aGlzLmltYWdlTG9hZGVkKTtcbiAgICB9XG5cbiAgICBwcml2YXRlIGltYWdlTG9hZGVkID0gKCk6IHZvaWQgPT4ge1xuICAgICAgICBpZiAoIXRoaXMuaW1hZ2UuY3VycmVudCkgcmV0dXJuO1xuICAgICAgICAvLyBGaXJzdCwgd2UgY2FsY3VsYXRlIHRoZSB6b29tLCBzbyB0aGF0IHRoZSBpbWFnZSBoYXMgdGhlIHNhbWUgc2l6ZSBhc1xuICAgICAgICAvLyB0aGUgdGh1bWJuYWlsXG4gICAgICAgIGNvbnN0IHsgdGh1bWJuYWlsSW5mbyB9ID0gdGhpcy5wcm9wcztcbiAgICAgICAgaWYgKHRodW1ibmFpbEluZm8/LndpZHRoKSB7XG4gICAgICAgICAgICB0aGlzLnNldFN0YXRlKHsgem9vbTogdGh1bWJuYWlsSW5mby53aWR0aCAvIHRoaXMuaW1hZ2UuY3VycmVudC5uYXR1cmFsV2lkdGggfSk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBPbmNlIHRoZSB6b29tIGlzIHNldCwgd2UgdGhlIGltYWdlIGlzIGNvbnNpZGVyZWQgbG9hZGVkIGFuZCB3ZSBjYW5cbiAgICAgICAgLy8gc3RhcnQgYW5pbWF0aW5nIGl0IGludG8gdGhlIGNlbnRlciBvZiB0aGUgc2NyZWVuXG4gICAgICAgIHRoaXMuaW1hZ2VJc0xvYWRlZCA9IHRydWU7XG4gICAgICAgIHRoaXMuYW5pbWF0aW5nTG9hZGluZyA9IHRydWU7XG4gICAgICAgIHRoaXMuc2V0Wm9vbUFuZFJvdGF0aW9uKCk7XG4gICAgICAgIHRoaXMuc2V0U3RhdGUoe1xuICAgICAgICAgICAgdHJhbnNsYXRpb25YOiAwLFxuICAgICAgICAgICAgdHJhbnNsYXRpb25ZOiAwLFxuICAgICAgICB9KTtcblxuICAgICAgICAvLyBPbmNlIHRoZSBwb3NpdGlvbiBpcyBzZXQsIHRoZXJlIGlzIG5vIG5lZWQgdG8gYW5pbWF0ZSBhbnltb3JlXG4gICAgICAgIHRoaXMuYW5pbWF0aW5nTG9hZGluZyA9IGZhbHNlO1xuICAgIH07XG5cbiAgICBwcml2YXRlIHJlY2FsY3VsYXRlWm9vbSA9ICgpOiB2b2lkID0+IHtcbiAgICAgICAgdGhpcy5zZXRab29tQW5kUm90YXRpb24oKTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBzZXRab29tQW5kUm90YXRpb24gPSAoaW5wdXRSb3RhdGlvbj86IG51bWJlcik6IHZvaWQgPT4ge1xuICAgICAgICBjb25zdCBpbWFnZSA9IHRoaXMuaW1hZ2UuY3VycmVudDtcbiAgICAgICAgY29uc3QgaW1hZ2VXcmFwcGVyID0gdGhpcy5pbWFnZVdyYXBwZXIuY3VycmVudDtcbiAgICAgICAgaWYgKCFpbWFnZSB8fCAhaW1hZ2VXcmFwcGVyKSByZXR1cm47XG5cbiAgICAgICAgY29uc3Qgcm90YXRpb24gPSBpbnB1dFJvdGF0aW9uID8/IHRoaXMuc3RhdGUucm90YXRpb247XG5cbiAgICAgICAgY29uc3QgaW1hZ2VJc05vdEZsaXBwZWQgPSByb3RhdGlvbiAlIDE4MCA9PT0gMDtcblxuICAgICAgICAvLyBJZiB0aGUgaW1hZ2UgaXMgcm90YXRlZCB0YWtlIGl0IGludG8gYWNjb3VudFxuICAgICAgICBjb25zdCB3aWR0aCA9IGltYWdlSXNOb3RGbGlwcGVkID8gaW1hZ2UubmF0dXJhbFdpZHRoIDogaW1hZ2UubmF0dXJhbEhlaWdodDtcbiAgICAgICAgY29uc3QgaGVpZ2h0ID0gaW1hZ2VJc05vdEZsaXBwZWQgPyBpbWFnZS5uYXR1cmFsSGVpZ2h0IDogaW1hZ2UubmF0dXJhbFdpZHRoO1xuXG4gICAgICAgIGNvbnN0IHpvb21YID0gaW1hZ2VXcmFwcGVyLmNsaWVudFdpZHRoIC8gd2lkdGg7XG4gICAgICAgIGNvbnN0IHpvb21ZID0gaW1hZ2VXcmFwcGVyLmNsaWVudEhlaWdodCAvIGhlaWdodDtcblxuICAgICAgICAvLyBJZiB0aGUgaW1hZ2UgaXMgc21hbGxlciBpbiBib3RoIGRpbWVuc2lvbnMgc2V0IGl0cyB0aGUgem9vbSB0byAxIHRvXG4gICAgICAgIC8vIGRpc3BsYXkgaXQgaW4gaXRzIG9yaWdpbmFsIHNpemVcbiAgICAgICAgaWYgKHpvb21YID49IDEgJiYgem9vbVkgPj0gMSkge1xuICAgICAgICAgICAgdGhpcy5zZXRTdGF0ZSh7XG4gICAgICAgICAgICAgICAgem9vbTogMSxcbiAgICAgICAgICAgICAgICBtaW5ab29tOiAxLFxuICAgICAgICAgICAgICAgIG1heFpvb206IDEsXG4gICAgICAgICAgICAgICAgcm90YXRpb246IHJvdGF0aW9uLFxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgLy8gV2Ugc2V0IG1pblpvb20gdG8gdGhlIG1pbiBvZiB0aGUgem9vbVggYW5kIHpvb21ZIHRvIGF2b2lkIG92ZXJmbG93IGluXG4gICAgICAgIC8vIGFueSBkaXJlY3Rpb24uIFdlIGFsc28gbXVsdGlwbHkgYnkgTUFYX1NDQUxFIHRvIGdldCBhIGdhcCBhcm91bmQgdGhlXG4gICAgICAgIC8vIGltYWdlIGJ5IGRlZmF1bHRcbiAgICAgICAgY29uc3QgbWluWm9vbSA9IE1hdGgubWluKHpvb21YLCB6b29tWSkgKiBNQVhfU0NBTEU7XG5cbiAgICAgICAgLy8gSWYgem9vbSBpcyBzbWFsbGVyIHRoYW4gbWluWm9vbSBkb24ndCBnbyBiZWxvdyB0aGF0IHZhbHVlXG4gICAgICAgIGNvbnN0IHpvb20gPSB0aGlzLnN0YXRlLnpvb20gPD0gdGhpcy5zdGF0ZS5taW5ab29tID8gbWluWm9vbSA6IHRoaXMuc3RhdGUuem9vbTtcblxuICAgICAgICB0aGlzLnNldFN0YXRlKHtcbiAgICAgICAgICAgIG1pblpvb206IG1pblpvb20sXG4gICAgICAgICAgICBtYXhab29tOiAxLFxuICAgICAgICAgICAgcm90YXRpb246IHJvdGF0aW9uLFxuICAgICAgICAgICAgem9vbTogem9vbSxcbiAgICAgICAgfSk7XG4gICAgfTtcblxuICAgIHByaXZhdGUgem9vbURlbHRhKGRlbHRhOiBudW1iZXIsIGFuY2hvclg/OiBudW1iZXIsIGFuY2hvclk/OiBudW1iZXIpOiB2b2lkIHtcbiAgICAgICAgdGhpcy56b29tKHRoaXMuc3RhdGUuem9vbSArIGRlbHRhLCBhbmNob3JYLCBhbmNob3JZKTtcbiAgICB9XG5cbiAgICBwcml2YXRlIHpvb20oem9vbUxldmVsOiBudW1iZXIsIGFuY2hvclg/OiBudW1iZXIsIGFuY2hvclk/OiBudW1iZXIpOiB2b2lkIHtcbiAgICAgICAgY29uc3Qgb2xkWm9vbSA9IHRoaXMuc3RhdGUuem9vbTtcbiAgICAgICAgY29uc3QgbWF4Wm9vbSA9IHRoaXMuc3RhdGUubWF4Wm9vbSA9PT0gdGhpcy5zdGF0ZS5taW5ab29tID8gMiAqIHRoaXMuc3RhdGUubWF4Wm9vbSA6IHRoaXMuc3RhdGUubWF4Wm9vbTtcbiAgICAgICAgY29uc3QgbmV3Wm9vbSA9IE1hdGgubWluKHpvb21MZXZlbCwgbWF4Wm9vbSk7XG4gICAgICAgIGlmIChuZXdab29tIDw9IHRoaXMuc3RhdGUubWluWm9vbSkge1xuICAgICAgICAgICAgLy8gWm9vbSBvdXQgZnVsbHlcbiAgICAgICAgICAgIHRoaXMuc2V0U3RhdGUoe1xuICAgICAgICAgICAgICAgIHpvb206IHRoaXMuc3RhdGUubWluWm9vbSxcbiAgICAgICAgICAgICAgICB0cmFuc2xhdGlvblg6IDAsXG4gICAgICAgICAgICAgICAgdHJhbnNsYXRpb25ZOiAwLFxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH0gZWxzZSBpZiAodHlwZW9mIGFuY2hvclggIT09IFwibnVtYmVyXCIgfHwgdHlwZW9mIGFuY2hvclkgIT09IFwibnVtYmVyXCIpIHtcbiAgICAgICAgICAgIC8vIFpvb20gcmVsYXRpdmUgdG8gdGhlIGNlbnRlciBvZiB0aGUgdmlld1xuICAgICAgICAgICAgdGhpcy5zZXRTdGF0ZSh7XG4gICAgICAgICAgICAgICAgem9vbTogbmV3Wm9vbSxcbiAgICAgICAgICAgICAgICB0cmFuc2xhdGlvblg6ICh0aGlzLnN0YXRlLnRyYW5zbGF0aW9uWCAqIG5ld1pvb20pIC8gb2xkWm9vbSxcbiAgICAgICAgICAgICAgICB0cmFuc2xhdGlvblk6ICh0aGlzLnN0YXRlLnRyYW5zbGF0aW9uWSAqIG5ld1pvb20pIC8gb2xkWm9vbSxcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9IGVsc2UgaWYgKHRoaXMuaW1hZ2UuY3VycmVudCkge1xuICAgICAgICAgICAgLy8gWm9vbSByZWxhdGl2ZSB0byB0aGUgZ2l2ZW4gcG9pbnQgb24gdGhlIGltYWdlLlxuICAgICAgICAgICAgLy8gRmlyc3Qgd2UgbmVlZCB0byBmaWd1cmUgb3V0IHRoZSBvZmZzZXQgb2YgdGhlIGFuY2hvciBwb2ludFxuICAgICAgICAgICAgLy8gcmVsYXRpdmUgdG8gdGhlIGNlbnRlciBvZiB0aGUgaW1hZ2UsIGFjY291bnRpbmcgZm9yIHJvdGF0aW9uLlxuICAgICAgICAgICAgbGV0IG9mZnNldFg6IG51bWJlcjtcbiAgICAgICAgICAgIGxldCBvZmZzZXRZOiBudW1iZXI7XG4gICAgICAgICAgICAvLyBUaGUgbW9kdWxvIG9wZXJhdG9yIGNhbiByZXR1cm4gbmVnYXRpdmUgdmFsdWVzIGZvciBzb21lXG4gICAgICAgICAgICAvLyByb3RhdGlvbnMsIHNvIHdlIGhhdmUgdG8gZG8gc29tZSBleHRyYSB3b3JrIHRvIG5vcm1hbGl6ZSBpdFxuICAgICAgICAgICAgY29uc3Qgcm90YXRpb24gPSAoKCh0aGlzLnN0YXRlLnJvdGF0aW9uICUgMzYwKSArIDM2MCkgJSAzNjApIGFzIDAgfCA5MCB8IDE4MCB8IDI3MDtcbiAgICAgICAgICAgIHN3aXRjaCAocm90YXRpb24pIHtcbiAgICAgICAgICAgICAgICBjYXNlIDA6XG4gICAgICAgICAgICAgICAgICAgIG9mZnNldFggPSB0aGlzLmltYWdlLmN1cnJlbnQuY2xpZW50V2lkdGggLyAyIC0gYW5jaG9yWDtcbiAgICAgICAgICAgICAgICAgICAgb2Zmc2V0WSA9IHRoaXMuaW1hZ2UuY3VycmVudC5jbGllbnRIZWlnaHQgLyAyIC0gYW5jaG9yWTtcbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgY2FzZSA5MDpcbiAgICAgICAgICAgICAgICAgICAgb2Zmc2V0WCA9IGFuY2hvclkgLSB0aGlzLmltYWdlLmN1cnJlbnQuY2xpZW50SGVpZ2h0IC8gMjtcbiAgICAgICAgICAgICAgICAgICAgb2Zmc2V0WSA9IHRoaXMuaW1hZ2UuY3VycmVudC5jbGllbnRXaWR0aCAvIDIgLSBhbmNob3JYO1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBjYXNlIDE4MDpcbiAgICAgICAgICAgICAgICAgICAgb2Zmc2V0WCA9IGFuY2hvclggLSB0aGlzLmltYWdlLmN1cnJlbnQuY2xpZW50V2lkdGggLyAyO1xuICAgICAgICAgICAgICAgICAgICBvZmZzZXRZID0gYW5jaG9yWSAtIHRoaXMuaW1hZ2UuY3VycmVudC5jbGllbnRIZWlnaHQgLyAyO1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBjYXNlIDI3MDpcbiAgICAgICAgICAgICAgICAgICAgb2Zmc2V0WCA9IHRoaXMuaW1hZ2UuY3VycmVudC5jbGllbnRIZWlnaHQgLyAyIC0gYW5jaG9yWTtcbiAgICAgICAgICAgICAgICAgICAgb2Zmc2V0WSA9IGFuY2hvclggLSB0aGlzLmltYWdlLmN1cnJlbnQuY2xpZW50V2lkdGggLyAyO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBBcHBseSB0aGUgem9vbSBhbmQgb2Zmc2V0XG4gICAgICAgICAgICB0aGlzLnNldFN0YXRlKHtcbiAgICAgICAgICAgICAgICB6b29tOiBuZXdab29tLFxuICAgICAgICAgICAgICAgIHRyYW5zbGF0aW9uWDogdGhpcy5zdGF0ZS50cmFuc2xhdGlvblggKyAobmV3Wm9vbSAtIG9sZFpvb20pICogb2Zmc2V0WCxcbiAgICAgICAgICAgICAgICB0cmFuc2xhdGlvblk6IHRoaXMuc3RhdGUudHJhbnNsYXRpb25ZICsgKG5ld1pvb20gLSBvbGRab29tKSAqIG9mZnNldFksXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHByaXZhdGUgb25XaGVlbCA9IChldjogV2hlZWxFdmVudCk6IHZvaWQgPT4ge1xuICAgICAgICBpZiAoZXYudGFyZ2V0ID09PSB0aGlzLmltYWdlLmN1cnJlbnQpIHtcbiAgICAgICAgICAgIGV2LnN0b3BQcm9wYWdhdGlvbigpO1xuICAgICAgICAgICAgZXYucHJldmVudERlZmF1bHQoKTtcblxuICAgICAgICAgICAgY29uc3QgeyBkZWx0YVkgfSA9IG5vcm1hbGl6ZVdoZWVsRXZlbnQoZXYpO1xuICAgICAgICAgICAgLy8gWm9vbSBpbiBvbiB0aGUgcG9pbnQgb24gdGhlIGltYWdlIHRhcmdldGVkIGJ5IHRoZSBjdXJzb3JcbiAgICAgICAgICAgIHRoaXMuem9vbURlbHRhKC1kZWx0YVkgKiBaT09NX0NPRUZGSUNJRU5ULCBldi5vZmZzZXRYLCBldi5vZmZzZXRZKTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICBwcml2YXRlIG9uWm9vbUluQ2xpY2sgPSAoKTogdm9pZCA9PiB7XG4gICAgICAgIHRoaXMuem9vbURlbHRhKFpPT01fU1RFUCk7XG4gICAgfTtcblxuICAgIHByaXZhdGUgb25ab29tT3V0Q2xpY2sgPSAoKTogdm9pZCA9PiB7XG4gICAgICAgIHRoaXMuem9vbURlbHRhKC1aT09NX1NURVApO1xuICAgIH07XG5cbiAgICBwcml2YXRlIG9uS2V5RG93biA9IChldjogS2V5Ym9hcmRFdmVudCk6IHZvaWQgPT4ge1xuICAgICAgICBjb25zdCBhY3Rpb24gPSBnZXRLZXlCaW5kaW5nc01hbmFnZXIoKS5nZXRBY2Nlc3NpYmlsaXR5QWN0aW9uKGV2KTtcbiAgICAgICAgc3dpdGNoIChhY3Rpb24pIHtcbiAgICAgICAgICAgIGNhc2UgS2V5QmluZGluZ0FjdGlvbi5Fc2NhcGU6XG4gICAgICAgICAgICAgICAgZXYuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgICAgICAgICAgICAgZXYucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgICAgICB0aGlzLnByb3BzLm9uRmluaXNoZWQoKTtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICBwcml2YXRlIG9uUm90YXRlQ291bnRlckNsb2Nrd2lzZUNsaWNrID0gKCk6IHZvaWQgPT4ge1xuICAgICAgICBjb25zdCBjdXIgPSB0aGlzLnN0YXRlLnJvdGF0aW9uO1xuICAgICAgICB0aGlzLnNldFpvb21BbmRSb3RhdGlvbihjdXIgLSA5MCk7XG4gICAgfTtcblxuICAgIHByaXZhdGUgb25Sb3RhdGVDbG9ja3dpc2VDbGljayA9ICgpOiB2b2lkID0+IHtcbiAgICAgICAgY29uc3QgY3VyID0gdGhpcy5zdGF0ZS5yb3RhdGlvbjtcbiAgICAgICAgdGhpcy5zZXRab29tQW5kUm90YXRpb24oY3VyICsgOTApO1xuICAgIH07XG5cbiAgICBwcml2YXRlIG9uRG93bmxvYWRDbGljayA9ICgpOiB2b2lkID0+IHtcbiAgICAgICAgY29uc3QgYSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJhXCIpO1xuICAgICAgICBhLmhyZWYgPSB0aGlzLnByb3BzLnNyYztcbiAgICAgICAgaWYgKHRoaXMucHJvcHMubmFtZSkgYS5kb3dubG9hZCA9IHRoaXMucHJvcHMubmFtZTtcbiAgICAgICAgYS50YXJnZXQgPSBcIl9ibGFua1wiO1xuICAgICAgICBhLnJlbCA9IFwibm9yZWZlcnJlciBub29wZW5lclwiO1xuICAgICAgICBhLmNsaWNrKCk7XG4gICAgfTtcblxuICAgIHByaXZhdGUgb25PcGVuQ29udGV4dE1lbnUgPSAoKTogdm9pZCA9PiB7XG4gICAgICAgIHRoaXMuc2V0U3RhdGUoe1xuICAgICAgICAgICAgY29udGV4dE1lbnVEaXNwbGF5ZWQ6IHRydWUsXG4gICAgICAgIH0pO1xuICAgIH07XG5cbiAgICBwcml2YXRlIG9uQ2xvc2VDb250ZXh0TWVudSA9ICgpOiB2b2lkID0+IHtcbiAgICAgICAgdGhpcy5zZXRTdGF0ZSh7XG4gICAgICAgICAgICBjb250ZXh0TWVudURpc3BsYXllZDogZmFsc2UsXG4gICAgICAgIH0pO1xuICAgIH07XG5cbiAgICBwcml2YXRlIG9uUGVybWFsaW5rQ2xpY2tlZCA9IChldjogUmVhY3QuTW91c2VFdmVudCk6IHZvaWQgPT4ge1xuICAgICAgICAvLyBUaGlzIGFsbG93cyB0aGUgcGVybWFsaW5rIHRvIGJlIG9wZW5lZCBpbiBhIG5ldyB0YWIvd2luZG93IG9yIGNvcGllZCBhc1xuICAgICAgICAvLyBtYXRyaXgudG8sIGJ1dCBhbHNvIGZvciBpdCB0byBlbmFibGUgcm91dGluZyB3aXRoaW4gRWxlbWVudCB3aGVuIGNsaWNrZWQuXG4gICAgICAgIGV2LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgIGRpcy5kaXNwYXRjaDxWaWV3Um9vbVBheWxvYWQ+KHtcbiAgICAgICAgICAgIGFjdGlvbjogQWN0aW9uLlZpZXdSb29tLFxuICAgICAgICAgICAgZXZlbnRfaWQ6IHRoaXMucHJvcHMubXhFdmVudD8uZ2V0SWQoKSxcbiAgICAgICAgICAgIGhpZ2hsaWdodGVkOiB0cnVlLFxuICAgICAgICAgICAgcm9vbV9pZDogdGhpcy5wcm9wcy5teEV2ZW50Py5nZXRSb29tSWQoKSxcbiAgICAgICAgICAgIG1ldHJpY3NUcmlnZ2VyOiB1bmRlZmluZWQsIC8vIHJvb20gZG9lc24ndCBjaGFuZ2VcbiAgICAgICAgfSk7XG4gICAgICAgIHRoaXMucHJvcHMub25GaW5pc2hlZCgpO1xuICAgIH07XG5cbiAgICBwcml2YXRlIG9uU3RhcnRNb3ZpbmcgPSAoZXY6IFJlYWN0Lk1vdXNlRXZlbnQpOiB2b2lkID0+IHtcbiAgICAgICAgZXYuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgICAgIGV2LnByZXZlbnREZWZhdWx0KCk7XG5cbiAgICAgICAgLy8gRG9uJ3QgZG8gYW55dGhpbmcgaWYgd2UgcHJlc3NlZCBhbnlcbiAgICAgICAgLy8gb3RoZXIgYnV0dG9uIHRoYW4gdGhlIGxlZnQgb25lXG4gICAgICAgIGlmIChldi5idXR0b24gIT09IDApIHJldHVybjtcblxuICAgICAgICAvLyBab29tIGluIGlmIHdlIGFyZSBjb21wbGV0ZWx5IHpvb21lZCBvdXQgYW5kIGluY3JlYXNlIHRoZSB6b29tIGZhY3RvciBmb3IgaW1hZ2VzXG4gICAgICAgIC8vIHNtYWxsZXIgdGhhbiB0aGUgdmlld3BvcnQgc2l6ZVxuICAgICAgICBpZiAodGhpcy5zdGF0ZS56b29tID09PSB0aGlzLnN0YXRlLm1pblpvb20pIHtcbiAgICAgICAgICAgIHRoaXMuem9vbShcbiAgICAgICAgICAgICAgICB0aGlzLnN0YXRlLm1heFpvb20gPT09IHRoaXMuc3RhdGUubWluWm9vbSA/IDIgKiB0aGlzLnN0YXRlLm1heFpvb20gOiB0aGlzLnN0YXRlLm1heFpvb20sXG4gICAgICAgICAgICAgICAgZXYubmF0aXZlRXZlbnQub2Zmc2V0WCxcbiAgICAgICAgICAgICAgICBldi5uYXRpdmVFdmVudC5vZmZzZXRZLFxuICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuc2V0U3RhdGUoeyBtb3Zpbmc6IHRydWUgfSk7XG4gICAgICAgIHRoaXMucHJldmlvdXNYID0gdGhpcy5zdGF0ZS50cmFuc2xhdGlvblg7XG4gICAgICAgIHRoaXMucHJldmlvdXNZID0gdGhpcy5zdGF0ZS50cmFuc2xhdGlvblk7XG4gICAgICAgIHRoaXMuaW5pdFggPSBldi5wYWdlWCAtIHRoaXMuc3RhdGUudHJhbnNsYXRpb25YO1xuICAgICAgICB0aGlzLmluaXRZID0gZXYucGFnZVkgLSB0aGlzLnN0YXRlLnRyYW5zbGF0aW9uWTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBvbk1vdmluZyA9IChldjogUmVhY3QuTW91c2VFdmVudCk6IHZvaWQgPT4ge1xuICAgICAgICBldi5zdG9wUHJvcGFnYXRpb24oKTtcbiAgICAgICAgZXYucHJldmVudERlZmF1bHQoKTtcblxuICAgICAgICBpZiAoIXRoaXMuc3RhdGUubW92aW5nKSByZXR1cm47XG5cbiAgICAgICAgdGhpcy5zZXRTdGF0ZSh7XG4gICAgICAgICAgICB0cmFuc2xhdGlvblg6IGV2LnBhZ2VYIC0gdGhpcy5pbml0WCxcbiAgICAgICAgICAgIHRyYW5zbGF0aW9uWTogZXYucGFnZVkgLSB0aGlzLmluaXRZLFxuICAgICAgICB9KTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBvbkVuZE1vdmluZyA9ICgpOiB2b2lkID0+IHtcbiAgICAgICAgLy8gWm9vbSBvdXQgaWYgd2UgaGF2ZW4ndCBtb3ZlZCBtdWNoXG4gICAgICAgIGlmIChcbiAgICAgICAgICAgIHRoaXMuc3RhdGUubW92aW5nICYmXG4gICAgICAgICAgICBNYXRoLmFicyh0aGlzLnN0YXRlLnRyYW5zbGF0aW9uWCAtIHRoaXMucHJldmlvdXNYKSA8IFpPT01fRElTVEFOQ0UgJiZcbiAgICAgICAgICAgIE1hdGguYWJzKHRoaXMuc3RhdGUudHJhbnNsYXRpb25ZIC0gdGhpcy5wcmV2aW91c1kpIDwgWk9PTV9ESVNUQU5DRVxuICAgICAgICApIHtcbiAgICAgICAgICAgIHRoaXMuem9vbSh0aGlzLnN0YXRlLm1pblpvb20pO1xuICAgICAgICAgICAgdGhpcy5pbml0WCA9IDA7XG4gICAgICAgICAgICB0aGlzLmluaXRZID0gMDtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLnNldFN0YXRlKHsgbW92aW5nOiBmYWxzZSB9KTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSByZW5kZXJDb250ZXh0TWVudSgpOiBKU1guRWxlbWVudCB7XG4gICAgICAgIGxldCBjb250ZXh0TWVudTogSlNYLkVsZW1lbnQgfCB1bmRlZmluZWQ7XG4gICAgICAgIGlmICh0aGlzLnN0YXRlLmNvbnRleHRNZW51RGlzcGxheWVkICYmIHRoaXMucHJvcHMubXhFdmVudCkge1xuICAgICAgICAgICAgY29udGV4dE1lbnUgPSAoXG4gICAgICAgICAgICAgICAgPE1lc3NhZ2VDb250ZXh0TWVudVxuICAgICAgICAgICAgICAgICAgICB7Li4uYWJvdmVMZWZ0T2YodGhpcy5jb250ZXh0TWVudUJ1dHRvbi5jdXJyZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpKX1cbiAgICAgICAgICAgICAgICAgICAgbXhFdmVudD17dGhpcy5wcm9wcy5teEV2ZW50fVxuICAgICAgICAgICAgICAgICAgICBwZXJtYWxpbmtDcmVhdG9yPXt0aGlzLnByb3BzLnBlcm1hbGlua0NyZWF0b3J9XG4gICAgICAgICAgICAgICAgICAgIG9uRmluaXNoZWQ9e3RoaXMub25DbG9zZUNvbnRleHRNZW51fVxuICAgICAgICAgICAgICAgICAgICBvbkNsb3NlRGlhbG9nPXt0aGlzLnByb3BzLm9uRmluaXNoZWR9XG4gICAgICAgICAgICAgICAgLz5cbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gPFJlYWN0LkZyYWdtZW50Pntjb250ZXh0TWVudX08L1JlYWN0LkZyYWdtZW50PjtcbiAgICB9XG5cbiAgICBwdWJsaWMgcmVuZGVyKCk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gICAgICAgIGNvbnN0IHNob3dFdmVudE1ldGEgPSAhIXRoaXMucHJvcHMubXhFdmVudDtcblxuICAgICAgICBsZXQgdHJhbnNpdGlvbkNsYXNzTmFtZTtcbiAgICAgICAgaWYgKHRoaXMuYW5pbWF0aW5nTG9hZGluZykgdHJhbnNpdGlvbkNsYXNzTmFtZSA9IFwibXhfSW1hZ2VWaWV3X2ltYWdlX2FuaW1hdGluZ0xvYWRpbmdcIjtcbiAgICAgICAgZWxzZSBpZiAodGhpcy5zdGF0ZS5tb3ZpbmcgfHwgIXRoaXMuaW1hZ2VJc0xvYWRlZCkgdHJhbnNpdGlvbkNsYXNzTmFtZSA9IFwiXCI7XG4gICAgICAgIGVsc2UgdHJhbnNpdGlvbkNsYXNzTmFtZSA9IFwibXhfSW1hZ2VWaWV3X2ltYWdlX2FuaW1hdGluZ1wiO1xuXG4gICAgICAgIGNvbnN0IHJvdGF0aW9uRGVncmVlcyA9IHRoaXMuc3RhdGUucm90YXRpb24gKyBcImRlZ1wiO1xuICAgICAgICBjb25zdCB6b29tID0gdGhpcy5zdGF0ZS56b29tO1xuICAgICAgICBjb25zdCB0cmFuc2xhdGVQaXhlbHNYID0gdGhpcy5zdGF0ZS50cmFuc2xhdGlvblggKyBcInB4XCI7XG4gICAgICAgIGNvbnN0IHRyYW5zbGF0ZVBpeGVsc1kgPSB0aGlzLnN0YXRlLnRyYW5zbGF0aW9uWSArIFwicHhcIjtcbiAgICAgICAgLy8gVGhlIG9yZGVyIG9mIHRoZSB2YWx1ZXMgaXMgaW1wb3J0YW50IVxuICAgICAgICAvLyBGaXJzdCwgd2UgdHJhbnNsYXRlIGFuZCBvbmx5IHRoZW4gd2Ugcm90YXRlLCBvdGhlcndpc2VcbiAgICAgICAgLy8gd2Ugd291bGQgYXBwbHkgdGhlIHRyYW5zbGF0aW9uIHRvIGFuIGFscmVhZHkgcm90YXRlZFxuICAgICAgICAvLyBpbWFnZSBjYXVzaW5nIGl0IHRyYW5zbGF0ZSBpbiB0aGUgd3JvbmcgZGlyZWN0aW9uLlxuICAgICAgICBjb25zdCBzdHlsZTogQ1NTUHJvcGVydGllcyA9IHtcbiAgICAgICAgICAgIHRyYW5zZm9ybTogYHRyYW5zbGF0ZVgoJHt0cmFuc2xhdGVQaXhlbHNYfSlcbiAgICAgICAgICAgICAgICAgICAgICAgIHRyYW5zbGF0ZVkoJHt0cmFuc2xhdGVQaXhlbHNZfSlcbiAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlKCR7em9vbX0pXG4gICAgICAgICAgICAgICAgICAgICAgICByb3RhdGUoJHtyb3RhdGlvbkRlZ3JlZXN9KWAsXG4gICAgICAgIH07XG5cbiAgICAgICAgaWYgKHRoaXMuc3RhdGUubW92aW5nKSBzdHlsZS5jdXJzb3IgPSBcImdyYWJiaW5nXCI7XG4gICAgICAgIGVsc2UgaWYgKHRoaXMuc3RhdGUuem9vbSA9PT0gdGhpcy5zdGF0ZS5taW5ab29tKSBzdHlsZS5jdXJzb3IgPSBcInpvb20taW5cIjtcbiAgICAgICAgZWxzZSBzdHlsZS5jdXJzb3IgPSBcInpvb20tb3V0XCI7XG5cbiAgICAgICAgbGV0IGluZm86IEpTWC5FbGVtZW50IHwgdW5kZWZpbmVkO1xuICAgICAgICBpZiAoc2hvd0V2ZW50TWV0YSkge1xuICAgICAgICAgICAgY29uc3QgbXhFdmVudCA9IHRoaXMucHJvcHMubXhFdmVudCE7XG4gICAgICAgICAgICBjb25zdCBzaG93VHdlbHZlSG91ciA9IFNldHRpbmdzU3RvcmUuZ2V0VmFsdWUoXCJzaG93VHdlbHZlSG91clRpbWVzdGFtcHNcIik7XG4gICAgICAgICAgICBsZXQgcGVybWFsaW5rID0gXCIjXCI7XG4gICAgICAgICAgICBpZiAodGhpcy5wcm9wcy5wZXJtYWxpbmtDcmVhdG9yKSB7XG4gICAgICAgICAgICAgICAgcGVybWFsaW5rID0gdGhpcy5wcm9wcy5wZXJtYWxpbmtDcmVhdG9yLmZvckV2ZW50KG14RXZlbnQuZ2V0SWQoKSEpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBjb25zdCBzZW5kZXJOYW1lID0gbXhFdmVudC5zZW5kZXI/Lm5hbWUgPz8gbXhFdmVudC5nZXRTZW5kZXIoKTtcbiAgICAgICAgICAgIGNvbnN0IHNlbmRlciA9IDxkaXYgY2xhc3NOYW1lPVwibXhfSW1hZ2VWaWV3X2luZm9fc2VuZGVyXCI+e3NlbmRlck5hbWV9PC9kaXY+O1xuICAgICAgICAgICAgY29uc3QgbWVzc2FnZVRpbWVzdGFtcCA9IChcbiAgICAgICAgICAgICAgICA8YVxuICAgICAgICAgICAgICAgICAgICBocmVmPXtwZXJtYWxpbmt9XG4gICAgICAgICAgICAgICAgICAgIG9uQ2xpY2s9e3RoaXMub25QZXJtYWxpbmtDbGlja2VkfVxuICAgICAgICAgICAgICAgICAgICBhcmlhLWxhYmVsPXtmb3JtYXRGdWxsRGF0ZShuZXcgRGF0ZShteEV2ZW50LmdldFRzKCkpLCBzaG93VHdlbHZlSG91ciwgZmFsc2UpfVxuICAgICAgICAgICAgICAgID5cbiAgICAgICAgICAgICAgICAgICAgPE1lc3NhZ2VUaW1lc3RhbXBcbiAgICAgICAgICAgICAgICAgICAgICAgIHNob3dGdWxsRGF0ZT17dHJ1ZX1cbiAgICAgICAgICAgICAgICAgICAgICAgIHNob3dUd2VsdmVIb3VyPXtzaG93VHdlbHZlSG91cn1cbiAgICAgICAgICAgICAgICAgICAgICAgIHRzPXtteEV2ZW50Lm