Merge branch 'develop' into travis/room-list/preview-copy

This commit is contained in:
Travis Ralston 2020-06-26 07:29:49 -06:00
commit 67cc84d00d
46 changed files with 1035 additions and 695 deletions

View file

@ -18,9 +18,7 @@ import React from 'react';
import * as sdk from '../../../index';
import SdkConfig from '../../../SdkConfig';
import AuthPage from "./AuthPage";
import * as Matrix from "matrix-js-sdk";
import {_td} from "../../../languageHandler";
import PlatformPeg from "../../../PlatformPeg";
// translatable strings for Welcome pages
_td("Sign in with SSO");
@ -39,15 +37,6 @@ export default class Welcome extends React.PureComponent {
pageUrl = 'welcome.html';
}
const {hsUrl, isUrl} = this.props.serverConfig;
const tmpClient = Matrix.createClient({
baseUrl: hsUrl,
idBaseUrl: isUrl,
});
const plaf = PlatformPeg.get();
const callbackUrl = plaf.getSSOCallbackUrl(tmpClient.getHomeserverUrl(), tmpClient.getIdentityServerUrl(),
this.props.fragmentAfterLogin);
return (
<AuthPage>
<div className="mx_Welcome">
@ -55,8 +44,8 @@ export default class Welcome extends React.PureComponent {
className="mx_WelcomePage"
url={pageUrl}
replaceMap={{
"$riot:ssoUrl": tmpClient.getSsoLoginUrl(callbackUrl.toString(), "sso"),
"$riot:casUrl": tmpClient.getSsoLoginUrl(callbackUrl.toString(), "cas"),
"$riot:ssoUrl": "#/start_sso",
"$riot:casUrl": "#/start_cas",
}}
/>
<LanguageSelector />

View file

@ -29,7 +29,7 @@ import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
import AppPermission from './AppPermission';
import AppWarning from './AppWarning';
import MessageSpinner from './MessageSpinner';
import Spinner from './Spinner';
import WidgetUtils from '../../../utils/WidgetUtils';
import dis from '../../../dispatcher/dispatcher';
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
@ -740,7 +740,7 @@ export default class AppTile extends React.Component {
if (this.props.show) {
const loadingElement = (
<div className="mx_AppLoading_spinner_fadeIn">
<MessageSpinner msg='Loading...' />
<Spinner message={_t("Loading...")} />
</div>
);
if (!this.state.hasPermissionToLoad) {

View file

@ -16,6 +16,8 @@ limitations under the License.
import React from "react";
import createReactClass from 'create-react-class';
import {_t} from "../../../languageHandler";
import SettingsStore from "../../../settings/SettingsStore";
export default createReactClass({
displayName: 'InlineSpinner',
@ -25,9 +27,25 @@ 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="mx_InlineSpinner">
<img src={require("../../../../res/img/spinner.gif")} width={w} height={h} className={imgClass} />
<div className={divClass}>
<img
src={imageSource}
width={w}
height={h}
className={imgClass}
aria-label={_t("Loading...")}
/>
</div>
);
},

View file

@ -1,35 +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 createReactClass from 'create-react-class';
export default createReactClass({
displayName: 'MessageSpinner',
render: function() {
const w = this.props.w || 32;
const h = this.props.h || 32;
const imgClass = this.props.imgClassName || "";
const msg = this.props.msg || "Loading...";
return (
<div className="mx_Spinner">
<div className="mx_Spinner_Msg">{ msg }</div>&nbsp;
<img src={require("../../../../res/img/spinner.gif")} width={w} height={h} className={imgClass} />
</div>
);
},
});

View file

@ -30,6 +30,7 @@ interface IProps {
isExplicit?: boolean;
// XXX: once design replaces all toggles make this the default
useCheckbox?: boolean;
disabled?: boolean;
onChange?(checked: boolean): void;
}
@ -78,14 +79,23 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
else label = _t(label);
if (this.props.useCheckbox) {
return <StyledCheckbox checked={this.state.value} onChange={this.checkBoxOnChange} disabled={!canChange} >
return <StyledCheckbox
checked={this.state.value}
onChange={this.checkBoxOnChange}
disabled={this.props.disabled || !canChange}
>
{label}
</StyledCheckbox>;
} else {
return (
<div className="mx_SettingsFlag">
<span className="mx_SettingsFlag_label">{label}</span>
<ToggleSwitch checked={this.state.value} onChange={this.onChange} disabled={!canChange} aria-label={label} />
<ToggleSwitch
checked={this.state.value}
onChange={this.onChange}
disabled={this.props.disabled || !canChange}
aria-label={label}
/>
</div>
);
}

View file

@ -16,19 +16,39 @@ limitations under the License.
*/
import React from "react";
import createReactClass from 'create-react-class';
import PropTypes from "prop-types";
import {_t} from "../../../languageHandler";
import SettingsStore from "../../../settings/SettingsStore";
export default createReactClass({
displayName: 'Spinner',
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");
}
render: function() {
const w = this.props.w || 32;
const h = this.props.h || 32;
const imgClass = this.props.imgClassName || "";
return (
<div className="mx_Spinner">
<img src={require("../../../../res/img/spinner.gif")} width={w} height={h} className={imgClass} />
</div>
);
},
});
return (
<div className={divClass}>
{ message && <React.Fragment><div className="mx_Spinner_Msg">{ message}</div>&nbsp;</React.Fragment> }
<img
src={imageSource}
width={w}
height={h}
className={imgClassName}
aria-label={_t("Loading...")}
/>
</div>
);
};
Spinner.propTypes = {
w: PropTypes.number,
h: PropTypes.number,
imgClassName: PropTypes.string,
message: PropTypes.node,
};
export default Spinner;

View file

@ -0,0 +1,61 @@
/*
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";
import classNames from "classnames";
import StyledRadioButton from "./StyledRadioButton";
interface IDefinition<T extends string> {
value: T;
className?: string;
disabled?: boolean;
label: React.ReactChild;
description?: React.ReactChild;
}
interface IProps<T extends string> {
name: string;
className?: string;
definitions: IDefinition<T>[];
value?: T; // if not provided no options will be selected
onChange(newValue: T);
}
function StyledRadioGroup<T extends string>({name, definitions, value, className, onChange}: IProps<T>) {
const _onChange = e => {
onChange(e.target.value);
};
return <React.Fragment>
{definitions.map(d => <React.Fragment>
<StyledRadioButton
key={d.value}
className={classNames(className, d.className)}
onChange={_onChange}
checked={d.value === value}
name={name}
value={d.value}
disabled={d.disabled}
>
{d.label}
</StyledRadioButton>
{d.description}
</React.Fragment>)}
</React.Fragment>;
}
export default StyledRadioGroup;

View file

@ -22,6 +22,7 @@ import MFileBody from './MFileBody';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { decryptFile } from '../../../utils/DecryptFile';
import { _t } from '../../../languageHandler';
import InlineSpinner from '../elements/InlineSpinner';
export default class MAudioBody extends React.Component {
constructor(props) {
@ -94,7 +95,7 @@ export default class MAudioBody extends React.Component {
// Not sure how tall the audio player is so not sure how tall it should actually be.
return (
<span className="mx_MAudioBody">
<img src={require("../../../../res/img/spinner.gif")} alt={content.body} width="16" height="16" />
<InlineSpinner />
</span>
);
}

View file

@ -26,6 +26,7 @@ import { decryptFile } from '../../../utils/DecryptFile';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import InlineSpinner from '../elements/InlineSpinner';
export default class MImageBody extends React.Component {
static propTypes = {
@ -365,12 +366,7 @@ export default class MImageBody extends React.Component {
// e2e image hasn't been decrypted yet
if (content.file !== undefined && this.state.decryptedUrl === null) {
placeholder = <img
src={require("../../../../res/img/spinner.gif")}
alt={content.body}
width="32"
height="32"
/>;
placeholder = <InlineSpinner w={32} h={32} />;
} else if (!this.state.imgLoaded) {
// Deliberately, getSpinner is left unimplemented here, MStickerBody overides
placeholder = this.getPlaceholder();

View file

@ -23,6 +23,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { decryptFile } from '../../../utils/DecryptFile';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
import InlineSpinner from '../elements/InlineSpinner';
export default createReactClass({
displayName: 'MVideoBody',
@ -147,7 +148,7 @@ export default createReactClass({
return (
<span className="mx_MVideoBody">
<div className="mx_MImageBody_thumbnail mx_MImageBody_thumbnail_spinner">
<img src={require("../../../../res/img/spinner.gif")} alt={content.body} width="16" height="16" />
<InlineSpinner />
</div>
</span>
);

View file

@ -141,6 +141,20 @@ export default class NotificationBadge extends React.PureComponent<IProps, IStat
}
}
export class StaticNotificationState extends EventEmitter implements INotificationState {
constructor(public symbol: string, public count: number, public color: NotificationColor) {
super();
}
public static forCount(count: number, color: NotificationColor): StaticNotificationState {
return new StaticNotificationState(null, count, color);
}
public static forSymbol(symbol: string, color: NotificationColor): StaticNotificationState {
return new StaticNotificationState(symbol, 0, color);
}
}
export class RoomNotificationState extends EventEmitter implements IDestroyable, INotificationState {
private _symbol: string;
private _count: number;

View file

@ -70,6 +70,7 @@ interface IProps {
interface IState {
notificationState: ListNotificationState;
menuDisplayed: boolean;
isResizing: boolean;
}
export default class RoomSublist2 extends React.Component<IProps, IState> {
@ -82,6 +83,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
this.state = {
notificationState: new ListNotificationState(this.props.isInvite, this.props.tagId),
menuDisplayed: false,
isResizing: false,
};
this.state.notificationState.setRooms(this.props.rooms);
}
@ -111,13 +113,21 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
this.forceUpdate(); // because the layout doesn't trigger a re-render
};
private onResizeStart = () => {
this.setState({isResizing: true});
};
private onResizeStop = () => {
this.setState({isResizing: false});
};
private onShowAllClick = () => {
this.props.layout.visibleTiles = this.props.layout.tilesWithPadding(this.numTiles, MAX_PADDING_HEIGHT);
this.forceUpdate(); // because the layout doesn't trigger a re-render
};
private onShowLessClick = () => {
this.props.layout.visibleTiles = this.props.layout.minVisibleTiles;
this.props.layout.visibleTiles = this.props.layout.defaultVisibleTiles;
this.forceUpdate(); // because the layout doesn't trigger a re-render
};
@ -320,8 +330,8 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
<span>{this.props.label}</span>
</AccessibleButton>
{this.renderMenu()}
{this.props.isMinimized ? null : addRoomButton}
{this.props.isMinimized ? null : badgeContainer}
{this.props.isMinimized ? null : addRoomButton}
</div>
{this.props.isMinimized ? badgeContainer : null}
{this.props.isMinimized ? addRoomButton : null}
@ -356,6 +366,12 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
const nVisible = Math.floor(layout.visibleTiles);
const visibleTiles = tiles.slice(0, nVisible);
const maxTilesFactored = layout.tilesWithResizerBoxFactor(tiles.length);
const showMoreBtnClasses = classNames({
'mx_RoomSublist2_showNButton': true,
'mx_RoomSublist2_isCutting': this.state.isResizing && layout.visibleTiles < maxTilesFactored,
});
// If we're hiding rooms, show a 'show more' button to the user. This button
// floats above the resize handle, if we have one present. If the user has all
// tiles visible, it becomes 'show less'.
@ -370,7 +386,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
);
if (this.props.isMinimized) showMoreText = null;
showNButton = (
<div onClick={this.onShowAllClick} className='mx_RoomSublist2_showNButton'>
<div onClick={this.onShowAllClick} className={showMoreBtnClasses}>
<span className='mx_RoomSublist2_showMoreButtonChevron mx_RoomSublist2_showNButtonChevron'>
{/* set by CSS masking */}
</span>
@ -386,7 +402,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
);
if (this.props.isMinimized) showLessText = null;
showNButton = (
<div onClick={this.onShowLessClick} className='mx_RoomSublist2_showNButton'>
<div onClick={this.onShowLessClick} className={showMoreBtnClasses}>
<span className='mx_RoomSublist2_showLessButtonChevron mx_RoomSublist2_showNButtonChevron'>
{/* set by CSS masking */}
</span>
@ -432,6 +448,8 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
resizeHandles={handles}
onResize={this.onResize}
className="mx_RoomSublist2_resizeBox"
onResizeStart={this.onResizeStart}
onResizeStop={this.onResizeStop}
>
{visibleTiles}
{showNButton}

View file

@ -39,12 +39,11 @@ export default class NotificationsSettingsTab extends React.Component {
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount() { // eslint-disable-line camelcase
Notifier.getSoundForRoom(this.props.roomId).then((soundData) => {
if (!soundData) {
return;
}
this.setState({currentSound: soundData.name || soundData.url});
});
const soundData = Notifier.getSoundForRoom(this.props.roomId);
if (!soundData) {
return;
}
this.setState({currentSound: soundData.name || soundData.url});
this._soundUpload = createRef();
}

View file

@ -33,6 +33,7 @@ import StyledCheckbox from '../../../elements/StyledCheckbox';
import SettingsFlag from '../../../elements/SettingsFlag';
import Field from '../../../elements/Field';
import EventTilePreview from '../../../elements/EventTilePreview';
import StyledRadioGroup from "../../../elements/StyledRadioGroup";
interface IProps {
}
@ -116,8 +117,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
};
}
private onThemeChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
const newTheme = e.target.value;
private onThemeChange = (newTheme: string): void => {
if (this.state.theme === newTheme) return;
// doing getValue in the .catch will still return the value we failed to set,
@ -277,19 +277,18 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
<div className="mx_SettingsTab_section mx_AppearanceUserSettingsTab_themeSection">
<span className="mx_SettingsTab_subheading">{_t("Theme")}</span>
{systemThemeSection}
<div className="mx_ThemeSelectors" onChange={this.onThemeChange}>
{orderedThemes.map(theme => {
return <StyledRadioButton
key={theme.id}
value={theme.id}
name="theme"
disabled={this.state.useSystemTheme}
checked={!this.state.useSystemTheme && theme.id === this.state.theme}
className={"mx_ThemeSelector_" + theme.id}
>
{theme.name}
</StyledRadioButton>;
})}
<div className="mx_ThemeSelectors">
<StyledRadioGroup
name="theme"
definitions={orderedThemes.map(t => ({
value: t.id,
label: t.name,
disabled: this.state.useSystemTheme,
className: "mx_ThemeSelector_" + t.id,
}))}
onChange={this.onThemeChange}
value={this.state.useSystemTheme ? undefined : this.state.theme}
/>
</div>
{customThemeForm}
<SettingsFlag name="useCompactLayout" level={SettingLevel.ACCOUNT} useCheckbox={true} />
@ -391,7 +390,13 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
let advanced: React.ReactNode;
if (this.state.showAdvanced) {
advanced = <div>
advanced = <>
<SettingsFlag
name="useCompactLayout"
level={SettingLevel.DEVICE}
useCheckbox={true}
disabled={this.state.useIRCLayout}
/>
<SettingsFlag
name="useSystemFont"
level={SettingLevel.DEVICE}
@ -413,7 +418,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
disabled={!this.state.useSystemFont}
value={this.state.systemFont}
/>
</div>;
</>;
}
return <div className="mx_SettingsTab_section mx_AppearanceUserSettingsTab_Advanced">
{toggle}