matrix-react-sdk
Version:
SDK for matrix.org using React
711 lines (701 loc) • 123 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useRoomHierarchy = exports.toLocalRoom = exports.showRoom = exports.joinRoom = exports.default = exports.HierarchyLevel = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _react = _interopRequireWildcard(require("react"));
var _matrix = require("matrix-js-sdk/src/matrix");
var _roomHierarchy = require("matrix-js-sdk/src/room-hierarchy");
var _classnames = _interopRequireDefault(require("classnames"));
var _lodash = require("lodash");
var _logger = require("matrix-js-sdk/src/logger");
var _types = require("matrix-js-sdk/src/types");
var _dispatcher = _interopRequireDefault(require("../../dispatcher/dispatcher"));
var _languageHandler = require("../../languageHandler");
var _AccessibleButton = _interopRequireDefault(require("../views/elements/AccessibleButton"));
var _Spinner = _interopRequireDefault(require("../views/elements/Spinner"));
var _SearchBox = _interopRequireDefault(require("./SearchBox"));
var _RoomAvatar = _interopRequireDefault(require("../views/avatars/RoomAvatar"));
var _StyledCheckbox = _interopRequireDefault(require("../views/elements/StyledCheckbox"));
var _BaseAvatar = _interopRequireDefault(require("../views/avatars/BaseAvatar"));
var _Media = require("../../customisations/Media");
var _InfoTooltip = _interopRequireDefault(require("../views/elements/InfoTooltip"));
var _TextWithTooltip = _interopRequireDefault(require("../views/elements/TextWithTooltip"));
var _useStateToggle = require("../../hooks/useStateToggle");
var _SpaceStore = require("../../stores/spaces/SpaceStore");
var _HtmlUtils = require("../../HtmlUtils");
var _useDispatcher = require("../../hooks/useDispatcher");
var _actions = require("../../dispatcher/actions");
var _RovingTabIndex = require("../../accessibility/RovingTabIndex");
var _MatrixClientContext = _interopRequireDefault(require("../../contexts/MatrixClientContext"));
var _useEventEmitter = require("../../hooks/useEventEmitter");
var _RoomUpgrade = require("../../utils/RoomUpgrade");
var _KeyboardShortcuts = require("../../accessibility/KeyboardShortcuts");
var _KeyBindingsManager = require("../../KeyBindingsManager");
var _useTopic = require("../../hooks/room/useTopic");
var _SDKContext = require("../../contexts/SDKContext");
var _Rooms = require("../../Rooms");
var _SettingsStore = _interopRequireDefault(require("../../settings/SettingsStore"));
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; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /*
Copyright 2024 New Vector Ltd.
Copyright 2021-2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
const Tile = ({
room,
suggested,
selected,
hasPermissions,
onToggleClick,
onViewRoomClick,
onJoinRoomClick,
numChildRooms,
children
}) => {
const cli = (0, _react.useContext)(_MatrixClientContext.default);
const joinedRoom = (0, _useEventEmitter.useTypedEventEmitterState)(cli, _matrix.ClientEvent.Room, () => {
const cliRoom = cli?.getRoom(room.room_id);
return cliRoom?.getMyMembership() === _types.KnownMembership.Join ? cliRoom : undefined;
});
const joinedRoomName = (0, _useEventEmitter.useTypedEventEmitterState)(joinedRoom, _matrix.RoomEvent.Name, room => room?.name);
const name = joinedRoomName || room.name || room.canonical_alias || room.aliases?.[0] || (room.room_type === _matrix.RoomType.Space ? (0, _languageHandler._t)("common|unnamed_space") : (0, _languageHandler._t)("common|unnamed_room"));
const [showChildren, toggleShowChildren] = (0, _useStateToggle.useStateToggle)(true);
const [onFocus, isActive, ref] = (0, _RovingTabIndex.useRovingTabIndex)();
const [busy, setBusy] = (0, _react.useState)(false);
const onPreviewClick = ev => {
ev.preventDefault();
ev.stopPropagation();
onViewRoomClick();
};
const onJoinClick = async ev => {
setBusy(true);
ev.preventDefault();
ev.stopPropagation();
try {
await onJoinRoomClick();
await (0, _RoomUpgrade.awaitRoomDownSync)(cli, room.room_id);
} finally {
setBusy(false);
}
};
let button;
if (busy) {
button = /*#__PURE__*/_react.default.createElement(_AccessibleButton.default, {
disabled: true,
onClick: onJoinClick,
kind: "primary_outline",
onFocus: onFocus,
tabIndex: isActive ? 0 : -1,
title: (0, _languageHandler._t)("space|joining_space")
}, /*#__PURE__*/_react.default.createElement(_Spinner.default, {
w: 24,
h: 24
}));
} else if (joinedRoom || room.join_rule === _matrix.JoinRule.Knock) {
// If the room is knockable, show the "View" button even if we are not a member; that
// allows us to reuse the "request to join" UX in RoomView.
button = /*#__PURE__*/_react.default.createElement(_AccessibleButton.default, {
onClick: onPreviewClick,
kind: "primary_outline",
onFocus: onFocus,
tabIndex: isActive ? 0 : -1
}, (0, _languageHandler._t)("action|view"));
} else {
button = /*#__PURE__*/_react.default.createElement(_AccessibleButton.default, {
onClick: onJoinClick,
kind: "primary",
onFocus: onFocus,
tabIndex: isActive ? 0 : -1
}, (0, _languageHandler._t)("action|join"));
}
let checkbox;
if (onToggleClick) {
if (hasPermissions) {
checkbox = /*#__PURE__*/_react.default.createElement(_StyledCheckbox.default, {
checked: !!selected,
onChange: onToggleClick,
tabIndex: isActive ? 0 : -1
});
} else {
checkbox = /*#__PURE__*/_react.default.createElement(_TextWithTooltip.default, {
tooltip: (0, _languageHandler._t)("space|user_lacks_permission"),
onClick: ev => {
ev.stopPropagation();
}
}, /*#__PURE__*/_react.default.createElement(_StyledCheckbox.default, {
disabled: true,
tabIndex: isActive ? 0 : -1
}));
}
}
let avatar;
if (joinedRoom) {
avatar = /*#__PURE__*/_react.default.createElement(_RoomAvatar.default, {
room: joinedRoom,
size: "20px"
});
} else {
avatar = /*#__PURE__*/_react.default.createElement(_BaseAvatar.default, {
name: name,
idName: room.room_id,
url: room.avatar_url ? (0, _Media.mediaFromMxc)(room.avatar_url).getSquareThumbnailHttp(20) : null,
size: "20px"
});
}
let description = (0, _languageHandler._t)("common|n_members", {
count: room.num_joined_members ?? 0
});
if (numChildRooms !== undefined) {
description += " · " + (0, _languageHandler._t)("common|n_rooms", {
count: numChildRooms
});
}
let topic;
if (joinedRoom) {
const topicObj = (0, _useTopic.getTopic)(joinedRoom);
topic = (0, _HtmlUtils.topicToHtml)(topicObj?.text, topicObj?.html);
} else {
topic = room.topic;
}
let topicSection;
if (topic) {
topicSection = /*#__PURE__*/_react.default.createElement(_HtmlUtils.Linkify, {
options: {
attributes: {
onClick(ev) {
// prevent clicks on links from bubbling up to the room tile
ev.stopPropagation();
}
}
}
}, " · ", topic);
}
let joinedSection;
if (joinedRoom) {
joinedSection = /*#__PURE__*/_react.default.createElement("div", {
className: "mx_SpaceHierarchy_roomTile_joined"
}, (0, _languageHandler._t)("common|joined"));
}
let suggestedSection;
if (suggested && (!joinedRoom || hasPermissions)) {
suggestedSection = /*#__PURE__*/_react.default.createElement(_InfoTooltip.default, {
tooltip: (0, _languageHandler._t)("space|suggested_tooltip")
}, (0, _languageHandler._t)("space|suggested"));
}
const content = /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", {
className: "mx_SpaceHierarchy_roomTile_item"
}, /*#__PURE__*/_react.default.createElement("div", {
className: "mx_SpaceHierarchy_roomTile_avatar"
}, avatar), /*#__PURE__*/_react.default.createElement("div", {
className: "mx_SpaceHierarchy_roomTile_name"
}, name, joinedSection, suggestedSection), /*#__PURE__*/_react.default.createElement("div", {
className: "mx_SpaceHierarchy_roomTile_info"
}, description, topicSection)), /*#__PURE__*/_react.default.createElement("div", {
className: "mx_SpaceHierarchy_actions"
}, button, checkbox));
let childToggle;
let childSection;
let onKeyDown;
if (children) {
// the chevron is purposefully a div rather than a button as it should be ignored for a11y
childToggle = /*#__PURE__*/_react.default.createElement("div", {
className: (0, _classnames.default)("mx_SpaceHierarchy_subspace_toggle", {
mx_SpaceHierarchy_subspace_toggle_shown: showChildren
}),
onClick: ev => {
ev.stopPropagation();
toggleShowChildren();
}
});
if (showChildren) {
const onChildrenKeyDown = e => {
const action = (0, _KeyBindingsManager.getKeyBindingsManager)().getAccessibilityAction(e);
switch (action) {
case _KeyboardShortcuts.KeyBindingAction.ArrowLeft:
e.preventDefault();
e.stopPropagation();
ref.current?.focus();
break;
}
};
childSection = /*#__PURE__*/_react.default.createElement("div", {
className: "mx_SpaceHierarchy_subspace_children",
onKeyDown: onChildrenKeyDown,
role: "group"
}, children);
}
onKeyDown = e => {
let handled = false;
const action = (0, _KeyBindingsManager.getKeyBindingsManager)().getAccessibilityAction(e);
switch (action) {
case _KeyboardShortcuts.KeyBindingAction.ArrowLeft:
if (showChildren) {
handled = true;
toggleShowChildren();
}
break;
case _KeyboardShortcuts.KeyBindingAction.ArrowRight:
handled = true;
if (showChildren) {
const childSection = ref.current?.nextElementSibling;
childSection?.querySelector(".mx_SpaceHierarchy_roomTile")?.focus();
} else {
toggleShowChildren();
}
break;
}
if (handled) {
e.preventDefault();
e.stopPropagation();
}
};
}
return /*#__PURE__*/_react.default.createElement("li", {
className: "mx_SpaceHierarchy_roomTileWrapper",
role: "treeitem",
"aria-selected": selected,
"aria-expanded": children ? showChildren : undefined
}, /*#__PURE__*/_react.default.createElement(_AccessibleButton.default, {
className: (0, _classnames.default)("mx_SpaceHierarchy_roomTile", {
mx_SpaceHierarchy_subspace: room.room_type === _matrix.RoomType.Space,
mx_SpaceHierarchy_joining: busy
}),
onClick: hasPermissions && onToggleClick ? onToggleClick : onPreviewClick,
onKeyDown: onKeyDown,
ref: ref,
onFocus: onFocus,
tabIndex: isActive ? 0 : -1
}, content, childToggle), childSection);
};
const showRoom = (cli, hierarchy, roomId, roomType) => {
const room = hierarchy.roomMap.get(roomId);
// Don't let the user view a room they won't be able to either peek or join:
// fail earlier so they don't have to click back to the directory.
if (cli.isGuest()) {
if (!room?.world_readable && !room?.guest_can_join) {
_dispatcher.default.dispatch({
action: "require_registration"
});
return;
}
}
const roomAlias = (0, _Rooms.getDisplayAliasForAliasSet)(room?.canonical_alias ?? "", room?.aliases ?? []) || undefined;
_dispatcher.default.dispatch({
action: _actions.Action.ViewRoom,
should_peek: true,
room_alias: roomAlias,
room_id: roomId,
via_servers: Array.from(hierarchy.viaMap.get(roomId) || []),
oob_data: {
avatarUrl: room?.avatar_url,
// XXX: This logic is duplicated from the JS SDK which would normally decide what the name is.
name: room?.name || roomAlias || (0, _languageHandler._t)("common|unnamed_room"),
roomType
},
metricsTrigger: "RoomDirectory"
});
};
exports.showRoom = showRoom;
const joinRoom = async (cli, hierarchy, roomId) => {
// Don't let the user view a room they won't be able to either peek or join:
// fail earlier so they don't have to click back to the directory.
if (cli.isGuest()) {
_dispatcher.default.dispatch({
action: "require_registration"
});
return;
}
try {
await cli.joinRoom(roomId, {
viaServers: Array.from(hierarchy.viaMap.get(roomId) || [])
});
} catch (err) {
if (err instanceof _matrix.MatrixError) {
_SDKContext.SdkContextClass.instance.roomViewStore.showJoinRoomError(err, roomId);
} else {
_logger.logger.warn("Got a non-MatrixError while joining room", err);
_SDKContext.SdkContextClass.instance.roomViewStore.showJoinRoomError(new _matrix.MatrixError({
error: (0, _languageHandler._t)("error|unknown")
}), roomId);
}
// rethrow error so that the caller can handle react to it too
throw err;
}
_dispatcher.default.dispatch({
action: _actions.Action.JoinRoomReady,
roomId,
metricsTrigger: "SpaceHierarchy"
});
};
exports.joinRoom = joinRoom;
const toLocalRoom = (cli, room, hierarchy) => {
const history = cli.getRoomUpgradeHistory(room.room_id, true, _SettingsStore.default.getValue("feature_dynamic_room_predecessors"));
// Pick latest room that is actually part of the hierarchy
let cliRoom = null;
for (let idx = history.length - 1; idx >= 0; --idx) {
if (hierarchy.roomMap.get(history[idx].roomId)) {
cliRoom = history[idx];
break;
}
}
if (cliRoom) {
return _objectSpread(_objectSpread({}, room), {}, {
room_id: cliRoom.roomId,
room_type: cliRoom.getType(),
name: cliRoom.name,
topic: cliRoom.currentState.getStateEvents(_matrix.EventType.RoomTopic, "")?.getContent().topic,
avatar_url: cliRoom.getMxcAvatarUrl() ?? undefined,
canonical_alias: cliRoom.getCanonicalAlias() ?? undefined,
aliases: cliRoom.getAltAliases(),
world_readable: cliRoom.currentState.getStateEvents(_matrix.EventType.RoomHistoryVisibility, "")?.getContent().history_visibility === _matrix.HistoryVisibility.WorldReadable,
guest_can_join: cliRoom.currentState.getStateEvents(_matrix.EventType.RoomGuestAccess, "")?.getContent().guest_access === _matrix.GuestAccess.CanJoin,
num_joined_members: cliRoom.getJoinedMemberCount()
});
}
return room;
};
exports.toLocalRoom = toLocalRoom;
const HierarchyLevel = ({
root,
roomSet,
hierarchy,
parents,
selectedMap,
onViewRoomClick,
onJoinRoomClick,
onToggleClick
}) => {
const cli = (0, _react.useContext)(_MatrixClientContext.default);
const space = cli.getRoom(root.room_id);
const hasPermissions = space?.currentState.maySendStateEvent(_matrix.EventType.SpaceChild, cli.getSafeUserId());
const sortedChildren = (0, _lodash.sortBy)(root.children_state, ev => {
return (0, _SpaceStore.getChildOrder)(ev.content.order, ev.origin_server_ts, ev.state_key);
});
const [subspaces, childRooms] = sortedChildren.reduce((result, ev) => {
const room = hierarchy.roomMap.get(ev.state_key);
if (room && roomSet.has(room)) {
result[room.room_type === _matrix.RoomType.Space ? 0 : 1].push(toLocalRoom(cli, room, hierarchy));
}
return result;
}, [[], []]);
const newParents = new Set(parents).add(root.room_id);
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, (0, _lodash.uniqBy)(childRooms, "room_id").map(room => /*#__PURE__*/_react.default.createElement(Tile, {
key: room.room_id,
room: room,
suggested: hierarchy.isSuggested(root.room_id, room.room_id),
selected: selectedMap?.get(root.room_id)?.has(room.room_id),
onViewRoomClick: () => onViewRoomClick(room.room_id, room.room_type),
onJoinRoomClick: () => onJoinRoomClick(room.room_id, newParents),
hasPermissions: hasPermissions,
onToggleClick: onToggleClick ? () => onToggleClick(root.room_id, room.room_id) : undefined
})), subspaces.filter(room => !newParents.has(room.room_id)).map(space => /*#__PURE__*/_react.default.createElement(Tile, {
key: space.room_id,
room: space,
numChildRooms: space.children_state.filter(ev => {
const room = hierarchy.roomMap.get(ev.state_key);
return room && roomSet.has(room) && !room.room_type;
}).length,
suggested: hierarchy.isSuggested(root.room_id, space.room_id),
selected: selectedMap?.get(root.room_id)?.has(space.room_id),
onViewRoomClick: () => onViewRoomClick(space.room_id, _matrix.RoomType.Space),
onJoinRoomClick: () => onJoinRoomClick(space.room_id, newParents),
hasPermissions: hasPermissions,
onToggleClick: onToggleClick ? () => onToggleClick(root.room_id, space.room_id) : undefined
}, /*#__PURE__*/_react.default.createElement(HierarchyLevel, {
root: space,
roomSet: roomSet,
hierarchy: hierarchy,
parents: newParents,
selectedMap: selectedMap,
onViewRoomClick: onViewRoomClick,
onJoinRoomClick: onJoinRoomClick,
onToggleClick: onToggleClick
}))));
};
exports.HierarchyLevel = HierarchyLevel;
const INITIAL_PAGE_SIZE = 20;
const useRoomHierarchy = space => {
const [rooms, setRooms] = (0, _react.useState)([]);
const [hierarchy, setHierarchy] = (0, _react.useState)();
const [error, setError] = (0, _react.useState)();
const resetHierarchy = (0, _react.useCallback)(() => {
setError(undefined);
const hierarchy = new _roomHierarchy.RoomHierarchy(space, INITIAL_PAGE_SIZE);
hierarchy.load().then(() => {
if (space !== hierarchy.root) return; // discard stale results
setRooms(hierarchy.rooms ?? []);
}, setError);
setHierarchy(hierarchy);
}, [space]);
(0, _react.useEffect)(resetHierarchy, [resetHierarchy]);
(0, _useDispatcher.useDispatcher)(_dispatcher.default, payload => {
if (payload.action === _actions.Action.UpdateSpaceHierarchy) {
setRooms([]); // TODO
resetHierarchy();
}
});
const loadMore = (0, _react.useCallback)(async pageSize => {
if (!hierarchy || hierarchy.loading || !hierarchy.canLoadMore || hierarchy.noSupport || error) return;
await hierarchy.load(pageSize).catch(setError);
setRooms(hierarchy.rooms ?? []);
}, [error, hierarchy]);
// Only return the hierarchy if it is for the space requested
if (hierarchy?.root !== space) {
return {
loading: true,
loadMore
};
}
return {
loading: hierarchy.loading,
rooms,
hierarchy,
loadMore,
error
};
};
exports.useRoomHierarchy = useRoomHierarchy;
const useIntersectionObserver = callback => {
const handleObserver = entries => {
const target = entries[0];
if (target.isIntersecting) {
callback();
}
};
const observerRef = (0, _react.useRef)();
return element => {
if (observerRef.current) {
observerRef.current.disconnect();
} else if (element) {
observerRef.current = new IntersectionObserver(handleObserver, {
root: element.parentElement,
rootMargin: "0px 0px 600px 0px"
});
}
if (observerRef.current && element) {
observerRef.current.observe(element);
}
};
};
const ManageButtons = ({
hierarchy,
selected,
setSelected,
setError
}) => {
const cli = (0, _react.useContext)(_MatrixClientContext.default);
const [removing, setRemoving] = (0, _react.useState)(false);
const [saving, setSaving] = (0, _react.useState)(false);
const selectedRelations = Array.from(selected.keys()).flatMap(parentId => {
return [...selected.get(parentId).values()].map(childId => [parentId, childId]);
});
const selectionAllSuggested = selectedRelations.every(([parentId, childId]) => {
return hierarchy.isSuggested(parentId, childId);
});
const disabled = !selectedRelations.length || removing || saving;
let buttonText = (0, _languageHandler._t)("common|saving");
if (!saving) {
buttonText = selectionAllSuggested ? (0, _languageHandler._t)("space|unmark_suggested") : (0, _languageHandler._t)("space|mark_suggested");
}
const title = !selectedRelations.length ? (0, _languageHandler._t)("space|select_room_below") : undefined;
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_AccessibleButton.default, {
onClick: async () => {
setRemoving(true);
try {
const userId = cli.getSafeUserId();
for (const [parentId, childId] of selectedRelations) {
await cli.sendStateEvent(parentId, _matrix.EventType.SpaceChild, {}, childId);
// remove the child->parent relation too, if we have permission to.
const childRoom = cli.getRoom(childId);
const parentRelation = childRoom?.currentState.getStateEvents(_matrix.EventType.SpaceParent, parentId);
if (childRoom?.currentState.maySendStateEvent(_matrix.EventType.SpaceParent, userId) && Array.isArray(parentRelation?.getContent().via)) {
await cli.sendStateEvent(childId, _matrix.EventType.SpaceParent, {}, parentId);
}
hierarchy.removeRelation(parentId, childId);
}
} catch (e) {
setError((0, _languageHandler._t)("space|failed_remove_rooms"));
}
setRemoving(false);
setSelected(new Map());
},
kind: "danger_outline",
disabled: disabled,
"aria-label": removing ? (0, _languageHandler._t)("redact|ongoing") : (0, _languageHandler._t)("action|remove"),
title: title,
placement: "top"
}, removing ? (0, _languageHandler._t)("redact|ongoing") : (0, _languageHandler._t)("action|remove")), /*#__PURE__*/_react.default.createElement(_AccessibleButton.default, {
onClick: async () => {
setSaving(true);
try {
for (const [parentId, childId] of selectedRelations) {
const suggested = !selectionAllSuggested;
const existingContent = hierarchy.getRelation(parentId, childId)?.content;
if (!existingContent || existingContent.suggested === suggested) continue;
const content = _objectSpread(_objectSpread({}, existingContent), {}, {
suggested: !selectionAllSuggested
});
await cli.sendStateEvent(parentId, _matrix.EventType.SpaceChild, content, childId);
// mutate the local state to save us having to refetch the world
existingContent.suggested = content.suggested;
}
} catch (e) {
setError("Failed to update some suggestions. Try again later");
}
setSaving(false);
setSelected(new Map());
},
kind: "primary_outline",
disabled: disabled,
"aria-label": buttonText,
title: title,
placement: "top"
}, buttonText));
};
const SpaceHierarchy = ({
space,
initialText = "",
showRoom,
additionalButtons
}) => {
const cli = (0, _react.useContext)(_MatrixClientContext.default);
const [query, setQuery] = (0, _react.useState)(initialText);
const [selected, setSelected] = (0, _react.useState)(new Map()); // Map<parentId, Set<childId>>
const {
loading,
rooms,
hierarchy,
loadMore,
error: hierarchyError
} = useRoomHierarchy(space);
const filteredRoomSet = (0, _react.useMemo)(() => {
if (!rooms?.length || !hierarchy) return new Set();
const lcQuery = query.toLowerCase().trim();
if (!lcQuery) return new Set(rooms);
const directMatches = rooms.filter(r => {
return r.name?.toLowerCase().includes(lcQuery) || r.topic?.toLowerCase().includes(lcQuery);
});
// Walk back up the tree to find all parents of the direct matches to show their place in the hierarchy
const visited = new Set();
const queue = [...directMatches.map(r => r.room_id)];
while (queue.length) {
const roomId = queue.pop();
visited.add(roomId);
hierarchy.backRefs.get(roomId)?.forEach(parentId => {
if (!visited.has(parentId)) {
queue.push(parentId);
}
});
}
return new Set(rooms.filter(r => visited.has(r.room_id)));
}, [rooms, hierarchy, query]);
const [error, setError] = (0, _react.useState)("");
let errorText = error;
if (!error && hierarchyError) {
errorText = (0, _languageHandler._t)("space|failed_load_rooms");
}
const loaderRef = useIntersectionObserver(loadMore);
if (!loading && hierarchy.noSupport) {
return /*#__PURE__*/_react.default.createElement("p", null, (0, _languageHandler._t)("space|incompatible_server_hierarchy"));
}
const onKeyDown = (ev, state) => {
const action = (0, _KeyBindingsManager.getKeyBindingsManager)().getAccessibilityAction(ev);
if (action === _KeyboardShortcuts.KeyBindingAction.ArrowDown && ev.currentTarget.classList.contains("mx_SpaceHierarchy_search")) {
state.refs[0]?.current?.focus();
}
};
const onToggleClick = (parentId, childId) => {
setError("");
if (!selected.has(parentId)) {
setSelected(new Map(selected.set(parentId, new Set([childId]))));
return;
}
const parentSet = selected.get(parentId);
if (!parentSet.has(childId)) {
setSelected(new Map(selected.set(parentId, new Set([...parentSet, childId]))));
return;
}
parentSet.delete(childId);
setSelected(new Map(selected.set(parentId, new Set(parentSet))));
};
return /*#__PURE__*/_react.default.createElement(_RovingTabIndex.RovingTabIndexProvider, {
onKeyDown: onKeyDown,
handleHomeEnd: true,
handleUpDown: true
}, ({
onKeyDownHandler
}) => {
let content;
if (!hierarchy || loading && !rooms?.length) {
content = /*#__PURE__*/_react.default.createElement(_Spinner.default, null);
} else {
const hasPermissions = space?.getMyMembership() === _types.KnownMembership.Join && space.currentState.maySendStateEvent(_matrix.EventType.SpaceChild, cli.getSafeUserId());
const root = hierarchy.roomMap.get(space.roomId);
let results;
if (filteredRoomSet.size && root) {
results = /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(HierarchyLevel, {
root: root,
roomSet: filteredRoomSet,
hierarchy: hierarchy,
parents: new Set(),
selectedMap: selected,
onToggleClick: hasPermissions ? onToggleClick : undefined,
onViewRoomClick: (roomId, roomType) => showRoom(cli, hierarchy, roomId, roomType),
onJoinRoomClick: async (roomId, parents) => {
for (const parent of parents) {
if (cli.getRoom(parent)?.getMyMembership() !== _types.KnownMembership.Join) {
await joinRoom(cli, hierarchy, parent);
}
}
await joinRoom(cli, hierarchy, roomId);
}
}));
} else if (!hierarchy.canLoadMore) {
results = /*#__PURE__*/_react.default.createElement("div", {
className: "mx_SpaceHierarchy_noResults"
}, /*#__PURE__*/_react.default.createElement("h3", null, (0, _languageHandler._t)("common|no_results_found")), /*#__PURE__*/_react.default.createElement("div", null, (0, _languageHandler._t)("space|no_search_result_hint")));
}
let loader;
if (hierarchy.canLoadMore) {
loader = /*#__PURE__*/_react.default.createElement("div", {
ref: loaderRef
}, /*#__PURE__*/_react.default.createElement(_Spinner.default, null));
}
content = /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", {
className: "mx_SpaceHierarchy_listHeader"
}, /*#__PURE__*/_react.default.createElement("h4", {
className: "mx_SpaceHierarchy_listHeader_header"
}, query.trim() ? (0, _languageHandler._t)("space|title_when_query_available") : (0, _languageHandler._t)("space|title_when_query_unavailable")), /*#__PURE__*/_react.default.createElement("div", {
className: "mx_SpaceHierarchy_listHeader_buttons"
}, additionalButtons, hasPermissions && /*#__PURE__*/_react.default.createElement(ManageButtons, {
hierarchy: hierarchy,
selected: selected,
setSelected: setSelected,
setError: setError
}))), errorText && /*#__PURE__*/_react.default.createElement("div", {
className: "mx_SpaceHierarchy_error"
}, errorText), /*#__PURE__*/_react.default.createElement("ul", {
className: "mx_SpaceHierarchy_list",
onKeyDown: onKeyDownHandler,
role: "tree",
"aria-label": (0, _languageHandler._t)("common|space")
}, results), loader);
}
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_SearchBox.default, {
className: "mx_SpaceHierarchy_search mx_textinput_icon mx_textinput_search",
placeholder: (0, _languageHandler._t)("space|search_placeholder"),
onSearch: setQuery,
autoFocus: true,
initialValue: initialText,
onKeyDown: onKeyDownHandler
}), content);
});
};
var _default = exports.default = SpaceHierarchy;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfcmVhY3QiLCJfaW50ZXJvcFJlcXVpcmVXaWxkY2FyZCIsInJlcXVpcmUiLCJfbWF0cml4IiwiX3Jvb21IaWVyYXJjaHkiLCJfY2xhc3NuYW1lcyIsIl9pbnRlcm9wUmVxdWlyZURlZmF1bHQiLCJfbG9kYXNoIiwiX2xvZ2dlciIsIl90eXBlcyIsIl9kaXNwYXRjaGVyIiwiX2xhbmd1YWdlSGFuZGxlciIsIl9BY2Nlc3NpYmxlQnV0dG9uIiwiX1NwaW5uZXIiLCJfU2VhcmNoQm94IiwiX1Jvb21BdmF0YXIiLCJfU3R5bGVkQ2hlY2tib3giLCJfQmFzZUF2YXRhciIsIl9NZWRpYSIsIl9JbmZvVG9vbHRpcCIsIl9UZXh0V2l0aFRvb2x0aXAiLCJfdXNlU3RhdGVUb2dnbGUiLCJfU3BhY2VTdG9yZSIsIl9IdG1sVXRpbHMiLCJfdXNlRGlzcGF0Y2hlciIsIl9hY3Rpb25zIiwiX1JvdmluZ1RhYkluZGV4IiwiX01hdHJpeENsaWVudENvbnRleHQiLCJfdXNlRXZlbnRFbWl0dGVyIiwiX1Jvb21VcGdyYWRlIiwiX0tleWJvYXJkU2hvcnRjdXRzIiwiX0tleUJpbmRpbmdzTWFuYWdlciIsIl91c2VUb3BpYyIsIl9TREtDb250ZXh0IiwiX1Jvb21zIiwiX1NldHRpbmdzU3RvcmUiLCJfZ2V0UmVxdWlyZVdpbGRjYXJkQ2FjaGUiLCJlIiwiV2Vha01hcCIsInIiLCJ0IiwiX19lc01vZHVsZSIsImRlZmF1bHQiLCJoYXMiLCJnZXQiLCJuIiwiX19wcm90b19fIiwiYSIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwidSIsImhhc093blByb3BlcnR5IiwiY2FsbCIsImkiLCJzZXQiLCJvd25LZXlzIiwia2V5cyIsImdldE93blByb3BlcnR5U3ltYm9scyIsIm8iLCJmaWx0ZXIiLCJlbnVtZXJhYmxlIiwicHVzaCIsImFwcGx5IiwiX29iamVjdFNwcmVhZCIsImFyZ3VtZW50cyIsImxlbmd0aCIsImZvckVhY2giLCJfZGVmaW5lUHJvcGVydHkyIiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9ycyIsImRlZmluZVByb3BlcnRpZXMiLCJUaWxlIiwicm9vbSIsInN1Z2dlc3RlZCIsInNlbGVjdGVkIiwiaGFzUGVybWlzc2lvbnMiLCJvblRvZ2dsZUNsaWNrIiwib25WaWV3Um9vbUNsaWNrIiwib25Kb2luUm9vbUNsaWNrIiwibnVtQ2hpbGRSb29tcyIsImNoaWxkcmVuIiwiY2xpIiwidXNlQ29udGV4dCIsIk1hdHJpeENsaWVudENvbnRleHQiLCJqb2luZWRSb29tIiwidXNlVHlwZWRFdmVudEVtaXR0ZXJTdGF0ZSIsIkNsaWVudEV2ZW50IiwiUm9vbSIsImNsaVJvb20iLCJnZXRSb29tIiwicm9vbV9pZCIsImdldE15TWVtYmVyc2hpcCIsIktub3duTWVtYmVyc2hpcCIsIkpvaW4iLCJ1bmRlZmluZWQiLCJqb2luZWRSb29tTmFtZSIsIlJvb21FdmVudCIsIk5hbWUiLCJuYW1lIiwiY2Fub25pY2FsX2FsaWFzIiwiYWxpYXNlcyIsInJvb21fdHlwZSIsIlJvb21UeXBlIiwiU3BhY2UiLCJfdCIsInNob3dDaGlsZHJlbiIsInRvZ2dsZVNob3dDaGlsZHJlbiIsInVzZVN0YXRlVG9nZ2xlIiwib25Gb2N1cyIsImlzQWN0aXZlIiwicmVmIiwidXNlUm92aW5nVGFiSW5kZXgiLCJidXN5Iiwic2V0QnVzeSIsInVzZVN0YXRlIiwib25QcmV2aWV3Q2xpY2siLCJldiIsInByZXZlbnREZWZhdWx0Iiwic3RvcFByb3BhZ2F0aW9uIiwib25Kb2luQ2xpY2siLCJhd2FpdFJvb21Eb3duU3luYyIsImJ1dHRvbiIsImNyZWF0ZUVsZW1lbnQiLCJkaXNhYmxlZCIsIm9uQ2xpY2siLCJraW5kIiwidGFiSW5kZXgiLCJ0aXRsZSIsInciLCJoIiwiam9pbl9ydWxlIiwiSm9pblJ1bGUiLCJLbm9jayIsImNoZWNrYm94IiwiY2hlY2tlZCIsIm9uQ2hhbmdlIiwidG9vbHRpcCIsImF2YXRhciIsInNpemUiLCJpZE5hbWUiLCJ1cmwiLCJhdmF0YXJfdXJsIiwibWVkaWFGcm9tTXhjIiwiZ2V0U3F1YXJlVGh1bWJuYWlsSHR0cCIsImRlc2NyaXB0aW9uIiwiY291bnQiLCJudW1fam9pbmVkX21lbWJlcnMiLCJ0b3BpYyIsInRvcGljT2JqIiwiZ2V0VG9waWMiLCJ0b3BpY1RvSHRtbCIsInRleHQiLCJodG1sIiwidG9waWNTZWN0aW9uIiwiTGlua2lmeSIsIm9wdGlvbnMiLCJhdHRyaWJ1dGVzIiwiam9pbmVkU2VjdGlvbiIsImNsYXNzTmFtZSIsInN1Z2dlc3RlZFNlY3Rpb24iLCJjb250ZW50IiwiRnJhZ21lbnQiLCJjaGlsZFRvZ2dsZSIsImNoaWxkU2VjdGlvbiIsIm9uS2V5RG93biIsImNsYXNzTmFtZXMiLCJteF9TcGFjZUhpZXJhcmNoeV9zdWJzcGFjZV90b2dnbGVfc2hvd24iLCJvbkNoaWxkcmVuS2V5RG93biIsImFjdGlvbiIsImdldEtleUJpbmRpbmdzTWFuYWdlciIsImdldEFjY2Vzc2liaWxpdHlBY3Rpb24iLCJLZXlCaW5kaW5nQWN0aW9uIiwiQXJyb3dMZWZ0IiwiY3VycmVudCIsImZvY3VzIiwicm9sZSIsImhhbmRsZWQiLCJBcnJvd1JpZ2h0IiwibmV4dEVsZW1lbnRTaWJsaW5nIiwicXVlcnlTZWxlY3RvciIsIm14X1NwYWNlSGllcmFyY2h5X3N1YnNwYWNlIiwibXhfU3BhY2VIaWVyYXJjaHlfam9pbmluZyIsInNob3dSb29tIiwiaGllcmFyY2h5Iiwicm9vbUlkIiwicm9vbVR5cGUiLCJyb29tTWFwIiwiaXNHdWVzdCIsIndvcmxkX3JlYWRhYmxlIiwiZ3Vlc3RfY2FuX2pvaW4iLCJkZWZhdWx0RGlzcGF0Y2hlciIsImRpc3BhdGNoIiwicm9vbUFsaWFzIiwiZ2V0RGlzcGxheUFsaWFzRm9yQWxpYXNTZXQiLCJBY3Rpb24iLCJWaWV3Um9vbSIsInNob3VsZF9wZWVrIiwicm9vbV9hbGlhcyIsInZpYV9zZXJ2ZXJzIiwiQXJyYXkiLCJmcm9tIiwidmlhTWFwIiwib29iX2RhdGEiLCJhdmF0YXJVcmwiLCJtZXRyaWNzVHJpZ2dlciIsImV4cG9ydHMiLCJqb2luUm9vbSIsInZpYVNlcnZlcnMiLCJlcnIiLCJNYXRyaXhFcnJvciIsIlNka0NvbnRleHRDbGFzcyIsImluc3RhbmNlIiwicm9vbVZpZXdTdG9yZSIsInNob3dKb2luUm9vbUVycm9yIiwibG9nZ2VyIiwid2FybiIsImVycm9yIiwiSm9pblJvb21SZWFkeSIsInRvTG9jYWxSb29tIiwiaGlzdG9yeSIsImdldFJvb21VcGdyYWRlSGlzdG9yeSIsIlNldHRpbmdzU3RvcmUiLCJnZXRWYWx1ZSIsImlkeCIsImdldFR5cGUiLCJjdXJyZW50U3RhdGUiLCJnZXRTdGF0ZUV2ZW50cyIsIkV2ZW50VHlwZSIsIlJvb21Ub3BpYyIsImdldENvbnRlbnQiLCJnZXRNeGNBdmF0YXJVcmwiLCJnZXRDYW5vbmljYWxBbGlhcyIsImdldEFsdEFsaWFzZXMiLCJSb29tSGlzdG9yeVZpc2liaWxpdHkiLCJoaXN0b3J5X3Zpc2liaWxpdHkiLCJIaXN0b3J5VmlzaWJpbGl0eSIsIldvcmxkUmVhZGFibGUiLCJSb29tR3Vlc3RBY2Nlc3MiLCJndWVzdF9hY2Nlc3MiLCJHdWVzdEFjY2VzcyIsIkNhbkpvaW4iLCJnZXRKb2luZWRNZW1iZXJDb3VudCIsIkhpZXJhcmNoeUxldmVsIiwicm9vdCIsInJvb21TZXQiLCJwYXJlbnRzIiwic2VsZWN0ZWRNYXAiLCJzcGFjZSIsIm1heVNlbmRTdGF0ZUV2ZW50IiwiU3BhY2VDaGlsZCIsImdldFNhZmVVc2VySWQiLCJzb3J0ZWRDaGlsZHJlbiIsInNvcnRCeSIsImNoaWxkcmVuX3N0YXRlIiwiZ2V0Q2hpbGRPcmRlciIsIm9yZGVyIiwib3JpZ2luX3NlcnZlcl90cyIsInN0YXRlX2tleSIsInN1YnNwYWNlcyIsImNoaWxkUm9vbXMiLCJyZWR1Y2UiLCJyZXN1bHQiLCJuZXdQYXJlbnRzIiwiU2V0IiwiYWRkIiwidW5pcUJ5IiwibWFwIiwia2V5IiwiaXNTdWdnZXN0ZWQiLCJJTklUSUFMX1BBR0VfU0laRSIsInVzZVJvb21IaWVyYXJjaHkiLCJyb29tcyIsInNldFJvb21zIiwic2V0SGllcmFyY2h5Iiwic2V0RXJyb3IiLCJyZXNldEhpZXJhcmNoeSIsInVzZUNhbGxiYWNrIiwiUm9vbUhpZXJhcmNoeSIsImxvYWQiLCJ0aGVuIiwidXNlRWZmZWN0IiwidXNlRGlzcGF0Y2hlciIsInBheWxvYWQiLCJVcGRhdGVTcGFjZUhpZXJhcmNoeSIsImxvYWRNb3JlIiwicGFnZVNpemUiLCJsb2FkaW5nIiwiY2FuTG9hZE1vcmUiLCJub1N1cHBvcnQiLCJjYXRjaCIsInVzZUludGVyc2VjdGlvbk9ic2VydmVyIiwiY2FsbGJhY2siLCJoYW5kbGVPYnNlcnZlciIsImVudHJpZXMiLCJ0YXJnZXQiLCJpc0ludGVyc2VjdGluZyIsIm9ic2VydmVyUmVmIiwidXNlUmVmIiwiZWxlbWVudCIsImRpc2Nvbm5lY3QiLCJJbnRlcnNlY3Rpb25PYnNlcnZlciIsInBhcmVudEVsZW1lbnQiLCJyb290TWFyZ2luIiwib2JzZXJ2ZSIsIk1hbmFnZUJ1dHRvbnMiLCJzZXRTZWxlY3RlZCIsInJlbW92aW5nIiwic2V0UmVtb3ZpbmciLCJzYXZpbmciLCJzZXRTYXZpbmciLCJzZWxlY3RlZFJlbGF0aW9ucyIsImZsYXRNYXAiLCJwYXJlbnRJZCIsInZhbHVlcyIsImNoaWxkSWQiLCJzZWxlY3Rpb25BbGxTdWdnZXN0ZWQiLCJldmVyeSIsImJ1dHRvblRleHQiLCJ1c2VySWQiLCJzZW5kU3RhdGVFdmVudCIsImNoaWxkUm9vbSIsInBhcmVudFJlbGF0aW9uIiwiU3BhY2VQYXJlbnQiLCJpc0FycmF5IiwidmlhIiwicmVtb3ZlUmVsYXRpb24iLCJNYXAiLCJwbGFjZW1lbnQiLCJleGlzdGluZ0NvbnRlbnQiLCJnZXRSZWxhdGlvbiIsIlNwYWNlSGllcmFyY2h5IiwiaW5pdGlhbFRleHQiLCJhZGRpdGlvbmFsQnV0dG9ucyIsInF1ZXJ5Iiwic2V0UXVlcnkiLCJoaWVyYXJjaHlFcnJvciIsImZpbHRlcmVkUm9vbVNldCIsInVzZU1lbW8iLCJsY1F1ZXJ5IiwidG9Mb3dlckNhc2UiLCJ0cmltIiwiZGlyZWN0TWF0Y2hlcyIsImluY2x1ZGVzIiwidmlzaXRlZCIsInF1ZXVlIiwicG9wIiwiYmFja1JlZnMiLCJlcnJvclRleHQiLCJsb2FkZXJSZWYiLCJzdGF0ZSIsIkFycm93RG93biIsImN1cnJlbnRUYXJnZXQiLCJjbGFzc0xpc3QiLCJjb250YWlucyIsInJlZnMiLCJwYXJlbnRTZXQiLCJkZWxldGUiLCJSb3ZpbmdUYWJJbmRleFByb3ZpZGVyIiwiaGFuZGxlSG9tZUVuZCIsImhhbmRsZVVwRG93biIsIm9uS2V5RG93bkhhbmRsZXIiLCJyZXN1bHRzIiwicGFyZW50IiwibG9hZGVyIiwicGxhY2Vob2xkZXIiLCJvblNlYXJjaCIsImF1dG9Gb2N1cyIsImluaXRpYWxWYWx1ZSIsIl9kZWZhdWx0Il0sInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvc3RydWN0dXJlcy9TcGFjZUhpZXJhcmNoeS50c3giXSwic291cmNlc0NvbnRlbnQiOlsiLypcbkNvcHlyaWdodCAyMDI0IE5ldyBWZWN0b3IgTHRkLlxuQ29weXJpZ2h0IDIwMjEtMjAyMyBUaGUgTWF0cml4Lm9yZyBGb3VuZGF0aW9uIEMuSS5DLlxuXG5TUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQUdQTC0zLjAtb25seSBPUiBHUEwtMy4wLW9ubHlcblBsZWFzZSBzZWUgTElDRU5TRSBmaWxlcyBpbiB0aGUgcmVwb3NpdG9yeSByb290IGZvciBmdWxsIGRldGFpbHMuXG4qL1xuXG5pbXBvcnQgUmVhY3QsIHtcbiAgICBEaXNwYXRjaCxcbiAgICBLZXlib2FyZEV2ZW50LFxuICAgIEtleWJvYXJkRXZlbnRIYW5kbGVyLFxuICAgIFJlYWN0RWxlbWVudCxcbiAgICBSZWFjdE5vZGUsXG4gICAgU2V0U3RhdGVBY3Rpb24sXG4gICAgdXNlQ2FsbGJhY2ssXG4gICAgdXNlQ29udGV4dCxcbiAgICB1c2VFZmZlY3QsXG4gICAgdXNlTWVtbyxcbiAgICB1c2VSZWYsXG4gICAgdXNlU3RhdGUsXG59IGZyb20gXCJyZWFjdFwiO1xuaW1wb3J0IHtcbiAgICBSb29tLFxuICAgIFJvb21FdmVudCxcbiAgICBDbGllbnRFdmVudCxcbiAgICBNYXRyaXhDbGllbnQsXG4gICAgTWF0cml4RXJyb3IsXG4gICAgRXZlbnRUeXBlLFxuICAgIFJvb21UeXBlLFxuICAgIEd1ZXN0QWNjZXNzLFxuICAgIEhpc3RvcnlWaXNpYmlsaXR5LFxuICAgIEhpZXJhcmNoeVJlbGF0aW9uLFxuICAgIEhpZXJhcmNoeVJvb20sXG4gICAgSm9pblJ1bGUsXG59IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9tYXRyaXhcIjtcbmltcG9ydCB7IFJvb21IaWVyYXJjaHkgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvcm9vbS1oaWVyYXJjaHlcIjtcbmltcG9ydCBjbGFzc05hbWVzIGZyb20gXCJjbGFzc25hbWVzXCI7XG5pbXBvcnQgeyBzb3J0QnksIHVuaXFCeSB9IGZyb20gXCJsb2Rhc2hcIjtcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9sb2dnZXJcIjtcbmltcG9ydCB7IEtub3duTWVtYmVyc2hpcCwgU3BhY2VDaGlsZEV2ZW50Q29udGVudCB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy90eXBlc1wiO1xuXG5pbXBvcnQgZGVmYXVsdERpc3BhdGNoZXIgZnJvbSBcIi4uLy4uL2Rpc3BhdGNoZXIvZGlzcGF0Y2hlclwiO1xuaW1wb3J0IHsgX3QgfSBmcm9tIFwiLi4vLi4vbGFuZ3VhZ2VIYW5kbGVyXCI7XG5pbXBvcnQgQWNjZXNzaWJsZUJ1dHRvbiwgeyBCdXR0b25FdmVudCB9IGZyb20gXCIuLi92aWV3cy9lbGVtZW50cy9BY2Nlc3NpYmxlQnV0dG9uXCI7XG5pbXBvcnQgU3Bpbm5lciBmcm9tIFwiLi4vdmlld3MvZWxlbWVudHMvU3Bpbm5lclwiO1xuaW1wb3J0IFNlYXJjaEJveCBmcm9tIFwiLi9TZWFyY2hCb3hcIjtcbmltcG9ydCBSb29tQXZhdGFyIGZyb20gXCIuLi92aWV3cy9hdmF0YXJzL1Jvb21BdmF0YXJcIjtcbmltcG9ydCBTdHlsZWRDaGVja2JveCBmcm9tIFwiLi4vdmlld3MvZWxlbWVudHMvU3R5bGVkQ2hlY2tib3hcIjtcbmltcG9ydCBCYXNlQXZhdGFyIGZyb20gXCIuLi92aWV3cy9hdmF0YXJzL0Jhc2VBdmF0YXJcIjtcbmltcG9ydCB7IG1lZGlhRnJvbU14YyB9IGZyb20gXCIuLi8uLi9jdXN0b21pc2F0aW9ucy9NZWRpYVwiO1xuaW1wb3J0IEluZm9Ub29sdGlwIGZyb20gXCIuLi92aWV3cy9lbGVtZW50cy9JbmZvVG9vbHRpcFwiO1xuaW1wb3J0IFRleHRXaXRoVG9vbHRpcCBmcm9tIFwiLi4vdmlld3MvZWxlbWVudHMvVGV4dFdpdGhUb29sdGlwXCI7XG5pbXBvcnQgeyB1c2VTdGF0ZVRvZ2dsZSB9IGZyb20gXCIuLi8uLi9ob29rcy91c2VTdGF0ZVRvZ2dsZVwiO1xuaW1wb3J0IHsgZ2V0Q2hpbGRPcmRlciB9IGZyb20gXCIuLi8uLi9zdG9yZXMvc3BhY2VzL1NwYWNlU3RvcmVcIjtcbmltcG9ydCB7IExpbmtpZnksIHRvcGljVG9IdG1sIH0gZnJvbSBcIi4uLy4uL0h0bWxVdGlsc1wiO1xuaW1wb3J0IHsgdXNlRGlzcGF0Y2hlciB9IGZyb20gXCIuLi8uLi9ob29rcy91c2VEaXNwYXRjaGVyXCI7XG5pbXBvcnQgeyBBY3Rpb24gfSBmcm9tIFwiLi4vLi4vZGlzcGF0Y2hlci9hY3Rpb25zXCI7XG5pbXBvcnQgeyBJU3RhdGUsIFJvdmluZ1RhYkluZGV4UHJvdmlkZXIsIHVzZVJvdmluZ1RhYkluZGV4IH0gZnJvbSBcIi4uLy4uL2FjY2Vzc2liaWxpdHkvUm92aW5nVGFiSW5kZXhcIjtcbmltcG9ydCBNYXRyaXhDbGllbnRDb250ZXh0IGZyb20gXCIuLi8uLi9jb250ZXh0cy9NYXRyaXhDbGllbnRDb250ZXh0XCI7XG5pbXBvcnQgeyB1c2VUeXBlZEV2ZW50RW1pdHRlclN0YXRlIH0gZnJvbSBcIi4uLy4uL2hvb2tzL3VzZUV2ZW50RW1pdHRlclwiO1xuaW1wb3J0IHsgSU9PQkRhdGEgfSBmcm9tIFwiLi4vLi4vc3RvcmVzL1RocmVlcGlkSW52aXRlU3RvcmVcIjtcbmltcG9ydCB7IGF3YWl0Um9vbURvd25TeW5jIH0gZnJvbSBcIi4uLy4uL3V0aWxzL1Jvb21VcGdyYWRlXCI7XG5pbXBvcnQgeyBWaWV3Um9vbVBheWxvYWQgfSBmcm9tIFwiLi4vLi4vZGlzcGF0Y2hlci9wYXlsb2Fkcy9WaWV3Um9vbVBheWxvYWRcIjtcbmltcG9ydCB7IEpvaW5Sb29tUmVhZHlQYXlsb2FkIH0gZnJvbSBcIi4uLy4uL2Rpc3BhdGNoZXIvcGF5bG9hZHMvSm9pblJvb21SZWFkeVBheWxvYWRcIjtcbmltcG9ydCB7IEtleUJpbmRpbmdBY3Rpb24gfSBmcm9tIFwiLi4vLi4vYWNjZXNzaWJpbGl0eS9LZXlib2FyZFNob3J0Y3V0c1wiO1xuaW1wb3J0IHsgZ2V0S2V5QmluZGluZ3NNYW5hZ2VyIH0gZnJvbSBcIi4uLy4uL0tleUJpbmRpbmdzTWFuYWdlclwiO1xuaW1wb3J0IHsgZ2V0VG9waWMgfSBmcm9tIFwiLi4vLi4vaG9va3Mvcm9vbS91c2VUb3BpY1wiO1xuaW1wb3J0IHsgU2RrQ29udGV4dENsYXNzIH0gZnJvbSBcIi4uLy4uL2NvbnRleHRzL1NES0NvbnRleHRcIjtcbmltcG9ydCB7IGdldERpc3BsYXlBbGlhc0ZvckFsaWFzU2V0IH0gZnJvbSBcIi4uLy4uL1Jvb21zXCI7XG5pbXBvcnQgU2V0dGluZ3NTdG9yZSBmcm9tIFwiLi4vLi4vc2V0dGluZ3MvU2V0dGluZ3NTdG9yZVwiO1xuXG5pbnRlcmZhY2UgSVByb3BzIHtcbiAgICBzcGFjZTogUm9vbTtcbiAgICBpbml0aWFsVGV4dD86IHN0cmluZztcbiAgICBhZGRpdGlvbmFsQnV0dG9ucz86IFJlYWN0Tm9kZTtcbiAgICBzaG93Um9vbShjbGk6IE1hdHJpeENsaWVudCwgaGllcmFyY2h5OiBSb29tSGllcmFyY2h5LCByb29tSWQ6IHN0cmluZywgcm9vbVR5cGU/OiBSb29tVHlwZSk6IHZvaWQ7XG59XG5cbmludGVyZmFjZSBJVGlsZVByb3BzIHtcbiAgICByb29tOiBIaWVyYXJjaHlSb29tO1xuICAgIHN1Z2dlc3RlZD86IGJvb2xlYW47XG4gICAgc2VsZWN0ZWQ/OiBib29sZWFuO1xuICAgIG51bUNoaWxkUm9vbXM/OiBudW1iZXI7XG4gICAgaGFzUGVybWlzc2lvbnM/OiBib29sZWFuO1xuICAgIGNoaWxkcmVuPzogUmVhY3ROb2RlO1xuICAgIG9uVmlld1Jvb21DbGljaygpOiB2b2lkO1xuICAgIG9uSm9pblJvb21DbGljaygpOiBQcm9taXNlPHVua25vd24+O1xuICAgIG9uVG9nZ2xlQ2xpY2s/KCk6IHZvaWQ7XG59XG5cbmNvbnN0IFRpbGU6IFJlYWN0LkZDPElUaWxlUHJvcHM+ID0gKHtcbiAgICByb29tLFxuICAgIHN1Z2dlc3RlZCxcbiAgICBzZWxlY3RlZCxcbiAgICBoYXNQZXJtaXNzaW9ucyxcbiAgICBvblRvZ2dsZUNsaWNrLFxuICAgIG9uVmlld1Jvb21DbGljayxcbiAgICBvbkpvaW5Sb29tQ2xpY2ssXG4gICAgbnVtQ2hpbGRSb29tcyxcbiAgICBjaGlsZHJlbixcbn0pID0+IHtcbiAgICBjb25zdCBjbGkgPSB1c2VDb250ZXh0KE1hdHJpeENsaWVudENvbnRleHQpO1xuICAgIGNvbnN0IGpvaW5lZFJvb20gPSB1c2VUeXBlZEV2ZW50RW1pdHRlclN0YXRlKGNsaSwgQ2xpZW50RXZlbnQuUm9vbSwgKCkgPT4ge1xuICAgICAgICBjb25zdCBjbGlSb29tID0gY2xpPy5nZXRSb29tKHJvb20ucm9vbV9pZCk7XG4gICAgICAgIHJldHVybiBjbGlSb29tPy5nZXRNeU1lbWJlcnNoaXAoKSA9PT0gS25vd25NZW1iZXJzaGlwLkpvaW4gPyBjbGlSb29tIDogdW5kZWZpbmVkO1xuICAgIH0pO1xuICAgIGNvbnN0IGpvaW5lZFJvb21OYW1lID0gdXNlVHlwZWRFdmVudEVtaXR0ZXJTdGF0ZShqb2luZWRSb29tLCBSb29tRXZlbnQuTmFtZSwgKHJvb20pID0+IHJvb20/Lm5hbWUpO1xuICAgIGNvbnN0IG5hbWUgPVxuICAgICAgICBqb2luZWRSb29tTmFtZSB8fFxuICAgICAgICByb29tLm5hbWUgfHxcbiAgICAgICAgcm9vbS5jYW5vbmljYWxfYWxpYXMgfHxcbiAgICAgICAgcm9vbS5hbGlhc2VzPy5bMF0gfHxcbiAgICAgICAgKHJvb20ucm9vbV90eXBlID09PSBSb29tVHlwZS5TcGFjZSA/IF90KFwiY29tbW9ufHVubmFtZWRfc3BhY2VcIikgOiBfdChcImNvbW1vbnx1bm5hbWVkX3Jvb21cIikpO1xuXG4gICAgY29uc3QgW3Nob3dDaGlsZHJlbiwgdG9nZ2xlU2hvd0NoaWxkcmVuXSA9IHVzZVN0YXRlVG9nZ2xlKHRydWUpO1xuICAgIGNvbnN0IFtvbkZvY3VzLCBpc0FjdGl2ZSwgcmVmXSA9IHVzZVJvdmluZ1RhYkluZGV4KCk7XG4gICAgY29uc3QgW2J1c3ksIHNldEJ1c3ldID0gdXNlU3RhdGUoZmFsc2UpO1xuXG4gICAgY29uc3Qgb25QcmV2aWV3Q2xpY2sgPSAoZXY6IEJ1dHRvbkV2ZW50KTogdm9pZCA9PiB7XG4gICAgICAgIGV2LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgIGV2LnN0b3BQcm9wYWdhdGlvbigpO1xuICAgICAgICBvblZpZXdSb29tQ2xpY2soKTtcbiAgICB9O1xuICAgIGNvbnN0IG9uSm9pbkNsaWNrID0gYXN5bmMgKGV2OiBCdXR0b25FdmVudCk6IFByb21pc2U8dm9pZD4gPT4ge1xuICAgICAgICBzZXRCdXN5KHRydWUpO1xuICAgICAgICBldi5wcmV2ZW50RGVmYXVsdCgpO1xuICAgICAgICBldi5zdG9wUHJvcGFnYXRpb24oKTtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGF3YWl0IG9uSm9pblJvb21DbGljaygpO1xuICAgICAgICAgICAgYXdhaXQgYXdhaXRSb29tRG93blN5bmMoY2xpLCByb29tLnJvb21faWQpO1xuICAgICAgICB9IGZpbmFsbHkge1xuICAgICAgICAgICAgc2V0QnVzeShmYWxzZSk7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgbGV0IGJ1dHRvbjogUmVhY3RFbGVtZW50O1xuICAgIGlmIChidXN5KSB7XG4gICAgICAgIGJ1dHRvbiA9IChcbiAgICAgICAgICAgIDxBY2Nlc3NpYmxlQnV0dG9uXG4gICAgICAgICAgICAgICAgZGlzYWJsZWQ9e3RydWV9XG4gICAgICAgICAgICAgICAgb25DbGljaz17b25Kb2luQ2xpY2t9XG4gICAgICAgICAgICAgICAga2luZD1cInByaW1hcnlfb3V0bGluZVwiXG4gICAgICAgICAgICAgICAgb25Gb2N1cz17b25Gb2N1c31cbiAgICAgICAgICAgICAgICB0YWJJbmRleD17aXNBY3RpdmUgPyAwIDogLTF9XG4gICAgICAgICAgICAgICAgdGl0bGU9e190KFwic3BhY2V8am9pbmluZ19zcGFjZVwiKX1cbiAgICAgICAgICAgID5cbiAgICAgICAgICAgICAgICA8U3Bpbm5lciB3PXsyNH0gaD17MjR9IC8+XG4gICAgICAgICAgICA8L0FjY2Vzc2libGVCdXR0b24+XG4gICAgICAgICk7XG4gICAgfSBlbHNlIGlmIChqb2luZWRSb29tIHx8IHJvb20uam9pbl9ydWxlID09PSBKb2luUnVsZS5Lbm9jaykge1xuICAgICAgICAvLyBJZiB0aGUgcm9vbSBpcyBrbm9ja2FibGUsIHNob3cgdGhlIFwiVmlld1wiIGJ1dHRvbiBldmVuIGlmIHdlIGFyZSBub3QgYSBtZW1iZXI7IHRoYXRcbiAgICAgICAgLy8gYWxsb3dzIHVzIHRvIHJldXNlIHRoZSBcInJlcXVlc3QgdG8gam9pblwiIFVYIGluIFJvb21WaWV3LlxuICAgICAgICBidXR0b24gPSAoXG4gICAgICAgICAgICA8QWNjZXNzaWJsZUJ1dHRvblxuICAgICAgICAgICAgICAgIG9uQ2xpY2s9e29uUHJldmlld0NsaWNrfVxuICAgICAgICAgICAgICAgIGtpbmQ9XCJwcmltYXJ5X291dGxpbmVcIlxuICAgICAgICAgICAgICAgIG9uRm9jdXM9e29uRm9jdXN9XG4gICAgICAgICAgICAgICAgdGFiSW5kZXg9e2lzQWN0aXZlID8gMCA6IC0xfVxuICAgICAgICAgICAgPlxuICAgICAgICAgICAgICAgIHtfdChcImFjdGlvbnx2aWV3XCIpfVxuICAgICAgICAgICAgPC9BY2Nlc3NpYmxlQnV0dG9uPlxuICAgICAgICApO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIGJ1dHRvbiA9IChcbiAgICAgICAgICAgIDxBY2Nlc3NpYmxlQnV0dG9uIG9uQ2xpY2s9e29uSm9pbkNsaWNrfSBraW5kPVwicHJpbWFyeVwiIG9uRm9jdXM9e29uRm9jdXN9IHRhYkluZGV4PXtpc0FjdGl2ZSA/IDAgOiAtMX0+XG4gICAgICAgICAgICAgICAge190KFwiYWN0aW9ufGpvaW5cIil9XG4gICAgICAgICAgICA8L0FjY2Vzc2libGVCdXR0b24+XG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgbGV0IGNoZWNrYm94OiBSZWFjdEVsZW1lbnQgfCB1bmRlZmluZWQ7XG4gICAgaWYgKG9uVG9nZ2xlQ2xpY2spIHtcbiAgICAgICAgaWYgKGhhc1Blcm1pc3Npb25zKSB7XG4gICAgICAgICAgICBjaGVja2JveCA9IDxTdHlsZWRDaGVja2JveCBjaGVja2VkPXshIXNlbGVjdGVkfSBvbkNoYW5nZT17b25Ub2dnbGVDbGlja30gdGFiSW5kZXg9e2lzQWN0aXZlID8gMCA6IC0xfSAvPjtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGNoZWNrYm94ID0gKFxuICAgICAgICAgICAgICAgIDxUZXh0V2l0aFRvb2x0aXBcbiAgICAgICAgICAgICAgICAgICAgdG9vbHRpcD17X3QoXCJzcGFjZXx1c2VyX2xhY2tzX3Blcm1pc3Npb25cIil9XG4gICAgICAgICAgICAgICAgICAgIG9uQ2xpY2s9eyhldikgPT4ge1xuICAgICAgICAgICAgICAgICAgICAgICAgZXYuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgICAgICAgICAgICAgICAgIH19XG4gICAgICAgICAgICAgICAgPlxuICAgICAgICAgICAgICAgICAgICA8U3R5bGVkQ2hlY2tib3ggZGlzYWJsZWQ9e3RydWV9IHRhYkluZGV4PXtpc0FjdGl2ZSA/IDAgOiAtMX0gLz5cbiAgICAgICAgICAgICAgICA8L1RleHRXaXRoVG9vbHRpcD5cbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBsZXQgYXZhdGFyOiBSZWFjdEVsZW1lbnQ7XG4gICAgaWYgKGpvaW5lZFJvb20pIHtcbiAgICAgICAgYXZhdGFyID0gPFJvb21BdmF0YXIgcm9vbT17am9pbmVkUm9vbX0gc2l6ZT1cIjIwcHhcIiAvPjtcbiAgICB9IGVsc2Uge1xuICAgICAgICBhdmF0YXIgPSAoXG4gICAgICAgICAgICA8QmFzZUF2YXRhclxuICAgICAgICAgICAgICAgIG5hbWU9e25hbWV9XG4gICAgICAgICAgICAgICAgaWROYW1lPXtyb29tLnJvb21faWR9XG4gICAgICAgICAgICAgICAgdXJsPXtyb29tLmF2YXRhcl91cmwgPyBtZWRpYUZyb21NeGMocm9vbS5hdmF0YXJfdXJsKS5nZXRTcXVhcmVUaHVtYm5haWxIdHRwKDIwKSA6IG51bGx9XG4gICAgICAgICAgICAgICAgc2l6ZT1cIjIwcHhcIlxuICAgICAgICAgICAgLz5cbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBsZXQgZGVzY3JpcHRpb24gPSBfdChcImNvbW1vbnxuX21lbWJlcnNcIiwgeyBjb3VudDogcm9vbS5udW1fam9pbmVkX21lbWJlcnMgPz8gMCB9KTtcbiAgICBpZiAobnVtQ2hpbGRSb29tcyAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGRlc2NyaXB0aW9uICs9IFwiIMK3IFwiICsgX3QoXCJjb21tb258bl9yb29tc1wiLCB7IGNvdW50OiBudW1DaGlsZFJvb21zIH0pO1xuICAgIH1cblxuICAgIGxldCB0b3BpYzogUmVhY3ROb2RlIHwgc3RyaW5nIHwgbnVsbDtcbiAgICBpZiAoam9pbmVkUm9vbSkge1xuICAgICAgICBjb25zdCB0b3BpY09iaiA9IGdldFRvcGljKGpvaW5lZFJvb20pO1xuICAgICAgICB0b3BpYyA9IHRvcGljVG9IdG1sKHRvcGljT2JqPy50ZXh0LCB0b3BpY09iaj8uaHRtbCk7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgdG9waWMgPSByb29tLnRvcGljO1xuICAgIH1cblxuICAgIGxldCB0b3BpY1NlY3Rpb246IFJlYWN0Tm9kZSB8IHVuZGVmaW5lZDtcbiAgICBpZiAodG9waWMpIHtcbiAgICAgICAgdG9waWNTZWN0aW9uID0gKFxuICAgICAgICAgICAgPExpbmtpZnlcbiAgICAgICAgICAgICAgICBvcHRpb25zPXt7XG4gICAgICAgICAgICAgICAgICAgIGF0dHJpYnV0ZXM6IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIG9uQ2xpY2soZXY6IE1vdXNlRXZlbnQpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBwcmV2ZW50IGNsaWNrcyBvbiBsaW5rcyBmcm9tIGJ1YmJsaW5nIHVwIHRvIHRoZSByb29tIHRpbGVcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBldi5zdG9wUHJvcGFnYXRpb24oKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgfX1cbiAgICAgICAgICAgID5cbiAgICAgICAgICAgICAgICB7XCIgwrcgXCJ9XG4gICAgICAgICAgICAgICAge3RvcGljfVxuICAgICAgICAgICAgPC9MaW5raWZ5PlxuICAgICAgICApO1xuICAgIH1cblxuICAgIGxldCBqb2luZWRTZWN0aW9uOiBSZWFjdEVsZW1lbnQgfCB1bmRlZmluZWQ7XG4gICAgaWYgKGpvaW5lZFJvb20pIHtcbiAgICAgICAgam9pbmVkU2VjdGlvbiA9IDxkaXYgY2xhc3NOYW1lPVwibXhfU3BhY2VIaWVyYXJjaHlfcm9vbVRpbGVfam9pbmVkXCI+e190KFwiY29tbW9ufGpvaW5lZFwiKX08L2Rpdj47XG4gICAgfVxuXG4gICAgbGV0IHN1Z2dlc3RlZFNlY3Rpb246IFJlYWN0RWxlbWVudCB8IHVuZGVmaW5lZDtcbiAgICBpZiAoc3VnZ2VzdGVkICYmICgham9pbmVkUm9vbSB8fCBoYXNQZXJtaXNzaW9ucykpIHtcbiAgICAgICAgc3VnZ2VzdGVkU2VjdGlvbiA9IDxJbmZvVG9vbHRpcCB0b29sdGlwPXtfdChcInNwYWNlfHN1Z2dlc3RlZF90b29sdGlwXCIpfT57X3QoXCJzcGFjZXxzdWdnZXN0ZWRcIil9PC9JbmZvVG9vbHRpcD47XG4gICAgfVxuXG4gICAgY29uc3QgY29udGVudCA9IChcbiAgICAgICAgPFJlYWN0LkZyYWdtZW50PlxuICAgICAgICAgICAgPGRpdiBjbGFzc05hbWU9XCJteF9TcGFjZUhpZXJhcmNoeV9yb29tVGlsZV9pdGVtXCI+XG4gICAgICAgICAgICAgICAgPGRpdiBjbGFzc05hbWU9XCJteF9TcGFjZUhpZXJhcmNoeV9yb29tVGlsZV9hdmF0YXJcIj57YXZhdGFyfTwvZGl2PlxuICAgICAgICAgICAgICAgIDxkaXYgY2xhc3NOYW1lPVwibXhfU3BhY2VIaWVyYXJjaHlfcm9vbVRpbGVfbmFtZVwiPlxuICAgICAgICAgICAgICAgICAgICB7bmFtZX1cbiAgICAgICAgICAgICAgICAgICAge2pvaW5lZFNlY3Rpb259XG4gICAgICAgICAgICAgICAgICAgIHtzdWdnZXN0ZWRTZWN0aW9ufVxuICAgICAgICAgICAgICAgIDwvZGl2PlxuICAgICAgICAgICAgICAgIDxkaXYgY2xhc3NOYW1lPVwibXhfU3BhY2VIaWVyYXJjaHlfcm9vbVRpbGVfaW5mb1wiPlxuICAgICAgICAgICAgICAgICAgICB7ZGVzY3JpcHRpb259XG4gICAgICAgICAgICAgICAgICAgIHt0b3BpY1NlY3Rpb259XG4gICAgICAgICAgICAgICAgPC9kaXY+XG4gICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICAgIDxkaXYgY2xhc3NOYW1lPVwibXhfU3BhY2VIaWVyYXJjaHlfYWN0aW9uc1wiPlxuICAgICAgICAgICAgICAgIHtidXR0b259XG4gICAgICAgICAgICAgICAge2NoZWNrYm94fVxuICAgICAgICAgICAgPC9kaXY+XG4gICAgICAgIDwvUmVhY3QuRnJhZ21lbnQ+XG4gICAgKTtcblxuICA