Convert PersistedElement to TS

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner 2021-09-16 18:57:32 +02:00
parent a8d3bb76ef
commit cebf256dc0
No known key found for this signature in database
GPG key ID: 55C211A1226CB17D

View file

@ -16,25 +16,26 @@ limitations under the License.
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { throttle } from "lodash"; import { throttle } from "lodash";
import ResizeObserver from 'resize-observer-polyfill';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils"; import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { ActionPayload } from "../../../dispatcher/payloads";
export const getPersistKey = (appId: string) => 'widget_' + appId;
// Shamelessly ripped off Modal.js. There's probably a better way // Shamelessly ripped off Modal.js. There's probably a better way
// of doing reusable widgets like dialog boxes & menus where we go and // of doing reusable widgets like dialog boxes & menus where we go and
// pass in a custom control as the actual body. // pass in a custom control as the actual body.
function getContainer(containerId) { function getContainer(containerId: string): HTMLDivElement {
return document.getElementById(containerId); return document.getElementById(containerId) as HTMLDivElement;
} }
function getOrCreateContainer(containerId) { function getOrCreateContainer(containerId: string): HTMLDivElement {
let container = getContainer(containerId); let container = getContainer(containerId);
if (!container) { if (!container) {
@ -46,7 +47,19 @@ function getOrCreateContainer(containerId) {
return container; return container;
} }
/* interface IProps {
// Unique identifier for this PersistedElement instance
// Any PersistedElements with the same persistKey will use
// the same DOM container.
persistKey: string;
// z-index for the element. Defaults to 9.
zIndex?: number;
style?: React.StyleHTMLAttributes<HTMLDivElement>;
}
/**
* Class of component that renders its children in a separate ReactDOM virtual tree * Class of component that renders its children in a separate ReactDOM virtual tree
* in a container element appended to document.body. * in a container element appended to document.body.
* *
@ -58,53 +71,40 @@ function getOrCreateContainer(containerId) {
* bounding rect as the parent of PE. * bounding rect as the parent of PE.
*/ */
@replaceableComponent("views.elements.PersistedElement") @replaceableComponent("views.elements.PersistedElement")
export default class PersistedElement extends React.Component { export default class PersistedElement extends React.Component<IProps> {
static propTypes = { private resizeObserver: ResizeObserver;
// Unique identifier for this PersistedElement instance private dispatcherRef: string;
// Any PersistedElements with the same persistKey will use private childContainer: HTMLDivElement;
// the same DOM container. private child: HTMLDivElement;
persistKey: PropTypes.string.isRequired,
// z-index for the element. Defaults to 9. constructor(props: IProps) {
zIndex: PropTypes.number, super(props);
};
constructor() { this.resizeObserver = new ResizeObserver(this.repositionChild);
super();
this.collectChildContainer = this.collectChildContainer.bind(this);
this.collectChild = this.collectChild.bind(this);
this._repositionChild = this._repositionChild.bind(this);
this._onAction = this._onAction.bind(this);
this.resizeObserver = new ResizeObserver(this._repositionChild);
// Annoyingly, a resize observer is insufficient, since we also care // Annoyingly, a resize observer is insufficient, since we also care
// about when the element moves on the screen without changing its // about when the element moves on the screen without changing its
// dimensions. Doesn't look like there's a ResizeObserver equivalent // dimensions. Doesn't look like there's a ResizeObserver equivalent
// for this, so we bodge it by listening for document resize and // for this, so we bodge it by listening for document resize and
// the timeline_resize action. // the timeline_resize action.
window.addEventListener('resize', this._repositionChild); window.addEventListener('resize', this.repositionChild);
this._dispatcherRef = dis.register(this._onAction); this.dispatcherRef = dis.register(this.onAction);
} }
/** /**
* Removes the DOM elements created when a PersistedElement with the given * Removes the DOM elements created when a PersistedElement with the given
* persistKey was mounted. The DOM elements will be re-added if another * persistKey was mounted. The DOM elements will be re-added if another
* PeristedElement is mounted in the future. * PersistedElement is mounted in the future.
* *
* @param {string} persistKey Key used to uniquely identify this PersistedElement * @param {string} persistKey Key used to uniquely identify this PersistedElement
*/ */
static destroyElement(persistKey) { public static destroyElement(persistKey: string): void {
const container = getContainer('mx_persistedElement_' + persistKey); const container = getContainer('mx_persistedElement_' + persistKey);
if (container) { if (container) {
container.remove(); container.remove();
} }
} }
static isMounted(persistKey) { private collectChildContainer = (ref: HTMLDivElement): void => {
return Boolean(getContainer('mx_persistedElement_' + persistKey));
}
collectChildContainer(ref) {
if (this.childContainer) { if (this.childContainer) {
this.resizeObserver.unobserve(this.childContainer); this.resizeObserver.unobserve(this.childContainer);
} }
@ -112,48 +112,48 @@ export default class PersistedElement extends React.Component {
if (ref) { if (ref) {
this.resizeObserver.observe(ref); this.resizeObserver.observe(ref);
} }
} };
collectChild(ref) { private collectChild = (ref: HTMLDivElement): void => {
this.child = ref; this.child = ref;
this.updateChild(); this.updateChild();
} };
componentDidMount() { public componentDidMount(): void {
this.updateChild(); this.updateChild();
this.renderApp(); this.renderApp();
} }
componentDidUpdate() { public componentDidUpdate(): void {
this.updateChild(); this.updateChild();
this.renderApp(); this.renderApp();
} }
componentWillUnmount() { public componentWillUnmount(): void {
this.updateChildVisibility(this.child, false); this.updateChildVisibility(this.child, false);
this.resizeObserver.disconnect(); this.resizeObserver.disconnect();
window.removeEventListener('resize', this._repositionChild); window.removeEventListener('resize', this.repositionChild);
dis.unregister(this._dispatcherRef); dis.unregister(this.dispatcherRef);
} }
_onAction(payload) { private onAction = (payload: ActionPayload): void => {
if (payload.action === 'timeline_resize') { if (payload.action === 'timeline_resize') {
this._repositionChild(); this.repositionChild();
} else if (payload.action === 'logout') { } else if (payload.action === 'logout') {
PersistedElement.destroyElement(this.props.persistKey); PersistedElement.destroyElement(this.props.persistKey);
} }
} };
_repositionChild() { private repositionChild = (): void => {
this.updateChildPosition(this.child, this.childContainer); this.updateChildPosition(this.child, this.childContainer);
} };
updateChild() { private updateChild(): void {
this.updateChildPosition(this.child, this.childContainer); this.updateChildPosition(this.child, this.childContainer);
this.updateChildVisibility(this.child, true); this.updateChildVisibility(this.child, true);
} }
renderApp() { private renderApp(): void {
const content = <MatrixClientContext.Provider value={MatrixClientPeg.get()}> const content = <MatrixClientContext.Provider value={MatrixClientPeg.get()}>
<div ref={this.collectChild} style={this.props.style}> <div ref={this.collectChild} style={this.props.style}>
{ this.props.children } { this.props.children }
@ -163,12 +163,12 @@ export default class PersistedElement extends React.Component {
ReactDOM.render(content, getOrCreateContainer('mx_persistedElement_'+this.props.persistKey)); ReactDOM.render(content, getOrCreateContainer('mx_persistedElement_'+this.props.persistKey));
} }
updateChildVisibility(child, visible) { private updateChildVisibility(child: HTMLDivElement, visible: boolean): void {
if (!child) return; if (!child) return;
child.style.display = visible ? 'block' : 'none'; child.style.display = visible ? 'block' : 'none';
} }
updateChildPosition = throttle((child, parent) => { private updateChildPosition = throttle((child: HTMLDivElement, parent: HTMLDivElement): void => {
if (!child || !parent) return; if (!child || !parent) return;
const parentRect = parent.getBoundingClientRect(); const parentRect = parent.getBoundingClientRect();
@ -182,9 +182,8 @@ export default class PersistedElement extends React.Component {
}); });
}, 100, { trailing: true, leading: true }); }, 100, { trailing: true, leading: true });
render() { public render(): JSX.Element {
return <div ref={this.collectChildContainer} />; return <div ref={this.collectChildContainer} />;
} }
} }
export const getPersistKey = (appId) => 'widget_' + appId;