matrix-react-sdk
Version:
SDK for matrix.org using React
482 lines (470 loc) • 79.9 kB
JavaScript
"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