Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Convert /structures to TS #6787

Merged
merged 17 commits into from
Sep 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import request from 'browser-request';
import { _t } from '../../languageHandler';
import sanitizeHtml from 'sanitize-html';
Expand All @@ -26,38 +25,43 @@ import { MatrixClientPeg } from '../../MatrixClientPeg';
import classnames from 'classnames';
import MatrixClientContext from "../../contexts/MatrixClientContext";
import AutoHideScrollbar from "./AutoHideScrollbar";
import { ActionPayload } from "../../dispatcher/payloads";

interface IProps {
// URL to request embedded page content from
url?: string;
// Class name prefix to apply for a given instance
className?: string;
// Whether to wrap the page in a scrollbar
scrollbar?: boolean;
// Map of keys to replace with values, e.g {$placeholder: "value"}
replaceMap?: Map<string, string>;
}

export default class EmbeddedPage extends React.PureComponent {
static propTypes = {
// URL to request embedded page content from
url: PropTypes.string,
// Class name prefix to apply for a given instance
className: PropTypes.string,
// Whether to wrap the page in a scrollbar
scrollbar: PropTypes.bool,
// Map of keys to replace with values, e.g {$placeholder: "value"}
replaceMap: PropTypes.object,
};
interface IState {
page: string;
}

static contextType = MatrixClientContext;
export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
public static contextType = MatrixClientContext;
private unmounted = false;
private dispatcherRef: string = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the = null isn't required but also not an issue


constructor(props, context) {
constructor(props: IProps, context: typeof MatrixClientContext) {
super(props, context);

this._dispatcherRef = null;

this.state = {
page: '',
};
}

translate(s) {
private translate(s: string): string {
// default implementation - skins may wish to extend this
return sanitizeHtml(_t(s));
}

componentDidMount() {
this._unmounted = false;
public componentDidMount(): void {
this.unmounted = false;

if (!this.props.url) {
return;
Expand All @@ -70,7 +74,7 @@ export default class EmbeddedPage extends React.PureComponent {
request(
{ method: "GET", url: this.props.url },
(err, response, body) => {
if (this._unmounted) {
if (this.unmounted) {
return;
}

Expand All @@ -92,22 +96,22 @@ export default class EmbeddedPage extends React.PureComponent {
},
);

this._dispatcherRef = dis.register(this.onAction);
this.dispatcherRef = dis.register(this.onAction);
}

componentWillUnmount() {
this._unmounted = true;
if (this._dispatcherRef !== null) dis.unregister(this._dispatcherRef);
public componentWillUnmount(): void {
this.unmounted = true;
if (this.dispatcherRef !== null) dis.unregister(this.dispatcherRef);
}

onAction = (payload) => {
private onAction = (payload: ActionPayload): void => {
// HACK: Workaround for the context's MatrixClient not being set up at render time.
if (payload.action === 'client_started') {
this.forceUpdate();
}
};

render() {
public render(): JSX.Element {
// HACK: Workaround for the context's MatrixClient not updating.
const client = this.context || MatrixClientPeg.get();
const isGuest = client ? client.isGuest() : true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,15 @@ limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import { replaceableComponent } from "../../utils/replaceableComponent";

@replaceableComponent("structures.GenericErrorPage")
export default class GenericErrorPage extends React.PureComponent {
static propTypes = {
title: PropTypes.object.isRequired, // jsx for title
message: PropTypes.object.isRequired, // jsx to display
};
interface IProps {
title: React.ReactNode;
message: React.ReactNode;
}

@replaceableComponent("structures.GenericErrorPage")
export default class GenericErrorPage extends React.PureComponent<IProps> {
render() {
return <div className='mx_GenericErrorPage'>
<div className='mx_GenericErrorPage_box'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,65 +14,59 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import React from "react";
import PropTypes from "prop-types";
import React, { createRef } from "react";
import AutoHideScrollbar from "./AutoHideScrollbar";
import { replaceableComponent } from "../../utils/replaceableComponent";

interface IProps {
// If true, the scrollbar will append mx_IndicatorScrollbar_leftOverflowIndicator
// and mx_IndicatorScrollbar_rightOverflowIndicator elements to the list for positioning
// by the parent element.
trackHorizontalOverflow?: boolean;

// If true, when the user tries to use their mouse wheel in the component it will
// scroll horizontally rather than vertically. This should only be used on components
// with no vertical scroll opportunity.
verticalScrollsHorizontally?: boolean;

children: React.ReactNode;
className: string;
}

interface IState {
leftIndicatorOffset: number | string;
rightIndicatorOffset: number | string;
}

@replaceableComponent("structures.IndicatorScrollbar")
export default class IndicatorScrollbar extends React.Component {
static propTypes = {
// If true, the scrollbar will append mx_IndicatorScrollbar_leftOverflowIndicator
// and mx_IndicatorScrollbar_rightOverflowIndicator elements to the list for positioning
// by the parent element.
trackHorizontalOverflow: PropTypes.bool,

// If true, when the user tries to use their mouse wheel in the component it will
// scroll horizontally rather than vertically. This should only be used on components
// with no vertical scroll opportunity.
verticalScrollsHorizontally: PropTypes.bool,
};
export default class IndicatorScrollbar extends React.Component<IProps, IState> {
private autoHideScrollbar = createRef<AutoHideScrollbar>();
private scrollElement: HTMLDivElement;
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
private likelyTrackpadUser: boolean = null;
private checkAgainForTrackpad = 0; // ts in milliseconds to recheck this._likelyTrackpadUser

constructor(props) {
constructor(props: IProps) {
super(props);
this._collectScroller = this._collectScroller.bind(this);
this._collectScrollerComponent = this._collectScrollerComponent.bind(this);
this.checkOverflow = this.checkOverflow.bind(this);
this._scrollElement = null;
this._autoHideScrollbar = null;
this._likelyTrackpadUser = null;
this._checkAgainForTrackpad = 0; // ts in milliseconds to recheck this._likelyTrackpadUser

this.state = {
leftIndicatorOffset: 0,
rightIndicatorOffset: 0,
};
}

moveToOrigin() {
if (!this._scrollElement) return;

this._scrollElement.scrollLeft = 0;
this._scrollElement.scrollTop = 0;
}

_collectScroller(scroller) {
if (scroller && !this._scrollElement) {
this._scrollElement = scroller;
private collectScroller = (scroller: HTMLDivElement): void => {
if (scroller && !this.scrollElement) {
this.scrollElement = scroller;
// Using the passive option to not block the main thread
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners
this._scrollElement.addEventListener("scroll", this.checkOverflow, { passive: true });
this.scrollElement.addEventListener("scroll", this.checkOverflow, { passive: true });
this.checkOverflow();
}
}

_collectScrollerComponent(autoHideScrollbar) {
this._autoHideScrollbar = autoHideScrollbar;
}
};

componentDidUpdate(prevProps) {
const prevLen = prevProps && prevProps.children && prevProps.children.length || 0;
const curLen = this.props.children && this.props.children.length || 0;
public componentDidUpdate(prevProps: IProps): void {
const prevLen = React.Children.count(prevProps.children);
const curLen = React.Children.count(this.props.children);
// check overflow only if amount of children changes.
// if we don't guard here, we end up with an infinite
// render > componentDidUpdate > checkOverflow > setState > render loop
Expand All @@ -81,62 +75,58 @@ export default class IndicatorScrollbar extends React.Component {
}
}

componentDidMount() {
public componentDidMount(): void {
this.checkOverflow();
}

checkOverflow() {
const hasTopOverflow = this._scrollElement.scrollTop > 0;
const hasBottomOverflow = this._scrollElement.scrollHeight >
(this._scrollElement.scrollTop + this._scrollElement.clientHeight);
const hasLeftOverflow = this._scrollElement.scrollLeft > 0;
const hasRightOverflow = this._scrollElement.scrollWidth >
(this._scrollElement.scrollLeft + this._scrollElement.clientWidth);
private checkOverflow = (): void => {
const hasTopOverflow = this.scrollElement.scrollTop > 0;
const hasBottomOverflow = this.scrollElement.scrollHeight >
(this.scrollElement.scrollTop + this.scrollElement.clientHeight);
const hasLeftOverflow = this.scrollElement.scrollLeft > 0;
const hasRightOverflow = this.scrollElement.scrollWidth >
(this.scrollElement.scrollLeft + this.scrollElement.clientWidth);

if (hasTopOverflow) {
this._scrollElement.classList.add("mx_IndicatorScrollbar_topOverflow");
this.scrollElement.classList.add("mx_IndicatorScrollbar_topOverflow");
} else {
this._scrollElement.classList.remove("mx_IndicatorScrollbar_topOverflow");
this.scrollElement.classList.remove("mx_IndicatorScrollbar_topOverflow");
}
if (hasBottomOverflow) {
this._scrollElement.classList.add("mx_IndicatorScrollbar_bottomOverflow");
this.scrollElement.classList.add("mx_IndicatorScrollbar_bottomOverflow");
} else {
this._scrollElement.classList.remove("mx_IndicatorScrollbar_bottomOverflow");
this.scrollElement.classList.remove("mx_IndicatorScrollbar_bottomOverflow");
}
if (hasLeftOverflow) {
this._scrollElement.classList.add("mx_IndicatorScrollbar_leftOverflow");
this.scrollElement.classList.add("mx_IndicatorScrollbar_leftOverflow");
} else {
this._scrollElement.classList.remove("mx_IndicatorScrollbar_leftOverflow");
this.scrollElement.classList.remove("mx_IndicatorScrollbar_leftOverflow");
}
if (hasRightOverflow) {
this._scrollElement.classList.add("mx_IndicatorScrollbar_rightOverflow");
this.scrollElement.classList.add("mx_IndicatorScrollbar_rightOverflow");
} else {
this._scrollElement.classList.remove("mx_IndicatorScrollbar_rightOverflow");
this.scrollElement.classList.remove("mx_IndicatorScrollbar_rightOverflow");
}

if (this.props.trackHorizontalOverflow) {
this.setState({
// Offset from absolute position of the container
leftIndicatorOffset: hasLeftOverflow ? `${this._scrollElement.scrollLeft}px` : '0',
leftIndicatorOffset: hasLeftOverflow ? `${this.scrollElement.scrollLeft}px` : '0',

// Negative because we're coming from the right
rightIndicatorOffset: hasRightOverflow ? `-${this._scrollElement.scrollLeft}px` : '0',
rightIndicatorOffset: hasRightOverflow ? `-${this.scrollElement.scrollLeft}px` : '0',
});
}
}

getScrollTop() {
return this._autoHideScrollbar.getScrollTop();
}
};

componentWillUnmount() {
if (this._scrollElement) {
this._scrollElement.removeEventListener("scroll", this.checkOverflow);
public componentWillUnmount(): void {
if (this.scrollElement) {
this.scrollElement.removeEventListener("scroll", this.checkOverflow);
}
}

onMouseWheel = (e) => {
if (this.props.verticalScrollsHorizontally && this._scrollElement) {
private onMouseWheel = (e: React.WheelEvent): void => {
if (this.props.verticalScrollsHorizontally && this.scrollElement) {
// xyThreshold is the amount of horizontal motion required for the component to
// ignore the vertical delta in a scroll. Used to stop trackpads from acting in
// strange ways. Should be positive.
Expand All @@ -150,19 +140,19 @@ export default class IndicatorScrollbar extends React.Component {
// for at least the next 1 minute.
const now = new Date().getTime();
if (Math.abs(e.deltaX) > 0) {
this._likelyTrackpadUser = true;
this._checkAgainForTrackpad = now + (1 * 60 * 1000);
this.likelyTrackpadUser = true;
this.checkAgainForTrackpad = now + (1 * 60 * 1000);
} else {
// if we haven't seen any horizontal scrolling for a while, assume
// the user might have plugged in a mousewheel
if (this._likelyTrackpadUser && now >= this._checkAgainForTrackpad) {
this._likelyTrackpadUser = false;
if (this.likelyTrackpadUser && now >= this.checkAgainForTrackpad) {
this.likelyTrackpadUser = false;
}
}

// don't mess with the horizontal scroll for trackpad users
// See https://github.com/vector-im/element-web/issues/10005
if (this._likelyTrackpadUser) {
if (this.likelyTrackpadUser) {
return;
}

Expand All @@ -178,13 +168,13 @@ export default class IndicatorScrollbar extends React.Component {

// noinspection JSSuspiciousNameCombination
const val = Math.abs(e.deltaY) < 25 ? (e.deltaY + additionalScroll) : e.deltaY;
this._scrollElement.scrollLeft += val * yRetention;
this.scrollElement.scrollLeft += val * yRetention;
}
}
};

render() {
// eslint-disable-next-line no-unused-vars
public render(): JSX.Element {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { children, trackHorizontalOverflow, verticalScrollsHorizontally, ...otherProps } = this.props;

const leftIndicatorStyle = { left: this.state.leftIndicatorOffset };
Expand All @@ -195,8 +185,8 @@ export default class IndicatorScrollbar extends React.Component {
? <div className="mx_IndicatorScrollbar_rightOverflowIndicator" style={rightIndicatorStyle} /> : null;

return (<AutoHideScrollbar
ref={this._collectScrollerComponent}
wrappedRef={this._collectScroller}
ref={this.autoHideScrollbar}
wrappedRef={this.collectScroller}
onWheel={this.onMouseWheel}
{...otherProps}
>
Expand Down
Loading