Merge branch 'develop' into matthew/slate
This commit is contained in:
commit
721410b710
30 changed files with 908 additions and 278 deletions
|
@ -177,6 +177,7 @@ module.exports = React.createClass({
|
|||
render: function() {
|
||||
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||
|
||||
/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/
|
||||
const {room, oobData, viewAvatarOnClick, ...otherProps} = this.props;
|
||||
|
||||
const roomName = room ? room.name : oobData.name;
|
||||
|
|
|
@ -169,6 +169,13 @@ export default class AppTile extends React.Component {
|
|||
this.dispatcherRef = dis.register(this._onWidgetAction);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
// Allow parents to access widget messaging
|
||||
if (this.props.collectWidgetMessaging) {
|
||||
this.props.collectWidgetMessaging(this.widgetMessaging);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// Widget action listeners
|
||||
dis.unregister(this.dispatcherRef);
|
||||
|
@ -274,6 +281,11 @@ export default class AppTile extends React.Component {
|
|||
}
|
||||
|
||||
_canUserModify() {
|
||||
// User widgets should always be modifiable by their creator
|
||||
if (this.props.userWidget && MatrixClientPeg.get().credentials.userId === this.props.creatorUserId) {
|
||||
return true;
|
||||
}
|
||||
// Check if the current user can modify widgets in the current room
|
||||
return WidgetUtils.canUserModifyWidgets(this.props.room.roomId);
|
||||
}
|
||||
|
||||
|
@ -352,6 +364,9 @@ export default class AppTile extends React.Component {
|
|||
if (!this.widgetMessaging) {
|
||||
this._onInitialLoad();
|
||||
}
|
||||
if (this._exposeWidgetMessaging) {
|
||||
this._exposeWidgetMessaging(this.widgetMessaging);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -389,6 +404,7 @@ export default class AppTile extends React.Component {
|
|||
}).catch((err) => {
|
||||
console.log(`Failed to get capabilities for widget type ${this.props.type}`, this.props.id, err);
|
||||
});
|
||||
|
||||
this.setState({loading: false});
|
||||
}
|
||||
|
||||
|
@ -529,12 +545,16 @@ export default class AppTile extends React.Component {
|
|||
|
||||
if (this.props.show) {
|
||||
const loadingElement = (
|
||||
<div>
|
||||
<div className="mx_AppLoading_spinner_fadeIn">
|
||||
<MessageSpinner msg='Loading...' />
|
||||
</div>
|
||||
);
|
||||
if (this.state.initialising) {
|
||||
appTileBody = loadingElement;
|
||||
appTileBody = (
|
||||
<div className={'mx_AppTileBody ' + (this.state.loading ? 'mx_AppLoading' : '')}>
|
||||
{ loadingElement }
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.hasPermissionToLoad == true) {
|
||||
if (this.isMixedContent()) {
|
||||
appTileBody = (
|
||||
|
@ -698,6 +718,8 @@ AppTile.propTypes = {
|
|||
// Optional function to be called on widget capability request
|
||||
// Called with an array of the requested capabilities
|
||||
onCapabilityRequest: PropTypes.func,
|
||||
// Is this an instance of a user widget
|
||||
userWidget: PropTypes.bool,
|
||||
};
|
||||
|
||||
AppTile.defaultProps = {
|
||||
|
@ -710,4 +732,5 @@ AppTile.defaultProps = {
|
|||
showPopout: true,
|
||||
handleMinimisePointerEvents: false,
|
||||
whitelistCapabilities: [],
|
||||
userWidget: false,
|
||||
};
|
||||
|
|
114
src/components/views/elements/PersistedElement.js
Normal file
114
src/components/views/elements/PersistedElement.js
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
Copyright 2018 New Vector Ltd.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
|
||||
// Shamelessly ripped off Modal.js. There's probably a better way
|
||||
// of doing reusable widgets like dialog boxes & menus where we go and
|
||||
// pass in a custom control as the actual body.
|
||||
|
||||
const ContainerId = "mx_PersistedElement";
|
||||
|
||||
function getOrCreateContainer() {
|
||||
let container = document.getElementById(ContainerId);
|
||||
|
||||
if (!container) {
|
||||
container = document.createElement("div");
|
||||
container.id = ContainerId;
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
// Greater than that of the ContextualMenu
|
||||
const PE_Z_INDEX = 3000;
|
||||
|
||||
/*
|
||||
* Class of component that renders its children in a separate ReactDOM virtual tree
|
||||
* in a container element appended to document.body.
|
||||
*
|
||||
* This prevents the children from being unmounted when the parent of PersistedElement
|
||||
* unmounts, allowing them to persist.
|
||||
*
|
||||
* When PE is unmounted, it hides the children using CSS. When mounted or updated, the
|
||||
* children are made visible and are positioned into a div that is given the same
|
||||
* bounding rect as the parent of PE.
|
||||
*/
|
||||
export default class PersistedElement extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.collectChildContainer = this.collectChildContainer.bind(this);
|
||||
this.collectChild = this.collectChild.bind(this);
|
||||
}
|
||||
|
||||
collectChildContainer(ref) {
|
||||
this.childContainer = ref;
|
||||
}
|
||||
|
||||
collectChild(ref) {
|
||||
this.child = ref;
|
||||
this.updateChild();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateChild();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.updateChild();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.updateChildVisibility(this.child, false);
|
||||
}
|
||||
|
||||
updateChild() {
|
||||
this.updateChildPosition(this.child, this.childContainer);
|
||||
this.updateChildVisibility(this.child, true);
|
||||
}
|
||||
|
||||
updateChildVisibility(child, visible) {
|
||||
if (!child) return;
|
||||
child.style.display = visible ? 'block' : 'none';
|
||||
}
|
||||
|
||||
updateChildPosition(child, parent) {
|
||||
if (!child || !parent) return;
|
||||
|
||||
const parentRect = parent.getBoundingClientRect();
|
||||
Object.assign(child.style, {
|
||||
position: 'absolute',
|
||||
top: parentRect.top + 'px',
|
||||
left: parentRect.left + 'px',
|
||||
width: parentRect.width + 'px',
|
||||
height: parentRect.height + 'px',
|
||||
zIndex: PE_Z_INDEX,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const content = <div ref={this.collectChild}>
|
||||
{this.props.children}
|
||||
</div>;
|
||||
|
||||
ReactDOM.render(content, getOrCreateContainer());
|
||||
|
||||
return <div ref={this.collectChildContainer}></div>;
|
||||
}
|
||||
}
|
||||
|
|
@ -81,7 +81,7 @@ export default class ReplyThread extends React.Component {
|
|||
|
||||
// Part of Replies fallback support
|
||||
static stripHTMLReply(html) {
|
||||
return html.replace(/^<blockquote data-mx-reply>[\s\S]+?<!--end-mx-reply--><\/blockquote>/, '');
|
||||
return html.replace(/^<mx-reply>[\s\S]+?<\/mx-reply>/, '');
|
||||
}
|
||||
|
||||
// Part of Replies fallback support
|
||||
|
@ -102,8 +102,8 @@ export default class ReplyThread extends React.Component {
|
|||
switch (ev.getContent().msgtype) {
|
||||
case 'm.text':
|
||||
case 'm.notice': {
|
||||
html = `<blockquote data-mx-reply><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
||||
+ `<br>${html || body}<!--end-mx-reply--></blockquote>`;
|
||||
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
||||
+ `<br>${html || body}</blockquote></mx-reply>`;
|
||||
const lines = body.trim().split('\n');
|
||||
if (lines.length > 0) {
|
||||
lines[0] = `<${mxid}> ${lines[0]}`;
|
||||
|
@ -112,28 +112,28 @@ export default class ReplyThread extends React.Component {
|
|||
break;
|
||||
}
|
||||
case 'm.image':
|
||||
html = `<blockquote data-mx-reply><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
||||
+ `<br>sent an image.<!--end-mx-reply--></blockquote>`;
|
||||
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
||||
+ `<br>sent an image.</blockquote></mx-reply>`;
|
||||
body = `> <${mxid}> sent an image.\n\n`;
|
||||
break;
|
||||
case 'm.video':
|
||||
html = `<blockquote data-mx-reply><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
||||
+ `<br>sent a video.<!--end-mx-reply--></blockquote>`;
|
||||
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
||||
+ `<br>sent a video.</blockquote></mx-reply>`;
|
||||
body = `> <${mxid}> sent a video.\n\n`;
|
||||
break;
|
||||
case 'm.audio':
|
||||
html = `<blockquote data-mx-reply><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
||||
+ `<br>sent an audio file.<!--end-mx-reply--></blockquote>`;
|
||||
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
||||
+ `<br>sent an audio file.</blockquote></mx-reply>`;
|
||||
body = `> <${mxid}> sent an audio file.\n\n`;
|
||||
break;
|
||||
case 'm.file':
|
||||
html = `<blockquote data-mx-reply><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
||||
+ `<br>sent a file.<!--end-mx-reply--></blockquote>`;
|
||||
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
||||
+ `<br>sent a file.</blockquote></mx-reply>`;
|
||||
body = `> <${mxid}> sent a file.\n\n`;
|
||||
break;
|
||||
case 'm.emote': {
|
||||
html = `<blockquote data-mx-reply><a href="${evLink}">In reply to</a> * `
|
||||
+ `<a href="${userLink}">${mxid}</a><br>${html || body}<!--end-mx-reply--></blockquote>`;
|
||||
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> * `
|
||||
+ `<a href="${userLink}">${mxid}</a><br>${html || body}</blockquote></mx-reply>`;
|
||||
const lines = body.trim().split('\n');
|
||||
if (lines.length > 0) {
|
||||
lines[0] = `* <${mxid}> ${lines[0]}`;
|
||||
|
|
|
@ -202,17 +202,12 @@ module.exports = withMatrixClient(React.createClass({
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!this._propsEqual(this.props, nextProps)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return !this._propsEqual(this.props, nextProps);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
const client = this.props.matrixClient;
|
||||
client.removeListener("deviceVerificationChanged",
|
||||
this.onDeviceVerificationChanged);
|
||||
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||
this.props.mxEvent.removeListener("Event.decrypted", this._onDecrypted);
|
||||
},
|
||||
|
||||
|
@ -227,7 +222,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
},
|
||||
|
||||
onDeviceVerificationChanged: function(userId, device) {
|
||||
if (userId == this.props.mxEvent.getSender()) {
|
||||
if (userId === this.props.mxEvent.getSender()) {
|
||||
this._verifyEvent(this.props.mxEvent);
|
||||
}
|
||||
},
|
||||
|
@ -262,7 +257,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
}
|
||||
|
||||
// need to deep-compare readReceipts
|
||||
if (key == 'readReceipts') {
|
||||
if (key === 'readReceipts') {
|
||||
const rA = objA[key];
|
||||
const rB = objB[key];
|
||||
if (rA === rB) {
|
||||
|
@ -336,7 +331,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
getReadAvatars: function() {
|
||||
// return early if there are no read receipts
|
||||
if (!this.props.readReceipts || this.props.readReceipts.length === 0) {
|
||||
return (<span className="mx_EventTile_readAvatars"></span>);
|
||||
return (<span className="mx_EventTile_readAvatars" />);
|
||||
}
|
||||
|
||||
const ReadReceiptMarker = sdk.getComponent('rooms.ReadReceiptMarker');
|
||||
|
@ -360,7 +355,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
left = (hidden ? MAX_READ_AVATARS - 1 : i) * -receiptOffset;
|
||||
|
||||
const userId = receipt.roomMember.userId;
|
||||
var readReceiptInfo;
|
||||
let readReceiptInfo;
|
||||
|
||||
if (this.props.readReceiptMap) {
|
||||
readReceiptInfo = this.props.readReceiptMap[userId];
|
||||
|
@ -502,17 +497,17 @@ module.exports = withMatrixClient(React.createClass({
|
|||
mx_EventTile: true,
|
||||
mx_EventTile_info: isInfoMessage,
|
||||
mx_EventTile_12hr: this.props.isTwelveHour,
|
||||
mx_EventTile_encrypting: this.props.eventSendStatus == 'encrypting',
|
||||
mx_EventTile_encrypting: this.props.eventSendStatus === 'encrypting',
|
||||
mx_EventTile_sending: isSending,
|
||||
mx_EventTile_notSent: this.props.eventSendStatus == 'not_sent',
|
||||
mx_EventTile_highlight: this.props.tileShape == 'notif' ? false : this.shouldHighlight(),
|
||||
mx_EventTile_notSent: this.props.eventSendStatus === 'not_sent',
|
||||
mx_EventTile_highlight: this.props.tileShape === 'notif' ? false : this.shouldHighlight(),
|
||||
mx_EventTile_selected: this.props.isSelectedEvent,
|
||||
mx_EventTile_continuation: this.props.tileShape ? '' : this.props.continuation,
|
||||
mx_EventTile_last: this.props.last,
|
||||
mx_EventTile_contextual: this.props.contextual,
|
||||
menu: this.state.menu,
|
||||
mx_EventTile_verified: this.state.verified == true,
|
||||
mx_EventTile_unverified: this.state.verified == false,
|
||||
mx_EventTile_verified: this.state.verified === true,
|
||||
mx_EventTile_unverified: this.state.verified === false,
|
||||
mx_EventTile_bad: isEncryptionFailure,
|
||||
mx_EventTile_emote: msgtype === 'm.emote',
|
||||
mx_EventTile_redacted: isRedacted,
|
||||
|
@ -522,7 +517,8 @@ module.exports = withMatrixClient(React.createClass({
|
|||
|
||||
const readAvatars = this.getReadAvatars();
|
||||
|
||||
let avatar, sender;
|
||||
let avatar;
|
||||
let sender;
|
||||
let avatarSize;
|
||||
let needsSenderProfile;
|
||||
|
||||
|
@ -556,11 +552,14 @@ module.exports = withMatrixClient(React.createClass({
|
|||
|
||||
if (needsSenderProfile) {
|
||||
let text = null;
|
||||
if (!this.props.tileShape) {
|
||||
if (!this.props.tileShape || this.props.tileShape === 'reply' || this.props.tileShape === 'reply_preview') {
|
||||
if (msgtype === 'm.image') text = _td('%(senderName)s sent an image');
|
||||
else if (msgtype === 'm.video') text = _td('%(senderName)s sent a video');
|
||||
else if (msgtype === 'm.file') text = _td('%(senderName)s uploaded a file');
|
||||
sender = <SenderProfile onClick={this.onSenderProfileClick} mxEvent={this.props.mxEvent} enableFlair={!text} text={text} />;
|
||||
sender = <SenderProfile onClick={this.onSenderProfileClick}
|
||||
mxEvent={this.props.mxEvent}
|
||||
enableFlair={!text}
|
||||
text={text} />;
|
||||
} else {
|
||||
sender = <SenderProfile mxEvent={this.props.mxEvent} enableFlair={true} />;
|
||||
}
|
||||
|
|
|
@ -338,10 +338,7 @@ export default class MessageComposer extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
let stickerpickerButton;
|
||||
if (SettingsStore.isFeatureEnabled('feature_sticker_messages')) {
|
||||
stickerpickerButton = <Stickerpicker key='stickerpicker_controls_button' room={this.props.room} />;
|
||||
}
|
||||
const stickerpickerButton = <Stickerpicker key='stickerpicker_controls_button' room={this.props.room} />;
|
||||
|
||||
controls.push(
|
||||
<MessageComposerInput
|
||||
|
|
|
@ -17,7 +17,6 @@ import React from 'react';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import Widgets from '../../../utils/widgets';
|
||||
import AppTile from '../elements/AppTile';
|
||||
import ContextualMenu from '../../structures/ContextualMenu';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import Modal from '../../../Modal';
|
||||
import sdk from '../../../index';
|
||||
|
@ -36,21 +35,28 @@ export default class Stickerpicker extends React.Component {
|
|||
this._launchManageIntegrations = this._launchManageIntegrations.bind(this);
|
||||
this._removeStickerpickerWidgets = this._removeStickerpickerWidgets.bind(this);
|
||||
this._onWidgetAction = this._onWidgetAction.bind(this);
|
||||
this._onResize = this._onResize.bind(this);
|
||||
this._onFinished = this._onFinished.bind(this);
|
||||
|
||||
this._collectWidgetMessaging = this._collectWidgetMessaging.bind(this);
|
||||
|
||||
this.popoverWidth = 300;
|
||||
this.popoverHeight = 300;
|
||||
|
||||
this.state = {
|
||||
showStickers: false,
|
||||
imError: null,
|
||||
stickerpickerX: null,
|
||||
stickerpickerY: null,
|
||||
stickerpickerWidget: null,
|
||||
widgetId: null,
|
||||
};
|
||||
}
|
||||
|
||||
_removeStickerpickerWidgets() {
|
||||
console.warn('Removing Stickerpicker widgets');
|
||||
if (this.widgetId) {
|
||||
this.scalarClient.disableWidgetAssets(widgetType, this.widgetId).then(() => {
|
||||
if (this.state.widgetId) {
|
||||
this.scalarClient.disableWidgetAssets(widgetType, this.state.widgetId).then(() => {
|
||||
console.warn('Assets disabled');
|
||||
}).catch((err) => {
|
||||
console.error('Failed to disable assets');
|
||||
|
@ -59,8 +65,7 @@ export default class Stickerpicker extends React.Component {
|
|||
console.warn('No widget ID specified, not disabling assets');
|
||||
}
|
||||
|
||||
// Wrap this in a timeout in order to avoid the DOM node from being pulled from under its feet
|
||||
setTimeout(() => this.stickersMenu.close());
|
||||
this.setState({showStickers: false});
|
||||
Widgets.removeStickerpickerWidgets().then(() => {
|
||||
this.forceUpdate();
|
||||
}).catch((e) => {
|
||||
|
@ -69,6 +74,9 @@ export default class Stickerpicker extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Close the sticker picker when the window resizes
|
||||
window.addEventListener('resize', this._onResize);
|
||||
|
||||
this.scalarClient = null;
|
||||
if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) {
|
||||
this.scalarClient = new ScalarAuthClient();
|
||||
|
@ -82,14 +90,24 @@ export default class Stickerpicker extends React.Component {
|
|||
if (!this.state.imError) {
|
||||
this.dispatcherRef = dis.register(this._onWidgetAction);
|
||||
}
|
||||
const stickerpickerWidget = Widgets.getStickerpickerWidgets()[0];
|
||||
this.setState({
|
||||
stickerpickerWidget,
|
||||
widgetId: stickerpickerWidget ? stickerpickerWidget.id : null,
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this._onResize);
|
||||
if (this.dispatcherRef) {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
this._sendVisibilityToWidget(this.state.showStickers);
|
||||
}
|
||||
|
||||
_imError(errorMsg, e) {
|
||||
console.error(errorMsg, e);
|
||||
this.setState({
|
||||
|
@ -102,9 +120,7 @@ export default class Stickerpicker extends React.Component {
|
|||
if (payload.action === "user_widget_updated") {
|
||||
this.forceUpdate();
|
||||
} else if (payload.action === "stickerpicker_close") {
|
||||
// Wrap this in a timeout in order to avoid the DOM node from being
|
||||
// pulled from under its feet
|
||||
setTimeout(() => this.stickersMenu.close());
|
||||
this.setState({showStickers: false});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,6 +143,21 @@ export default class Stickerpicker extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
_collectWidgetMessaging(widgetMessaging) {
|
||||
this._appWidgetMessaging = widgetMessaging;
|
||||
|
||||
// Do this now instead of in componentDidMount because we might not have had the
|
||||
// reference to widgetMessaging when mounting
|
||||
this._sendVisibilityToWidget(true);
|
||||
}
|
||||
|
||||
_sendVisibilityToWidget(visible) {
|
||||
if (this._appWidgetMessaging && visible !== this._prevSentVisibility) {
|
||||
this._appWidgetMessaging.sendVisibility(visible);
|
||||
this._prevSentVisibility = visible;
|
||||
}
|
||||
}
|
||||
|
||||
_getStickerpickerContent() {
|
||||
// Handle Integration Manager errors
|
||||
if (this.state._imError) {
|
||||
|
@ -137,14 +168,18 @@ export default class Stickerpicker extends React.Component {
|
|||
// TODO - Add support for Stickerpickers from multiple app stores.
|
||||
// Render content from multiple stickerpack sources, each within their
|
||||
// own iframe, within the stickerpicker UI element.
|
||||
const stickerpickerWidget = Widgets.getStickerpickerWidgets()[0];
|
||||
const stickerpickerWidget = this.state.stickerpickerWidget;
|
||||
let stickersContent;
|
||||
|
||||
// Use a separate ReactDOM tree to render the AppTile separately so that it persists and does
|
||||
// not unmount when we (a) close the sticker picker (b) switch rooms. It's properties are still
|
||||
// updated.
|
||||
const PersistedElement = sdk.getComponent("elements.PersistedElement");
|
||||
|
||||
// Load stickerpack content
|
||||
if (stickerpickerWidget && stickerpickerWidget.content && stickerpickerWidget.content.url) {
|
||||
// Set default name
|
||||
stickerpickerWidget.content.name = stickerpickerWidget.name || _t("Stickerpack");
|
||||
this.widgetId = stickerpickerWidget.id;
|
||||
|
||||
stickersContent = (
|
||||
<div className='mx_Stickers_content_container'>
|
||||
|
@ -157,15 +192,17 @@ export default class Stickerpicker extends React.Component {
|
|||
width: this.popoverWidth,
|
||||
}}
|
||||
>
|
||||
<PersistedElement>
|
||||
<AppTile
|
||||
collectWidgetMessaging={this._collectWidgetMessaging}
|
||||
id={stickerpickerWidget.id}
|
||||
url={stickerpickerWidget.content.url}
|
||||
name={stickerpickerWidget.content.name}
|
||||
room={this.props.room}
|
||||
type={stickerpickerWidget.content.type}
|
||||
fullWidth={true}
|
||||
userId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId}
|
||||
creatorUserId={MatrixClientPeg.get().credentials.userId}
|
||||
userId={MatrixClientPeg.get().credentials.userId}
|
||||
creatorUserId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId}
|
||||
waitForIframeLoad={true}
|
||||
show={true}
|
||||
showMenubar={true}
|
||||
|
@ -177,8 +214,10 @@ export default class Stickerpicker extends React.Component {
|
|||
showPopout={false}
|
||||
onMinimiseClick={this._onHideStickersClick}
|
||||
handleMinimisePointerEvents={true}
|
||||
whitelistCapabilities={['m.sticker']}
|
||||
whitelistCapabilities={['m.sticker', 'visibility']}
|
||||
userWidget={true}
|
||||
/>
|
||||
</PersistedElement>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -186,12 +225,7 @@ export default class Stickerpicker extends React.Component {
|
|||
// Default content to show if stickerpicker widget not added
|
||||
console.warn("No available sticker picker widgets");
|
||||
stickersContent = this._defaultStickerpickerContent();
|
||||
this.widgetId = null;
|
||||
this.forceUpdate();
|
||||
}
|
||||
this.setState({
|
||||
showStickers: false,
|
||||
});
|
||||
return stickersContent;
|
||||
}
|
||||
|
||||
|
@ -201,29 +235,17 @@ export default class Stickerpicker extends React.Component {
|
|||
* @param {Event} e Event that triggered the function
|
||||
*/
|
||||
_onShowStickersClick(e) {
|
||||
const GenericElementContextMenu = sdk.getComponent('context_menus.GenericElementContextMenu');
|
||||
const buttonRect = e.target.getBoundingClientRect();
|
||||
|
||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||
const x = buttonRect.right + window.pageXOffset - 42;
|
||||
const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
|
||||
// const self = this;
|
||||
this.stickersMenu = ContextualMenu.createMenu(GenericElementContextMenu, {
|
||||
chevronOffset: 10,
|
||||
chevronFace: 'bottom',
|
||||
left: x,
|
||||
top: y,
|
||||
menuWidth: this.popoverWidth,
|
||||
menuHeight: this.popoverHeight,
|
||||
element: this._getStickerpickerContent(),
|
||||
onFinished: this._onFinished,
|
||||
menuPaddingTop: 0,
|
||||
menuPaddingLeft: 0,
|
||||
menuPaddingRight: 0,
|
||||
|
||||
this.setState({
|
||||
showStickers: true,
|
||||
stickerPickerX: x,
|
||||
stickerPickerY: y,
|
||||
});
|
||||
|
||||
|
||||
this.setState({showStickers: true});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -231,7 +253,14 @@ export default class Stickerpicker extends React.Component {
|
|||
* @param {Event} ev Event that triggered the function call
|
||||
*/
|
||||
_onHideStickersClick(ev) {
|
||||
setTimeout(() => this.stickersMenu.close());
|
||||
this.setState({showStickers: false});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the window is resized
|
||||
*/
|
||||
_onResize() {
|
||||
this.setState({showStickers: false});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -250,20 +279,37 @@ export default class Stickerpicker extends React.Component {
|
|||
this.scalarClient.getScalarInterfaceUrlForRoom(
|
||||
this.props.room,
|
||||
'type_' + widgetType,
|
||||
this.widgetId,
|
||||
this.state.widgetId,
|
||||
) :
|
||||
null;
|
||||
Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, {
|
||||
src: src,
|
||||
}, "mx_IntegrationsManager");
|
||||
|
||||
// Wrap this in a timeout in order to avoid the DOM node from being pulled from under its feet
|
||||
setTimeout(() => this.stickersMenu.close());
|
||||
this.setState({showStickers: false});
|
||||
}
|
||||
|
||||
render() {
|
||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
const ContextualMenu = sdk.getComponent('structures.ContextualMenu');
|
||||
const GenericElementContextMenu = sdk.getComponent('context_menus.GenericElementContextMenu');
|
||||
let stickersButton;
|
||||
|
||||
const stickerPicker = <ContextualMenu
|
||||
elementClass={GenericElementContextMenu}
|
||||
chevronOffset={10}
|
||||
chevronFace={'bottom'}
|
||||
left={this.state.stickerPickerX}
|
||||
top={this.state.stickerPickerY}
|
||||
menuWidth={this.popoverWidth}
|
||||
menuHeight={this.popoverHeight}
|
||||
element={this._getStickerpickerContent()}
|
||||
onFinished={this._onFinished}
|
||||
menuPaddingTop={0}
|
||||
menuPaddingLeft={0}
|
||||
menuPaddingRight={0}
|
||||
/>;
|
||||
|
||||
if (this.state.showStickers) {
|
||||
// Show hide-stickers button
|
||||
stickersButton =
|
||||
|
@ -288,6 +334,9 @@ export default class Stickerpicker extends React.Component {
|
|||
<TintableSvg src="img/icons-show-stickers.svg" width="35" height="35" />
|
||||
</div>;
|
||||
}
|
||||
return stickersButton;
|
||||
return <div>
|
||||
{stickersButton}
|
||||
{this.state.showStickers && stickerPicker}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue