Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into joriks/eslint-config

This commit is contained in:
Jorik Schellekens 2020-07-20 16:22:32 +01:00
commit b3fa855bd8
454 changed files with 13522 additions and 17619 deletions

View file

@ -64,7 +64,6 @@ export default function AccessibleButton({
className,
...restProps
}: IProps) {
const newProps: IAccessibleButtonProps = restProps;
if (!disabled) {
newProps.onClick = onClick;
@ -119,7 +118,7 @@ export default function AccessibleButton({
AccessibleButton.defaultProps = {
element: 'div',
role: 'button',
tabIndex: "0",
tabIndex: 0,
};
AccessibleButton.displayName = "AccessibleButton";

View file

@ -16,21 +16,28 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import AccessibleButton from "./AccessibleButton";
import * as sdk from "../../../index";
import Tooltip from './Tooltip';
export default class AccessibleTooltipButton extends React.PureComponent {
static propTypes = {
...AccessibleButton.propTypes,
// The tooltip to render on hover
title: PropTypes.string.isRequired,
};
interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
title: string;
tooltip?: React.ReactNode;
tooltipClassName?: string;
}
state = {
hover: false,
};
interface IState {
hover: boolean;
}
export default class AccessibleTooltipButton extends React.PureComponent<ITooltipProps, IState> {
constructor(props: ITooltipProps) {
super(props);
this.state = {
hover: false,
};
}
onMouseOver = () => {
this.setState({
@ -38,25 +45,27 @@ export default class AccessibleTooltipButton extends React.PureComponent {
});
};
onMouseOut = () => {
onMouseLeave = () => {
this.setState({
hover: false,
});
};
render() {
const Tooltip = sdk.getComponent("elements.Tooltip");
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
const {title, children, ...props} = this.props;
const {title, tooltip, children, tooltipClassName, ...props} = this.props;
const tip = this.state.hover ? <Tooltip
className="mx_AccessibleTooltipButton_container"
tooltipClassName="mx_AccessibleTooltipButton_tooltip"
label={title}
tooltipClassName={classNames("mx_AccessibleTooltipButton_tooltip", tooltipClassName)}
label={tooltip || title}
/> : <div />;
return (
<AccessibleButton {...props} onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} aria-label={title}>
<AccessibleButton
{...props}
onMouseOver={this.onMouseOver}
onMouseLeave={this.onMouseLeave}
aria-label={title}
>
{ children }
{ tip }
</AccessibleButton>

View file

@ -58,18 +58,6 @@ export default createReactClass({
imgUrls.push(require("../../../../res/img/icon-email-user.svg"));
}
// Removing networks for now as they're not really supported
/*
var network;
if (this.props.networkUrl !== "") {
network = (
<div className="mx_AddressTile_network">
<BaseAvatar width={25} height={25} name={this.props.networkName} title="Riot" url={this.props.networkUrl} />
</div>
);
}
*/
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const TintableSvg = sdk.getComponent("elements.TintableSvg");

View file

@ -1,7 +1,7 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018, 2019 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.
@ -21,6 +21,7 @@ import PropTypes from 'prop-types';
import url from 'url';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig';
import WidgetUtils from "../../../utils/WidgetUtils";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
@ -76,6 +77,7 @@ export default class AppPermission extends React.Component {
}
render() {
const brand = SdkConfig.get().brand;
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar");
@ -96,7 +98,7 @@ export default class AppPermission extends React.Component {
<li>{_t("Your avatar URL")}</li>
<li>{_t("Your user ID")}</li>
<li>{_t("Your theme")}</li>
<li>{_t("Riot URL")}</li>
<li>{_t("%(brand)s URL", { brand })}</li>
<li>{_t("Room ID")}</li>
<li>{_t("Widget ID")}</li>
</ul>

View file

@ -1,40 +0,0 @@
/*
Copyright 2017 Vector Creations 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.
*/
import React from 'react';
import * as sdk from '../../../index';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
const CreateRoomButton = function(props) {
const ActionButton = sdk.getComponent('elements.ActionButton');
return (
<ActionButton action="view_create_room"
mouseOverAction={props.callout ? "callout_create_room" : null}
label={_t("Create new room")}
iconPath={require("../../../../res/img/icons-create-room.svg")}
size={props.size}
tooltip={props.tooltip}
/>
);
};
CreateRoomButton.propTypes = {
size: PropTypes.string,
tooltip: PropTypes.bool,
};
export default CreateRoomButton;

View file

@ -16,7 +16,7 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from '../../../languageHandler.js';
import {_t} from '../../../languageHandler';
import Field from "./Field";
import AccessibleButton from "./AccessibleButton";

View file

@ -50,7 +50,7 @@ interface IProps {
// to the user.
onValidate?: (input: IFieldState) => Promise<IValidationResult>;
// If specified, overrides the value returned by onValidate.
flagInvalid?: boolean;
forceValidity?: boolean;
// If specified, contents will appear as a tooltip on the element and
// validation feedback tooltips will be suppressed.
tooltipContent?: React.ReactNode;
@ -203,7 +203,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
public render() {
const {
element, prefixComponent, postfixComponent, className, onValidate, children,
tooltipContent, flagInvalid, tooltipClassName, list, ...inputProps} = this.props;
tooltipContent, forceValidity, tooltipClassName, list, ...inputProps} = this.props;
// Set some defaults for the <input> element
const ref = input => this.input = input;
@ -228,15 +228,15 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
postfixContainer = <span className="mx_Field_postfix">{postfixComponent}</span>;
}
const hasValidationFlag = flagInvalid !== null && flagInvalid !== undefined;
const hasValidationFlag = forceValidity !== null && forceValidity !== undefined;
const fieldClasses = classNames("mx_Field", `mx_Field_${this.props.element}`, className, {
// If we have a prefix element, leave the label always at the top left and
// don't animate it, as it looks a bit clunky and would add complexity to do
// properly.
mx_Field_labelAlwaysTopLeft: prefixComponent,
mx_Field_valid: onValidate && this.state.valid === true,
mx_Field_valid: hasValidationFlag ? forceValidity : onValidate && this.state.valid === true,
mx_Field_invalid: hasValidationFlag
? flagInvalid
? !forceValidity
: onValidate && this.state.valid === false,
});
@ -248,6 +248,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
tooltipClassName={classNames("mx_Field_tooltip", tooltipClassName)}
visible={(this.state.focused && this.props.forceTooltipVisible) || this.state.feedbackVisible}
label={tooltipContent || this.state.feedback}
forceOnRight
/>;
}

View file

@ -27,18 +27,15 @@ export default createReactClass({
const h = this.props.h || 16;
const imgClass = this.props.imgClassName || "";
let divClass;
let imageSource;
if (SettingsStore.isFeatureEnabled('feature_new_spinner')) {
divClass = "mx_InlineSpinner mx_Spinner_spin";
imageSource = require("../../../../res/img/spinner.svg");
} else {
divClass = "mx_InlineSpinner";
imageSource = require("../../../../res/img/spinner.gif");
}
return (
<div className={divClass}>
<div className="mx_InlineSpinner">
<img
src={imageSource}
width={w}

View file

@ -1,336 +0,0 @@
/*
Copyright 2019 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 React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
const InteractiveTooltipContainerId = "mx_InteractiveTooltip_Container";
// If the distance from tooltip to window edge is below this value, the tooltip
// will flip around to the other side of the target.
const MIN_SAFE_DISTANCE_TO_WINDOW_EDGE = 20;
function getOrCreateContainer() {
let container = document.getElementById(InteractiveTooltipContainerId);
if (!container) {
container = document.createElement("div");
container.id = InteractiveTooltipContainerId;
document.body.appendChild(container);
}
return container;
}
function isInRect(x, y, rect) {
const { top, right, bottom, left } = rect;
return x >= left && x <= right && y >= top && y <= bottom;
}
/**
* Returns the positive slope of the diagonal of the rect.
*
* @param {DOMRect} rect
* @return {integer}
*/
function getDiagonalSlope(rect) {
const { top, right, bottom, left } = rect;
return (bottom - top) / (right - left);
}
function isInUpperLeftHalf(x, y, rect) {
const { bottom, left } = rect;
// Negative slope because Y values grow downwards and for this case, the
// diagonal goes from larger to smaller Y values.
const diagonalSlope = getDiagonalSlope(rect) * -1;
return isInRect(x, y, rect) && (y <= bottom + diagonalSlope * (x - left));
}
function isInLowerRightHalf(x, y, rect) {
const { bottom, left } = rect;
// Negative slope because Y values grow downwards and for this case, the
// diagonal goes from larger to smaller Y values.
const diagonalSlope = getDiagonalSlope(rect) * -1;
return isInRect(x, y, rect) && (y >= bottom + diagonalSlope * (x - left));
}
function isInUpperRightHalf(x, y, rect) {
const { top, left } = rect;
// Positive slope because Y values grow downwards and for this case, the
// diagonal goes from smaller to larger Y values.
const diagonalSlope = getDiagonalSlope(rect) * 1;
return isInRect(x, y, rect) && (y <= top + diagonalSlope * (x - left));
}
function isInLowerLeftHalf(x, y, rect) {
const { top, left } = rect;
// Positive slope because Y values grow downwards and for this case, the
// diagonal goes from smaller to larger Y values.
const diagonalSlope = getDiagonalSlope(rect) * 1;
return isInRect(x, y, rect) && (y >= top + diagonalSlope * (x - left));
}
/*
* This style of tooltip takes a "target" element as its child and centers the
* tooltip along one edge of the target.
*/
export default class InteractiveTooltip extends React.Component {
static propTypes = {
// Content to show in the tooltip
content: PropTypes.node.isRequired,
// Function to call when visibility of the tooltip changes
onVisibilityChange: PropTypes.func,
// flag to forcefully hide this tooltip
forceHidden: PropTypes.bool,
};
constructor() {
super();
this.state = {
contentRect: null,
visible: false,
};
}
componentDidUpdate() {
// Whenever this passthrough component updates, also render the tooltip
// in a separate DOM tree. This allows the tooltip content to participate
// the normal React rendering cycle: when this component re-renders, the
// tooltip content re-renders.
// Once we upgrade to React 16, this could be done a bit more naturally
// using the portals feature instead.
this.renderTooltip();
}
componentWillUnmount() {
document.removeEventListener("mousemove", this.onMouseMove);
}
collectContentRect = (element) => {
// We don't need to clean up when unmounting, so ignore
if (!element) return;
this.setState({
contentRect: element.getBoundingClientRect(),
});
}
collectTarget = (element) => {
this.target = element;
}
canTooltipFitAboveTarget() {
const { contentRect } = this.state;
const targetRect = this.target.getBoundingClientRect();
const targetTop = targetRect.top + window.pageYOffset;
return (
!contentRect ||
(targetTop - contentRect.height > MIN_SAFE_DISTANCE_TO_WINDOW_EDGE)
);
}
onMouseMove = (ev) => {
const { clientX: x, clientY: y } = ev;
const { contentRect } = this.state;
const targetRect = this.target.getBoundingClientRect();
// When moving the mouse from the target to the tooltip, we create a
// safe area that includes the tooltip, the target, and the trapezoid
// ABCD between them:
// ┌───────────┐
// │ │
// │ │
// A └───E───F───┘ B
// V
// ┌─┐
// │ │
// C└─┘D
//
// As long as the mouse remains inside the safe area, the tooltip will
// stay open.
const buffer = 50;
if (isInRect(x, y, targetRect)) {
return;
}
if (this.canTooltipFitAboveTarget()) {
const contentRectWithBuffer = {
top: contentRect.top - buffer,
right: contentRect.right + buffer,
bottom: contentRect.bottom,
left: contentRect.left - buffer,
};
const trapezoidLeft = {
top: contentRect.bottom,
right: targetRect.left,
bottom: targetRect.bottom,
left: contentRect.left - buffer,
};
const trapezoidCenter = {
top: contentRect.bottom,
right: targetRect.right,
bottom: targetRect.bottom,
left: targetRect.left,
};
const trapezoidRight = {
top: contentRect.bottom,
right: contentRect.right + buffer,
bottom: targetRect.bottom,
left: targetRect.right,
};
if (
isInRect(x, y, contentRectWithBuffer) ||
isInUpperRightHalf(x, y, trapezoidLeft) ||
isInRect(x, y, trapezoidCenter) ||
isInUpperLeftHalf(x, y, trapezoidRight)
) {
return;
}
} else {
const contentRectWithBuffer = {
top: contentRect.top,
right: contentRect.right + buffer,
bottom: contentRect.bottom + buffer,
left: contentRect.left - buffer,
};
const trapezoidLeft = {
top: targetRect.top,
right: targetRect.left,
bottom: contentRect.top,
left: contentRect.left - buffer,
};
const trapezoidCenter = {
top: targetRect.top,
right: targetRect.right,
bottom: contentRect.top,
left: targetRect.left,
};
const trapezoidRight = {
top: targetRect.top,
right: contentRect.right + buffer,
bottom: contentRect.top,
left: targetRect.right,
};
if (
isInRect(x, y, contentRectWithBuffer) ||
isInLowerRightHalf(x, y, trapezoidLeft) ||
isInRect(x, y, trapezoidCenter) ||
isInLowerLeftHalf(x, y, trapezoidRight)
) {
return;
}
}
this.hideTooltip();
}
onTargetMouseOver = (ev) => {
this.showTooltip();
}
showTooltip() {
// Don't enter visible state if we haven't collected the target yet
if (!this.target) {
return;
}
this.setState({
visible: true,
});
if (this.props.onVisibilityChange) {
this.props.onVisibilityChange(true);
}
document.addEventListener("mousemove", this.onMouseMove);
}
hideTooltip() {
this.setState({
visible: false,
});
if (this.props.onVisibilityChange) {
this.props.onVisibilityChange(false);
}
document.removeEventListener("mousemove", this.onMouseMove);
}
renderTooltip() {
const { contentRect, visible } = this.state;
if (this.props.forceHidden === true || !visible) {
ReactDOM.render(null, getOrCreateContainer());
return null;
}
const targetRect = this.target.getBoundingClientRect();
// The window X and Y offsets are to adjust position when zoomed in to page
const targetLeft = targetRect.left + window.pageXOffset;
const targetBottom = targetRect.bottom + window.pageYOffset;
const targetTop = targetRect.top + window.pageYOffset;
// Place the tooltip above the target by default. If we find that the
// tooltip content would extend past the safe area towards the window
// edge, flip around to below the target.
const position = {};
let chevronFace = null;
if (this.canTooltipFitAboveTarget()) {
position.bottom = window.innerHeight - targetTop;
chevronFace = "bottom";
} else {
position.top = targetBottom;
chevronFace = "top";
}
// Center the tooltip horizontally with the target's center.
position.left = targetLeft + targetRect.width / 2;
const chevron = <div className={"mx_InteractiveTooltip_chevron_" + chevronFace} />;
const menuClasses = classNames({
'mx_InteractiveTooltip': true,
'mx_InteractiveTooltip_withChevron_top': chevronFace === 'top',
'mx_InteractiveTooltip_withChevron_bottom': chevronFace === 'bottom',
});
const menuStyle = {};
if (contentRect) {
menuStyle.left = `-${contentRect.width / 2}px`;
}
const tooltip = <div className="mx_InteractiveTooltip_wrapper" style={{...position}}>
<div className={menuClasses}
style={menuStyle}
ref={this.collectContentRect}
>
{chevron}
{this.props.content}
</div>
</div>;
ReactDOM.render(tooltip, getOrCreateContainer());
}
render() {
// We use `cloneElement` here to append some props to the child content
// without using a wrapper element which could disrupt layout.
return React.cloneElement(this.props.children, {
ref: this.collectTarget,
onMouseOver: this.onTargetMouseOver,
});
}
}

View file

@ -17,10 +17,10 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import SettingsStore from "../../../settings/SettingsStore";
import AccessibleTooltipButton from "./AccessibleTooltipButton";
export default class ManageIntegsButton extends React.Component {
constructor(props) {
@ -45,9 +45,8 @@ export default class ManageIntegsButton extends React.Component {
render() {
let integrationsButton = <div />;
if (IntegrationManagers.sharedInstance().hasManager()) {
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
integrationsButton = (
<AccessibleButton
<AccessibleTooltipButton
className='mx_RoomHeader_button mx_RoomHeader_manageIntegsButton'
title={_t("Manage Integrations")}
onClick={this.onManageIntegrations}

View file

@ -1,6 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
@ -23,6 +23,7 @@ import { _t } from '../../../languageHandler';
import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
import * as sdk from "../../../index";
import {MatrixEvent} from "matrix-js-sdk";
import {isValid3pidInvite} from "../../../RoomInvite";
export default createReactClass({
displayName: 'MemberEventListSummary',
@ -284,6 +285,9 @@ export default createReactClass({
_getTransition: function(e) {
if (e.mxEvent.getType() === 'm.room.third_party_invite') {
// Handle 3pid invites the same as invites so they get bundled together
if (!isValid3pidInvite(e.mxEvent)) {
return 'invite_withdrawal';
}
return 'invited';
}

View file

@ -1,39 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 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 React from "react";
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
export default createReactClass({
displayName: 'ProgressBar',
propTypes: {
value: PropTypes.number,
max: PropTypes.number,
},
render: function() {
// Would use an HTML5 progress tag but if that doesn't animate if you
// use the HTML attributes rather than styles
const progressStyle = {
width: ((this.props.value / this.props.max) * 100)+"%",
};
return (
<div className="mx_ProgressBar"><div className="mx_ProgressBar_fill" style={progressStyle}></div></div>
);
},
});

View file

@ -0,0 +1,28 @@
/*
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 React from "react";
interface IProps {
value: number;
max: number;
}
const ProgressBar: React.FC<IProps> = ({value, max}) => {
return <progress className="mx_ProgressBar" max={max} value={value} />;
};
export default ProgressBar;

View file

@ -27,6 +27,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import escapeHtml from "escape-html";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {Action} from "../../../dispatcher/actions";
import sanitizeHtml from "sanitize-html";
// This component does no cycle detection, simply because the only way to make such a cycle would be to
// craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would
@ -92,7 +93,21 @@ export default class ReplyThread extends React.Component {
// Part of Replies fallback support
static stripHTMLReply(html) {
return html.replace(/^<mx-reply>[\s\S]+?<\/mx-reply>/, '');
// Sanitize the original HTML for inclusion in <mx-reply>. We allow
// any HTML, since the original sender could use special tags that we
// don't recognize, but want to pass along to any recipients who do
// recognize them -- recipients should be sanitizing before displaying
// anyways. However, we sanitize to 1) remove any mx-reply, so that we
// don't generate a nested mx-reply, and 2) make sure that the HTML is
// properly formatted (e.g. tags are closed where necessary)
return sanitizeHtml(
html,
{
allowedTags: false, // false means allow everything
allowedAttributes: false,
exclusiveFilter: (frame) => frame.tag === "mx-reply",
},
);
}
// Part of Replies fallback support
@ -102,15 +117,19 @@ export default class ReplyThread extends React.Component {
let {body, formatted_body: html} = ev.getContent();
if (this.getParentEventId(ev)) {
if (body) body = this.stripPlainReply(body);
if (html) html = this.stripHTMLReply(html);
}
if (!body) body = ""; // Always ensure we have a body, for reasons.
// Escape the body to use as HTML below.
// We also run a nl2br over the result to fix the fallback representation. We do this
// after converting the text to safe HTML to avoid user-provided BR's from being converted.
if (!html) html = escapeHtml(body).replace(/\n/g, '<br/>');
if (html) {
// sanitize the HTML before we put it in an <mx-reply>
html = this.stripHTMLReply(html);
} else {
// Escape the body to use as HTML below.
// We also run a nl2br over the result to fix the fallback representation. We do this
// after converting the text to safe HTML to avoid user-provided BR's from being converted.
html = escapeHtml(body).replace(/\n/g, '<br/>');
}
// dev note: do not rely on `body` being safe for HTML usage below.

View file

@ -21,18 +21,15 @@ import {_t} from "../../../languageHandler";
import SettingsStore from "../../../settings/SettingsStore";
const Spinner = ({w = 32, h = 32, imgClassName, message}) => {
let divClass;
let imageSource;
if (SettingsStore.isFeatureEnabled('feature_new_spinner')) {
divClass = "mx_Spinner mx_Spinner_spin";
imageSource = require("../../../../res/img/spinner.svg");
} else {
divClass = "mx_Spinner";
imageSource = require("../../../../res/img/spinner.gif");
}
return (
<div className={divClass}>
<div className="mx_Spinner">
{ message && <React.Fragment><div className="mx_Spinner_Msg">{ message}</div>&nbsp;</React.Fragment> }
<img
src={imageSource}

View file

@ -18,6 +18,7 @@ import React from 'react';
import classnames from 'classnames';
interface IProps extends React.InputHTMLAttributes<HTMLInputElement> {
outlined?: boolean;
}
interface IState {
@ -29,7 +30,7 @@ export default class StyledRadioButton extends React.PureComponent<IProps, IStat
};
public render() {
const { children, className, disabled, ...otherProps } = this.props;
const { children, className, disabled, outlined, ...otherProps } = this.props;
const _className = classnames(
'mx_RadioButton',
className,
@ -37,12 +38,13 @@ export default class StyledRadioButton extends React.PureComponent<IProps, IStat
"mx_RadioButton_disabled": disabled,
"mx_RadioButton_enabled": !disabled,
"mx_RadioButton_checked": this.props.checked,
"mx_RadioButton_outlined": outlined,
});
return <label className={_className}>
<input type='radio' disabled={disabled} {...otherProps} />
{/* Used to render the radio button circle */}
<div><div></div></div>
<span>{children}</span>
<div><div /></div>
<div className="mx_RadioButton_content">{children}</div>
<div className="mx_RadioButton_spacer" />
</label>;
}

View file

@ -32,24 +32,25 @@ interface IProps<T extends string> {
className?: string;
definitions: IDefinition<T>[];
value?: T; // if not provided no options will be selected
outlined?: boolean;
onChange(newValue: T);
}
function StyledRadioGroup<T extends string>({name, definitions, value, className, onChange}: IProps<T>) {
function StyledRadioGroup<T extends string>({name, definitions, value, className, outlined, onChange}: IProps<T>) {
const _onChange = e => {
onChange(e.target.value);
};
return <React.Fragment>
{definitions.map(d => <React.Fragment>
{definitions.map(d => <React.Fragment key={d.value}>
<StyledRadioButton
key={d.value}
className={classNames(className, d.className)}
onChange={_onChange}
checked={d.value === value}
name={name}
value={d.value}
disabled={d.disabled}
outlined={outlined}
>
{d.label}
</StyledRadioButton>

View file

@ -29,6 +29,7 @@ import FlairStore from '../../../stores/FlairStore';
import GroupStore from '../../../stores/GroupStore';
import TagOrderStore from '../../../stores/TagOrderStore';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import AccessibleButton from "./AccessibleButton";
// A class for a child of TagPanel (possibly wrapped in a DNDTagTile) that represents
// a thing to click on for the user to filter the visible rooms in the RoomList to:
@ -114,7 +115,7 @@ export default createReactClass({
this.setState({ hover: true });
},
onMouseOut: function() {
onMouseLeave: function() {
this.setState({ hover: false });
},
@ -130,7 +131,7 @@ export default createReactClass({
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const profile = this.state.profile || {};
const name = profile.name || this.props.tag;
const avatarHeight = 40;
const avatarHeight = 32;
const httpUrl = profile.avatarUrl ? this.context.mxcUrlToHttp(
profile.avatarUrl, avatarHeight, avatarHeight, "crop",
@ -151,11 +152,14 @@ export default createReactClass({
badgeElement = (<div className={badgeClasses}>{FormattingUtils.formatCount(badge.count)}</div>);
}
// FIXME: this ought to use AccessibleButton for a11y but that causes onMouseOut/onMouseOver to fire too much
const contextButton = this.state.hover || this.props.menuDisplayed ?
<div className="mx_TagTile_context_button" onClick={this.openMenu} ref={this.props.contextMenuButtonRef}>
<AccessibleButton
className="mx_TagTile_context_button"
onClick={this.openMenu}
ref={this.props.contextMenuButtonRef}
>
{"\u00B7\u00B7\u00B7"}
</div> : <div ref={this.props.contextMenuButtonRef} />;
</AccessibleButton> : <div ref={this.props.contextMenuButtonRef} />;
const AccessibleTooltipButton = sdk.getComponent("elements.AccessibleTooltipButton");
@ -168,7 +172,7 @@ export default createReactClass({
<div
className="mx_TagTile_avatar"
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
onMouseLeave={this.onMouseLeave}
>
<BaseAvatar
name={name}

View file

@ -37,7 +37,7 @@ export default class TextWithTooltip extends React.Component {
this.setState({hover: true});
};
onMouseOut = () => {
onMouseLeave = () => {
this.setState({hover: false});
};
@ -45,7 +45,7 @@ export default class TextWithTooltip extends React.Component {
const Tooltip = sdk.getComponent("elements.Tooltip");
return (
<span onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} className={this.props.class}>
<span onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave} className={this.props.class}>
{this.props.children}
<Tooltip
label={this.props.tooltip}

View file

@ -18,18 +18,15 @@ limitations under the License.
*/
import React, { Component } from 'react';
import React, {Component, CSSProperties} from 'react';
import ReactDOM from 'react-dom';
import dis from '../../../dispatcher/dispatcher';
import classNames from 'classnames';
import { ViewTooltipPayload } from '../../../dispatcher/payloads/ViewTooltipPayload';
import { Action } from '../../../dispatcher/actions';
const MIN_TOOLTIP_HEIGHT = 25;
interface IProps {
// Class applied to the element used to position the tooltip
className: string;
className?: string;
// Class applied to the tooltip itself
tooltipClassName?: string;
// Whether the tooltip is visible or hidden.
@ -38,6 +35,7 @@ interface IProps {
visible?: boolean;
// the react element to put into the tooltip
label: React.ReactNode;
forceOnRight?: boolean;
}
export default class Tooltip extends React.Component<IProps> {
@ -68,18 +66,12 @@ export default class Tooltip extends React.Component<IProps> {
// Remove the wrapper element, as the tooltip has finished using it
public componentWillUnmount() {
dis.dispatch<ViewTooltipPayload>({
action: Action.ViewTooltip,
tooltip: null,
parent: null,
});
ReactDOM.unmountComponentAtNode(this.tooltipContainer);
document.body.removeChild(this.tooltipContainer);
window.removeEventListener('scroll', this.renderTooltip, true);
}
private updatePosition(style: {[key: string]: any}) {
private updatePosition(style: CSSProperties) {
const parentBox = this.parent.getBoundingClientRect();
let offset = 0;
if (parentBox.height > MIN_TOOLTIP_HEIGHT) {
@ -89,8 +81,14 @@ export default class Tooltip extends React.Component<IProps> {
// we need so that we're still centered.
offset = Math.floor(parentBox.height - MIN_TOOLTIP_HEIGHT);
}
style.top = (parentBox.top - 2) + window.pageYOffset + offset;
style.left = 6 + parentBox.right + window.pageXOffset;
if (!this.props.forceOnRight && parentBox.right > window.innerWidth / 2) {
style.right = window.innerWidth - parentBox.right - window.pageXOffset - 8;
} else {
style.left = parentBox.right + window.pageXOffset + 6;
}
return style;
}
@ -99,7 +97,6 @@ export default class Tooltip extends React.Component<IProps> {
// positioned, also taking into account any window zoom
// NOTE: The additional 6 pixels for the left position, is to take account of the
// tooltips chevron
const parent = ReactDOM.findDOMNode(this).parentNode as Element;
const style = this.updatePosition({});
// Hide the entire container when not visible. This prevents flashing of the tooltip
// if it is not meant to be visible on first mount.
@ -119,19 +116,12 @@ export default class Tooltip extends React.Component<IProps> {
// Render the tooltip manually, as we wish it not to be rendered within the parent
this.tooltip = ReactDOM.render<Element>(tooltip, this.tooltipContainer);
// Tell the roomlist about us so it can manipulate us if it wishes
dis.dispatch<ViewTooltipPayload>({
action: Action.ViewTooltip,
tooltip: this.tooltip,
parent: parent,
});
};
public render() {
// Render a placeholder
return (
<div className={this.props.className} >
<div className={this.props.className}>
</div>
);
}

View file

@ -34,7 +34,7 @@ export default createReactClass({
});
},
onMouseOut: function() {
onMouseLeave: function() {
this.setState({
hover: false,
});
@ -48,7 +48,7 @@ export default createReactClass({
label={this.props.helpText}
/> : <div />;
return (
<div className="mx_TooltipButton" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} >
<div className="mx_TooltipButton" onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave}>
?
{ tip }
</div>