mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-16 21:24:59 +08:00
Merge remote-tracking branch 'origin/develop' into jryans/defer-cross-signing-setup
This commit is contained in:
commit
0219824d6f
@ -94,6 +94,7 @@
|
||||
"react-focus-lock": "^2.4.1",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"rfc4648": "^1.4.0",
|
||||
"sanitize-html": "^1.27.1",
|
||||
"tar-js": "^0.3.0",
|
||||
"text-encoding-utf-8": "^1.0.2",
|
||||
|
@ -15,18 +15,39 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
the tile title bar is 5 (top border) + 12 (title, buttons) + 5 (bottom padding) px = 22px
|
||||
the body is assumed to be 300px (assumed by at least the sticker pickerm, perhaps elsewhere),
|
||||
so the body height would be 300px - 22px (room for title bar) = 278px
|
||||
BUT! the sticker picker also assumes it's a little less high than that because the iframe
|
||||
for the sticker picker doesn't have any padding or margin on it's bottom.
|
||||
so subtracking another 5px, which brings us at 273px.
|
||||
*/
|
||||
$AppsDrawerBodyHeight: 273px;
|
||||
$MiniAppTileHeight: 114px;
|
||||
|
||||
.mx_AppsDrawer {
|
||||
margin: 5px;
|
||||
margin: 5px 5px 5px 18px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.mx_AppsContainer_resizerHandle {
|
||||
cursor: ns-resize;
|
||||
border-radius: 3px;
|
||||
|
||||
// Override styles from library
|
||||
width: unset !important;
|
||||
height: 4px !important;
|
||||
|
||||
// This is positioned directly below frame
|
||||
position: absolute;
|
||||
bottom: -8px !important; // override from library
|
||||
|
||||
// Together, these make the bar 64px wide
|
||||
// These are also overridden from the library
|
||||
left: calc(50% - 32px) !important;
|
||||
right: calc(50% - 32px) !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.mx_AppsContainer_resizerHandle {
|
||||
opacity: 0.8;
|
||||
background: $primary-fg-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AppsDrawer_hidden {
|
||||
@ -36,15 +57,23 @@ $AppsDrawerBodyHeight: 273px;
|
||||
.mx_AppsContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mx_AppsDrawer_minimised .mx_AppsContainer {
|
||||
// override the re-resizable inline styles
|
||||
height: inherit !important;
|
||||
min-height: inherit !important;
|
||||
}
|
||||
|
||||
.mx_AddWidget_button {
|
||||
order: 2;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
margin: 5px auto 5px auto;
|
||||
margin: -3px auto 5px 0;
|
||||
color: $accent-color;
|
||||
font-size: $font-12px;
|
||||
}
|
||||
@ -65,40 +94,52 @@ $AppsDrawerBodyHeight: 273px;
|
||||
.mx_AppTile {
|
||||
max-width: 960px;
|
||||
width: 50%;
|
||||
margin-right: 5px;
|
||||
border: 5px solid $widget-menu-bar-bg-color;
|
||||
border-radius: 4px;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.mx_AppTile:last-child {
|
||||
margin-right: 1px;
|
||||
& + .mx_AppTile {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AppTileFullWidth {
|
||||
max-width: 960px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 5px solid $widget-menu-bar-bg-color;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mx_AppTile_mini {
|
||||
max-width: 960px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: $MiniAppTileHeight;
|
||||
}
|
||||
|
||||
.mx_AppTile_persistedWrapper {
|
||||
height: $AppsDrawerBodyHeight;
|
||||
.mx_AppTile.mx_AppTile_minimised,
|
||||
.mx_AppTileFullWidth.mx_AppTile_minimised,
|
||||
.mx_AppTile_mini.mx_AppTile_minimised {
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.mx_AppTile .mx_AppTile_persistedWrapper,
|
||||
.mx_AppTileFullWidth .mx_AppTile_persistedWrapper,
|
||||
.mx_AppTile_mini .mx_AppTile_persistedWrapper {
|
||||
height: 114px;
|
||||
min-width: 300px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mx_AppTile_persistedWrapper div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mx_AppTileMenuBar {
|
||||
@ -110,6 +151,7 @@ $AppsDrawerBodyHeight: 273px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mx_AppTileMenuBar_expanded {
|
||||
@ -172,7 +214,7 @@ $AppsDrawerBodyHeight: 273px;
|
||||
}
|
||||
|
||||
.mx_AppTileBody {
|
||||
height: $AppsDrawerBodyHeight;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -183,6 +225,13 @@ $AppsDrawerBodyHeight: 273px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mx_AppTile .mx_AppTileBody,
|
||||
.mx_AppTileFullWidth .mx_AppTileBody,
|
||||
.mx_AppTile_mini .mx_AppTileBody_mini {
|
||||
height: inherit;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mx_AppTileBody_mini iframe {
|
||||
border: none;
|
||||
width: 100%;
|
||||
@ -191,7 +240,7 @@ $AppsDrawerBodyHeight: 273px;
|
||||
|
||||
.mx_AppTileBody iframe {
|
||||
width: 100%;
|
||||
height: $AppsDrawerBodyHeight;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
padding: 0;
|
||||
@ -331,7 +380,7 @@ form.mx_Custom_Widget_Form div {
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
height: $AppsDrawerBodyHeight;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mx_AppLoading .mx_Spinner {
|
||||
@ -358,3 +407,16 @@ form.mx_Custom_Widget_Form div {
|
||||
.mx_AppLoading iframe {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_AppsDrawer_minimised .mx_AppsContainer_resizerHandle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Avoid apptile iframes capturing mouse event focus when resizing */
|
||||
.mx_AppsDrawer_resizing iframe {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mx_AppsDrawer_resizing .mx_AppTile_persistedWrapper {
|
||||
z-index: 1;
|
||||
}
|
||||
|
@ -36,6 +36,10 @@ limitations under the License.
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AppTile_persistedWrapper div {
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.mx_IncomingCallBox {
|
||||
min-width: 250px;
|
||||
background-color: $primary-bg-color;
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017, 2018 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -67,6 +67,7 @@ import {generateHumanReadableId} from "./utils/NamingUtils";
|
||||
import {Jitsi} from "./widgets/Jitsi";
|
||||
import {WidgetType} from "./widgets/WidgetType";
|
||||
import {SettingLevel} from "./settings/SettingLevel";
|
||||
import {base32} from "rfc4648";
|
||||
|
||||
global.mxCalls = {
|
||||
//room_id: MatrixCall
|
||||
@ -388,10 +389,21 @@ async function _startCallApp(roomId, type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confId = `JitsiConference${generateHumanReadableId()}`;
|
||||
const jitsiDomain = Jitsi.getInstance().preferredDomain;
|
||||
const jitsiAuth = await Jitsi.getInstance().getJitsiAuth();
|
||||
let confId;
|
||||
if (jitsiAuth === 'openidtoken-jwt') {
|
||||
// Create conference ID from room ID
|
||||
// For compatibility with Jitsi, use base32 without padding.
|
||||
// More details here:
|
||||
// https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
|
||||
confId = base32.stringify(Buffer.from(roomId), { pad: false });
|
||||
} else {
|
||||
// Create a random human readable conference ID
|
||||
confId = `JitsiConference${generateHumanReadableId()}`;
|
||||
}
|
||||
|
||||
let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl();
|
||||
let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl({auth: jitsiAuth});
|
||||
|
||||
// TODO: Remove URL hacks when the mobile clients eventually support v2 widgets
|
||||
const parsedUrl = new URL(widgetUrl);
|
||||
@ -403,6 +415,7 @@ async function _startCallApp(roomId, type) {
|
||||
conferenceId: confId,
|
||||
isAudioOnly: type === 'voice',
|
||||
domain: jitsiDomain,
|
||||
auth: jitsiAuth,
|
||||
};
|
||||
|
||||
const widgetId = (
|
||||
|
@ -154,6 +154,19 @@ export const Commands = [
|
||||
},
|
||||
category: CommandCategories.messages,
|
||||
}),
|
||||
new Command({
|
||||
command: 'lenny',
|
||||
args: '<message>',
|
||||
description: _td('Prepends ( ͡° ͜ʖ ͡°) to a plain-text message'),
|
||||
runFn: function(roomId, args) {
|
||||
let message = '( ͡° ͜ʖ ͡°)';
|
||||
if (args) {
|
||||
message = message + ' ' + args;
|
||||
}
|
||||
return success(MatrixClientPeg.get().sendTextMessage(roomId, message));
|
||||
},
|
||||
category: CommandCategories.messages,
|
||||
}),
|
||||
new Command({
|
||||
command: 'plain',
|
||||
args: '<message>',
|
||||
|
@ -186,7 +186,14 @@ export default class WidgetMessaging {
|
||||
isUserWidget: this.isUserWidget,
|
||||
|
||||
onFinished: async (confirm) => {
|
||||
const responseBody = {success: confirm};
|
||||
const responseBody = {
|
||||
// Legacy (early draft) fields
|
||||
success: confirm,
|
||||
|
||||
// New style MSC1960 fields
|
||||
state: confirm ? "allowed" : "blocked",
|
||||
original_request_id: ev.requestId, // eslint-disable-line camelcase
|
||||
};
|
||||
if (confirm) {
|
||||
const credentials = await MatrixClientPeg.get().getOpenIdToken();
|
||||
Object.assign(responseBody, credentials);
|
||||
|
@ -1322,7 +1322,7 @@ export default class GroupView extends React.Component {
|
||||
</div>
|
||||
<GroupHeaderButtons />
|
||||
</div>
|
||||
<MainSplit panel={rightPanel}>
|
||||
<MainSplit panel={rightPanel} resizeNotifier={this.props.resizeNotifier}>
|
||||
<AutoHideScrollbar className="mx_GroupView_body">
|
||||
{ this._getMembershipSection() }
|
||||
{ this._getGroupSection() }
|
||||
|
@ -257,6 +257,12 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
window.localStorage.setItem("mx_lhs_size", '' + size);
|
||||
this.props.resizeNotifier.notifyLeftHandleResized();
|
||||
},
|
||||
onResizeStart: () => {
|
||||
this.props.resizeNotifier.startResizing();
|
||||
},
|
||||
onResizeStop: () => {
|
||||
this.props.resizeNotifier.stopResizing();
|
||||
},
|
||||
};
|
||||
const resizer = new Resizer(
|
||||
this._resizeContainer.current,
|
||||
@ -650,12 +656,13 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
break;
|
||||
|
||||
case PageTypes.UserView:
|
||||
pageElement = <UserView userId={this.props.currentUserId} />;
|
||||
pageElement = <UserView userId={this.props.currentUserId} resizeNotifier={this.props.resizeNotifier} />;
|
||||
break;
|
||||
case PageTypes.GroupView:
|
||||
pageElement = <GroupView
|
||||
groupId={this.props.currentGroupId}
|
||||
isNew={this.props.currentGroupIsNew}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
/>;
|
||||
break;
|
||||
}
|
||||
|
@ -19,9 +19,18 @@ import React from 'react';
|
||||
import { Resizable } from 're-resizable';
|
||||
|
||||
export default class MainSplit extends React.Component {
|
||||
_onResized = (event, direction, refToElement, delta) => {
|
||||
_onResizeStart = () => {
|
||||
this.props.resizeNotifier.startResizing();
|
||||
};
|
||||
|
||||
_onResize = () => {
|
||||
this.props.resizeNotifier.notifyRightHandleResized();
|
||||
};
|
||||
|
||||
_onResizeStop = (event, direction, refToElement, delta) => {
|
||||
this.props.resizeNotifier.stopResizing();
|
||||
window.localStorage.setItem("mx_rhs_size", this._loadSidePanelSize().width + delta.width);
|
||||
}
|
||||
};
|
||||
|
||||
_loadSidePanelSize() {
|
||||
let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size"), 10);
|
||||
@ -58,7 +67,9 @@ export default class MainSplit extends React.Component {
|
||||
bottomLeft: false,
|
||||
topLeft: false,
|
||||
}}
|
||||
onResizeStop={this._onResized}
|
||||
onResizeStart={this._onResizeStart}
|
||||
onResize={this._onResize}
|
||||
onResizeStop={this._onResizeStop}
|
||||
className="mx_RightPanel_ResizeWrapper"
|
||||
handleClasses={{left: "mx_RightPanel_ResizeHandle"}}
|
||||
>
|
||||
|
@ -225,11 +225,12 @@ export default class RightPanel extends React.Component {
|
||||
const GroupRoomInfo = sdk.getComponent('groups.GroupRoomInfo');
|
||||
|
||||
let panel = <div />;
|
||||
const roomId = this.props.room ? this.props.room.roomId : undefined;
|
||||
|
||||
switch (this.state.phase) {
|
||||
case RightPanelPhases.RoomMemberList:
|
||||
if (this.props.room.roomId) {
|
||||
panel = <MemberList roomId={this.props.room.roomId} key={this.props.room.roomId} />;
|
||||
if (roomId) {
|
||||
panel = <MemberList roomId={roomId} key={roomId} />;
|
||||
}
|
||||
break;
|
||||
case RightPanelPhases.GroupMemberList:
|
||||
@ -244,8 +245,8 @@ export default class RightPanel extends React.Component {
|
||||
case RightPanelPhases.EncryptionPanel:
|
||||
panel = <UserInfo
|
||||
user={this.state.member}
|
||||
roomId={this.props.room.roomId}
|
||||
key={this.props.room.roomId || this.state.member.userId}
|
||||
room={this.props.room}
|
||||
key={roomId || this.state.member.userId}
|
||||
onClose={this.onCloseUserInfo}
|
||||
phase={this.state.phase}
|
||||
verificationRequest={this.state.verificationRequest}
|
||||
@ -253,7 +254,7 @@ export default class RightPanel extends React.Component {
|
||||
/>;
|
||||
break;
|
||||
case RightPanelPhases.Room3pidMemberInfo:
|
||||
panel = <ThirdPartyMemberInfo event={this.state.event} key={this.props.room.roomId} />;
|
||||
panel = <ThirdPartyMemberInfo event={this.state.event} key={roomId} />;
|
||||
break;
|
||||
case RightPanelPhases.GroupMemberInfo:
|
||||
panel = <UserInfo
|
||||
@ -272,7 +273,7 @@ export default class RightPanel extends React.Component {
|
||||
panel = <NotificationPanel />;
|
||||
break;
|
||||
case RightPanelPhases.FilePanel:
|
||||
panel = <FilePanel roomId={this.props.room.roomId} resizeNotifier={this.props.resizeNotifier} />;
|
||||
panel = <FilePanel roomId={roomId} resizeNotifier={this.props.resizeNotifier} />;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1545,9 +1545,9 @@ export default class RoomView extends React.Component {
|
||||
|
||||
// header + footer + status + give us at least 120px of scrollback at all times.
|
||||
let auxPanelMaxHeight = window.innerHeight -
|
||||
(83 + // height of RoomHeader
|
||||
(54 + // height of RoomHeader
|
||||
36 + // height of the status area
|
||||
72 + // minimum height of the message compmoser
|
||||
51 + // minimum height of the message compmoser
|
||||
120); // amount of desired scrollback
|
||||
|
||||
// XXX: this is a bit of a hack and might possibly cause the video to push out the page anyway
|
||||
@ -1884,15 +1884,19 @@ export default class RoomView extends React.Component {
|
||||
}
|
||||
|
||||
const auxPanel = (
|
||||
<AuxPanel room={this.state.room}
|
||||
fullHeight={false}
|
||||
userId={this.context.credentials.userId}
|
||||
conferenceHandler={this.props.ConferenceHandler}
|
||||
draggingFile={this.state.draggingFile}
|
||||
displayConfCallNotification={this.state.displayConfCallNotification}
|
||||
maxHeight={this.state.auxPanelMaxHeight}
|
||||
showApps={this.state.showApps}
|
||||
hideAppsDrawer={false} >
|
||||
<AuxPanel
|
||||
room={this.state.room}
|
||||
fullHeight={false}
|
||||
userId={this.context.credentials.userId}
|
||||
conferenceHandler={this.props.ConferenceHandler}
|
||||
draggingFile={this.state.draggingFile}
|
||||
displayConfCallNotification={this.state.displayConfCallNotification}
|
||||
maxHeight={this.state.auxPanelMaxHeight}
|
||||
showApps={this.state.showApps}
|
||||
hideAppsDrawer={false}
|
||||
onResize={this.onResize}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
>
|
||||
{ aux }
|
||||
</AuxPanel>
|
||||
);
|
||||
@ -2090,10 +2094,7 @@ export default class RoomView extends React.Component {
|
||||
onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
|
||||
e2eStatus={this.state.e2eStatus}
|
||||
/>
|
||||
<MainSplit
|
||||
panel={rightPanel}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
>
|
||||
<MainSplit panel={rightPanel} resizeNotifier={this.props.resizeNotifier}>
|
||||
<div className={fadableSectionClasses}>
|
||||
{auxPanel}
|
||||
<div className={timelineClasses}>
|
||||
|
@ -163,7 +163,7 @@ export default class ScrollPanel extends React.Component {
|
||||
this._pendingFillRequests = {b: null, f: null};
|
||||
|
||||
if (this.props.resizeNotifier) {
|
||||
this.props.resizeNotifier.on("middlePanelResized", this.onResize);
|
||||
this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize);
|
||||
}
|
||||
|
||||
this.resetScrollState();
|
||||
@ -193,11 +193,12 @@ export default class ScrollPanel extends React.Component {
|
||||
this.unmounted = true;
|
||||
|
||||
if (this.props.resizeNotifier) {
|
||||
this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize);
|
||||
this.props.resizeNotifier.removeListener("middlePanelResizedNoisy", this.onResize);
|
||||
}
|
||||
}
|
||||
|
||||
onScroll = ev => {
|
||||
if (this.props.resizeNotifier.isResizing) return; // skip scroll events caused by resizing
|
||||
debuglog("onScroll", this._getScrollNode().scrollTop);
|
||||
this._scrollTimeout.restart();
|
||||
this._saveScrollState();
|
||||
@ -207,6 +208,7 @@ export default class ScrollPanel extends React.Component {
|
||||
};
|
||||
|
||||
onResize = () => {
|
||||
debuglog("onResize");
|
||||
this.checkScroll();
|
||||
// update preventShrinkingState if present
|
||||
if (this.preventShrinkingState) {
|
||||
@ -236,7 +238,6 @@ export default class ScrollPanel extends React.Component {
|
||||
// when scrolled all the way down. E.g. Chrome 72 on debian.
|
||||
// so check difference <= 1;
|
||||
return Math.abs(sn.scrollHeight - (sn.scrollTop + sn.clientHeight)) <= 1;
|
||||
|
||||
};
|
||||
|
||||
// returns the vertical height in the given direction that can be removed from
|
||||
|
@ -80,7 +80,9 @@ export default class UserView extends React.Component {
|
||||
const RightPanel = sdk.getComponent('structures.RightPanel');
|
||||
const MainSplit = sdk.getComponent('structures.MainSplit');
|
||||
const panel = <RightPanel user={this.state.member} />;
|
||||
return (<MainSplit panel={panel}><HomePage /></MainSplit>);
|
||||
return (<MainSplit panel={panel} resizeNotifier={this.props.resizeNotifier}>
|
||||
<HomePage />
|
||||
</MainSplit>);
|
||||
} else {
|
||||
return (<div />);
|
||||
}
|
||||
|
@ -124,7 +124,11 @@ export default class LoginComponent extends React.Component {
|
||||
'm.login.cas': () => this._renderSsoStep("cas"),
|
||||
'm.login.sso': () => this._renderSsoStep("sso"),
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
// eslint-disable-next-line camelcase
|
||||
UNSAFE_componentWillMount() {
|
||||
this._initLoginLogic();
|
||||
}
|
||||
|
||||
|
@ -310,20 +310,7 @@ export default class AppTile extends React.Component {
|
||||
if (this.props.onEditClick) {
|
||||
this.props.onEditClick();
|
||||
} else {
|
||||
// TODO: Open the right manager for the widget
|
||||
if (SettingsStore.getValue("feature_many_integration_managers")) {
|
||||
IntegrationManagers.sharedInstance().openAll(
|
||||
this.props.room,
|
||||
'type_' + this.props.app.type,
|
||||
this.props.app.id,
|
||||
);
|
||||
} else {
|
||||
IntegrationManagers.sharedInstance().getPrimaryManager().open(
|
||||
this.props.room,
|
||||
'type_' + this.props.app.type,
|
||||
this.props.app.id,
|
||||
);
|
||||
}
|
||||
WidgetUtils.editWidget(this.props.room, this.props.app);
|
||||
}
|
||||
}
|
||||
|
||||
@ -626,7 +613,10 @@ export default class AppTile extends React.Component {
|
||||
|
||||
if (WidgetType.JITSI.matches(this.props.app.type)) {
|
||||
console.log("Replacing Jitsi widget URL with local wrapper");
|
||||
url = WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: true});
|
||||
url = WidgetUtils.getLocalJitsiWrapperUrl({
|
||||
forLocalRender: true,
|
||||
auth: this.props.app.data ? this.props.app.data.auth : null,
|
||||
});
|
||||
url = this._addWurlParams(url);
|
||||
} else {
|
||||
url = this._getSafeUrl(this.state.widgetUrl);
|
||||
@ -637,7 +627,10 @@ export default class AppTile extends React.Component {
|
||||
_getPopoutUrl() {
|
||||
if (WidgetType.JITSI.matches(this.props.app.type)) {
|
||||
return this._templatedUrl(
|
||||
WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: false}),
|
||||
WidgetUtils.getLocalJitsiWrapperUrl({
|
||||
forLocalRender: false,
|
||||
auth: this.props.app.data ? this.props.app.data.auth : null,
|
||||
}),
|
||||
this.props.app.type,
|
||||
);
|
||||
} else {
|
||||
@ -804,14 +797,16 @@ export default class AppTile extends React.Component {
|
||||
const showMinimiseButton = this.props.showMinimise && this.props.show;
|
||||
const showMaximiseButton = this.props.showMinimise && !this.props.show;
|
||||
|
||||
let appTileClass;
|
||||
let appTileClasses;
|
||||
if (this.props.miniMode) {
|
||||
appTileClass = 'mx_AppTile_mini';
|
||||
appTileClasses = {mx_AppTile_mini: true};
|
||||
} else if (this.props.fullWidth) {
|
||||
appTileClass = 'mx_AppTileFullWidth';
|
||||
appTileClasses = {mx_AppTileFullWidth: true};
|
||||
} else {
|
||||
appTileClass = 'mx_AppTile';
|
||||
appTileClasses = {mx_AppTile: true};
|
||||
}
|
||||
appTileClasses.mx_AppTile_minimised = !this.props.show;
|
||||
appTileClasses = classNames(appTileClasses);
|
||||
|
||||
const menuBarClasses = classNames({
|
||||
mx_AppTileMenuBar: true,
|
||||
@ -843,7 +838,7 @@ export default class AppTile extends React.Component {
|
||||
}
|
||||
|
||||
return <React.Fragment>
|
||||
<div className={appTileClass} id={this.props.app.id}>
|
||||
<div className={appTileClasses} id={this.props.app.id}>
|
||||
{ this.props.showMenubar &&
|
||||
<div ref={this._menu_bar} className={menuBarClasses} onClick={this.onClickMenuBar}>
|
||||
<span className="mx_AppTileMenuBarTitle" style={{pointerEvents: (this.props.handleMinimisePointerEvents ? 'all' : false)}}>
|
||||
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {throttle} from "lodash";
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
@ -156,7 +156,7 @@ export default class PersistedElement extends React.Component {
|
||||
child.style.display = visible ? 'block' : 'none';
|
||||
}
|
||||
|
||||
updateChildPosition(child, parent) {
|
||||
updateChildPosition = throttle((child, parent) => {
|
||||
if (!child || !parent) return;
|
||||
|
||||
const parentRect = parent.getBoundingClientRect();
|
||||
@ -167,9 +167,9 @@ export default class PersistedElement extends React.Component {
|
||||
width: parentRect.width + 'px',
|
||||
height: parentRect.height + 'px',
|
||||
});
|
||||
}
|
||||
}, 100, {trailing: true, leading: true});
|
||||
|
||||
render() {
|
||||
return <div ref={this.collectChildContainer}></div>;
|
||||
return <div ref={this.collectChildContainer} />;
|
||||
}
|
||||
}
|
||||
|
@ -57,11 +57,14 @@ export default class PowerSelector extends React.Component {
|
||||
customValue: this.props.value,
|
||||
selectValue: 0,
|
||||
};
|
||||
|
||||
this._initStateFromProps(this.props);
|
||||
}
|
||||
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
// eslint-disable-next-line camelcase
|
||||
UNSAFE_componentWillMount() {
|
||||
this._initStateFromProps(this.props);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
this._initStateFromProps(newProps);
|
||||
|
@ -20,7 +20,7 @@ limitations under the License.
|
||||
import React, {useCallback, useMemo, useState, useEffect, useContext} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import {Group, RoomMember, User} from 'matrix-js-sdk';
|
||||
import {Group, RoomMember, User, Room} from 'matrix-js-sdk';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import Modal from '../../../Modal';
|
||||
import * as sdk from '../../../index';
|
||||
@ -1471,11 +1471,9 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => {
|
||||
</React.Fragment>;
|
||||
};
|
||||
|
||||
const UserInfo = ({user, groupId, roomId, onClose, phase=RightPanelPhases.RoomMemberInfo, ...props}) => {
|
||||
const UserInfo = ({user, groupId, room, onClose, phase=RightPanelPhases.RoomMemberInfo, ...props}) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
// Load room if we are given a room id and memoize it - this can be undefined for User Info/Group Member Info
|
||||
const room = useMemo(() => roomId ? cli.getRoom(roomId) : null, [cli, roomId]);
|
||||
// fetch latest room member if we have a room, so we don't show historical information, falling back to user
|
||||
const member = useMemo(() => room ? (room.getMember(user.userId) || user) : user, [room, user]);
|
||||
|
||||
@ -1529,7 +1527,7 @@ UserInfo.propTypes = {
|
||||
]).isRequired,
|
||||
group: PropTypes.instanceOf(Group),
|
||||
groupId: PropTypes.string,
|
||||
roomId: PropTypes.string,
|
||||
room: PropTypes.instanceOf(Room),
|
||||
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {useState} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import AppTile from '../elements/AppTile';
|
||||
@ -29,6 +29,10 @@ import WidgetEchoStore from "../../../stores/WidgetEchoStore";
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import classNames from 'classnames';
|
||||
import {Resizable} from "re-resizable";
|
||||
import {useLocalStorageState} from "../../../hooks/useLocalStorageState";
|
||||
import ResizeNotifier from "../../../utils/ResizeNotifier";
|
||||
|
||||
// The maximum number of widgets that can be added in a room
|
||||
const MAX_WIDGETS = 2;
|
||||
@ -37,6 +41,7 @@ export default class AppsDrawer extends React.Component {
|
||||
static propTypes = {
|
||||
userId: PropTypes.string.isRequired,
|
||||
room: PropTypes.object.isRequired,
|
||||
resizeNotifier: PropTypes.instanceOf(ResizeNotifier).isRequired,
|
||||
showApps: PropTypes.bool, // Should apps be rendered
|
||||
hide: PropTypes.bool, // If rendered, should apps drawer be visible
|
||||
};
|
||||
@ -161,7 +166,7 @@ export default class AppsDrawer extends React.Component {
|
||||
return (<AppTile
|
||||
key={app.id}
|
||||
app={app}
|
||||
fullWidth={arr.length<2 ? true : false}
|
||||
fullWidth={arr.length < 2}
|
||||
room={this.props.room}
|
||||
userId={this.props.userId}
|
||||
show={this.props.showApps}
|
||||
@ -172,8 +177,8 @@ export default class AppsDrawer extends React.Component {
|
||||
/>);
|
||||
});
|
||||
|
||||
if (apps.length == 0) {
|
||||
return <div></div>;
|
||||
if (apps.length === 0) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
let addWidget;
|
||||
@ -202,14 +207,68 @@ export default class AppsDrawer extends React.Component {
|
||||
spinner = <Loader />;
|
||||
}
|
||||
|
||||
const classes = classNames({
|
||||
"mx_AppsDrawer": true,
|
||||
"mx_AppsDrawer_hidden": this.props.hide,
|
||||
"mx_AppsDrawer_fullWidth": apps.length < 2,
|
||||
"mx_AppsDrawer_minimised": !this.props.showApps,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={'mx_AppsDrawer' + (this.props.hide ? ' mx_AppsDrawer_hidden' : '')}>
|
||||
<div id='apps' className='mx_AppsContainer'>
|
||||
<div className={classes}>
|
||||
<PersistentVResizer
|
||||
id={"apps-drawer_" + this.props.room.roomId}
|
||||
minHeight={100}
|
||||
maxHeight={this.props.maxHeight ? this.props.maxHeight - 50 : undefined}
|
||||
handleClass="mx_AppsContainer_resizerHandle"
|
||||
className="mx_AppsContainer"
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
>
|
||||
{ apps }
|
||||
{ spinner }
|
||||
</div>
|
||||
</PersistentVResizer>
|
||||
{ this._canUserModify() && addWidget }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const PersistentVResizer = ({
|
||||
id,
|
||||
minHeight,
|
||||
maxHeight,
|
||||
className,
|
||||
handleWrapperClass,
|
||||
handleClass,
|
||||
resizeNotifier,
|
||||
children,
|
||||
}) => {
|
||||
const [height, setHeight] = useLocalStorageState("pvr_" + id, 100);
|
||||
const [resizing, setResizing] = useState(false);
|
||||
|
||||
return <Resizable
|
||||
size={{height: Math.min(height, maxHeight)}}
|
||||
minHeight={minHeight}
|
||||
maxHeight={maxHeight}
|
||||
onResizeStart={() => {
|
||||
if (!resizing) setResizing(true);
|
||||
resizeNotifier.startResizing();
|
||||
}}
|
||||
onResize={() => {
|
||||
resizeNotifier.notifyTimelineHeightChanged();
|
||||
}}
|
||||
onResizeStop={(e, dir, ref, d) => {
|
||||
setHeight(height + d.height);
|
||||
if (resizing) setResizing(false);
|
||||
resizeNotifier.stopResizing();
|
||||
}}
|
||||
handleWrapperClass={handleWrapperClass}
|
||||
handleClasses={{bottom: handleClass}}
|
||||
className={classNames(className, {
|
||||
mx_AppsDrawer_resizing: resizing,
|
||||
})}
|
||||
enable={{bottom: true}}
|
||||
>
|
||||
{ children }
|
||||
</Resizable>;
|
||||
};
|
||||
|
@ -204,6 +204,7 @@ export default class AuxPanel extends React.Component {
|
||||
maxHeight={this.props.maxHeight}
|
||||
showApps={this.props.showApps}
|
||||
hide={this.props.hideAppsDrawer}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
/>;
|
||||
|
||||
let stateViews = null;
|
||||
|
44
src/hooks/useLocalStorageState.ts
Normal file
44
src/hooks/useLocalStorageState.ts
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {Dispatch, SetStateAction, useCallback, useEffect, useState} from "react";
|
||||
|
||||
const getValue = <T>(key: string, initialValue: T): T => {
|
||||
try {
|
||||
const item = window.localStorage.getItem(key);
|
||||
return item ? JSON.parse(item) : initialValue;
|
||||
} catch (error) {
|
||||
return initialValue;
|
||||
}
|
||||
};
|
||||
|
||||
// Hook behaving like useState but persisting the value to localStorage. Returns same as useState
|
||||
export const useLocalStorageState = <T>(key: string, initialValue: T) => {
|
||||
const lsKey = "mx_" + key;
|
||||
|
||||
const [value, setValue] = useState<T>(getValue(lsKey, initialValue));
|
||||
|
||||
useEffect(() => {
|
||||
setValue(getValue(lsKey, initialValue));
|
||||
}, [lsKey, initialValue]);
|
||||
|
||||
const _setValue: Dispatch<SetStateAction<T>> = useCallback((v: T) => {
|
||||
window.localStorage.setItem(lsKey, JSON.stringify(v));
|
||||
setValue(v);
|
||||
}, [lsKey]);
|
||||
|
||||
return [value, _setValue];
|
||||
};
|
@ -149,6 +149,7 @@
|
||||
"Command error": "Command error",
|
||||
"Usage": "Usage",
|
||||
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Prepends ¯\\_(ツ)_/¯ to a plain-text message",
|
||||
"Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message",
|
||||
"Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown",
|
||||
"Sends a message as html, without interpreting it as markdown": "Sends a message as html, without interpreting it as markdown",
|
||||
"Searches DuckDuckGo for results": "Searches DuckDuckGo for results",
|
||||
|
@ -105,6 +105,9 @@ export default class Resizer {
|
||||
if (this.classNames.resizing) {
|
||||
this.container.classList.add(this.classNames.resizing);
|
||||
}
|
||||
if (this.config.onResizeStart) {
|
||||
this.config.onResizeStart();
|
||||
}
|
||||
|
||||
const {sizer, distributor} = this._createSizerAndDistributor(resizeHandle);
|
||||
distributor.start();
|
||||
@ -119,6 +122,9 @@ export default class Resizer {
|
||||
if (this.classNames.resizing) {
|
||||
this.container.classList.remove(this.classNames.resizing);
|
||||
}
|
||||
if (this.config.onResizeStop) {
|
||||
this.config.onResizeStop();
|
||||
}
|
||||
distributor.finish();
|
||||
body.removeEventListener("mouseup", finishResize, false);
|
||||
document.removeEventListener("mouseleave", finishResize, false);
|
||||
|
@ -56,6 +56,18 @@ export default class Sizer {
|
||||
return this.vertical ? this.container.offsetTop : this.container.offsetLeft;
|
||||
}
|
||||
|
||||
/** @return {number} container offset to document */
|
||||
_getPageOffset() {
|
||||
let element = this.container;
|
||||
let offset = 0;
|
||||
while (element) {
|
||||
const pos = this.vertical ? element.offsetTop : element.offsetLeft;
|
||||
offset = offset + pos;
|
||||
element = element.offsetParent;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
setItemSize(item, size) {
|
||||
if (this.vertical) {
|
||||
item.style.height = `${Math.round(size)}px`;
|
||||
@ -80,9 +92,9 @@ export default class Sizer {
|
||||
offsetFromEvent(event) {
|
||||
const pos = this.vertical ? event.pageY : event.pageX;
|
||||
if (this.reverse) {
|
||||
return (this._getOffset() + this.getTotalSize()) - pos;
|
||||
return (this._getPageOffset() + this.getTotalSize()) - pos;
|
||||
} else {
|
||||
return pos - this._getOffset();
|
||||
return pos - this._getPageOffset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +18,10 @@ import { SettingLevel } from "./SettingLevel";
|
||||
|
||||
export type CallbackFn = (changedInRoomId: string, atLevel: SettingLevel, newValAtLevel: any) => void;
|
||||
|
||||
const IRRELEVANT_ROOM = Symbol("any room");
|
||||
const IRRELEVANT_ROOM: string = null;
|
||||
|
||||
interface RoomWatcherMap {
|
||||
// @ts-ignore - TS wants string-only keys but we know better - https://github.com/Microsoft/TypeScript/issues/1863
|
||||
[roomId: string | symbol]: CallbackFn[];
|
||||
[roomId: string]: CallbackFn[];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,7 +68,7 @@ export class WatchManager {
|
||||
if (!inRoomId) {
|
||||
// Fire updates to all the individual room watchers too, as they probably
|
||||
// care about the change higher up.
|
||||
callbacks.push(...Object.values(roomWatchers).reduce((r, a) => [...r, ...a], []));
|
||||
callbacks.push(...Object.values(roomWatchers).flat(1));
|
||||
} else if (roomWatchers[IRRELEVANT_ROOM]) {
|
||||
callbacks.push(...roomWatchers[IRRELEVANT_ROOM]);
|
||||
}
|
||||
|
@ -93,13 +93,13 @@ class WidgetEchoStore extends EventEmitter {
|
||||
if (this._roomWidgetEcho[roomId] === undefined) this._roomWidgetEcho[roomId] = {};
|
||||
|
||||
this._roomWidgetEcho[roomId][widgetId] = state;
|
||||
this.emit('update');
|
||||
this.emit('update', roomId, widgetId);
|
||||
}
|
||||
|
||||
removeRoomWidgetEcho(roomId, widgetId) {
|
||||
delete this._roomWidgetEcho[roomId][widgetId];
|
||||
if (Object.keys(this._roomWidgetEcho[roomId]).length === 0) delete this._roomWidgetEcho[roomId];
|
||||
this.emit('update');
|
||||
this.emit('update', roomId, widgetId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,19 @@ export default class ResizeNotifier extends EventEmitter {
|
||||
// with default options, will call fn once at first call, and then every x ms
|
||||
// if there was another call in that timespan
|
||||
this._throttledMiddlePanel = throttle(() => this.emit("middlePanelResized"), 200);
|
||||
this._isResizing = false;
|
||||
}
|
||||
|
||||
get isResizing() {
|
||||
return this._isResizing;
|
||||
}
|
||||
|
||||
startResizing() {
|
||||
this._isResizing = true;
|
||||
}
|
||||
|
||||
stopResizing() {
|
||||
this._isResizing = false;
|
||||
}
|
||||
|
||||
_noisyMiddlePanel() {
|
||||
|
@ -405,6 +405,7 @@ export default class WidgetUtils {
|
||||
app.creatorUserId = senderUserId;
|
||||
|
||||
app.id = appId;
|
||||
app.roomId = roomId;
|
||||
app.eventId = eventId;
|
||||
app.name = app.name || app.type;
|
||||
|
||||
@ -448,16 +449,21 @@ export default class WidgetUtils {
|
||||
return encodeURIComponent(`${widgetLocation}::${widgetUrl}`);
|
||||
}
|
||||
|
||||
static getLocalJitsiWrapperUrl(opts: {forLocalRender?: boolean}={}) {
|
||||
static getLocalJitsiWrapperUrl(opts: {forLocalRender?: boolean, auth?: string}={}) {
|
||||
// NB. we can't just encodeURIComponent all of these because the $ signs need to be there
|
||||
const queryString = [
|
||||
const queryStringParts = [
|
||||
'conferenceDomain=$domain',
|
||||
'conferenceId=$conferenceId',
|
||||
'isAudioOnly=$isAudioOnly',
|
||||
'displayName=$matrix_display_name',
|
||||
'avatarUrl=$matrix_avatar_url',
|
||||
'userId=$matrix_user_id',
|
||||
].join('&');
|
||||
'roomId=$matrix_room_id',
|
||||
];
|
||||
if (opts.auth) {
|
||||
queryStringParts.push(`auth=${opts.auth}`);
|
||||
}
|
||||
const queryString = queryStringParts.join('&');
|
||||
|
||||
let baseUrl = window.location;
|
||||
if (window.location.protocol !== "https:" && !opts.forLocalRender) {
|
||||
@ -471,4 +477,13 @@ export default class WidgetUtils {
|
||||
const url = new URL("jitsi.html#" + queryString, baseUrl); // this strips hash fragment from baseUrl
|
||||
return url.href;
|
||||
}
|
||||
|
||||
static editWidget(room, app) {
|
||||
// TODO: Open the right manager for the widget
|
||||
if (SettingsStore.getValue("feature_many_integration_managers")) {
|
||||
IntegrationManagers.sharedInstance().openAll(room, 'type_' + app.type, app.id);
|
||||
} else {
|
||||
IntegrationManagers.sharedInstance().getPrimaryManager().open(room, 'type_' + app.type, app.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -332,6 +332,9 @@ export function tryTransformPermalinkToLocalHref(permalink: string): string {
|
||||
if (permalinkParts.roomIdOrAlias) {
|
||||
const eventIdPart = permalinkParts.eventId ? `/${permalinkParts.eventId}` : '';
|
||||
permalink = `#/room/${permalinkParts.roomIdOrAlias}${eventIdPart}`;
|
||||
if (permalinkParts.viaServers.length > 0) {
|
||||
permalink += new SpecPermalinkConstructor().encodeServerCandidates(permalinkParts.viaServers);
|
||||
}
|
||||
} else if (permalinkParts.groupId) {
|
||||
permalink = `#/group/${permalinkParts.groupId}`;
|
||||
} else if (permalinkParts.userId) {
|
||||
|
@ -34,6 +34,30 @@ export class Jitsi {
|
||||
return this.domain || 'jitsi.riot.im';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for auth needed by looking up a well-known file
|
||||
*
|
||||
* If the file does not exist, we assume no auth.
|
||||
*
|
||||
* See https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
|
||||
*/
|
||||
public async getJitsiAuth(): Promise<string|null> {
|
||||
if (!this.preferredDomain) {
|
||||
return null;
|
||||
}
|
||||
let data;
|
||||
try {
|
||||
const response = await fetch(`https://${this.preferredDomain}/.well-known/element/jitsi`);
|
||||
data = await response.json();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
if (data.auth) {
|
||||
return data.auth;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public start() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli.on("WellKnown.client", this.update);
|
||||
|
@ -34,6 +34,7 @@ export enum KnownWidgetActions {
|
||||
GetCapabilities = "capabilities",
|
||||
SendEvent = "send_event",
|
||||
UpdateVisibility = "visibility",
|
||||
GetOpenIDCredentials = "get_openid",
|
||||
ReceiveOpenIDCredentials = "openid_credentials",
|
||||
SetAlwaysOnScreen = "set_always_on_screen",
|
||||
ClientReady = "im.vector.ready",
|
||||
@ -64,6 +65,13 @@ export interface FromWidgetRequest extends WidgetRequest {
|
||||
response: any;
|
||||
}
|
||||
|
||||
export interface OpenIDCredentials {
|
||||
accessToken: string;
|
||||
tokenType: string;
|
||||
matrixServerName: string;
|
||||
expiresIn: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles Element <--> Widget interactions for embedded/standalone widgets.
|
||||
*
|
||||
@ -73,10 +81,12 @@ export interface FromWidgetRequest extends WidgetRequest {
|
||||
* the given promise resolves.
|
||||
*/
|
||||
export class WidgetApi extends EventEmitter {
|
||||
private origin: string;
|
||||
private readonly origin: string;
|
||||
private inFlightRequests: { [requestId: string]: (reply: FromWidgetRequest) => void } = {};
|
||||
private readyPromise: Promise<any>;
|
||||
private readonly readyPromise: Promise<any>;
|
||||
private readyPromiseResolve: () => void;
|
||||
private openIDCredentialsCallback: () => void;
|
||||
public openIDCredentials: OpenIDCredentials;
|
||||
|
||||
/**
|
||||
* Set this to true if your widget is expecting a ready message from the client. False otherwise (default).
|
||||
@ -120,6 +130,10 @@ export class WidgetApi extends EventEmitter {
|
||||
// Acknowledge that we're shut down now
|
||||
this.replyToRequest(<ToWidgetRequest>payload, {});
|
||||
});
|
||||
} else if (payload.action === KnownWidgetActions.ReceiveOpenIDCredentials) {
|
||||
// Save OpenID credentials
|
||||
this.setOpenIDCredentials(<ToWidgetRequest>payload);
|
||||
this.replyToRequest(<ToWidgetRequest>payload, {});
|
||||
} else {
|
||||
console.warn(`[WidgetAPI] Got unexpected action: ${payload.action}`);
|
||||
}
|
||||
@ -134,6 +148,32 @@ export class WidgetApi extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
public setOpenIDCredentials(value: WidgetRequest) {
|
||||
const data = value.data;
|
||||
if (data.state === 'allowed') {
|
||||
this.openIDCredentials = {
|
||||
accessToken: data.access_token,
|
||||
tokenType: data.token_type,
|
||||
matrixServerName: data.matrix_server_name,
|
||||
expiresIn: data.expires_in,
|
||||
}
|
||||
} else if (data.state === 'blocked') {
|
||||
this.openIDCredentials = null;
|
||||
}
|
||||
if (['allowed', 'blocked'].includes(data.state) && this.openIDCredentialsCallback) {
|
||||
this.openIDCredentialsCallback()
|
||||
}
|
||||
}
|
||||
|
||||
public requestOpenIDCredentials(credentialsResponseCallback: () => void) {
|
||||
this.openIDCredentialsCallback = credentialsResponseCallback;
|
||||
this.callAction(
|
||||
KnownWidgetActions.GetOpenIDCredentials,
|
||||
{},
|
||||
this.setOpenIDCredentials,
|
||||
);
|
||||
}
|
||||
|
||||
public waitReady(): Promise<any> {
|
||||
return this.readyPromise;
|
||||
}
|
||||
|
@ -7557,6 +7557,11 @@ retry@^0.10.0:
|
||||
resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4"
|
||||
integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=
|
||||
|
||||
rfc4648@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.4.0.tgz#c75b2856ad2e2d588b6ddb985d556f1f7f2a2abd"
|
||||
integrity sha512-3qIzGhHlMHA6PoT6+cdPKZ+ZqtxkIvg8DZGKA5z6PQ33/uuhoJ+Ws/D/J9rXW6gXodgH8QYlz2UCl+sdUDmNIg==
|
||||
|
||||
rimraf@2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
||||
|
Loading…
Reference in New Issue
Block a user