Make AppTile in Stickerpicker persistent using PersistedElement

This commit is contained in:
Luke Barnard 2018-05-11 15:07:51 +01:00
parent 6500797d2a
commit 746eeee33c
2 changed files with 123 additions and 0 deletions

View file

@ -0,0 +1,116 @@
/*
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 classNames = require('classnames');
const React = require('react');
const ReactDOM = require('react-dom');
import PropTypes from 'prop-types';
// 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>;
}
}

View file

@ -150,6 +150,11 @@ export default class Stickerpicker extends React.Component {
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
@ -166,6 +171,7 @@ export default class Stickerpicker extends React.Component {
width: this.popoverWidth,
}}
>
<PersistedElement>
<AppTile
id={stickerpickerWidget.id}
url={stickerpickerWidget.content.url}
@ -189,6 +195,7 @@ export default class Stickerpicker extends React.Component {
whitelistCapabilities={['m.sticker']}
userWidget={true}
/>
</PersistedElement>
</div>
</div>
);