Remove create-react-class

This commit is contained in:
Michael Telatynski 2020-08-29 12:14:16 +01:00
parent 672d0fe97b
commit 72498df28f
108 changed files with 3059 additions and 3545 deletions

View file

@ -18,16 +18,13 @@ limitations under the License.
import { _t } from '../../../languageHandler';
import React from 'react';
import createReactClass from 'create-react-class';
export default createReactClass({
displayName: 'AuthFooter',
render: function() {
export default class AuthFooter extends React.Component {
render() {
return (
<div className="mx_AuthFooter">
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">{ _t("powered by Matrix") }</a>
</div>
);
},
});
}
}

View file

@ -17,17 +17,14 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
export default createReactClass({
displayName: 'AuthHeader',
propTypes: {
export default class AuthHeader extends React.Component {
static propTypes = {
disableLanguageSelector: PropTypes.bool,
},
};
render: function() {
render() {
const AuthHeaderLogo = sdk.getComponent('auth.AuthHeaderLogo');
const LanguageSelector = sdk.getComponent('views.auth.LanguageSelector');
@ -37,5 +34,5 @@ export default createReactClass({
<LanguageSelector disabled={this.props.disableLanguageSelector} />
</div>
);
},
});
}
}

View file

@ -15,7 +15,6 @@ limitations under the License.
*/
import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
@ -24,36 +23,31 @@ const DIV_ID = 'mx_recaptcha';
/**
* A pure UI component which displays a captcha form.
*/
export default createReactClass({
displayName: 'CaptchaForm',
propTypes: {
export default class CaptchaForm extends React.Component {
static propTypes = {
sitePublicKey: PropTypes.string,
// called with the captcha response
onCaptchaResponse: PropTypes.func,
},
};
getDefaultProps: function() {
return {
onCaptchaResponse: () => {},
};
},
static defaultProps = {
onCaptchaResponse: () => {},
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
errorText: null,
};
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._captchaWidgetId = null;
this._recaptchaContainer = createRef();
},
}
componentDidMount: function() {
componentDidMount() {
// Just putting a script tag into the returned jsx doesn't work, annoyingly,
// so we do this instead.
if (global.grecaptcha) {
@ -68,13 +62,13 @@ export default createReactClass({
);
this._recaptchaContainer.current.appendChild(scriptTag);
}
},
}
componentWillUnmount: function() {
componentWillUnmount() {
this._resetRecaptcha();
},
}
_renderRecaptcha: function(divId) {
_renderRecaptcha(divId) {
if (!global.grecaptcha) {
console.error("grecaptcha not loaded!");
throw new Error("Recaptcha did not load successfully");
@ -93,15 +87,15 @@ export default createReactClass({
sitekey: publicKey,
callback: this.props.onCaptchaResponse,
});
},
}
_resetRecaptcha: function() {
_resetRecaptcha() {
if (this._captchaWidgetId !== null) {
global.grecaptcha.reset(this._captchaWidgetId);
}
},
}
_onCaptchaLoaded: function() {
_onCaptchaLoaded() {
console.log("Loaded recaptcha script.");
try {
this._renderRecaptcha(DIV_ID);
@ -110,9 +104,9 @@ export default createReactClass({
errorText: e.toString(),
});
}
},
}
render: function() {
render() {
let error = null;
if (this.state.errorText) {
error = (
@ -131,5 +125,5 @@ export default createReactClass({
{ error }
</div>
);
},
});
}
}

View file

@ -16,14 +16,11 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig';
export default createReactClass({
displayName: 'CustomServerDialog',
render: function() {
export default class CustomServerDialog extends React.Component {
render() {
const brand = SdkConfig.get().brand;
return (
<div className="mx_ErrorDialog">
@ -46,5 +43,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
*/
import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import url from 'url';
import classnames from 'classnames';
@ -75,14 +74,10 @@ import AccessibleButton from "../elements/AccessibleButton";
export const DEFAULT_PHASE = 0;
export const PasswordAuthEntry = createReactClass({
displayName: 'PasswordAuthEntry',
export class PasswordAuthEntry extends React.Component {
static LOGIN_TYPE = "m.login.password";
statics: {
LOGIN_TYPE: "m.login.password",
},
propTypes: {
static propTypes = {
matrixClient: PropTypes.object.isRequired,
submitAuthDict: PropTypes.func.isRequired,
errorText: PropTypes.string,
@ -90,19 +85,17 @@ export const PasswordAuthEntry = createReactClass({
// happen?
busy: PropTypes.bool,
onPhaseChange: PropTypes.func.isRequired,
},
};
componentDidMount: function() {
componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE);
},
}
getInitialState: function() {
return {
password: "",
};
},
state = {
password: "",
};
_onSubmit: function(e) {
_onSubmit = e => {
e.preventDefault();
if (this.props.busy) return;
@ -117,16 +110,16 @@ export const PasswordAuthEntry = createReactClass({
},
password: this.state.password,
});
},
};
_onPasswordFieldChange: function(ev) {
_onPasswordFieldChange = ev => {
// enable the submit button iff the password is non-empty
this.setState({
password: ev.target.value,
});
},
};
render: function() {
render() {
const passwordBoxClass = classnames({
"error": this.props.errorText,
});
@ -176,36 +169,32 @@ export const PasswordAuthEntry = createReactClass({
{ errorSection }
</div>
);
},
});
}
}
export const RecaptchaAuthEntry = createReactClass({
displayName: 'RecaptchaAuthEntry',
export class RecaptchaAuthEntry extends React.Component {
static LOGIN_TYPE = "m.login.recaptcha";
statics: {
LOGIN_TYPE: "m.login.recaptcha",
},
propTypes: {
static propTypes = {
submitAuthDict: PropTypes.func.isRequired,
stageParams: PropTypes.object.isRequired,
errorText: PropTypes.string,
busy: PropTypes.bool,
onPhaseChange: PropTypes.func.isRequired,
},
};
componentDidMount: function() {
componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE);
},
}
_onCaptchaResponse: function(response) {
_onCaptchaResponse = response => {
this.props.submitAuthDict({
type: RecaptchaAuthEntry.LOGIN_TYPE,
response: response,
});
},
};
render: function() {
render() {
if (this.props.busy) {
const Loader = sdk.getComponent("elements.Spinner");
return <Loader />;
@ -241,31 +230,24 @@ export const RecaptchaAuthEntry = createReactClass({
{ errorSection }
</div>
);
},
});
}
}
export const TermsAuthEntry = createReactClass({
displayName: 'TermsAuthEntry',
export class TermsAuthEntry extends React.Component {
static LOGIN_TYPE = "m.login.terms";
statics: {
LOGIN_TYPE: "m.login.terms",
},
propTypes: {
static propTypes = {
submitAuthDict: PropTypes.func.isRequired,
stageParams: PropTypes.object.isRequired,
errorText: PropTypes.string,
busy: PropTypes.bool,
showContinue: PropTypes.bool,
onPhaseChange: PropTypes.func.isRequired,
},
};
componentDidMount: function() {
this.props.onPhaseChange(DEFAULT_PHASE);
},
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Move this to constructor
componentWillMount: function() {
// example stageParams:
//
// {
@ -310,17 +292,22 @@ export const TermsAuthEntry = createReactClass({
pickedPolicies.push(langPolicy);
}
this.setState({
"toggledPolicies": initToggles,
"policies": pickedPolicies,
});
},
this.state = {
toggledPolicies: initToggles,
policies: pickedPolicies,
};
}
tryContinue: function() {
componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE);
}
tryContinue = () => {
this._trySubmit();
},
};
_togglePolicy: function(policyId) {
_togglePolicy(policyId) {
const newToggles = {};
for (const policy of this.state.policies) {
let checked = this.state.toggledPolicies[policy.id];
@ -329,9 +316,9 @@ export const TermsAuthEntry = createReactClass({
newToggles[policy.id] = checked;
}
this.setState({"toggledPolicies": newToggles});
},
}
_trySubmit: function() {
_trySubmit = () => {
let allChecked = true;
for (const policy of this.state.policies) {
const checked = this.state.toggledPolicies[policy.id];
@ -340,9 +327,9 @@ export const TermsAuthEntry = createReactClass({
if (allChecked) this.props.submitAuthDict({type: TermsAuthEntry.LOGIN_TYPE});
else this.setState({errorText: _t("Please review and accept all of the homeserver's policies")});
},
};
render: function() {
render() {
if (this.props.busy) {
const Loader = sdk.getComponent("elements.Spinner");
return <Loader />;
@ -387,17 +374,13 @@ export const TermsAuthEntry = createReactClass({
{ submitButton }
</div>
);
},
});
}
}
export const EmailIdentityAuthEntry = createReactClass({
displayName: 'EmailIdentityAuthEntry',
export class EmailIdentityAuthEntry extends React.Component {
static LOGIN_TYPE = "m.login.email.identity";
statics: {
LOGIN_TYPE: "m.login.email.identity",
},
propTypes: {
static propTypes = {
matrixClient: PropTypes.object.isRequired,
submitAuthDict: PropTypes.func.isRequired,
authSessionId: PropTypes.string.isRequired,
@ -407,13 +390,13 @@ export const EmailIdentityAuthEntry = createReactClass({
fail: PropTypes.func.isRequired,
setEmailSid: PropTypes.func.isRequired,
onPhaseChange: PropTypes.func.isRequired,
},
};
componentDidMount: function() {
componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE);
},
}
render: function() {
render() {
// This component is now only displayed once the token has been requested,
// so we know the email has been sent. It can also get loaded after the user
// has clicked the validation link if the server takes a while to propagate
@ -434,17 +417,13 @@ export const EmailIdentityAuthEntry = createReactClass({
</div>
);
}
},
});
}
}
export const MsisdnAuthEntry = createReactClass({
displayName: 'MsisdnAuthEntry',
export class MsisdnAuthEntry extends React.Component {
static LOGIN_TYPE = "m.login.msisdn";
statics: {
LOGIN_TYPE: "m.login.msisdn",
},
propTypes: {
static propTypes = {
inputs: PropTypes.shape({
phoneCountry: PropTypes.string,
phoneNumber: PropTypes.string,
@ -454,16 +433,14 @@ export const MsisdnAuthEntry = createReactClass({
submitAuthDict: PropTypes.func.isRequired,
matrixClient: PropTypes.object,
onPhaseChange: PropTypes.func.isRequired,
},
};
getInitialState: function() {
return {
token: '',
requestingToken: false,
};
},
state = {
token: '',
requestingToken: false,
};
componentDidMount: function() {
componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE);
this._submitUrl = null;
@ -477,12 +454,12 @@ export const MsisdnAuthEntry = createReactClass({
}).finally(() => {
this.setState({requestingToken: false});
});
},
}
/*
* Requests a verification token by SMS.
*/
_requestMsisdnToken: function() {
_requestMsisdnToken() {
return this.props.matrixClient.requestRegisterMsisdnToken(
this.props.inputs.phoneCountry,
this.props.inputs.phoneNumber,
@ -493,15 +470,15 @@ export const MsisdnAuthEntry = createReactClass({
this._sid = result.sid;
this._msisdn = result.msisdn;
});
},
}
_onTokenChange: function(e) {
_onTokenChange = e => {
this.setState({
token: e.target.value,
});
},
};
_onFormSubmit: async function(e) {
_onFormSubmit = async e => {
e.preventDefault();
if (this.state.token == '') return;
@ -552,9 +529,9 @@ export const MsisdnAuthEntry = createReactClass({
this.props.fail(e);
console.log("Failed to submit msisdn token");
}
},
};
render: function() {
render() {
if (this.state.requestingToken) {
const Loader = sdk.getComponent("elements.Spinner");
return <Loader />;
@ -598,8 +575,8 @@ export const MsisdnAuthEntry = createReactClass({
</div>
);
}
},
});
}
}
export class SSOAuthEntry extends React.Component {
static propTypes = {
@ -686,46 +663,46 @@ export class SSOAuthEntry extends React.Component {
}
}
export const FallbackAuthEntry = createReactClass({
displayName: 'FallbackAuthEntry',
propTypes: {
export class FallbackAuthEntry extends React.Component {
static propTypes = {
matrixClient: PropTypes.object.isRequired,
authSessionId: PropTypes.string.isRequired,
loginType: PropTypes.string.isRequired,
submitAuthDict: PropTypes.func.isRequired,
errorText: PropTypes.string,
onPhaseChange: PropTypes.func.isRequired,
},
};
componentDidMount: function() {
this.props.onPhaseChange(DEFAULT_PHASE);
},
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
// we have to make the user click a button, as browsers will block
// the popup if we open it immediately.
this._popupWindow = null;
window.addEventListener("message", this._onReceiveMessage);
this._fallbackButton = createRef();
},
}
componentWillUnmount: function() {
componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE);
}
componentWillUnmount() {
window.removeEventListener("message", this._onReceiveMessage);
if (this._popupWindow) {
this._popupWindow.close();
}
},
}
focus: function() {
focus = () => {
if (this._fallbackButton.current) {
this._fallbackButton.current.focus();
}
},
};
_onShowFallbackClick: function(e) {
_onShowFallbackClick = e => {
e.preventDefault();
e.stopPropagation();
@ -735,18 +712,18 @@ export const FallbackAuthEntry = createReactClass({
);
this._popupWindow = window.open(url);
this._popupWindow.opener = null;
},
};
_onReceiveMessage: function(event) {
_onReceiveMessage = event => {
if (
event.data === "authDone" &&
event.origin === this.props.matrixClient.getHomeserverUrl()
) {
this.props.submitAuthDict({});
}
},
};
render: function() {
render() {
let errorSection;
if (this.props.errorText) {
errorSection = (
@ -761,8 +738,8 @@ export const FallbackAuthEntry = createReactClass({
{errorSection}
</div>
);
},
});
}
}
const AuthEntryComponents = [
PasswordAuthEntry,

View file

@ -18,7 +18,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import * as Email from '../../../email';
@ -42,10 +41,8 @@ const PASSWORD_MIN_SCORE = 3; // safely unguessable: moderate protection from of
/**
* A pure UI component which displays a registration form.
*/
export default createReactClass({
displayName: 'RegistrationForm',
propTypes: {
export default class RegistrationForm extends React.Component {
static propTypes = {
// Values pre-filled in the input boxes when the component loads
defaultEmail: PropTypes.string,
defaultPhoneCountry: PropTypes.string,
@ -58,17 +55,17 @@ export default createReactClass({
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
canSubmit: PropTypes.bool,
serverRequiresIdServer: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
onValidationChange: console.error,
canSubmit: true,
};
},
static defaultProps = {
onValidationChange: console.error,
canSubmit: true,
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
// Field error codes by field ID
fieldValid: {},
// The ISO2 country code selected in the phone number entry
@ -80,9 +77,9 @@ export default createReactClass({
passwordConfirm: this.props.defaultPassword || "",
passwordComplexity: null,
};
},
}
onSubmit: async function(ev) {
onSubmit = async ev => {
ev.preventDefault();
if (!this.props.canSubmit) return;
@ -118,7 +115,7 @@ export default createReactClass({
title: _t("Warning!"),
description: desc,
button: _t("Continue"),
onFinished: function(confirmed) {
onFinished(confirmed) {
if (confirmed) {
self._doSubmit(ev);
}
@ -127,9 +124,9 @@ export default createReactClass({
} else {
self._doSubmit(ev);
}
},
};
_doSubmit: function(ev) {
_doSubmit(ev) {
const email = this.state.email.trim();
const promise = this.props.onRegisterClick({
username: this.state.username.trim(),
@ -145,7 +142,7 @@ export default createReactClass({
ev.target.disabled = false;
});
}
},
}
async verifyFieldsBeforeSubmit() {
// Blur the active element if any, so we first run its blur validation,
@ -196,12 +193,12 @@ export default createReactClass({
invalidField.focus();
invalidField.validate({ allowEmpty: false, focused: true });
return false;
},
}
/**
* @returns {boolean} true if all fields were valid last time they were validated.
*/
allFieldsValid: function() {
allFieldsValid() {
const keys = Object.keys(this.state.fieldValid);
for (let i = 0; i < keys.length; ++i) {
if (!this.state.fieldValid[keys[i]]) {
@ -209,7 +206,7 @@ export default createReactClass({
}
}
return true;
},
}
findFirstInvalidField(fieldIDs) {
for (const fieldID of fieldIDs) {
@ -218,34 +215,34 @@ export default createReactClass({
}
}
return null;
},
}
markFieldValid: function(fieldID, valid) {
markFieldValid(fieldID, valid) {
const { fieldValid } = this.state;
fieldValid[fieldID] = valid;
this.setState({
fieldValid,
});
},
}
onEmailChange(ev) {
onEmailChange = ev => {
this.setState({
email: ev.target.value,
});
},
};
async onEmailValidate(fieldState) {
const result = await this.validateEmailRules(fieldState);
onEmailValidate = async fieldState => {
const result = await RegistrationForm.validateEmailRules(fieldState);
this.markFieldValid(FIELD_EMAIL, result.valid);
return result;
},
};
validateEmailRules: withValidation({
static validateEmailRules = withValidation({
description: () => _t("Use an email address to recover your account"),
rules: [
{
key: "required",
test: function({ value, allowEmpty }) {
test({ value, allowEmpty }) {
return allowEmpty || !this._authStepIsRequired('m.login.email.identity') || !!value;
},
invalid: () => _t("Enter email address (required on this homeserver)"),
@ -256,31 +253,31 @@ export default createReactClass({
invalid: () => _t("Doesn't look like a valid email address"),
},
],
}),
});
onPasswordChange(ev) {
onPasswordChange = ev => {
this.setState({
password: ev.target.value,
});
},
};
onPasswordValidate(result) {
onPasswordValidate = result => {
this.markFieldValid(FIELD_PASSWORD, result.valid);
},
};
onPasswordConfirmChange(ev) {
onPasswordConfirmChange = ev => {
this.setState({
passwordConfirm: ev.target.value,
});
},
};
async onPasswordConfirmValidate(fieldState) {
const result = await this.validatePasswordConfirmRules(fieldState);
onPasswordConfirmValidate = async fieldState => {
const result = await RegistrationForm.validatePasswordConfirmRules(fieldState);
this.markFieldValid(FIELD_PASSWORD_CONFIRM, result.valid);
return result;
},
};
validatePasswordConfirmRules: withValidation({
static validatePasswordConfirmRules = withValidation({
rules: [
{
key: "required",
@ -289,39 +286,39 @@ export default createReactClass({
},
{
key: "match",
test: function({ value }) {
test({ value }) {
return !value || value === this.state.password;
},
invalid: () => _t("Passwords don't match"),
},
],
}),
});
onPhoneCountryChange(newVal) {
onPhoneCountryChange = newVal => {
this.setState({
phoneCountry: newVal.iso2,
phonePrefix: newVal.prefix,
});
},
};
onPhoneNumberChange(ev) {
onPhoneNumberChange = ev => {
this.setState({
phoneNumber: ev.target.value,
});
},
};
async onPhoneNumberValidate(fieldState) {
const result = await this.validatePhoneNumberRules(fieldState);
onPhoneNumberValidate = async fieldState => {
const result = await RegistrationForm.validatePhoneNumberRules(fieldState);
this.markFieldValid(FIELD_PHONE_NUMBER, result.valid);
return result;
},
};
validatePhoneNumberRules: withValidation({
static validatePhoneNumberRules = withValidation({
description: () => _t("Other users can invite you to rooms using your contact details"),
rules: [
{
key: "required",
test: function({ value, allowEmpty }) {
test({ value, allowEmpty }) {
return allowEmpty || !this._authStepIsRequired('m.login.msisdn') || !!value;
},
invalid: () => _t("Enter phone number (required on this homeserver)"),
@ -332,21 +329,21 @@ export default createReactClass({
invalid: () => _t("Doesn't look like a valid phone number"),
},
],
}),
});
onUsernameChange(ev) {
onUsernameChange = ev => {
this.setState({
username: ev.target.value,
});
},
};
async onUsernameValidate(fieldState) {
const result = await this.validateUsernameRules(fieldState);
onUsernameValidate = async fieldState => {
const result = await RegistrationForm.validateUsernameRules(fieldState);
this.markFieldValid(FIELD_USERNAME, result.valid);
return result;
},
};
validateUsernameRules: withValidation({
static validateUsernameRules = withValidation({
description: () => _t("Use lowercase letters, numbers, dashes and underscores only"),
rules: [
{
@ -360,7 +357,7 @@ export default createReactClass({
invalid: () => _t("Some characters not allowed"),
},
],
}),
});
/**
* A step is required if all flows include that step.
@ -372,7 +369,7 @@ export default createReactClass({
return this.props.flows.every((flow) => {
return flow.stages.includes(step);
});
},
}
/**
* A step is used if any flows include that step.
@ -384,7 +381,7 @@ export default createReactClass({
return this.props.flows.some((flow) => {
return flow.stages.includes(step);
});
},
}
_showEmail() {
const haveIs = Boolean(this.props.serverConfig.isUrl);
@ -395,7 +392,7 @@ export default createReactClass({
return false;
}
return true;
},
}
_showPhoneNumber() {
const threePidLogin = !SdkConfig.get().disable_3pid_login;
@ -408,7 +405,7 @@ export default createReactClass({
return false;
}
return true;
},
}
renderEmail() {
if (!this._showEmail()) {
@ -426,7 +423,7 @@ export default createReactClass({
onChange={this.onEmailChange}
onValidate={this.onEmailValidate}
/>;
},
}
renderPassword() {
return <PassphraseField
@ -437,7 +434,7 @@ export default createReactClass({
onChange={this.onPasswordChange}
onValidate={this.onPasswordValidate}
/>;
},
}
renderPasswordConfirm() {
const Field = sdk.getComponent('elements.Field');
@ -451,7 +448,7 @@ export default createReactClass({
onChange={this.onPasswordConfirmChange}
onValidate={this.onPasswordConfirmValidate}
/>;
},
}
renderPhoneNumber() {
if (!this._showPhoneNumber()) {
@ -477,7 +474,7 @@ export default createReactClass({
onChange={this.onPhoneNumberChange}
onValidate={this.onPhoneNumberValidate}
/>;
},
}
renderUsername() {
const Field = sdk.getComponent('elements.Field');
@ -491,9 +488,9 @@ export default createReactClass({
onChange={this.onUsernameChange}
onValidate={this.onUsernameValidate}
/>;
},
}
render: function() {
render() {
let yourMatrixAccountText = _t('Create your Matrix account on %(serverName)s', {
serverName: this.props.serverConfig.hsName,
});
@ -578,5 +575,5 @@ export default createReactClass({
</form>
</div>
);
},
});
}
}

View file

@ -19,7 +19,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {EventStatus} from 'matrix-js-sdk';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
@ -37,10 +36,8 @@ function canCancel(eventStatus) {
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
}
export default createReactClass({
displayName: 'MessageContextMenu',
propTypes: {
export default class MessageContextMenu extends React.Component {
static propTypes = {
/* the MatrixEvent associated with the context menu */
mxEvent: PropTypes.object.isRequired,
@ -52,28 +49,26 @@ export default createReactClass({
/* callback called when the menu is dismissed */
onFinished: PropTypes.func,
},
};
getInitialState: function() {
return {
canRedact: false,
canPin: false,
};
},
state = {
canRedact: false,
canPin: false,
};
componentDidMount: function() {
componentDidMount() {
MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions);
this._checkPermissions();
},
}
componentWillUnmount: function() {
componentWillUnmount() {
const cli = MatrixClientPeg.get();
if (cli) {
cli.removeListener('RoomMember.powerLevel', this._checkPermissions);
}
},
}
_checkPermissions: function() {
_checkPermissions = () => {
const cli = MatrixClientPeg.get();
const room = cli.getRoom(this.props.mxEvent.getRoomId());
@ -84,47 +79,47 @@ export default createReactClass({
if (!SettingsStore.getValue("feature_pinning")) canPin = false;
this.setState({canRedact, canPin});
},
};
_isPinned: function() {
_isPinned() {
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
const pinnedEvent = room.currentState.getStateEvents('m.room.pinned_events', '');
if (!pinnedEvent) return false;
const content = pinnedEvent.getContent();
return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId());
},
}
onResendClick: function() {
onResendClick = () => {
Resend.resend(this.props.mxEvent);
this.closeMenu();
},
};
onResendEditClick: function() {
onResendEditClick = () => {
Resend.resend(this.props.mxEvent.replacingEvent());
this.closeMenu();
},
};
onResendRedactionClick: function() {
onResendRedactionClick = () => {
Resend.resend(this.props.mxEvent.localRedactionEvent());
this.closeMenu();
},
};
onResendReactionsClick: function() {
onResendReactionsClick = () => {
for (const reaction of this._getUnsentReactions()) {
Resend.resend(reaction);
}
this.closeMenu();
},
};
onReportEventClick: function() {
onReportEventClick = () => {
const ReportEventDialog = sdk.getComponent("dialogs.ReportEventDialog");
Modal.createTrackedDialog('Report Event', '', ReportEventDialog, {
mxEvent: this.props.mxEvent,
}, 'mx_Dialog_reportEvent');
this.closeMenu();
},
};
onViewSourceClick: function() {
onViewSourceClick = () => {
const ev = this.props.mxEvent.replacingEvent() || this.props.mxEvent;
const ViewSource = sdk.getComponent('structures.ViewSource');
Modal.createTrackedDialog('View Event Source', '', ViewSource, {
@ -133,9 +128,9 @@ export default createReactClass({
content: ev.event,
}, 'mx_Dialog_viewsource');
this.closeMenu();
},
};
onViewClearSourceClick: function() {
onViewClearSourceClick = () => {
const ev = this.props.mxEvent.replacingEvent() || this.props.mxEvent;
const ViewSource = sdk.getComponent('structures.ViewSource');
Modal.createTrackedDialog('View Clear Event Source', '', ViewSource, {
@ -145,9 +140,9 @@ export default createReactClass({
content: ev._clearEvent,
}, 'mx_Dialog_viewsource');
this.closeMenu();
},
};
onRedactClick: function() {
onRedactClick = () => {
const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
Modal.createTrackedDialog('Confirm Redact Dialog', '', ConfirmRedactDialog, {
onFinished: async (proceed) => {
@ -176,9 +171,9 @@ export default createReactClass({
},
}, 'mx_Dialog_confirmredact');
this.closeMenu();
},
};
onCancelSendClick: function() {
onCancelSendClick = () => {
const mxEvent = this.props.mxEvent;
const editEvent = mxEvent.replacingEvent();
const redactEvent = mxEvent.localRedactionEvent();
@ -199,17 +194,17 @@ export default createReactClass({
Resend.removeFromQueue(this.props.mxEvent);
}
this.closeMenu();
},
};
onForwardClick: function() {
onForwardClick = () => {
dis.dispatch({
action: 'forward_event',
event: this.props.mxEvent,
});
this.closeMenu();
},
};
onPinClick: function() {
onPinClick = () => {
MatrixClientPeg.get().getStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', '')
.catch((e) => {
// Intercept the Event Not Found error and fall through the promise chain with no event.
@ -230,28 +225,28 @@ export default createReactClass({
cli.sendStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', {pinned: eventIds}, '');
});
this.closeMenu();
},
};
closeMenu: function() {
closeMenu = () => {
if (this.props.onFinished) this.props.onFinished();
},
};
onUnhidePreviewClick: function() {
onUnhidePreviewClick = () => {
if (this.props.eventTileOps) {
this.props.eventTileOps.unhideWidget();
}
this.closeMenu();
},
};
onQuoteClick: function() {
onQuoteClick = () => {
dis.dispatch({
action: 'quote',
event: this.props.mxEvent,
});
this.closeMenu();
},
};
onPermalinkClick: function(e: Event) {
onPermalinkClick = (e: Event) => {
e.preventDefault();
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share room message dialog', '', ShareDialog, {
@ -259,12 +254,12 @@ export default createReactClass({
permalinkCreator: this.props.permalinkCreator,
});
this.closeMenu();
},
};
onCollapseReplyThreadClick: function() {
onCollapseReplyThreadClick = () => {
this.props.collapseReplyThread();
this.closeMenu();
},
};
_getReactions(filter) {
const cli = MatrixClientPeg.get();
@ -277,17 +272,17 @@ export default createReactClass({
relation.event_id === eventId &&
filter(e);
});
},
}
_getPendingReactions() {
return this._getReactions(e => canCancel(e.status));
},
}
_getUnsentReactions() {
return this._getReactions(e => e.status === EventStatus.NOT_SENT);
},
}
render: function() {
render() {
const cli = MatrixClientPeg.get();
const me = cli.getUserId();
const mxEvent = this.props.mxEvent;
@ -489,5 +484,5 @@ export default createReactClass({
{ reportEventButton }
</div>
);
},
});
}
}

View file

@ -19,7 +19,6 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t, _td } from '../../../languageHandler';
import * as sdk from '../../../index';
@ -45,10 +44,8 @@ const addressTypeName = {
};
export default createReactClass({
displayName: "AddressPickerDialog",
propTypes: {
export default class AddressPickerDialog extends React.Component {
static propTypes = {
title: PropTypes.string.isRequired,
description: PropTypes.node,
// Extra node inserted after picker input, dropdown and errors
@ -66,26 +63,28 @@ export default createReactClass({
// Whether the current user should be included in the addresses returned. Only
// applicable when pickerType is `user`. Default: false.
includeSelf: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
value: "",
focus: true,
validAddressTypes: addressTypes,
pickerType: 'user',
includeSelf: false,
};
},
static defaultProps = {
value: "",
focus: true,
validAddressTypes: addressTypes,
pickerType: 'user',
includeSelf: false,
};
constructor(props) {
super(props);
this._textinput = createRef();
getInitialState: function() {
let validAddressTypes = this.props.validAddressTypes;
// Remove email from validAddressTypes if no IS is configured. It may be added at a later stage by the user
if (!MatrixClientPeg.get().getIdentityServerUrl() && validAddressTypes.includes("email")) {
validAddressTypes = validAddressTypes.filter(type => type !== "email");
}
return {
this.state = {
// Whether to show an error message because of an invalid address
invalidAddressError: false,
// List of UserAddressType objects representing
@ -106,19 +105,14 @@ export default createReactClass({
// dialog is open and represents the supported list of address types at this time.
validAddressTypes,
};
},
}
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._textinput = createRef();
},
componentDidMount: function() {
componentDidMount() {
if (this.props.focus) {
// Set the cursor at the end of the text input
this._textinput.current.value = this.props.value;
}
},
}
getPlaceholder() {
const { placeholder } = this.props;
@ -127,9 +121,9 @@ export default createReactClass({
}
// Otherwise it's a function, as checked by prop types.
return placeholder(this.state.validAddressTypes);
},
}
onButtonClick: function() {
onButtonClick = () => {
let selectedList = this.state.selectedList.slice();
// Check the text input field to see if user has an unconverted address
// If there is and it's valid add it to the local selectedList
@ -138,13 +132,13 @@ export default createReactClass({
if (selectedList === null) return;
}
this.props.onFinished(true, selectedList);
},
};
onCancel: function() {
onCancel = () => {
this.props.onFinished(false);
},
};
onKeyDown: function(e) {
onKeyDown = e => {
const textInput = this._textinput.current ? this._textinput.current.value : undefined;
if (e.key === Key.ESCAPE) {
@ -181,9 +175,9 @@ export default createReactClass({
e.preventDefault();
this._addAddressesToList([textInput]);
}
},
};
onQueryChanged: function(ev) {
onQueryChanged = ev => {
const query = ev.target.value;
if (this.queryChangedDebouncer) {
clearTimeout(this.queryChangedDebouncer);
@ -216,28 +210,24 @@ export default createReactClass({
searchError: null,
});
}
},
};
onDismissed: function(index) {
return () => {
const selectedList = this.state.selectedList.slice();
selectedList.splice(index, 1);
this.setState({
selectedList,
suggestedList: [],
query: "",
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
};
},
onDismissed = index => () => {
const selectedList = this.state.selectedList.slice();
selectedList.splice(index, 1);
this.setState({
selectedList,
suggestedList: [],
query: "",
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
};
onClick: function(index) {
return () => {
this.onSelected(index);
};
},
onClick = index => () => {
this.onSelected(index);
};
onSelected: function(index) {
onSelected = index => {
const selectedList = this.state.selectedList.slice();
selectedList.push(this._getFilteredSuggestions()[index]);
this.setState({
@ -246,9 +236,9 @@ export default createReactClass({
query: "",
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
},
};
_doNaiveGroupSearch: function(query) {
_doNaiveGroupSearch(query) {
const lowerCaseQuery = query.toLowerCase();
this.setState({
busy: true,
@ -280,9 +270,9 @@ export default createReactClass({
busy: false,
});
});
},
}
_doNaiveGroupRoomSearch: function(query) {
_doNaiveGroupRoomSearch(query) {
const lowerCaseQuery = query.toLowerCase();
const results = [];
GroupStore.getGroupRooms(this.props.groupId).forEach((r) => {
@ -302,9 +292,9 @@ export default createReactClass({
this.setState({
busy: false,
});
},
}
_doRoomSearch: function(query) {
_doRoomSearch(query) {
const lowerCaseQuery = query.toLowerCase();
const rooms = MatrixClientPeg.get().getRooms();
const results = [];
@ -359,9 +349,9 @@ export default createReactClass({
this.setState({
busy: false,
});
},
}
_doUserDirectorySearch: function(query) {
_doUserDirectorySearch(query) {
this.setState({
busy: true,
query,
@ -393,9 +383,9 @@ export default createReactClass({
busy: false,
});
});
},
}
_doLocalSearch: function(query) {
_doLocalSearch(query) {
this.setState({
query,
searchError: null,
@ -417,9 +407,9 @@ export default createReactClass({
});
});
this._processResults(results, query);
},
}
_processResults: function(results, query) {
_processResults(results, query) {
const suggestedList = [];
results.forEach((result) => {
if (result.room_id) {
@ -485,9 +475,9 @@ export default createReactClass({
}, () => {
if (this.addressSelector) this.addressSelector.moveSelectionTop();
});
},
}
_addAddressesToList: function(addressTexts) {
_addAddressesToList(addressTexts) {
const selectedList = this.state.selectedList.slice();
let hasError = false;
@ -529,9 +519,9 @@ export default createReactClass({
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
return hasError ? null : selectedList;
},
}
_lookupThreepid: async function(medium, address) {
async _lookupThreepid(medium, address) {
let cancelled = false;
// Note that we can't safely remove this after we're done
// because we don't know that it's the same one, so we just
@ -577,9 +567,9 @@ export default createReactClass({
searchError: _t('Something went wrong!'),
});
}
},
}
_getFilteredSuggestions: function() {
_getFilteredSuggestions() {
// map addressType => set of addresses to avoid O(n*m) operation
const selectedAddresses = {};
this.state.selectedList.forEach(({address, addressType}) => {
@ -591,17 +581,17 @@ export default createReactClass({
return this.state.suggestedList.filter(({address, addressType}) => {
return !(selectedAddresses[addressType] && selectedAddresses[addressType].has(address));
});
},
}
_onPaste: function(e) {
_onPaste = e => {
// Prevent the text being pasted into the textarea
e.preventDefault();
const text = e.clipboardData.getData("text");
// Process it as a list of addresses to add instead
this._addAddressesToList(text.split(/[\s,]+/));
},
};
onUseDefaultIdentityServerClick(e) {
onUseDefaultIdentityServerClick = e => {
e.preventDefault();
// Update the IS in account data. Actually using it may trigger terms.
@ -612,15 +602,15 @@ export default createReactClass({
const { validAddressTypes } = this.state;
validAddressTypes.push('email');
this.setState({ validAddressTypes });
},
};
onManageSettingsClick(e) {
onManageSettingsClick = e => {
e.preventDefault();
dis.fire(Action.ViewUserSettings);
this.onCancel();
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const AddressSelector = sdk.getComponent("elements.AddressSelector");
@ -738,5 +728,5 @@ export default createReactClass({
onCancel={this.onCancel} />
</BaseDialog>
);
},
});
}
}

View file

@ -16,37 +16,36 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
import {SettingLevel} from "../../../settings/SettingLevel";
export default createReactClass({
propTypes: {
export default class AskInviteAnywayDialog extends React.Component {
static propTypes = {
unknownProfileUsers: PropTypes.array.isRequired, // [ {userId, errorText}... ]
onInviteAnyways: PropTypes.func.isRequired,
onGiveUp: PropTypes.func.isRequired,
onFinished: PropTypes.func.isRequired,
},
};
_onInviteClicked: function() {
_onInviteClicked = () => {
this.props.onInviteAnyways();
this.props.onFinished(true);
},
};
_onInviteNeverWarnClicked: function() {
_onInviteNeverWarnClicked = () => {
SettingsStore.setValue("promptBeforeInviteUnknownUsers", null, SettingLevel.ACCOUNT, false);
this.props.onInviteAnyways();
this.props.onFinished(true);
},
};
_onGiveUpClicked: function() {
_onGiveUpClicked = () => {
this.props.onGiveUp();
this.props.onFinished(false);
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const errorList = this.props.unknownProfileUsers
@ -78,5 +77,5 @@ export default createReactClass({
</div>
</BaseDialog>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import FocusLock from 'react-focus-lock';
import PropTypes from 'prop-types';
import classNames from 'classnames';
@ -34,10 +33,8 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
* Includes a div for the title, and a keypress handler which cancels the
* dialog on escape.
*/
export default createReactClass({
displayName: 'BaseDialog',
propTypes: {
export default class BaseDialog extends React.Component {
static propTypes = {
// onFinished callback to call when Escape is pressed
// Take a boolean which is true if the dialog was dismissed
// with a positive / confirm action or false if it was
@ -81,21 +78,20 @@ export default createReactClass({
PropTypes.object,
PropTypes.arrayOf(PropTypes.string),
]),
},
};
getDefaultProps: function() {
return {
hasCancel: true,
fixedWidth: true,
};
},
static defaultProps = {
hasCancel: true,
fixedWidth: true,
};
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount() {
this._matrixClient = MatrixClientPeg.get();
},
}
_onKeyDown: function(e) {
_onKeyDown = (e) => {
if (this.props.onKeyDown) {
this.props.onKeyDown(e);
}
@ -104,13 +100,13 @@ export default createReactClass({
e.preventDefault();
this.props.onFinished(false);
}
},
};
_onCancelClick: function(e) {
_onCancelClick = (e) => {
this.props.onFinished(false);
},
};
render: function() {
render() {
let cancelButton;
if (this.props.hasCancel) {
cancelButton = (
@ -161,5 +157,5 @@ export default createReactClass({
</FocusLock>
</MatrixClientContext.Provider>
);
},
});
}
}

View file

@ -15,17 +15,14 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
/*
* A dialog for confirming a redaction.
*/
export default createReactClass({
displayName: 'ConfirmRedactDialog',
render: function() {
export default class ConfirmRedactDialog extends React.Component {
render() {
const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog');
return (
<QuestionDialog onFinished={this.props.onFinished}
@ -36,5 +33,5 @@ export default createReactClass({
button={_t("Remove")}>
</QuestionDialog>
);
},
});
}
}

View file

@ -15,7 +15,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk';
import * as sdk from '../../../index';
@ -30,9 +29,8 @@ import { GroupMemberType } from '../../../groups';
* to make it obvious what is going to happen.
* Also tweaks the style for 'dangerous' actions (albeit only with colour)
*/
export default createReactClass({
displayName: 'ConfirmUserActionDialog',
propTypes: {
export default class ConfirmUserActionDialog extends React.Component {
static propTypes = {
// matrix-js-sdk (room) member object. Supply either this or 'groupMember'
member: PropTypes.object,
// group member object. Supply either this or 'member'
@ -48,35 +46,36 @@ export default createReactClass({
askReason: PropTypes.bool,
danger: PropTypes.bool,
onFinished: PropTypes.func.isRequired,
},
};
getDefaultProps: () => ({
static defaultProps = {
danger: false,
askReason: false,
}),
};
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount: function() {
this._reasonField = null;
},
}
onOk: function() {
onOk = () => {
let reason;
if (this._reasonField) {
reason = this._reasonField.value;
}
this.props.onFinished(true, reason);
},
};
onCancel: function() {
onCancel = () => {
this.props.onFinished(false);
},
};
_collectReasonField: function(e) {
_collectReasonField = e => {
this._reasonField = e;
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
@ -134,5 +133,5 @@ export default createReactClass({
onCancel={this.onCancel} />
</BaseDialog>
);
},
});
}
}

View file

@ -15,46 +15,42 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
export default createReactClass({
displayName: 'CreateGroupDialog',
propTypes: {
export default class CreateGroupDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
},
};
getInitialState: function() {
return {
groupName: '',
groupId: '',
groupError: null,
creating: false,
createError: null,
};
},
state = {
groupName: '',
groupId: '',
groupError: null,
creating: false,
createError: null,
};
_onGroupNameChange: function(e) {
_onGroupNameChange = e => {
this.setState({
groupName: e.target.value,
});
},
};
_onGroupIdChange: function(e) {
_onGroupIdChange = e => {
this.setState({
groupId: e.target.value,
});
},
};
_onGroupIdBlur: function(e) {
_onGroupIdBlur = e => {
this._checkGroupId();
},
};
_checkGroupId: function(e) {
_checkGroupId(e) {
let error = null;
if (!this.state.groupId) {
error = _t("Community IDs cannot be empty.");
@ -67,9 +63,9 @@ export default createReactClass({
createError: null,
});
return error;
},
}
_onFormSubmit: function(e) {
_onFormSubmit = e => {
e.preventDefault();
if (this._checkGroupId()) return;
@ -94,13 +90,13 @@ export default createReactClass({
}).finally(() => {
this.setState({creating: false});
});
},
};
_onCancel: function() {
_onCancel = () => {
this.props.onFinished(false);
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const Spinner = sdk.getComponent('elements.Spinner');
@ -171,5 +167,5 @@ export default createReactClass({
</form>
</BaseDialog>
);
},
});
}
}

View file

@ -16,7 +16,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import SdkConfig from '../../../SdkConfig';
@ -28,16 +27,17 @@ import {privateShouldBeEncrypted} from "../../../createRoom";
import TagOrderStore from "../../../stores/TagOrderStore";
import GroupStore from "../../../stores/GroupStore";
export default createReactClass({
displayName: 'CreateRoomDialog',
propTypes: {
export default class CreateRoomDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
defaultPublic: PropTypes.bool,
},
};
constructor(props) {
super(props);
getInitialState() {
const config = SdkConfig.get();
return {
this.state = {
isPublic: this.props.defaultPublic || false,
isEncrypted: privateShouldBeEncrypted(),
name: "",
@ -47,7 +47,7 @@ export default createReactClass({
noFederate: config.default_federate === false,
nameIsValid: false,
};
},
}
_roomCreateOptions() {
const opts = {};
@ -77,27 +77,27 @@ export default createReactClass({
}
return opts;
},
}
componentDidMount() {
this._detailsRef.addEventListener("toggle", this.onDetailsToggled);
// move focus to first field when showing dialog
this._nameFieldRef.focus();
},
}
componentWillUnmount() {
this._detailsRef.removeEventListener("toggle", this.onDetailsToggled);
},
}
_onKeyDown: function(event) {
_onKeyDown = event => {
if (event.key === Key.ENTER) {
this.onOk();
event.preventDefault();
event.stopPropagation();
}
},
};
onOk: async function() {
onOk = async () => {
const activeElement = document.activeElement;
if (activeElement) {
activeElement.blur();
@ -123,51 +123,51 @@ export default createReactClass({
field.validate({ allowEmpty: false, focused: true });
}
}
},
};
onCancel: function() {
onCancel = () => {
this.props.onFinished(false);
},
};
onNameChange(ev) {
onNameChange = ev => {
this.setState({name: ev.target.value});
},
};
onTopicChange(ev) {
onTopicChange = ev => {
this.setState({topic: ev.target.value});
},
};
onPublicChange(isPublic) {
onPublicChange = isPublic => {
this.setState({isPublic});
},
};
onEncryptedChange(isEncrypted) {
onEncryptedChange = isEncrypted => {
this.setState({isEncrypted});
},
};
onAliasChange(alias) {
onAliasChange = alias => {
this.setState({alias});
},
};
onDetailsToggled(ev) {
onDetailsToggled = ev => {
this.setState({detailsOpen: ev.target.open});
},
};
onNoFederateChange(noFederate) {
onNoFederateChange = noFederate => {
this.setState({noFederate});
},
};
collectDetailsRef(ref) {
collectDetailsRef = ref => {
this._detailsRef = ref;
},
};
async onNameValidate(fieldState) {
const result = await this._validateRoomName(fieldState);
onNameValidate = async fieldState => {
const result = await CreateRoomDialog._validateRoomName(fieldState);
this.setState({nameIsValid: result.valid});
return result;
},
};
_validateRoomName: withValidation({
static _validateRoomName = withValidation({
rules: [
{
key: "required",
@ -175,9 +175,9 @@ export default createReactClass({
invalid: () => _t("Please enter a name for the room"),
},
],
}),
});
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const Field = sdk.getComponent('views.elements.Field');
@ -275,5 +275,5 @@ export default createReactClass({
onCancel={this.onCancel} />
</BaseDialog>
);
},
});
}
}

View file

@ -26,14 +26,12 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'ErrorDialog',
propTypes: {
export default class ErrorDialog extends React.Component {
static propTypes = {
title: PropTypes.string,
description: PropTypes.oneOfType([
PropTypes.element,
@ -43,18 +41,16 @@ export default createReactClass({
focus: PropTypes.bool,
onFinished: PropTypes.func.isRequired,
headerImage: PropTypes.string,
},
};
getDefaultProps: function() {
return {
focus: true,
title: null,
description: null,
button: null,
};
},
static defaultProps = {
focus: true,
title: null,
description: null,
button: null,
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog
@ -74,5 +70,5 @@ export default createReactClass({
</div>
</BaseDialog>
);
},
});
}
}

View file

@ -17,15 +17,13 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import classNames from "classnames";
export default createReactClass({
displayName: 'InfoDialog',
propTypes: {
export default class InfoDialog extends React.Component {
static propTypes = {
className: PropTypes.string,
title: PropTypes.string,
description: PropTypes.node,
@ -33,21 +31,19 @@ export default createReactClass({
onFinished: PropTypes.func,
hasCloseButton: PropTypes.bool,
onKeyDown: PropTypes.func,
},
};
getDefaultProps: function() {
return {
title: '',
description: '',
hasCloseButton: false,
};
},
static defaultProps = {
title: '',
description: '',
hasCloseButton: false,
};
onFinished: function() {
onFinished = () => {
this.props.onFinished();
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return (
@ -69,5 +65,5 @@ export default createReactClass({
</DialogButtons>
</BaseDialog>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
@ -27,10 +26,8 @@ import AccessibleButton from '../elements/AccessibleButton';
import {ERROR_USER_CANCELLED} from "../../structures/InteractiveAuth";
import {SSOAuthEntry} from "../auth/InteractiveAuthEntryComponents";
export default createReactClass({
displayName: 'InteractiveAuthDialog',
propTypes: {
export default class InteractiveAuthDialog extends React.Component {
static propTypes = {
// matrix client to use for UI auth requests
matrixClient: PropTypes.object.isRequired,
@ -70,19 +67,17 @@ export default createReactClass({
//
// Default is defined in _getDefaultDialogAesthetics()
aestheticsForStagePhases: PropTypes.object,
},
};
getInitialState: function() {
return {
authError: null,
state = {
authError: null,
// See _onUpdateStagePhase()
uiaStage: null,
uiaStagePhase: null,
};
},
// See _onUpdateStagePhase()
uiaStage: null,
uiaStagePhase: null,
};
_getDefaultDialogAesthetics: function() {
_getDefaultDialogAesthetics() {
const ssoAesthetics = {
[SSOAuthEntry.PHASE_PREAUTH]: {
title: _t("Use Single Sign On to continue"),
@ -102,9 +97,9 @@ export default createReactClass({
[SSOAuthEntry.LOGIN_TYPE]: ssoAesthetics,
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: ssoAesthetics,
};
},
}
_onAuthFinished: function(success, result) {
_onAuthFinished = (success, result) => {
if (success) {
this.props.onFinished(true, result);
} else {
@ -116,18 +111,18 @@ export default createReactClass({
});
}
}
},
};
_onUpdateStagePhase: function(newStage, newPhase) {
_onUpdateStagePhase = (newStage, newPhase) => {
// We copy the stage and stage phase params into state for title selection in render()
this.setState({uiaStage: newStage, uiaStagePhase: newPhase});
},
};
_onDismissClick: function() {
_onDismissClick = () => {
this.props.onFinished(false);
},
};
render: function() {
render() {
const InteractiveAuth = sdk.getComponent("structures.InteractiveAuth");
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
@ -190,5 +185,5 @@ export default createReactClass({
{ content }
</BaseDialog>
);
},
});
}
}

View file

@ -16,14 +16,12 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'QuestionDialog',
propTypes: {
export default class QuestionDialog extends React.Component {
static propTypes = {
title: PropTypes.string,
description: PropTypes.node,
extraButtons: PropTypes.node,
@ -34,29 +32,27 @@ export default createReactClass({
headerImage: PropTypes.string,
quitOnly: PropTypes.bool, // quitOnly doesn't show the cancel button just the quit [x].
fixedWidth: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
title: "",
description: "",
extraButtons: null,
focus: true,
hasCancelButton: true,
danger: false,
quitOnly: false,
};
},
static defaultProps = {
title: "",
description: "",
extraButtons: null,
focus: true,
hasCancelButton: true,
danger: false,
quitOnly: false,
};
onOk: function() {
onOk = () => {
this.props.onFinished(true);
},
};
onCancel: function() {
onCancel = () => {
this.props.onFinished(false);
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
let primaryButtonClass = "";
@ -88,5 +84,5 @@ export default createReactClass({
</DialogButtons>
</BaseDialog>
);
},
});
}
}

View file

@ -15,38 +15,33 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'RoomUpgradeDialog',
propTypes: {
export default class RoomUpgradeDialog extends React.Component {
static propTypes = {
room: PropTypes.object.isRequired,
onFinished: PropTypes.func.isRequired,
},
};
componentDidMount: async function() {
state = {
busy: true,
};
async componentDidMount() {
const recommended = await this.props.room.getRecommendedVersion();
this._targetVersion = recommended.version;
this.setState({busy: false});
},
}
getInitialState: function() {
return {
busy: true,
};
},
_onCancelClick: function() {
_onCancelClick = () => {
this.props.onFinished(false);
},
};
_onUpgradeClick: function() {
_onUpgradeClick = () => {
this.setState({busy: true});
MatrixClientPeg.get().upgradeRoom(this.props.room.roomId, this._targetVersion).then(() => {
this.props.onFinished(true);
@ -59,9 +54,9 @@ export default createReactClass({
}).finally(() => {
this.setState({busy: false});
});
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const Spinner = sdk.getComponent('views.elements.Spinner');
@ -106,5 +101,5 @@ export default createReactClass({
{buttons}
</BaseDialog>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import SdkConfig from '../../../SdkConfig';
@ -25,20 +24,18 @@ import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'SessionRestoreErrorDialog',
propTypes: {
export default class SessionRestoreErrorDialog extends React.Component {
static propTypes = {
error: PropTypes.string.isRequired,
onFinished: PropTypes.func.isRequired,
},
};
_sendBugReport: function() {
_sendBugReport = () => {
const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog");
Modal.createTrackedDialog('Session Restore Error', 'Send Bug Report Dialog', BugReportDialog, {});
},
};
_onClearStorageClick: function() {
_onClearStorageClick = () => {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('Session Restore Confirm Logout', '', QuestionDialog, {
title: _t("Sign out"),
@ -48,15 +45,15 @@ export default createReactClass({
danger: true,
onFinished: this.props.onFinished,
});
},
};
_onRefreshClick: function() {
_onRefreshClick = () => {
// Is this likely to help? Probably not, but giving only one button
// that clears your storage seems awful.
window.location.reload(true);
},
};
render: function() {
render() {
const brand = SdkConfig.get().brand;
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
@ -110,5 +107,5 @@ export default createReactClass({
{ dialogButtons }
</BaseDialog>
);
},
});
}
}

View file

@ -16,7 +16,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import * as Email from '../../../email';
@ -30,26 +29,23 @@ import Modal from '../../../Modal';
*
* On success, `onFinished(true)` is called.
*/
export default createReactClass({
displayName: 'SetEmailDialog',
propTypes: {
export default class SetEmailDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
},
};
getInitialState: function() {
return {
emailAddress: '',
emailBusy: false,
};
},
state = {
emailAddress: '',
emailBusy: false,
};
onEmailAddressChanged: function(value) {
onEmailAddressChanged = value => {
this.setState({
emailAddress: value,
});
},
};
onSubmit: function() {
onSubmit = () => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
@ -81,21 +77,21 @@ export default createReactClass({
});
});
this.setState({emailBusy: true});
},
};
onCancelled: function() {
onCancelled = () => {
this.props.onFinished(false);
},
};
onEmailDialogFinished: function(ok) {
onEmailDialogFinished = ok => {
if (ok) {
this.verifyEmailAddress();
} else {
this.setState({emailBusy: false});
}
},
};
verifyEmailAddress: function() {
verifyEmailAddress() {
this._addThreepid.checkEmailLinkClicked().then(() => {
this.props.onFinished(true);
}, (err) => {
@ -119,9 +115,9 @@ export default createReactClass({
});
}
});
},
}
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const Spinner = sdk.getComponent('elements.Spinner');
const EditableText = sdk.getComponent('elements.EditableText');
@ -161,5 +157,5 @@ export default createReactClass({
</div>
</BaseDialog>
);
},
});
}
}

View file

@ -16,7 +16,6 @@ limitations under the License.
*/
import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
@ -34,18 +33,22 @@ const USERNAME_CHECK_DEBOUNCE_MS = 250;
*
* On success, `onFinished(true, newDisplayName)` is called.
*/
export default createReactClass({
displayName: 'SetMxIdDialog',
propTypes: {
export default class SetMxIdDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
// Called when the user requests to register with a different homeserver
onDifferentServerClicked: PropTypes.func.isRequired,
// Called if the user wants to switch to login instead
onLoginClick: PropTypes.func.isRequired,
},
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this._input_value = createRef();
this._uiAuth = createRef();
this.state = {
// The entered username
username: '',
// Indicate ongoing work on the username
@ -60,21 +63,15 @@ export default createReactClass({
// Indicate error with auth
authError: '',
};
},
}
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._input_value = createRef();
this._uiAuth = createRef();
},
componentDidMount: function() {
componentDidMount() {
this._input_value.current.select();
this._matrixClient = MatrixClientPeg.get();
},
}
onValueChange: function(ev) {
onValueChange = ev => {
this.setState({
username: ev.target.value,
usernameBusy: true,
@ -99,24 +96,24 @@ export default createReactClass({
});
}, USERNAME_CHECK_DEBOUNCE_MS);
});
},
};
onKeyUp: function(ev) {
onKeyUp = ev => {
if (ev.key === Key.ENTER) {
this.onSubmit();
}
},
};
onSubmit: function(ev) {
onSubmit = ev => {
if (this._uiAuth.current) {
this._uiAuth.current.tryContinue();
}
this.setState({
doingUIAuth: true,
});
},
};
_doUsernameCheck: function() {
_doUsernameCheck() {
// We do a quick check ahead of the username availability API to ensure the
// user ID roughly looks okay from a Matrix perspective.
if (!SAFE_LOCALPART_REGEX.test(this.state.username)) {
@ -167,13 +164,13 @@ export default createReactClass({
this.setState(newState);
},
);
},
}
_generatePassword: function() {
_generatePassword() {
return Math.random().toString(36).slice(2);
},
}
_makeRegisterRequest: function(auth) {
_makeRegisterRequest = auth => {
// Not upgrading - changing mxids
const guestAccessToken = null;
if (!this._generatedPassword) {
@ -187,9 +184,9 @@ export default createReactClass({
{},
guestAccessToken,
);
},
};
_onUIAuthFinished: function(success, response) {
_onUIAuthFinished = (success, response) => {
this.setState({
doingUIAuth: false,
});
@ -207,9 +204,9 @@ export default createReactClass({
accessToken: response.access_token,
password: this._generatedPassword,
});
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth');
@ -303,5 +300,5 @@ export default createReactClass({
</div>
</BaseDialog>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
@ -63,32 +62,25 @@ const WarmFuzzy = function(props) {
*
* On success, `onFinished()` when finished
*/
export default createReactClass({
displayName: 'SetPasswordDialog',
propTypes: {
export default class SetPasswordDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
},
};
getInitialState: function() {
return {
error: null,
};
},
state = {
error: null,
};
componentDidMount: function() {
console.info('SetPasswordDialog component did mount');
},
_onPasswordChanged: function(res) {
_onPasswordChanged = res => {
Modal.createDialog(WarmFuzzy, {
didSetEmail: res.didSetEmail,
onFinished: () => {
this.props.onFinished();
},
});
},
};
_onPasswordChangeError: function(err) {
_onPasswordChangeError = err => {
let errMsg = err.error || "";
if (err.httpStatus === 403) {
errMsg = _t('Failed to change password. Is your password correct?');
@ -101,9 +93,9 @@ export default createReactClass({
this.setState({
error: errMsg,
});
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const ChangePassword = sdk.getComponent('views.settings.ChangePassword');
@ -132,5 +124,5 @@ export default createReactClass({
</div>
</BaseDialog>
);
},
});
}
}

View file

@ -15,14 +15,12 @@ limitations under the License.
*/
import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import Field from "../elements/Field";
export default createReactClass({
displayName: 'TextInputDialog',
propTypes: {
export default class TextInputDialog extends React.Component {
static propTypes = {
title: PropTypes.string,
description: PropTypes.oneOfType([
PropTypes.element,
@ -36,39 +34,36 @@ export default createReactClass({
hasCancel: PropTypes.bool,
validator: PropTypes.func, // result of withValidation
fixedWidth: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
title: "",
value: "",
description: "",
focus: true,
hasCancel: true,
};
},
static defaultProps = {
title: "",
value: "",
description: "",
focus: true,
hasCancel: true,
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this._field = createRef();
this.state = {
value: this.props.value,
valid: false,
};
},
}
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._field = createRef();
},
componentDidMount: function() {
componentDidMount() {
if (this.props.focus) {
// Set the cursor at the end of the text input
// this._field.current.value = this.props.value;
this._field.current.focus();
}
},
}
onOk: async function(ev) {
onOk = async ev => {
ev.preventDefault();
if (this.props.validator) {
await this._field.current.validate({ allowEmpty: false });
@ -80,27 +75,27 @@ export default createReactClass({
}
}
this.props.onFinished(true, this.state.value);
},
};
onCancel: function() {
onCancel = () => {
this.props.onFinished(false);
},
};
onChange: function(ev) {
onChange = ev => {
this.setState({
value: ev.target.value,
});
},
};
onValidate: async function(fieldState) {
onValidate = async fieldState => {
const result = await this.props.validator(fieldState);
this.setState({
valid: result.valid,
});
return result;
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return (
@ -137,5 +132,5 @@ export default createReactClass({
/>
</BaseDialog>
);
},
});
}
}

View file

@ -16,16 +16,13 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import AccessibleButton from './AccessibleButton';
import dis from '../../../dispatcher/dispatcher';
import * as sdk from '../../../index';
import Analytics from '../../../Analytics';
export default createReactClass({
displayName: 'RoleButton',
propTypes: {
export default class ActionButton extends React.Component {
static propTypes = {
size: PropTypes.string,
tooltip: PropTypes.bool,
action: PropTypes.string.isRequired,
@ -33,39 +30,35 @@ export default createReactClass({
label: PropTypes.string.isRequired,
iconPath: PropTypes.string,
className: PropTypes.string,
},
};
getDefaultProps: function() {
return {
size: "25",
tooltip: false,
};
},
static defaultProps = {
size: "25",
tooltip: false,
};
getInitialState: function() {
return {
showTooltip: false,
};
},
state = {
showTooltip: false,
};
_onClick: function(ev) {
_onClick = (ev) => {
ev.stopPropagation();
Analytics.trackEvent('Action Button', 'click', this.props.action);
dis.dispatch({action: this.props.action});
},
};
_onMouseEnter: function() {
_onMouseEnter = () => {
if (this.props.tooltip) this.setState({showTooltip: true});
if (this.props.mouseOverAction) {
dis.dispatch({action: this.props.mouseOverAction});
}
},
};
_onMouseLeave: function() {
_onMouseLeave = () => {
this.setState({showTooltip: false});
},
};
render: function() {
render() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
let tooltip;
@ -94,5 +87,5 @@ export default createReactClass({
{ tooltip }
</AccessibleButton>
);
},
});
}
}

View file

@ -17,15 +17,12 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import classNames from 'classnames';
import { UserAddressType } from '../../../UserAddress';
export default createReactClass({
displayName: 'AddressSelector',
propTypes: {
export default class AddressSelector extends React.Component {
static propTypes = {
onSelected: PropTypes.func.isRequired,
// List of the addresses to display
@ -37,90 +34,91 @@ export default createReactClass({
// Element to put as a header on top of the list
header: PropTypes.node,
},
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
selected: this.props.selected === undefined ? 0 : this.props.selected,
hover: false,
};
},
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(props) {
UNSAFE_componentWillReceiveProps(props) {
// Make sure the selected item isn't outside the list bounds
const selected = this.state.selected;
const maxSelected = this._maxSelected(props.addressList);
if (selected > maxSelected) {
this.setState({ selected: maxSelected });
}
},
}
componentDidUpdate: function() {
componentDidUpdate() {
// As the user scrolls with the arrow keys keep the selected item
// at the top of the window.
if (this.scrollElement && this.props.addressList.length > 0 && !this.state.hover) {
const elementHeight = this.addressListElement.getBoundingClientRect().height;
this.scrollElement.scrollTop = (this.state.selected * elementHeight) - elementHeight;
}
},
}
moveSelectionTop: function() {
moveSelectionTop = () => {
if (this.state.selected > 0) {
this.setState({
selected: 0,
hover: false,
});
}
},
};
moveSelectionUp: function() {
moveSelectionUp = () => {
if (this.state.selected > 0) {
this.setState({
selected: this.state.selected - 1,
hover: false,
});
}
},
};
moveSelectionDown: function() {
moveSelectionDown = () => {
if (this.state.selected < this._maxSelected(this.props.addressList)) {
this.setState({
selected: this.state.selected + 1,
hover: false,
});
}
},
};
chooseSelection: function() {
chooseSelection = () => {
this.selectAddress(this.state.selected);
},
};
onClick: function(index) {
onClick = index => {
this.selectAddress(index);
},
};
onMouseEnter: function(index) {
onMouseEnter = index => {
this.setState({
selected: index,
hover: true,
});
},
};
onMouseLeave: function() {
onMouseLeave = () => {
this.setState({ hover: false });
},
};
selectAddress: function(index) {
selectAddress = index => {
// Only try to select an address if one exists
if (this.props.addressList.length !== 0) {
this.props.onSelected(index);
this.setState({ hover: false });
}
},
};
createAddressListTiles: function() {
const self = this;
createAddressListTiles() {
const AddressTile = sdk.getComponent("elements.AddressTile");
const maxSelected = this._maxSelected(this.props.addressList);
const addressList = [];
@ -157,15 +155,15 @@ export default createReactClass({
}
}
return addressList;
},
}
_maxSelected: function(list) {
_maxSelected(list) {
const listSize = list.length === 0 ? 0 : list.length - 1;
const maxSelected = listSize > (this.props.truncateAt - 1) ? (this.props.truncateAt - 1) : listSize;
return maxSelected;
},
}
render: function() {
render() {
const classes = classNames({
"mx_AddressSelector": true,
"mx_AddressSelector_empty": this.props.addressList.length === 0,
@ -177,5 +175,5 @@ export default createReactClass({
{ this.createAddressListTiles() }
</div>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames';
import * as sdk from "../../../index";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
@ -25,25 +24,21 @@ import { _t } from '../../../languageHandler';
import { UserAddressType } from '../../../UserAddress.js';
export default createReactClass({
displayName: 'AddressTile',
propTypes: {
export default class AddressTile extends React.Component {
static propTypes = {
address: UserAddressType.isRequired,
canDismiss: PropTypes.bool,
onDismissed: PropTypes.func,
justified: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
canDismiss: false,
onDismissed: function() {}, // NOP
justified: false,
};
},
static defaultProps = {
canDismiss: false,
onDismissed: function() {}, // NOP
justified: false,
};
render: function() {
render() {
const address = this.props.address;
const name = address.displayName || address.address;
@ -144,5 +139,5 @@ export default createReactClass({
{ dismiss }
</div>
);
},
});
}
}

View file

@ -18,16 +18,13 @@ limitations under the License.
import React from "react";
import PropTypes from "prop-types";
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
/**
* Basic container for buttons in modal dialogs.
*/
export default createReactClass({
displayName: "DialogButtons",
propTypes: {
export default class DialogButtons extends React.Component {
static propTypes = {
// The primary button which is styled differently and has default focus.
primaryButton: PropTypes.node.isRequired,
@ -57,20 +54,18 @@ export default createReactClass({
// disables only the primary button
primaryDisabled: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
hasCancel: true,
disabled: false,
};
},
static defaultProps = {
hasCancel: true,
disabled: false,
};
_onCancelClick: function() {
_onCancelClick = () => {
this.props.onCancel();
},
};
render: function() {
render() {
let primaryButtonClassName = "mx_Dialog_primary";
if (this.props.primaryButtonClass) {
primaryButtonClassName += " " + this.props.primaryButtonClass;
@ -104,5 +99,5 @@ export default createReactClass({
</button>
</div>
);
},
});
}
}

View file

@ -17,13 +17,10 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {Key} from "../../../Keyboard";
export default createReactClass({
displayName: 'EditableText',
propTypes: {
export default class EditableText extends React.Component {
static propTypes = {
onValueChanged: PropTypes.func,
initialValue: PropTypes.string,
label: PropTypes.string,
@ -36,60 +33,62 @@ export default createReactClass({
// Will cause onValueChanged(value, true) to fire on blur
blurToSubmit: PropTypes.bool,
editable: PropTypes.bool,
},
};
Phases: {
static Phases = {
Display: "display",
Edit: "edit",
},
};
getDefaultProps: function() {
return {
onValueChanged: function() {},
initialValue: '',
label: '',
placeholder: '',
editable: true,
className: "mx_EditableText",
placeholderClassName: "mx_EditableText_placeholder",
blurToSubmit: false,
};
},
static defaultProps = {
onValueChanged() {},
initialValue: '',
label: '',
placeholder: '',
editable: true,
className: "mx_EditableText",
placeholderClassName: "mx_EditableText_placeholder",
blurToSubmit: false,
};
getInitialState: function() {
return {
phase: this.Phases.Display,
};
},
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(nextProps) {
if (nextProps.initialValue !== this.props.initialValue) {
this.value = nextProps.initialValue;
if (this._editable_div.current) {
this.showPlaceholder(!this.value);
}
}
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
// we track value as an JS object field rather than in React state
// as React doesn't play nice with contentEditable.
this.value = '';
this.placeholder = false;
this._editable_div = createRef();
},
componentDidMount: function() {
this.state = {
phase: EditableText.Phases.Display,
};
}
state = {
phase: EditableText.Phases.Display,
};
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.initialValue !== this.props.initialValue) {
this.value = nextProps.initialValue;
if (this._editable_div.current) {
this.showPlaceholder(!this.value);
}
}
}
componentDidMount() {
this.value = this.props.initialValue;
if (this._editable_div.current) {
this.showPlaceholder(!this.value);
}
},
}
showPlaceholder: function(show) {
showPlaceholder = show => {
if (show) {
this._editable_div.current.textContent = this.props.placeholder;
this._editable_div.current.setAttribute("class", this.props.className
@ -101,38 +100,36 @@ export default createReactClass({
this._editable_div.current.setAttribute("class", this.props.className);
this.placeholder = false;
}
},
};
getValue: function() {
return this.value;
},
getValue = () => this.value;
setValue: function(value) {
setValue = value => {
this.value = value;
this.showPlaceholder(!this.value);
},
};
edit: function() {
edit = () => {
this.setState({
phase: this.Phases.Edit,
phase: EditableText.Phases.Edit,
});
},
};
cancelEdit: function() {
cancelEdit = () => {
this.setState({
phase: this.Phases.Display,
phase: EditableText.Phases.Display,
});
this.value = this.props.initialValue;
this.showPlaceholder(!this.value);
this.onValueChanged(false);
this._editable_div.current.blur();
},
};
onValueChanged: function(shouldSubmit) {
onValueChanged = shouldSubmit => {
this.props.onValueChanged(this.value, shouldSubmit);
},
};
onKeyDown: function(ev) {
onKeyDown = ev => {
// console.log("keyDown: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
if (this.placeholder) {
@ -145,9 +142,9 @@ export default createReactClass({
}
// console.log("keyDown: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
},
};
onKeyUp: function(ev) {
onKeyUp = ev => {
// console.log("keyUp: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
if (!ev.target.textContent) {
@ -163,17 +160,17 @@ export default createReactClass({
}
// console.log("keyUp: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
},
};
onClickDiv: function(ev) {
onClickDiv = ev => {
if (!this.props.editable) return;
this.setState({
phase: this.Phases.Edit,
phase: EditableText.Phases.Edit,
});
},
};
onFocus: function(ev) {
onFocus = ev => {
//ev.target.setSelectionRange(0, ev.target.textContent.length);
const node = ev.target.childNodes[0];
@ -186,21 +183,21 @@ export default createReactClass({
sel.removeAllRanges();
sel.addRange(range);
}
},
};
onFinish: function(ev, shouldSubmit) {
onFinish = (ev, shouldSubmit) => {
const self = this;
const submit = (ev.key === Key.ENTER) || shouldSubmit;
this.setState({
phase: this.Phases.Display,
phase: EditableText.Phases.Display,
}, () => {
if (this.value !== this.props.initialValue) {
self.onValueChanged(submit);
}
});
},
};
onBlur: function(ev) {
onBlur = ev => {
const sel = window.getSelection();
sel.removeAllRanges();
@ -211,13 +208,13 @@ export default createReactClass({
}
this.showPlaceholder(!this.value);
},
};
render: function() {
render() {
const {className, editable, initialValue, label, labelClassName} = this.props;
let editableEl;
if (!editable || (this.state.phase === this.Phases.Display && (label || labelClassName) && !this.value)) {
if (!editable || (this.state.phase === EditableText.Phases.Display && (label || labelClassName) && !this.value)) {
// show the label
editableEl = <div className={className + " " + labelClassName} onClick={this.onClickDiv}>
{ label || initialValue }
@ -234,5 +231,5 @@ export default createReactClass({
}
return editableEl;
},
});
}
}

View file

@ -15,14 +15,11 @@ 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',
render: function() {
export default class InlineSpinner extends React.Component {
render() {
const w = this.props.w || 16;
const h = this.props.h || 16;
const imgClass = this.props.imgClassName || "";
@ -45,5 +42,5 @@ export default createReactClass({
/>
</div>
);
},
});
}
}

View file

@ -18,17 +18,14 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
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',
propTypes: {
export default class MemberEventListSummary extends React.Component {
static propTypes = {
// An array of member events to summarise
events: PropTypes.arrayOf(PropTypes.instanceOf(MatrixEvent)).isRequired,
// An array of EventTiles to render when expanded
@ -43,17 +40,15 @@ export default createReactClass({
onToggle: PropTypes.func,
// Whether or not to begin with state.expanded=true
startExpanded: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
summaryLength: 1,
threshold: 3,
avatarsMaxLength: 5,
};
},
static defaultProps = {
summaryLength: 1,
threshold: 3,
avatarsMaxLength: 5,
};
shouldComponentUpdate: function(nextProps) {
shouldComponentUpdate(nextProps) {
// Update if
// - The number of summarised events has changed
// - or if the summary is about to toggle to become collapsed
@ -62,7 +57,7 @@ export default createReactClass({
nextProps.events.length !== this.props.events.length ||
nextProps.events.length < this.props.threshold
);
},
}
/**
* Generate the text for users aggregated by their transition sequences (`eventAggregates`) where
@ -73,7 +68,7 @@ export default createReactClass({
* `Object.keys(eventAggregates)`.
* @returns {string} the textual summary of the aggregated events that occurred.
*/
_generateSummary: function(eventAggregates, orderedTransitionSequences) {
_generateSummary(eventAggregates, orderedTransitionSequences) {
const summaries = orderedTransitionSequences.map((transitions) => {
const userNames = eventAggregates[transitions];
const nameList = this._renderNameList(userNames);
@ -105,7 +100,7 @@ export default createReactClass({
}
return summaries.join(", ");
},
}
/**
* @param {string[]} users an array of user display names or user IDs.
@ -113,9 +108,9 @@ export default createReactClass({
* more items in `users` than `this.props.summaryLength`, which is the number of names
* included before "and [n] others".
*/
_renderNameList: function(users) {
_renderNameList(users) {
return formatCommaSeparatedList(users, this.props.summaryLength);
},
}
/**
* Canonicalise an array of transitions such that some pairs of transitions become
@ -124,7 +119,7 @@ export default createReactClass({
* @param {string[]} transitions an array of transitions.
* @returns {string[]} an array of transitions.
*/
_getCanonicalTransitions: function(transitions) {
_getCanonicalTransitions(transitions) {
const modMap = {
'joined': {
'after': 'left',
@ -155,7 +150,7 @@ export default createReactClass({
res.push(transition);
}
return res;
},
}
/**
* Transform an array of transitions into an array of transitions and how many times
@ -171,7 +166,7 @@ export default createReactClass({
* @param {string[]} transitions the array of transitions to transform.
* @returns {object[]} an array of coalesced transitions.
*/
_coalesceRepeatedTransitions: function(transitions) {
_coalesceRepeatedTransitions(transitions) {
const res = [];
for (let i = 0; i < transitions.length; i++) {
if (res.length > 0 && res[res.length - 1].transitionType === transitions[i]) {
@ -184,7 +179,7 @@ export default createReactClass({
}
}
return res;
},
}
/**
* For a certain transition, t, describe what happened to the users that
@ -268,11 +263,11 @@ export default createReactClass({
}
return res;
},
}
_getTransitionSequence: function(events) {
_getTransitionSequence(events) {
return events.map(this._getTransition);
},
}
/**
* Label a given membership event, `e`, where `getContent().membership` has
@ -282,7 +277,7 @@ export default createReactClass({
* @returns {string?} the transition type given to this event. This defaults to `null`
* if a transition is not recognised.
*/
_getTransition: function(e) {
_getTransition(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)) {
@ -323,9 +318,9 @@ export default createReactClass({
}
default: return null;
}
},
}
_getAggregate: function(userEvents) {
_getAggregate(userEvents) {
// A map of aggregate type to arrays of display names. Each aggregate type
// is a comma-delimited string of transitions, e.g. "joined,left,kicked".
// The array of display names is the array of users who went through that
@ -364,9 +359,9 @@ export default createReactClass({
names: aggregate,
indices: aggregateIndices,
};
},
}
render: function() {
render() {
const eventsToRender = this.props.events;
// Map user IDs to an array of objects:
@ -420,5 +415,5 @@ export default createReactClass({
children={this.props.children}
summaryMembers={avatarMembers}
summaryText={this._generateSummary(aggregate.names, orderedTransitionSequences)} />;
},
});
}
}

View file

@ -16,49 +16,44 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import RoomViewStore from '../../../stores/RoomViewStore';
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
import WidgetUtils from '../../../utils/WidgetUtils';
import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
export default createReactClass({
displayName: 'PersistentApp',
export default class PersistentApp extends React.Component {
state = {
roomId: RoomViewStore.getRoomId(),
persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(),
};
getInitialState: function() {
return {
roomId: RoomViewStore.getRoomId(),
persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(),
};
},
componentDidMount: function() {
componentDidMount() {
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
ActiveWidgetStore.on('update', this._onActiveWidgetStoreUpdate);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
if (this._roomStoreToken) {
this._roomStoreToken.remove();
}
ActiveWidgetStore.removeListener('update', this._onActiveWidgetStoreUpdate);
},
}
_onRoomViewStoreUpdate: function(payload) {
_onRoomViewStoreUpdate = payload => {
if (RoomViewStore.getRoomId() === this.state.roomId) return;
this.setState({
roomId: RoomViewStore.getRoomId(),
});
},
};
_onActiveWidgetStoreUpdate: function() {
_onActiveWidgetStoreUpdate = () => {
this.setState({
persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(),
});
},
};
render: function() {
render() {
if (this.state.persistentWidgetId) {
const persistentWidgetInRoomId = ActiveWidgetStore.getRoomId(this.state.persistentWidgetId);
if (this.state.roomId !== persistentWidgetInRoomId) {
@ -91,6 +86,6 @@ export default createReactClass({
}
}
return null;
},
});
}
}

View file

@ -16,7 +16,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import classNames from 'classnames';
@ -32,27 +31,29 @@ import {Action} from "../../../dispatcher/actions";
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
const REGEX_LOCAL_PERMALINK = /^#\/(?:user|room|group)\/(([#!@+])[^/]*)$/;
const Pill = createReactClass({
statics: {
isPillUrl: (url) => {
return !!getPrimaryPermalinkEntity(url);
},
isMessagePillUrl: (url) => {
return !!REGEX_LOCAL_PERMALINK.exec(url);
},
roomNotifPos: (text) => {
return text.indexOf("@room");
},
roomNotifLen: () => {
return "@room".length;
},
TYPE_USER_MENTION: 'TYPE_USER_MENTION',
TYPE_ROOM_MENTION: 'TYPE_ROOM_MENTION',
TYPE_GROUP_MENTION: 'TYPE_GROUP_MENTION',
TYPE_AT_ROOM_MENTION: 'TYPE_AT_ROOM_MENTION', // '@room' mention
},
class Pill extends React.Component {
static isPillUrl(url) {
return !!getPrimaryPermalinkEntity(url);
}
props: {
static isMessagePillUrl(url) {
return !!REGEX_LOCAL_PERMALINK.exec(url);
}
static roomNotifPos(text) {
return text.indexOf("@room");
}
static roomNotifLen() {
return "@room".length;
}
static TYPE_USER_MENTION = 'TYPE_USER_MENTION';
static TYPE_ROOM_MENTION = 'TYPE_ROOM_MENTION';
static TYPE_GROUP_MENTION = 'TYPE_GROUP_MENTION';
static TYPE_AT_ROOM_MENTION = 'TYPE_AT_ROOM_MENTION'; // '@room' mention
static propTypes = {
// The Type of this Pill. If url is given, this is auto-detected.
type: PropTypes.string,
// The URL to pillify (no validation is done, see isPillUrl and isMessagePillUrl)
@ -65,23 +66,21 @@ const Pill = createReactClass({
shouldShowPillAvatar: PropTypes.bool,
// Whether to render this pill as if it were highlit by a selection
isSelected: PropTypes.bool,
},
};
getInitialState() {
return {
// ID/alias of the room/user
resourceId: null,
// Type of pill
pillType: null,
state = {
// ID/alias of the room/user
resourceId: null,
// Type of pill
pillType: null,
// The member related to the user pill
member: null,
// The group related to the group pill
group: null,
// The room related to the room pill
room: null,
};
},
// The member related to the user pill
member: null,
// The group related to the group pill
group: null,
// The room related to the room pill
room: null,
};
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
async UNSAFE_componentWillReceiveProps(nextProps) {
@ -155,7 +154,7 @@ const Pill = createReactClass({
}
}
this.setState({resourceId, pillType, member, group, room});
},
}
componentDidMount() {
this._unmounted = false;
@ -163,13 +162,13 @@ const Pill = createReactClass({
// eslint-disable-next-line new-cap
this.UNSAFE_componentWillReceiveProps(this.props); // HACK: We shouldn't be calling lifecycle functions ourselves.
},
}
componentWillUnmount() {
this._unmounted = true;
},
}
doProfileLookup: function(userId, member) {
doProfileLookup(userId, member) {
MatrixClientPeg.get().getProfileInfo(userId).then((resp) => {
if (this._unmounted) {
return;
@ -188,15 +187,16 @@ const Pill = createReactClass({
}).catch((err) => {
console.error('Could not retrieve profile data for ' + userId + ':', err);
});
},
}
onUserPillClicked: function() {
onUserPillClicked = () => {
dis.dispatch({
action: Action.ViewUser,
member: this.state.member,
});
},
render: function() {
};
render() {
const BaseAvatar = sdk.getComponent('views.avatars.BaseAvatar');
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
@ -285,7 +285,7 @@ const Pill = createReactClass({
// Deliberately render nothing if the URL isn't recognised
return null;
}
},
});
}
}
export default Pill;

View file

@ -16,16 +16,13 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as Roles from '../../../Roles';
import { _t } from '../../../languageHandler';
import Field from "./Field";
import {Key} from "../../../Keyboard";
export default createReactClass({
displayName: 'PowerSelector',
propTypes: {
export default class PowerSelector extends React.Component {
static propTypes = {
value: PropTypes.number.isRequired,
// The maximum value that can be set with the power selector
maxValue: PropTypes.number.isRequired,
@ -42,10 +39,17 @@ export default createReactClass({
// The name to annotate the selector with
label: PropTypes.string,
},
}
getInitialState: function() {
return {
static defaultProps = {
maxValue: Infinity,
usersDefault: 0,
};
constructor(props) {
super(props);
this.state = {
levelRoleMap: {},
// List of power levels to show in the drop-down
options: [],
@ -53,26 +57,16 @@ export default createReactClass({
customValue: this.props.value,
selectValue: 0,
};
},
getDefaultProps: function() {
return {
maxValue: Infinity,
usersDefault: 0,
};
},
componentDidMount: function() {
// TODO: [REACT-WARNING] Move this to class constructor
this._initStateFromProps(this.props);
},
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(newProps) {
UNSAFE_componentWillReceiveProps(newProps) {
this._initStateFromProps(newProps);
},
}
_initStateFromProps: function(newProps) {
_initStateFromProps(newProps) {
// This needs to be done now because levelRoleMap has translated strings
const levelRoleMap = Roles.levelRoleMap(newProps.usersDefault);
const options = Object.keys(levelRoleMap).filter(level => {
@ -92,9 +86,9 @@ export default createReactClass({
customLevel: newProps.value,
selectValue: isCustom ? "SELECT_VALUE_CUSTOM" : newProps.value,
});
},
}
onSelectChange: function(event) {
onSelectChange = event => {
const isCustom = event.target.value === "SELECT_VALUE_CUSTOM";
if (isCustom) {
this.setState({custom: true});
@ -102,20 +96,20 @@ export default createReactClass({
this.props.onChange(event.target.value, this.props.powerLevelKey);
this.setState({selectValue: event.target.value});
}
},
};
onCustomChange: function(event) {
onCustomChange = event => {
this.setState({customValue: event.target.value});
},
};
onCustomBlur: function(event) {
onCustomBlur = event => {
event.preventDefault();
event.stopPropagation();
this.props.onChange(parseInt(this.state.customValue), this.props.powerLevelKey);
},
};
onCustomKeyDown: function(event) {
onCustomKeyDown = event => {
if (event.key === Key.ENTER) {
event.preventDefault();
event.stopPropagation();
@ -127,9 +121,9 @@ export default createReactClass({
// handle the onBlur safely.
event.target.blur();
}
},
};
render: function() {
render() {
let picker;
const label = typeof this.props.label === "undefined" ? _t("Power level") : this.props.label;
if (this.state.custom) {
@ -166,5 +160,5 @@ export default createReactClass({
{ picker }
</div>
);
},
});
}
}

View file

@ -18,7 +18,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
@ -37,10 +36,8 @@ import SettingsStore from "../../../settings/SettingsStore";
// - Rooms that are part of the group
// - Direct messages with members of the group
// with the intention that this could be expanded to arbitrary tags in future.
export default createReactClass({
displayName: 'TagTile',
propTypes: {
export default class TagTile extends React.Component {
static propTypes = {
// A string tag such as "m.favourite" or a group ID such as "+groupid:domain.bla"
// For now, only group IDs are handled.
tag: PropTypes.string,
@ -48,20 +45,16 @@ export default createReactClass({
openMenu: PropTypes.func,
menuDisplayed: PropTypes.bool,
selected: PropTypes.bool,
},
};
statics: {
contextType: MatrixClientContext,
},
static contextType = MatrixClientContext;
getInitialState() {
return {
// Whether the mouse is over the tile
hover: false,
// The profile data of the group if this.props.tag is a group ID
profile: null,
};
},
state = {
// Whether the mouse is over the tile
hover: false,
// The profile data of the group if this.props.tag is a group ID
profile: null,
};
componentDidMount() {
this.unmounted = false;
@ -71,16 +64,16 @@ export default createReactClass({
// New rooms or members may have been added to the group, fetch async
this._refreshGroup(this.props.tag);
}
},
}
componentWillUnmount() {
this.unmounted = true;
if (this.props.tag[0] === '+') {
FlairStore.removeListener('updateGroupProfile', this._onFlairStoreUpdated);
}
},
}
_onFlairStoreUpdated() {
_onFlairStoreUpdated = () => {
if (this.unmounted) return;
FlairStore.getGroupProfileCached(
this.context,
@ -91,14 +84,14 @@ export default createReactClass({
}).catch((err) => {
console.warn('Could not fetch group profile for ' + this.props.tag, err);
});
},
};
_refreshGroup(groupId) {
GroupStore.refreshGroupRooms(groupId);
GroupStore.refreshGroupMembers(groupId);
},
}
onClick: function(e) {
onClick = e => {
e.preventDefault();
e.stopPropagation();
dis.dispatch({
@ -111,27 +104,27 @@ export default createReactClass({
// New rooms or members may have been added to the group, fetch async
this._refreshGroup(this.props.tag);
}
},
};
onMouseOver: function() {
onMouseOver = () => {
if (SettingsStore.getValue("feature_communities_v2_prototypes")) return;
this.setState({ hover: true });
},
};
onMouseLeave: function() {
onMouseLeave = () => {
this.setState({ hover: false });
},
};
openMenu: function(e) {
openMenu = e => {
// Prevent the TagTile onClick event firing as well
e.stopPropagation();
e.preventDefault();
if (SettingsStore.getValue("feature_communities_v2_prototypes")) return;
this.setState({ hover: false });
this.props.openMenu();
},
};
render: function() {
render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const profile = this.state.profile || {};
const name = profile.name || this.props.tag;
@ -192,5 +185,5 @@ export default createReactClass({
{badgeElement}
</div>
</AccessibleTooltipButton>;
},
});
}
}

View file

@ -17,49 +17,44 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import Tinter from "../../../Tinter";
const TintableSvg = createReactClass({
displayName: 'TintableSvg',
propTypes: {
class TintableSvg extends React.Component {
static propTypes = {
src: PropTypes.string.isRequired,
width: PropTypes.string.isRequired,
height: PropTypes.string.isRequired,
className: PropTypes.string,
},
};
statics: {
// list of currently mounted TintableSvgs
mounts: {},
idSequence: 0,
},
// list of currently mounted TintableSvgs
static mounts = {};
static idSequence = 0;
componentDidMount: function() {
componentDidMount() {
this.fixups = [];
this.id = TintableSvg.idSequence++;
TintableSvg.mounts[this.id] = this;
},
}
componentWillUnmount: function() {
componentWillUnmount() {
delete TintableSvg.mounts[this.id];
},
}
tint: function() {
tint = () => {
// TODO: only bother running this if the global tint settings have changed
// since we loaded!
Tinter.applySvgFixups(this.fixups);
},
};
onLoad: function(event) {
onLoad = event => {
// console.log("TintableSvg.onLoad for " + this.props.src);
this.fixups = Tinter.calcSvgFixups([event.target]);
Tinter.applySvgFixups(this.fixups);
},
};
render: function() {
render() {
return (
<object className={"mx_TintableSvg " + (this.props.className ? this.props.className : "")}
type="image/svg+xml"
@ -70,8 +65,8 @@ const TintableSvg = createReactClass({
tabIndex="-1"
/>
);
},
});
}
}
// Register with the Tinter so that we will be told if the tint changes
Tinter.registerTintable(function() {

View file

@ -16,31 +16,26 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
export default createReactClass({
displayName: 'TooltipButton',
export default class TooltipButton extends React.Component {
state = {
hover: false,
};
getInitialState: function() {
return {
hover: false,
};
},
onMouseOver: function() {
onMouseOver = () => {
this.setState({
hover: true,
});
},
};
onMouseLeave: function() {
onMouseLeave = () => {
this.setState({
hover: false,
});
},
};
render: function() {
render() {
const Tooltip = sdk.getComponent("elements.Tooltip");
const tip = this.state.hover ? <Tooltip
className="mx_TooltipButton_container"
@ -53,5 +48,5 @@ export default createReactClass({
{ tip }
</div>
);
},
});
}
}

View file

@ -17,13 +17,10 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'TruncatedList',
propTypes: {
export default class TruncatedList extends React.Component {
static propTypes = {
// The number of elements to show before truncating. If negative, no truncation is done.
truncateAt: PropTypes.number,
// The className to apply to the wrapping div
@ -40,20 +37,18 @@ export default createReactClass({
// A function which will be invoked when an overflow element is required.
// This will be inserted after the children.
createOverflowElement: PropTypes.func,
},
};
getDefaultProps: function() {
return {
truncateAt: 2,
createOverflowElement: function(overflowCount, totalCount) {
return (
<div>{ _t("And %(count)s more...", {count: overflowCount}) }</div>
);
},
};
},
static defaultProps ={
truncateAt: 2,
createOverflowElement(overflowCount, totalCount) {
return (
<div>{ _t("And %(count)s more...", {count: overflowCount}) }</div>
);
},
};
_getChildren: function(start, end) {
_getChildren(start, end) {
if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildren(start, end);
} else {
@ -64,9 +59,9 @@ export default createReactClass({
return c != null;
}).slice(start, end);
}
},
}
_getChildCount: function() {
_getChildCount() {
if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildCount();
} else {
@ -74,9 +69,9 @@ export default createReactClass({
return c != null;
}).length;
}
},
}
render: function() {
render() {
let overflowNode = null;
const totalChildren = this._getChildCount();
@ -98,5 +93,5 @@ export default createReactClass({
{ overflowNode }
</div>
);
},
});
}
}

View file

@ -18,7 +18,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import {_t} from '../../../languageHandler';
@ -29,50 +28,48 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex";
// XXX this class copies a lot from RoomTile.js
export default createReactClass({
displayName: 'GroupInviteTile',
propTypes: {
export default class GroupInviteTile extends React.Component {
static propTypes: {
group: PropTypes.object.isRequired,
},
};
statics: {
contextType: MatrixClientContext,
},
static contextType = MatrixClientContext;
getInitialState: function() {
return ({
constructor(props) {
super(props);
this.state = {
hover: false,
badgeHover: false,
menuDisplayed: false,
selected: this.props.group.groupId === null, // XXX: this needs linking to LoggedInView/GroupView state
});
},
};
}
onClick: function(e) {
onClick = e => {
dis.dispatch({
action: 'view_group',
group_id: this.props.group.groupId,
});
},
};
onMouseEnter: function() {
onMouseEnter = () => {
const state = {hover: true};
// Only allow non-guests to access the context menu
if (!this.context.isGuest()) {
state.badgeHover = true;
}
this.setState(state);
},
};
onMouseLeave: function() {
onMouseLeave = () => {
this.setState({
badgeHover: false,
hover: false,
});
},
};
_showContextMenu: function(boundingClientRect) {
_showContextMenu(boundingClientRect) {
// Only allow non-guests to access the context menu
if (MatrixClientPeg.get().isGuest()) return;
@ -86,17 +83,17 @@ export default createReactClass({
}
this.setState(state);
},
}
onContextMenuButtonClick: function(e) {
onContextMenuButtonClick = e => {
// Prevent the RoomTile onClick event firing as well
e.stopPropagation();
e.preventDefault();
this._showContextMenu(e.target.getBoundingClientRect());
},
};
onContextMenu: function(e) {
onContextMenu = e => {
// Prevent the native context menu
e.preventDefault();
@ -105,15 +102,15 @@ export default createReactClass({
top: e.clientY,
height: 0,
});
},
};
closeMenu: function() {
closeMenu = () => {
this.setState({
contextMenuPosition: null,
});
},
};
render: function() {
render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
@ -197,5 +194,5 @@ export default createReactClass({
{ contextMenu }
</React.Fragment>;
},
});
}
}

View file

@ -16,7 +16,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
@ -30,33 +29,29 @@ import {Action} from "../../../dispatcher/actions";
const INITIAL_LOAD_NUM_MEMBERS = 30;
export default createReactClass({
displayName: 'GroupMemberList',
propTypes: {
export default class GroupMemberList extends React.Component {
static propTypes = {
groupId: PropTypes.string.isRequired,
},
};
getInitialState: function() {
return {
members: null,
membersError: null,
invitedMembers: null,
invitedMembersError: null,
truncateAt: INITIAL_LOAD_NUM_MEMBERS,
};
},
state = {
members: null,
membersError: null,
invitedMembers: null,
invitedMembersError: null,
truncateAt: INITIAL_LOAD_NUM_MEMBERS,
};
componentDidMount: function() {
componentDidMount() {
this._unmounted = false;
this._initGroupStore(this.props.groupId);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
this._unmounted = true;
},
}
_initGroupStore: function(groupId) {
_initGroupStore(groupId) {
GroupStore.registerListener(groupId, () => {
this._fetchMembers();
});
@ -73,17 +68,17 @@ export default createReactClass({
});
}
});
},
}
_fetchMembers: function() {
_fetchMembers() {
if (this._unmounted) return;
this.setState({
members: GroupStore.getGroupMembers(this.props.groupId),
invitedMembers: GroupStore.getGroupInvitedMembers(this.props.groupId),
});
},
}
_createOverflowTile: function(overflowCount, totalCount) {
_createOverflowTile = (overflowCount, totalCount) => {
// For now we'll pretend this is any entity. It should probably be a separate tile.
const EntityTile = sdk.getComponent("rooms.EntityTile");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
@ -94,19 +89,19 @@ export default createReactClass({
} name={text} presenceState="online" suppressOnHover={true}
onClick={this._showFullMemberList} />
);
},
};
_showFullMemberList: function() {
_showFullMemberList = () => {
this.setState({
truncateAt: -1,
});
},
};
onSearchQueryChanged: function(ev) {
onSearchQueryChanged = ev => {
this.setState({ searchQuery: ev.target.value });
},
};
makeGroupMemberTiles: function(query, memberList, memberListError) {
makeGroupMemberTiles(query, memberList, memberListError) {
if (memberListError) {
return <div className="warning">{ _t("Failed to load group members") }</div>;
}
@ -160,9 +155,9 @@ export default createReactClass({
>
{ memberTiles }
</TruncatedList>;
},
}
onInviteToGroupButtonClick() {
onInviteToGroupButtonClick = () => {
showGroupInviteDialog(this.props.groupId).then(() => {
dis.dispatch({
action: Action.SetRightPanelPhase,
@ -170,9 +165,9 @@ export default createReactClass({
refireParams: { groupId: this.props.groupId },
});
});
},
};
render: function() {
render() {
if (this.state.fetching || this.state.fetchingInvitedMembers) {
const Spinner = sdk.getComponent("elements.Spinner");
return (<div className="mx_MemberList">
@ -230,5 +225,5 @@ export default createReactClass({
{ inputBox }
</div>
);
},
});
}
}

View file

@ -18,37 +18,28 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import { GroupMemberType } from '../../../groups';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
export default createReactClass({
displayName: 'GroupMemberTile',
propTypes: {
export default class GroupMemberTile extends React.Component {
static propTypes = {
groupId: PropTypes.string.isRequired,
member: GroupMemberType.isRequired,
},
};
getInitialState: function() {
return {};
},
static contextType = MatrixClientContext;
statics: {
contextType: MatrixClientContext,
},
onClick: function(e) {
onClick = e => {
dis.dispatch({
action: 'view_group_user',
member: this.props.member,
groupId: this.props.groupId,
});
},
};
render: function() {
render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const EntityTile = sdk.getComponent('rooms.EntityTile');
@ -74,5 +65,5 @@ export default createReactClass({
powerStatus={this.props.member.isPrivileged ? EntityTile.POWER_STATUS_ADMIN : null}
/>
);
},
});
}
}

View file

@ -16,44 +16,39 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import GroupStore from '../../../stores/GroupStore';
import ToggleSwitch from "../elements/ToggleSwitch";
export default createReactClass({
displayName: 'GroupPublicityToggle',
propTypes: {
export default class GroupPublicityToggle extends React.Component {
static propTypes = {
groupId: PropTypes.string.isRequired,
},
};
getInitialState() {
return {
busy: false,
ready: false,
isGroupPublicised: false, // assume false as <ToggleSwitch /> expects a boolean
};
},
state = {
busy: false,
ready: false,
isGroupPublicised: false, // assume false as <ToggleSwitch /> expects a boolean
};
componentDidMount: function() {
componentDidMount() {
this._initGroupStore(this.props.groupId);
},
}
_initGroupStore: function(groupId) {
_initGroupStore(groupId) {
this._groupStoreToken = GroupStore.registerListener(groupId, () => {
this.setState({
isGroupPublicised: Boolean(GroupStore.getGroupPublicity(groupId)),
ready: GroupStore.isStateReady(groupId, GroupStore.STATE_KEY.Summary),
});
});
},
}
componentWillUnmount() {
if (this._groupStoreToken) this._groupStoreToken.unregister();
},
}
_onPublicityToggle: function() {
_onPublicityToggle = () => {
this.setState({
busy: true,
// Optimistic early update
@ -64,7 +59,7 @@ export default createReactClass({
busy: false,
});
});
},
};
render() {
const GroupTile = sdk.getComponent('groups.GroupTile');
@ -76,5 +71,5 @@ export default createReactClass({
disabled={!this.state.ready || this.state.busy}
onChange={this._onPublicityToggle} />
</div>;
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import dis from '../../../dispatcher/dispatcher';
import Modal from '../../../Modal';
import * as sdk from '../../../index';
@ -26,30 +25,24 @@ import GroupStore from '../../../stores/GroupStore';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
export default createReactClass({
displayName: 'GroupRoomInfo',
export default class GroupRoomInfo extends React.Component {
static contextType = MatrixClientContext;
statics: {
contextType: MatrixClientContext,
},
propTypes: {
static propTypes = {
groupId: PropTypes.string,
groupRoomId: PropTypes.string,
},
};
getInitialState: function() {
return {
isUserPrivilegedInGroup: null,
groupRoom: null,
groupRoomPublicityLoading: false,
groupRoomRemoveLoading: false,
};
},
state = {
isUserPrivilegedInGroup: null,
groupRoom: null,
groupRoomPublicityLoading: false,
groupRoomRemoveLoading: false,
};
componentDidMount: function() {
componentDidMount() {
this._initGroupStore(this.props.groupId);
},
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps(newProps) {
@ -57,19 +50,19 @@ export default createReactClass({
this._unregisterGroupStore(this.props.groupId);
this._initGroupStore(newProps.groupId);
}
},
}
componentWillUnmount() {
this._unregisterGroupStore(this.props.groupId);
},
}
_initGroupStore(groupId) {
GroupStore.registerListener(groupId, this.onGroupStoreUpdated);
},
}
_unregisterGroupStore(groupId) {
GroupStore.unregisterListener(this.onGroupStoreUpdated);
},
}
_updateGroupRoom() {
this.setState({
@ -77,16 +70,16 @@ export default createReactClass({
(r) => r.roomId === this.props.groupRoomId,
),
});
},
}
onGroupStoreUpdated: function() {
onGroupStoreUpdated = () => {
this.setState({
isUserPrivilegedInGroup: GroupStore.isUserPrivileged(this.props.groupId),
});
this._updateGroupRoom();
},
};
_onRemove: function(e) {
_onRemove = e => {
const groupId = this.props.groupId;
const roomName = this.state.groupRoom.displayname;
e.preventDefault();
@ -119,15 +112,15 @@ export default createReactClass({
});
},
});
},
};
_onCancel: function(e) {
_onCancel = e => {
dis.dispatch({
action: "view_group_room_list",
});
},
};
_changeGroupRoomPublicity(e) {
_changeGroupRoomPublicity = e => {
const isPublic = e.target.value === "public";
this.setState({
groupRoomPublicityLoading: true,
@ -150,9 +143,9 @@ export default createReactClass({
groupRoomPublicityLoading: false,
});
});
},
};
render: function() {
render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const InlineSpinner = sdk.getComponent('elements.InlineSpinner');
if (this.state.groupRoomRemoveLoading || !this.state.groupRoom) {
@ -235,5 +228,5 @@ export default createReactClass({
</AutoHideScrollbar>
</div>
);
},
});
}
}

View file

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
import GroupStore from '../../../stores/GroupStore';
@ -25,34 +24,32 @@ import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
const INITIAL_LOAD_NUM_ROOMS = 30;
export default createReactClass({
propTypes: {
export default class GroupRoomList extends React.Component {
static propTypes = {
groupId: PropTypes.string.isRequired,
},
};
getInitialState: function() {
return {
rooms: null,
truncateAt: INITIAL_LOAD_NUM_ROOMS,
searchQuery: "",
};
},
state = {
rooms: null,
truncateAt: INITIAL_LOAD_NUM_ROOMS,
searchQuery: "",
};
componentDidMount: function() {
componentDidMount() {
this._unmounted = false;
this._initGroupStore(this.props.groupId);
},
}
componentWillUnmount() {
this._unmounted = true;
this._unregisterGroupStore();
},
}
_unregisterGroupStore() {
GroupStore.unregisterListener(this.onGroupStoreUpdated);
},
}
_initGroupStore: function(groupId) {
_initGroupStore(groupId) {
GroupStore.registerListener(groupId, this.onGroupStoreUpdated);
// XXX: This should be more fluxy - let's get the error from GroupStore .getError or something
// XXX: This is also leaked - we should remove it when unmounting
@ -62,16 +59,16 @@ export default createReactClass({
rooms: null,
});
});
},
}
onGroupStoreUpdated: function() {
onGroupStoreUpdated = () => {
if (this._unmounted) return;
this.setState({
rooms: GroupStore.getGroupRooms(this.props.groupId),
});
},
};
_createOverflowTile: function(overflowCount, totalCount) {
_createOverflowTile = (overflowCount, totalCount) => {
// For now we'll pretend this is any entity. It should probably be a separate tile.
const EntityTile = sdk.getComponent("rooms.EntityTile");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
@ -82,25 +79,25 @@ export default createReactClass({
} name={text} presenceState="online" suppressOnHover={true}
onClick={this._showFullRoomList} />
);
},
};
_showFullRoomList: function() {
_showFullRoomList = () => {
this.setState({
truncateAt: -1,
});
},
};
onSearchQueryChanged: function(ev) {
onSearchQueryChanged = ev => {
this.setState({ searchQuery: ev.target.value });
},
};
onAddRoomToGroupButtonClick() {
onAddRoomToGroupButtonClick = () => {
showGroupAddRoomDialog(this.props.groupId).then(() => {
this.forceUpdate();
});
},
};
makeGroupRoomTiles: function(query) {
makeGroupRoomTiles(query) {
const GroupRoomTile = sdk.getComponent("groups.GroupRoomTile");
query = (query || "").toLowerCase();
@ -123,9 +120,9 @@ export default createReactClass({
});
return roomList;
},
}
render: function() {
render() {
if (this.state.rooms === null) {
return null;
}
@ -160,5 +157,5 @@ export default createReactClass({
{ inputBox }
</div>
);
},
});
}
}

View file

@ -16,29 +16,28 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import { GroupRoomType } from '../../../groups';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
const GroupRoomTile = createReactClass({
displayName: 'GroupRoomTile',
propTypes: {
class GroupRoomTile extends React.Component {
static propTypes = {
groupId: PropTypes.string.isRequired,
groupRoom: GroupRoomType.isRequired,
},
};
onClick: function(e) {
static contextType = MatrixClientContext
onClick = e => {
dis.dispatch({
action: 'view_group_room',
groupId: this.props.groupId,
groupRoomId: this.props.groupRoom.roomId,
});
},
};
render: function() {
render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const avatarUrl = this.context.mxcUrlToHttp(
@ -63,10 +62,7 @@ const GroupRoomTile = createReactClass({
</div>
</AccessibleButton>
);
},
});
GroupRoomTile.contextType = MatrixClientContext;
}
}
export default GroupRoomTile;

View file

@ -16,7 +16,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { Draggable, Droppable } from 'react-beautiful-dnd';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
@ -25,53 +24,45 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
function nop() {}
const GroupTile = createReactClass({
displayName: 'GroupTile',
propTypes: {
class GroupTile extends React.Component {
static propTypes = {
groupId: PropTypes.string.isRequired,
// Whether to show the short description of the group on the tile
showDescription: PropTypes.bool,
// Height of the group avatar in pixels
avatarHeight: PropTypes.number,
draggable: PropTypes.bool,
},
};
statics: {
contextType: MatrixClientContext,
},
static contextType = MatrixClientContext;
getInitialState() {
return {
profile: null,
};
},
static defaultProps = {
showDescription: true,
avatarHeight: 50,
draggable: true,
};
getDefaultProps() {
return {
showDescription: true,
avatarHeight: 50,
draggable: true,
};
},
state = {
profile: null,
};
componentDidMount: function() {
componentDidMount() {
FlairStore.getGroupProfileCached(this.context, this.props.groupId).then((profile) => {
this.setState({profile});
}).catch((err) => {
console.error('Error whilst getting cached profile for GroupTile', err);
});
},
}
onMouseDown: function(e) {
onMouseDown = e => {
e.preventDefault();
dis.dispatch({
action: 'view_group',
group_id: this.props.groupId,
});
},
};
render: function() {
render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const profile = this.state.profile || {};
@ -135,7 +126,7 @@ const GroupTile = createReactClass({
<div className="mx_GroupTile_groupId">{ this.props.groupId }</div>
</div>
</AccessibleButton>;
},
});
}
}
export default GroupTile;

View file

@ -15,33 +15,26 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
export default createReactClass({
displayName: 'GroupUserSettings',
export default class GroupUserSettings extends React.Component {
static contextType = MatrixClientContext;
statics: {
contextType: MatrixClientContext,
},
state = {
error: null,
groups: null,
};
getInitialState() {
return {
error: null,
groups: null,
};
},
componentDidMount: function() {
componentDidMount() {
this.context.getJoinedGroups().then((result) => {
this.setState({groups: result.groups || [], error: null});
}, (err) => {
console.error(err);
this.setState({groups: null, error: err});
});
},
}
render() {
let text = "";
@ -70,5 +63,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import filesize from 'filesize';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import * as sdk from '../../../index';
@ -117,16 +116,8 @@ function computedStyle(element) {
return cssText;
}
export default createReactClass({
displayName: 'MFileBody',
getInitialState: function() {
return {
decryptedBlob: (this.props.decryptedBlob ? this.props.decryptedBlob : null),
};
},
propTypes: {
export default class MFileBody extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
/* already decrypted blob */
@ -135,7 +126,19 @@ export default createReactClass({
onHeightChanged: PropTypes.func,
/* the shape of the tile, used */
tileShape: PropTypes.string,
},
};
constructor(props) {
super(props);
this.state = {
decryptedBlob: (this.props.decryptedBlob ? this.props.decryptedBlob : null),
};
this._iframe = createRef();
this._dummyLink = createRef();
this._downloadImage = createRef();
}
/**
* Extracts a human readable label for the file attachment to use as
@ -144,7 +147,7 @@ export default createReactClass({
* @params {Object} content The "content" key of the matrix event.
* @return {string} the human readable link text for the attachment.
*/
presentableTextForFile: function(content) {
presentableTextForFile(content) {
let linkText = _t("Attachment");
if (content.body && content.body.length > 0) {
// The content body should be the name of the file including a
@ -163,40 +166,33 @@ export default createReactClass({
linkText += ' (' + filesize(content.info.size) + ')';
}
return linkText;
},
}
_getContentUrl: function() {
_getContentUrl() {
const content = this.props.mxEvent.getContent();
return MatrixClientPeg.get().mxcUrlToHttp(content.url);
},
}
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._iframe = createRef();
this._dummyLink = createRef();
this._downloadImage = createRef();
},
componentDidMount: function() {
componentDidMount() {
// Add this to the list of mounted components to receive notifications
// when the tint changes.
this.id = nextMountId++;
mounts[this.id] = this;
this.tint();
},
}
componentDidUpdate: function(prevProps, prevState) {
componentDidUpdate(prevProps, prevState) {
if (this.props.onHeightChanged && !prevState.decryptedBlob && this.state.decryptedBlob) {
this.props.onHeightChanged();
}
},
}
componentWillUnmount: function() {
componentWillUnmount() {
// Remove this from the list of mounted components
delete mounts[this.id];
},
}
tint: function() {
tint = () => {
// Update our tinted copy of require("../../../../res/img/download.svg")
if (this._downloadImage.current) {
this._downloadImage.current.src = tintedDownloadImageURL;
@ -210,9 +206,9 @@ export default createReactClass({
style: computedStyle(this._dummyLink.current),
}, "*");
}
},
};
render: function() {
render() {
const content = this.props.mxEvent.getContent();
const text = this.presentableTextForFile(content);
const isEncrypted = content.file !== undefined;
@ -378,5 +374,5 @@ export default createReactClass({
{ _t("Invalid file%(extra)s", { extra: extra }) }
</span>;
}
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import MFileBody from './MFileBody';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { decryptFile } from '../../../utils/DecryptFile';
@ -25,27 +24,23 @@ import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
import InlineSpinner from '../elements/InlineSpinner';
export default createReactClass({
displayName: 'MVideoBody',
propTypes: {
export default class MVideoBody extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
/* called when the video has loaded */
onHeightChanged: PropTypes.func.isRequired,
},
};
getInitialState: function() {
return {
decryptedUrl: null,
decryptedThumbnailUrl: null,
decryptedBlob: null,
error: null,
};
},
state = {
decryptedUrl: null,
decryptedThumbnailUrl: null,
decryptedBlob: null,
error: null,
};
thumbScale: function(fullWidth, fullHeight, thumbWidth, thumbHeight) {
thumbScale(fullWidth, fullHeight, thumbWidth, thumbHeight) {
if (!fullWidth || !fullHeight) {
// Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even
// log this because it's spammy
@ -64,18 +59,18 @@ export default createReactClass({
// height is the dominant dimension so scaling will be fixed on that
return heightMulti;
}
},
}
_getContentUrl: function() {
_getContentUrl() {
const content = this.props.mxEvent.getContent();
if (content.file !== undefined) {
return this.state.decryptedUrl;
} else {
return MatrixClientPeg.get().mxcUrlToHttp(content.url);
}
},
}
_getThumbUrl: function() {
_getThumbUrl() {
const content = this.props.mxEvent.getContent();
if (content.file !== undefined) {
return this.state.decryptedThumbnailUrl;
@ -84,9 +79,9 @@ export default createReactClass({
} else {
return null;
}
},
}
componentDidMount: function() {
componentDidMount() {
const content = this.props.mxEvent.getContent();
if (content.file !== undefined && this.state.decryptedUrl === null) {
let thumbnailPromise = Promise.resolve(null);
@ -118,18 +113,18 @@ export default createReactClass({
});
});
}
},
}
componentWillUnmount: function() {
componentWillUnmount() {
if (this.state.decryptedUrl) {
URL.revokeObjectURL(this.state.decryptedUrl);
}
if (this.state.decryptedThumbnailUrl) {
URL.revokeObjectURL(this.state.decryptedThumbnailUrl);
}
},
}
render: function() {
render() {
const content = this.props.mxEvent.getContent();
if (this.state.error !== null) {
@ -182,5 +177,5 @@ export default createReactClass({
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} />
</span>
);
},
});
}
}

View file

@ -16,17 +16,14 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import SettingsStore from "../../../settings/SettingsStore";
import {Mjolnir} from "../../../mjolnir/Mjolnir";
import RedactedBody from "./RedactedBody";
import UnknownBody from "./UnknownBody";
export default createReactClass({
displayName: 'MessageEvent',
propTypes: {
export default class MessageEvent extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
@ -47,22 +44,23 @@ export default createReactClass({
/* the maximum image height to use, if the event is an image */
maxImageHeight: PropTypes.number,
},
};
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._body = createRef();
},
}
getEventTileOps: function() {
getEventTileOps = () => {
return this._body.current && this._body.current.getEventTileOps ? this._body.current.getEventTileOps() : null;
},
};
onTileUpdate: function() {
onTileUpdate = () => {
this.forceUpdate();
},
};
render: function() {
render() {
const bodyTypes = {
'm.text': sdk.getComponent('messages.TextualBody'),
'm.notice': sdk.getComponent('messages.TextualBody'),
@ -123,5 +121,5 @@ export default createReactClass({
onHeightChanged={this.props.onHeightChanged}
onMessageAllowed={this.onTileUpdate}
/>;
},
});
}
}

View file

@ -18,22 +18,19 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import AccessibleButton from '../elements/AccessibleButton';
export default createReactClass({
displayName: 'RoomAvatarEvent',
propTypes: {
export default class RoomAvatarEvent extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
},
};
onAvatarClick: function() {
onAvatarClick = () => {
const cli = MatrixClientPeg.get();
const ev = this.props.mxEvent;
const httpUrl = cli.mxcUrlToHttp(ev.getContent().url);
@ -50,9 +47,9 @@ export default createReactClass({
name: text,
};
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
},
};
render: function() {
render() {
const ev = this.props.mxEvent;
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
@ -86,5 +83,5 @@ export default createReactClass({
}
</div>
);
},
});
}
}

View file

@ -17,22 +17,19 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import dis from '../../../dispatcher/dispatcher';
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
export default createReactClass({
displayName: 'RoomCreate',
propTypes: {
export default class RoomCreate extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
},
};
_onLinkClicked: function(e) {
_onLinkClicked = e => {
e.preventDefault();
const predecessor = this.props.mxEvent.getContent()['predecessor'];
@ -43,9 +40,9 @@ export default createReactClass({
highlighted: true,
room_id: predecessor['room_id'],
});
},
};
render: function() {
render() {
const predecessor = this.props.mxEvent.getContent()['predecessor'];
if (predecessor === undefined) {
return <div />; // We should never have been instaniated in this case
@ -66,5 +63,5 @@ export default createReactClass({
{_t("Click here to see older messages.")}
</a>
</div>;
},
});
}
}

View file

@ -16,31 +16,25 @@
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import Flair from '../elements/Flair.js';
import FlairStore from '../../../stores/FlairStore';
import { _t } from '../../../languageHandler';
import {getUserNameColorClass} from '../../../utils/FormattingUtils';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
export default createReactClass({
displayName: 'SenderProfile',
propTypes: {
export default class SenderProfile extends React.Component {
static propTypes = {
mxEvent: PropTypes.object.isRequired, // event whose sender we're showing
text: PropTypes.string, // Text to show. Defaults to sender name
onClick: PropTypes.func,
},
};
statics: {
contextType: MatrixClientContext,
},
static contextType = MatrixClientContext;
getInitialState() {
return {
userGroups: null,
relatedGroups: [],
};
},
state = {
userGroups: null,
relatedGroups: [],
};
componentDidMount() {
this.unmounted = false;
@ -54,20 +48,20 @@ export default createReactClass({
});
this.context.on('RoomState.events', this.onRoomStateEvents);
},
}
componentWillUnmount() {
this.unmounted = true;
this.context.removeListener('RoomState.events', this.onRoomStateEvents);
},
}
onRoomStateEvents(event) {
onRoomStateEvents = event => {
if (event.getType() === 'm.room.related_groups' &&
event.getRoomId() === this.props.mxEvent.getRoomId()
) {
this._updateRelatedGroups();
}
},
};
_updateRelatedGroups() {
if (this.unmounted) return;
@ -78,7 +72,7 @@ export default createReactClass({
this.setState({
relatedGroups: relatedGroupsEvent ? relatedGroupsEvent.getContent().groups || [] : [],
});
},
}
_getDisplayedGroups(userGroups, relatedGroups) {
let displayedGroups = userGroups || [];
@ -90,7 +84,7 @@ export default createReactClass({
displayedGroups = [];
}
return displayedGroups;
},
}
render() {
const {mxEvent} = this.props;
@ -138,5 +132,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -19,7 +19,6 @@ limitations under the License.
import React, {createRef} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import highlight from 'highlight.js';
import * as HtmlUtils from '../../../HtmlUtils';
import {formatDate} from '../../../DateUtils';
@ -37,10 +36,8 @@ import {toRightOf} from "../../structures/ContextMenu";
import {copyPlaintext} from "../../../utils/strings";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
export default createReactClass({
displayName: 'TextualBody',
propTypes: {
export default class TextualBody extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
@ -58,10 +55,14 @@ export default createReactClass({
/* the shape of the tile, used */
tileShape: PropTypes.string,
},
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this._content = createRef();
this.state = {
// the URLs (if any) to be previewed with a LinkPreviewWidget
// inside this TextualBody.
links: [],
@ -69,20 +70,15 @@ export default createReactClass({
// track whether the preview widget is hidden
widgetHidden: false,
};
},
}
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._content = createRef();
},
componentDidMount: function() {
componentDidMount() {
this._unmounted = false;
this._pills = [];
if (!this.props.editState) {
this._applyFormatting();
}
},
}
_applyFormatting() {
this.activateSpoilers([this._content.current]);
@ -119,9 +115,9 @@ export default createReactClass({
}
this._addCodeCopyButton();
}
},
}
componentDidUpdate: function(prevProps) {
componentDidUpdate(prevProps) {
if (!this.props.editState) {
const stoppedEditing = prevProps.editState && !this.props.editState;
const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId;
@ -129,14 +125,14 @@ export default createReactClass({
this._applyFormatting();
}
}
},
}
componentWillUnmount: function() {
componentWillUnmount() {
this._unmounted = true;
unmountPills(this._pills);
},
}
shouldComponentUpdate: function(nextProps, nextState) {
shouldComponentUpdate(nextProps, nextState) {
//console.info("shouldComponentUpdate: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
// exploit that events are immutable :)
@ -148,9 +144,9 @@ export default createReactClass({
nextProps.editState !== this.props.editState ||
nextState.links !== this.state.links ||
nextState.widgetHidden !== this.state.widgetHidden);
},
}
calculateUrlPreview: function() {
calculateUrlPreview() {
//console.info("calculateUrlPreview: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
if (this.props.showUrlPreview) {
@ -176,9 +172,9 @@ export default createReactClass({
this.setState({ links: [] });
}
}
},
}
activateSpoilers: function(nodes) {
activateSpoilers(nodes) {
let node = nodes[0];
while (node) {
if (node.tagName === "SPAN" && typeof node.getAttribute("data-mx-spoiler") === "string") {
@ -204,9 +200,9 @@ export default createReactClass({
node = node.nextSibling;
}
},
}
findLinks: function(nodes) {
findLinks(nodes) {
let links = [];
for (let i = 0; i < nodes.length; i++) {
@ -223,9 +219,9 @@ export default createReactClass({
}
}
return links;
},
}
isLinkPreviewable: function(node) {
isLinkPreviewable(node) {
// don't try to preview relative links
if (!node.getAttribute("href").startsWith("http://") &&
!node.getAttribute("href").startsWith("https://")) {
@ -256,7 +252,7 @@ export default createReactClass({
return true;
}
}
},
}
_addCodeCopyButton() {
// Add 'copy' buttons to pre blocks
@ -288,41 +284,39 @@ export default createReactClass({
div.appendChild(p);
div.appendChild(button);
});
},
}
onCancelClick: function(event) {
onCancelClick = event => {
this.setState({ widgetHidden: true });
// FIXME: persist this somewhere smarter than local storage
if (global.localStorage) {
global.localStorage.setItem("hide_preview_" + this.props.mxEvent.getId(), "1");
}
this.forceUpdate();
},
};
onEmoteSenderClick: function(event) {
onEmoteSenderClick = event => {
const mxEvent = this.props.mxEvent;
dis.dispatch({
action: 'insert_mention',
user_id: mxEvent.getSender(),
});
},
};
getEventTileOps: function() {
return {
isWidgetHidden: () => {
return this.state.widgetHidden;
},
getEventTileOps = () => ({
isWidgetHidden: () => {
return this.state.widgetHidden;
},
unhideWidget: () => {
this.setState({ widgetHidden: false });
if (global.localStorage) {
global.localStorage.removeItem("hide_preview_" + this.props.mxEvent.getId());
}
},
};
},
unhideWidget: () => {
this.setState({widgetHidden: false});
if (global.localStorage) {
global.localStorage.removeItem("hide_preview_" + this.props.mxEvent.getId());
}
},
});
onStarterLinkClick: function(starterLink, ev) {
onStarterLinkClick = (starterLink, ev) => {
ev.preventDefault();
// We need to add on our scalar token to the starter link, but we may not have one!
// In addition, we can't fetch one on click and then go to it immediately as that
@ -353,7 +347,7 @@ export default createReactClass({
"Do you wish to continue?", { integrationsUrl: integrationsUrl }) }
</div>,
button: _t("Continue"),
onFinished: function(confirmed) {
onFinished(confirmed) {
if (!confirmed) {
return;
}
@ -367,14 +361,14 @@ export default createReactClass({
},
});
});
},
};
_openHistoryDialog: async function() {
_openHistoryDialog = async () => {
const MessageEditHistoryDialog = sdk.getComponent("views.dialogs.MessageEditHistoryDialog");
Modal.createDialog(MessageEditHistoryDialog, {mxEvent: this.props.mxEvent});
},
};
_renderEditedMarker: function() {
_renderEditedMarker() {
const date = this.props.mxEvent.replacingEventDate();
const dateString = date && formatDate(date);
@ -397,9 +391,9 @@ export default createReactClass({
<span>{`(${_t("edited")})`}</span>
</AccessibleTooltipButton>
);
},
}
render: function() {
render() {
if (this.props.editState) {
const EditMessageComposer = sdk.getComponent('rooms.EditMessageComposer');
return <EditMessageComposer editState={this.props.editState} className="mx_EventTile_content" />;
@ -468,5 +462,5 @@ export default createReactClass({
</span>
);
}
},
});
}
}

View file

@ -17,22 +17,19 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as TextForEvent from "../../../TextForEvent";
export default createReactClass({
displayName: 'TextualEvent',
propTypes: {
export default class TextualEvent extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
},
};
render: function() {
render() {
const text = TextForEvent.textForEvent(this.props.mxEvent);
if (text == null || text.length === 0) return null;
return (
<div className="mx_TextualEvent">{ text }</div>
);
},
});
}
}

View file

@ -19,7 +19,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from "../../../index";
import { _t, _td } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
@ -29,20 +28,18 @@ import {Action} from "../../../dispatcher/actions";
import {SettingLevel} from "../../../settings/SettingLevel";
export default createReactClass({
displayName: 'UrlPreviewSettings',
propTypes: {
export default class UrlPreviewSettings extends React.Component {
static propTypes = {
room: PropTypes.object,
},
};
_onClickUserSettings: (e) => {
_onClickUserSettings = (e) => {
e.preventDefault();
e.stopPropagation();
dis.fire(Action.ViewUserSettings);
},
};
render: function() {
render() {
const SettingsFlag = sdk.getComponent("elements.SettingsFlag");
const roomId = this.props.room.roomId;
const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId);
@ -110,5 +107,5 @@ export default createReactClass({
<label>{ previewsForRoomAccount }</label>
</div>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import AppTile from '../elements/AppTile';
import Modal from '../../../Modal';
@ -34,50 +33,50 @@ import SettingsStore from "../../../settings/SettingsStore";
// The maximum number of widgets that can be added in a room
const MAX_WIDGETS = 2;
export default createReactClass({
displayName: 'AppsDrawer',
propTypes: {
export default class AppsDrawer extends React.Component {
static propTypes = {
userId: PropTypes.string.isRequired,
room: PropTypes.object.isRequired,
showApps: PropTypes.bool, // Should apps be rendered
hide: PropTypes.bool, // If rendered, should apps drawer be visible
},
};
getDefaultProps: () => ({
static defaultProps = {
showApps: true,
hide: false,
}),
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
apps: this._getApps(),
};
},
}
componentDidMount: function() {
componentDidMount() {
ScalarMessaging.startListening();
MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
WidgetEchoStore.on('update', this._updateApps);
this.dispatcherRef = dis.register(this.onAction);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
ScalarMessaging.stopListening();
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
}
WidgetEchoStore.removeListener('update', this._updateApps);
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
},
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps(newProps) {
// Room has changed probably, update apps
this._updateApps();
},
}
onAction: function(action) {
onAction = (action) => {
const hideWidgetKey = this.props.room.roomId + '_hide_widget_drawer';
switch (action.action) {
case 'appsDrawer':
@ -93,16 +92,16 @@ export default createReactClass({
break;
}
},
};
onRoomStateEvents: function(ev, state) {
onRoomStateEvents = (ev, state) => {
if (ev.getRoomId() !== this.props.room.roomId || ev.getType() !== 'im.vector.modular.widgets') {
return;
}
this._updateApps();
},
};
_getApps: function() {
_getApps() {
const widgets = WidgetEchoStore.getEchoedRoomWidgets(
this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room),
);
@ -111,33 +110,33 @@ export default createReactClass({
ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(),
);
});
},
}
_updateApps: function() {
_updateApps = () => {
const apps = this._getApps();
this.setState({
apps: apps,
});
},
};
_canUserModify: function() {
_canUserModify() {
try {
return WidgetUtils.canUserModifyWidgets(this.props.room.roomId);
} catch (err) {
console.error(err);
return false;
}
},
}
_launchManageIntegrations: function() {
_launchManageIntegrations() {
if (SettingsStore.getValue("feature_many_integration_managers")) {
IntegrationManagers.sharedInstance().openAll();
} else {
IntegrationManagers.sharedInstance().getPrimaryManager().open(this.props.room, 'add_integ');
}
},
}
onClickAddWidget: function(e) {
onClickAddWidget = (e) => {
e.preventDefault();
// Display a warning dialog if the max number of widgets have already been added to the room
const apps = this._getApps();
@ -152,9 +151,9 @@ export default createReactClass({
return;
}
this._launchManageIntegrations();
},
};
render: function() {
render() {
const apps = this.state.apps.map((app, index, arr) => {
const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, this.props.room.roomId);
@ -211,5 +210,5 @@ export default createReactClass({
{ this._canUserModify() && addWidget }
</div>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import * as sdk from '../../../index';
import dis from "../../../dispatcher/dispatcher";
@ -31,10 +30,8 @@ import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import CallView from "../voip/CallView";
export default createReactClass({
displayName: 'AuxPanel',
propTypes: {
export default class AuxPanel extends React.Component {
static propTypes = {
// js-sdk room object
room: PropTypes.object.isRequired,
userId: PropTypes.string.isRequired,
@ -58,42 +55,46 @@ export default createReactClass({
// content in a way that is likely to make it change size.
onResize: PropTypes.func,
fullHeight: PropTypes.bool,
},
};
getDefaultProps: () => ({
static defaultProps = {
showApps: true,
hideAppsDrawer: false,
}),
};
getInitialState: function() {
return { counters: this._computeCounters() };
},
constructor(props) {
super(props);
componentDidMount: function() {
this.state = {
counters: this._computeCounters(),
};
}
componentDidMount() {
const cli = MatrixClientPeg.get();
cli.on("RoomState.events", this._rateLimitedUpdate);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
const cli = MatrixClientPeg.get();
if (cli) {
cli.removeListener("RoomState.events", this._rateLimitedUpdate);
}
},
}
shouldComponentUpdate: function(nextProps, nextState) {
shouldComponentUpdate(nextProps, nextState) {
return (!ObjectUtils.shallowEqual(this.props, nextProps) ||
!ObjectUtils.shallowEqual(this.state, nextState));
},
}
componentDidUpdate: function(prevProps, prevState) {
componentDidUpdate(prevProps, prevState) {
// most changes are likely to cause a resize
if (this.props.onResize) {
this.props.onResize();
}
},
}
onConferenceNotificationClick: function(ev, type) {
onConferenceNotificationClick = (ev, type) => {
dis.dispatch({
action: 'place_call',
type: type,
@ -101,15 +102,15 @@ export default createReactClass({
});
ev.stopPropagation();
ev.preventDefault();
},
};
_rateLimitedUpdate: new RateLimitedFunc(function() {
_rateLimitedUpdate = new RateLimitedFunc(() => {
if (SettingsStore.getValue("feature_state_counters")) {
this.setState({counters: this._computeCounters()});
}
}, 500),
}, 500);
_computeCounters: function() {
_computeCounters() {
let counters = [];
if (this.props.room && SettingsStore.getValue("feature_state_counters")) {
@ -140,9 +141,9 @@ export default createReactClass({
}
return counters;
},
}
render: function() {
render() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
let fileDropTarget = null;
@ -274,5 +275,5 @@ export default createReactClass({
{ this.props.children }
</AutoHideScrollbar>
);
},
});
}
}

View file

@ -18,7 +18,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import AccessibleButton from '../elements/AccessibleButton';
import { _t } from '../../../languageHandler';
@ -51,10 +50,8 @@ function presenceClassForMember(presenceState, lastActiveAgo, showPresence) {
}
}
const EntityTile = createReactClass({
displayName: 'EntityTile',
propTypes: {
class EntityTile extends React.Component {
static propTypes = {
name: PropTypes.string,
title: PropTypes.string,
avatarJsx: PropTypes.any, // <BaseAvatar />
@ -70,33 +67,29 @@ const EntityTile = createReactClass({
showPresence: PropTypes.bool,
subtextLabel: PropTypes.string,
e2eStatus: PropTypes.string,
},
};
getDefaultProps: function() {
return {
shouldComponentUpdate: function(nextProps, nextState) { return true; },
onClick: function() {},
presenceState: "offline",
presenceLastActiveAgo: 0,
presenceLastTs: 0,
showInviteButton: false,
suppressOnHover: false,
showPresence: true,
};
},
static defaultProps = {
shouldComponentUpdate: function(nextProps, nextState) { return true; },
onClick: function() {},
presenceState: "offline",
presenceLastActiveAgo: 0,
presenceLastTs: 0,
showInviteButton: false,
suppressOnHover: false,
showPresence: true,
};
getInitialState: function() {
return {
hover: false,
};
},
state = {
hover: false,
};
shouldComponentUpdate: function(nextProps, nextState) {
shouldComponentUpdate(nextProps, nextState) {
if (this.state.hover !== nextState.hover) return true;
return this.props.shouldComponentUpdate(nextProps, nextState);
},
}
render: function() {
render() {
const mainClassNames = {
"mx_EntityTile": true,
"mx_EntityTile_noHover": this.props.suppressOnHover,
@ -193,8 +186,8 @@ const EntityTile = createReactClass({
</AccessibleButton>
</div>
);
},
});
}
}
EntityTile.POWER_STATUS_MODERATOR = "moderator";
EntityTile.POWER_STATUS_ADMIN = "admin";

View file

@ -20,7 +20,6 @@ limitations under the License.
import ReplyThread from "../elements/ReplyThread";
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from "classnames";
import { _t, _td } from '../../../languageHandler';
import * as TextForEvent from "../../../TextForEvent";
@ -127,10 +126,8 @@ const MAX_READ_AVATARS = 5;
// | '--------------------------------------' |
// '----------------------------------------------------------'
export default createReactClass({
displayName: 'EventTile',
propTypes: {
export default class EventTile extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
@ -209,17 +206,19 @@ export default createReactClass({
// whether to use the irc layout
useIRCLayout: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
// no-op function because onHeightChanged is optional yet some sub-components assume its existence
onHeightChanged: function() {},
};
},
static defaultProps = {
// no-op function because onHeightChanged is optional yet some sub-components assume its existence
onHeightChanged: function() {},
};
getInitialState: function() {
return {
static contextType = MatrixClientContext;
constructor(props) {
super(props);
this.state = {
// Whether the action bar is focused.
actionBarFocused: false,
// Whether all read receipts are being displayed. If not, only display
@ -232,23 +231,16 @@ export default createReactClass({
// The Relations model from the JS SDK for reactions to `mxEvent`
reactions: this.getReactions(),
};
},
statics: {
contextType: MatrixClientContext,
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
// don't do RR animations until we are mounted
this._suppressReadReceiptAnimation = true;
this._verifyEvent(this.props.mxEvent);
this._tile = createRef();
this._replyThread = createRef();
},
}
componentDidMount: function() {
componentDidMount() {
this._suppressReadReceiptAnimation = false;
const client = this.context;
client.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
@ -257,26 +249,26 @@ export default createReactClass({
if (this.props.showReactions) {
this.props.mxEvent.on("Event.relationsCreated", this._onReactionsCreated);
}
},
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(nextProps) {
UNSAFE_componentWillReceiveProps(nextProps) {
// re-check the sender verification as outgoing events progress through
// the send process.
if (nextProps.eventSendStatus !== this.props.eventSendStatus) {
this._verifyEvent(nextProps.mxEvent);
}
},
}
shouldComponentUpdate: function(nextProps, nextState) {
shouldComponentUpdate(nextProps, nextState) {
if (!ObjectUtils.shallowEqual(this.state, nextState)) {
return true;
}
return !this._propsEqual(this.props, nextProps);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
const client = this.context;
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
client.removeListener("userTrustStatusChanged", this.onUserVerificationChanged);
@ -284,31 +276,31 @@ export default createReactClass({
if (this.props.showReactions) {
this.props.mxEvent.removeListener("Event.relationsCreated", this._onReactionsCreated);
}
},
}
/** called when the event is decrypted after we show it.
*/
_onDecrypted: function() {
_onDecrypted = () => {
// we need to re-verify the sending device.
// (we call onHeightChanged in _verifyEvent to handle the case where decryption
// has caused a change in size of the event tile)
this._verifyEvent(this.props.mxEvent);
this.forceUpdate();
},
};
onDeviceVerificationChanged: function(userId, device) {
onDeviceVerificationChanged = (userId, device) => {
if (userId === this.props.mxEvent.getSender()) {
this._verifyEvent(this.props.mxEvent);
}
},
};
onUserVerificationChanged: function(userId, _trustStatus) {
onUserVerificationChanged = (userId, _trustStatus) => {
if (userId === this.props.mxEvent.getSender()) {
this._verifyEvent(this.props.mxEvent);
}
},
};
_verifyEvent: async function(mxEvent) {
async _verifyEvent(mxEvent) {
if (!mxEvent.isEncrypted()) {
return;
}
@ -360,9 +352,9 @@ export default createReactClass({
this.setState({
verified: E2E_STATE.VERIFIED,
}, this.props.onHeightChanged); // Decryption may have caused a change in size
},
}
_propsEqual: function(objA, objB) {
_propsEqual(objA, objB) {
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
@ -408,9 +400,9 @@ export default createReactClass({
}
}
return true;
},
}
shouldHighlight: function() {
shouldHighlight() {
const actions = this.context.getPushActionsForEvent(this.props.mxEvent.replacingEvent() || this.props.mxEvent);
if (!actions || !actions.tweaks) { return false; }
@ -420,15 +412,15 @@ export default createReactClass({
}
return actions.tweaks.highlight;
},
}
toggleAllReadAvatars: function() {
toggleAllReadAvatars = () => {
this.setState({
allReadAvatars: !this.state.allReadAvatars,
});
},
};
getReadAvatars: function() {
getReadAvatars() {
// return early if there are no read receipts
if (!this.props.readReceipts || this.props.readReceipts.length === 0) {
return (<span className="mx_EventTile_readAvatars" />);
@ -494,17 +486,17 @@ export default createReactClass({
{ remText }
{ avatars }
</span>;
},
}
onSenderProfileClick: function(event) {
onSenderProfileClick = event => {
const mxEvent = this.props.mxEvent;
dis.dispatch({
action: 'insert_mention',
user_id: mxEvent.getSender(),
});
},
};
onRequestKeysClick: function() {
onRequestKeysClick = () => {
this.setState({
// Indicate in the UI that the keys have been requested (this is expected to
// be reset if the component is mounted in the future).
@ -515,9 +507,9 @@ export default createReactClass({
// is received for the request with the required keys, the event could be
// decrypted successfully.
this.context.cancelAndResendEventRoomKeyRequest(this.props.mxEvent);
},
};
onPermalinkClicked: function(e) {
onPermalinkClicked = e => {
// This allows the permalink to be opened in a new tab/window or copied as
// matrix.to, but also for it to enable routing within Element when clicked.
e.preventDefault();
@ -527,9 +519,9 @@ export default createReactClass({
highlighted: true,
room_id: this.props.mxEvent.getRoomId(),
});
},
};
_renderE2EPadlock: function() {
_renderE2EPadlock() {
const ev = this.props.mxEvent;
// event could not be decrypted
@ -570,23 +562,19 @@ export default createReactClass({
// no padlock needed
return null;
},
}
onActionBarFocusChange(focused) {
onActionBarFocusChange = focused => {
this.setState({
actionBarFocused: focused,
});
},
};
getTile() {
return this._tile.current;
},
getTile = () => this._tile.current;
getReplyThread() {
return this._replyThread.current;
},
getReplyThread = () => this._replyThread.current;
getReactions() {
getReactions = () => {
if (
!this.props.showReactions ||
!this.props.getRelationsForEvent
@ -602,9 +590,9 @@ export default createReactClass({
console.trace("Stacktrace for https://github.com/vector-im/element-web/issues/11120");
}
return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction");
},
};
_onReactionsCreated(relationType, eventType) {
_onReactionsCreated = (relationType, eventType) => {
if (relationType !== "m.annotation" || eventType !== "m.reaction") {
return;
}
@ -612,9 +600,9 @@ export default createReactClass({
this.setState({
reactions: this.getReactions(),
});
},
};
render: function() {
render() {
const MessageTimestamp = sdk.getComponent('messages.MessageTimestamp');
const SenderProfile = sdk.getComponent('messages.SenderProfile');
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
@ -947,8 +935,8 @@ export default createReactClass({
);
}
}
},
});
}
}
// XXX this'll eventually be dynamic based on the fields once we have extensible event types
const messageTypes = ['m.room.message', 'm.sticker'];

View file

@ -17,49 +17,46 @@
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
import dis from '../../../dispatcher/dispatcher';
import {Key} from '../../../Keyboard';
export default createReactClass({
displayName: 'ForwardMessage',
propTypes: {
export default class ForwardMessage extends React.Component {
static propTypes = {
onCancelClick: PropTypes.func.isRequired,
},
};
componentDidMount: function() {
componentDidMount() {
dis.dispatch({
action: 'panel_disable',
middleDisabled: true,
});
document.addEventListener('keydown', this._onKeyDown);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
dis.dispatch({
action: 'panel_disable',
middleDisabled: false,
});
document.removeEventListener('keydown', this._onKeyDown);
},
}
_onKeyDown: function(ev) {
_onKeyDown = ev => {
switch (ev.key) {
case Key.ESCAPE:
this.props.onCancelClick();
break;
}
},
};
render: function() {
render() {
return (
<div className="mx_ForwardMessage">
<h1>{ _t('Please select the destination room for this message') }</h1>
</div>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { AllHtmlEntities } from 'html-entities';
import {linkifyElement} from '../../../HtmlUtils';
import SettingsStore from "../../../settings/SettingsStore";
@ -27,24 +26,21 @@ import Modal from "../../../Modal";
import * as ImageUtils from "../../../ImageUtils";
import { _t } from "../../../languageHandler";
export default createReactClass({
displayName: 'LinkPreviewWidget',
propTypes: {
export default class LinkPreviewWidget extends React.Component {
static propTypes = {
link: PropTypes.string.isRequired, // the URL being previewed
mxEvent: PropTypes.object.isRequired, // the Event associated with the preview
onCancelClick: PropTypes.func, // called when the preview's cancel ('hide') button is clicked
onHeightChanged: PropTypes.func, // called when the preview's contents has loaded
},
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
preview: null,
};
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this.unmounted = false;
MatrixClientPeg.get().getUrlPreview(this.props.link, this.props.mxEvent.getTs()).then((res)=>{
if (this.unmounted) {
@ -59,25 +55,25 @@ export default createReactClass({
});
this._description = createRef();
},
}
componentDidMount: function() {
componentDidMount() {
if (this._description.current) {
linkifyElement(this._description.current);
}
},
}
componentDidUpdate: function() {
componentDidUpdate() {
if (this._description.current) {
linkifyElement(this._description.current);
}
},
}
componentWillUnmount: function() {
componentWillUnmount() {
this.unmounted = true;
},
}
onImageClick: function(ev) {
onImageClick = ev => {
const p = this.state.preview;
if (ev.button != 0 || ev.metaKey) return;
ev.preventDefault();
@ -98,9 +94,9 @@ export default createReactClass({
};
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
},
};
render: function() {
render() {
const p = this.state.preview;
if (!p || Object.keys(p).length === 0) {
return <div />;
@ -149,5 +145,5 @@ export default createReactClass({
</AccessibleButton>
</div>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig';
import dis from '../../../dispatcher/dispatcher';
@ -36,23 +35,19 @@ const SHOW_MORE_INCREMENT = 100;
// matches all ASCII punctuation: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
const SORT_REGEX = /[\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]+/g;
export default createReactClass({
displayName: 'MemberList',
export default class MemberList extends React.Component {
constructor(props) {
super(props);
getInitialState: function() {
const cli = MatrixClientPeg.get();
if (cli.hasLazyLoadMembersEnabled()) {
// show an empty list
return this._getMembersState([]);
this.state = this._getMembersState([]);
} else {
return this._getMembersState(this.roomMembers());
this.state = this._getMembersState(this.roomMembers());
}
},
// TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount: function() {
this._mounted = true;
const cli = MatrixClientPeg.get();
if (cli.hasLazyLoadMembersEnabled()) {
this._showMembersAccordingToMembershipWithLL();
cli.on("Room.myMembership", this.onMyMembership);
@ -66,9 +61,9 @@ export default createReactClass({
if (enablePresenceByHsUrl && enablePresenceByHsUrl[hsUrl] !== undefined) {
this._showPresence = enablePresenceByHsUrl[hsUrl];
}
},
}
_listenForMembersChanges: function() {
_listenForMembersChanges() {
const cli = MatrixClientPeg.get();
cli.on("RoomState.members", this.onRoomStateMember);
cli.on("RoomMember.name", this.onRoomMemberName);
@ -80,9 +75,9 @@ export default createReactClass({
cli.on("User.presence", this.onUserPresenceChange);
cli.on("User.currentlyActive", this.onUserPresenceChange);
// cli.on("Room.timeline", this.onRoomTimeline);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
this._mounted = false;
const cli = MatrixClientPeg.get();
if (cli) {
@ -98,14 +93,14 @@ export default createReactClass({
// cancel any pending calls to the rate_limited_funcs
this._updateList.cancelPendingCall();
},
}
/**
* If lazy loading is enabled, either:
* show a spinner and load the members if the user is joined,
* or show the members available so far if the user is invited
*/
_showMembersAccordingToMembershipWithLL: async function() {
async _showMembersAccordingToMembershipWithLL() {
const cli = MatrixClientPeg.get();
if (cli.hasLazyLoadMembersEnabled()) {
const cli = MatrixClientPeg.get();
@ -125,9 +120,9 @@ export default createReactClass({
this.setState(this._getMembersState(this.roomMembers()));
}
}
},
}
_getMembersState: function(members) {
_getMembersState(members) {
// set the state after determining _showPresence to make sure it's
// taken into account while rerendering
return {
@ -142,9 +137,9 @@ export default createReactClass({
truncateAtInvited: INITIAL_LOAD_NUM_INVITED,
searchQuery: "",
};
},
}
onUserPresenceChange(event, user) {
onUserPresenceChange = (event, user) => {
// Attach a SINGLE listener for global presence changes then locate the
// member tile and re-render it. This is more efficient than every tile
// ever attaching their own listener.
@ -153,9 +148,9 @@ export default createReactClass({
if (tile) {
this._updateList(); // reorder the membership list
}
},
};
onRoom: function(room) {
onRoom = room => {
if (room.roomId !== this.props.roomId) {
return;
}
@ -163,40 +158,40 @@ export default createReactClass({
// we need to wait till the room is fully populated with state
// before refreshing the member list else we get a stale list.
this._showMembersAccordingToMembershipWithLL();
},
};
onMyMembership: function(room, membership, oldMembership) {
onMyMembership = (room, membership, oldMembership) => {
if (room.roomId === this.props.roomId && membership === "join") {
this._showMembersAccordingToMembershipWithLL();
}
},
};
onRoomStateMember: function(ev, state, member) {
onRoomStateMember = (ev, state, member) => {
if (member.roomId !== this.props.roomId) {
return;
}
this._updateList();
},
};
onRoomMemberName: function(ev, member) {
onRoomMemberName = (ev, member) => {
if (member.roomId !== this.props.roomId) {
return;
}
this._updateList();
},
};
onRoomStateEvent: function(event, state) {
onRoomStateEvent = (event, state) => {
if (event.getRoomId() === this.props.roomId &&
event.getType() === "m.room.third_party_invite") {
this._updateList();
}
},
};
_updateList: rate_limited_func(function() {
_updateList = rate_limited_func(() => {
this._updateListNow();
}, 500),
}, 500);
_updateListNow: function() {
_updateListNow() {
// console.log("Updating memberlist");
const newState = {
loading: false,
@ -205,9 +200,9 @@ export default createReactClass({
newState.filteredJoinedMembers = this._filterMembers(newState.members, 'join', this.state.searchQuery);
newState.filteredInvitedMembers = this._filterMembers(newState.members, 'invite', this.state.searchQuery);
this.setState(newState);
},
}
getMembersWithUser: function() {
getMembersWithUser() {
if (!this.props.roomId) return [];
const cli = MatrixClientPeg.get();
const room = cli.getRoom(this.props.roomId);
@ -228,9 +223,9 @@ export default createReactClass({
});
return allMembers;
},
}
roomMembers: function() {
roomMembers() {
const ConferenceHandler = CallHandler.getConferenceHandler();
const allMembers = this.getMembersWithUser();
@ -244,17 +239,17 @@ export default createReactClass({
});
filteredAndSortedMembers.sort(this.memberSort);
return filteredAndSortedMembers;
},
}
_createOverflowTileJoined: function(overflowCount, totalCount) {
_createOverflowTileJoined(overflowCount, totalCount) {
return this._createOverflowTile(overflowCount, totalCount, this._showMoreJoinedMemberList);
},
}
_createOverflowTileInvited: function(overflowCount, totalCount) {
_createOverflowTileInvited(overflowCount, totalCount) {
return this._createOverflowTile(overflowCount, totalCount, this._showMoreInvitedMemberList);
},
}
_createOverflowTile: function(overflowCount, totalCount, onClick) {
_createOverflowTile(overflowCount, totalCount, onClick) {
// For now we'll pretend this is any entity. It should probably be a separate tile.
const EntityTile = sdk.getComponent("rooms.EntityTile");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
@ -265,33 +260,33 @@ export default createReactClass({
} name={text} presenceState="online" suppressOnHover={true}
onClick={onClick} />
);
},
}
_showMoreJoinedMemberList: function() {
_showMoreJoinedMemberList = () => {
this.setState({
truncateAtJoined: this.state.truncateAtJoined + SHOW_MORE_INCREMENT,
});
},
};
_showMoreInvitedMemberList: function() {
_showMoreInvitedMemberList = () => {
this.setState({
truncateAtInvited: this.state.truncateAtInvited + SHOW_MORE_INCREMENT,
});
},
};
memberString: function(member) {
memberString(member) {
if (!member) {
return "(null)";
} else {
const u = member.user;
return "(" + member.name + ", " + member.powerLevel + ", " + (u ? u.lastActiveAgo : "<null>") + ", " + (u ? u.getLastActiveTs() : "<null>") + ", " + (u ? u.currentlyActive : "<null>") + ", " + (u ? u.presence : "<null>") + ")";
}
},
}
// returns negative if a comes before b,
// returns 0 if a and b are equivalent in ordering
// returns positive if a comes after b.
memberSort: function(memberA, memberB) {
memberSort = (memberA, memberB) => {
// order by presence, with "active now" first.
// ...and then by power level
// ...and then by last active
@ -348,24 +343,24 @@ export default createReactClass({
ignorePunctuation: true,
sensitivity: "base",
});
},
};
onSearchQueryChanged: function(searchQuery) {
onSearchQueryChanged = searchQuery => {
this.setState({
searchQuery,
filteredJoinedMembers: this._filterMembers(this.state.members, 'join', searchQuery),
filteredInvitedMembers: this._filterMembers(this.state.members, 'invite', searchQuery),
});
},
};
_onPending3pidInviteClick: function(inviteEvent) {
_onPending3pidInviteClick = inviteEvent => {
dis.dispatch({
action: 'view_3pid_invite',
event: inviteEvent,
});
},
};
_filterMembers: function(members, membership, query) {
_filterMembers(members, membership, query) {
return members.filter((m) => {
if (query) {
query = query.toLowerCase();
@ -379,9 +374,9 @@ export default createReactClass({
return m.membership === membership;
});
},
}
_getPending3PidInvites: function() {
_getPending3PidInvites() {
// include 3pid invites (m.room.third_party_invite) state events.
// The HS may have already converted these into m.room.member invites so
// we shouldn't add them if the 3pid invite state key (token) is in the
@ -399,9 +394,9 @@ export default createReactClass({
return true;
});
}
},
}
_makeMemberTiles: function(members) {
_makeMemberTiles(members) {
const MemberTile = sdk.getComponent("rooms.MemberTile");
const EntityTile = sdk.getComponent("rooms.EntityTile");
@ -415,30 +410,30 @@ export default createReactClass({
onClick={() => this._onPending3pidInviteClick(m)} />;
}
});
},
}
_getChildrenJoined: function(start, end) {
_getChildrenJoined(start, end) {
return this._makeMemberTiles(this.state.filteredJoinedMembers.slice(start, end));
},
}
_getChildCountJoined: function() {
_getChildCountJoined() {
return this.state.filteredJoinedMembers.length;
},
}
_getChildrenInvited: function(start, end) {
_getChildrenInvited(start, end) {
let targets = this.state.filteredInvitedMembers;
if (end > this.state.filteredInvitedMembers.length) {
targets = targets.concat(this._getPending3PidInvites());
}
return this._makeMemberTiles(targets.slice(start, end));
},
}
_getChildCountInvited: function() {
_getChildCountInvited() {
return this.state.filteredInvitedMembers.length + (this._getPending3PidInvites() || []).length;
},
}
render: function() {
render() {
if (this.state.loading) {
const Spinner = sdk.getComponent("elements.Spinner");
return <div className="mx_MemberList"><Spinner /></div>;
@ -501,9 +496,9 @@ export default createReactClass({
onSearch={ this.onSearchQueryChanged } />
</div>
);
},
}
onInviteButtonClick: function() {
onInviteButtonClick = () => {
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({action: 'require_registration'});
return;
@ -514,5 +509,5 @@ export default createReactClass({
action: 'view_invite',
roomId: this.props.roomId,
});
},
});
};
}

View file

@ -18,34 +18,31 @@ limitations under the License.
import SettingsStore from "../../../settings/SettingsStore";
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from "../../../index";
import dis from "../../../dispatcher/dispatcher";
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import {Action} from "../../../dispatcher/actions";
export default createReactClass({
displayName: 'MemberTile',
propTypes: {
export default class MemberTile extends React.Component {
static propTypes = {
member: PropTypes.any.isRequired, // RoomMember
showPresence: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
showPresence: true,
};
},
static defaultProps = {
showPresence: true,
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
statusMessage: this.getStatusMessage(),
isRoomEncrypted: false,
e2eStatus: null,
};
},
}
componentDidMount() {
const cli = MatrixClientPeg.get();
@ -72,7 +69,7 @@ export default createReactClass({
cli.on("RoomState.events", this.onRoomStateEvents);
}
}
},
}
componentWillUnmount() {
const cli = MatrixClientPeg.get();
@ -90,9 +87,9 @@ export default createReactClass({
cli.removeListener("userTrustStatusChanged", this.onUserTrustStatusChanged);
cli.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
}
},
}
onRoomStateEvents: function(ev) {
onRoomStateEvents = ev => {
if (ev.getType() !== "m.room.encryption") return;
const { roomId } = this.props.member;
if (ev.getRoomId() !== roomId) return;
@ -104,19 +101,19 @@ export default createReactClass({
isRoomEncrypted: true,
});
this.updateE2EStatus();
},
};
onUserTrustStatusChanged: function(userId, trustStatus) {
onUserTrustStatusChanged = (userId, trustStatus) => {
if (userId !== this.props.member.userId) return;
this.updateE2EStatus();
},
};
onDeviceVerificationChanged: function(userId, deviceId, deviceInfo) {
onDeviceVerificationChanged = (userId, deviceId, deviceInfo) => {
if (userId !== this.props.member.userId) return;
this.updateE2EStatus();
},
};
updateE2EStatus: async function() {
async updateE2EStatus() {
const cli = MatrixClientPeg.get();
const { userId } = this.props.member;
const isMe = userId === cli.getUserId();
@ -142,7 +139,7 @@ export default createReactClass({
this.setState({
e2eStatus: anyDeviceUnverified ? "warning" : "verified",
});
},
}
getStatusMessage() {
const { user } = this.props.member;
@ -150,16 +147,16 @@ export default createReactClass({
return "";
}
return user._unstable_statusMessage;
},
}
_onStatusMessageCommitted() {
_onStatusMessageCommitted = () => {
// The `User` object has observed a status message change.
this.setState({
statusMessage: this.getStatusMessage(),
});
},
};
shouldComponentUpdate: function(nextProps, nextState) {
shouldComponentUpdate(nextProps, nextState) {
if (
this.member_last_modified_time === undefined ||
this.member_last_modified_time < nextProps.member.getLastModifiedTime()
@ -180,27 +177,27 @@ export default createReactClass({
return true;
}
return false;
},
}
onClick: function(e) {
onClick = e => {
dis.dispatch({
action: Action.ViewUser,
member: this.props.member,
});
},
};
_getDisplayName: function() {
_getDisplayName() {
return this.props.member.name;
},
}
getPowerLabel: function() {
getPowerLabel() {
return _t("%(userName)s (power %(powerLevelNumber)s)", {
userName: this.props.member.userId,
powerLevelNumber: this.props.member.powerLevel,
});
},
}
render: function() {
render() {
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
const EntityTile = sdk.getComponent('rooms.EntityTile');
@ -260,5 +257,5 @@ export default createReactClass({
onClick={this.onClick}
/>
);
},
});
}
}

View file

@ -16,7 +16,6 @@ limitations under the License.
import React from "react";
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import dis from "../../../dispatcher/dispatcher";
import AccessibleButton from "../elements/AccessibleButton";
@ -25,22 +24,23 @@ import MemberAvatar from "../avatars/MemberAvatar";
import { _t } from '../../../languageHandler';
import {formatFullDate} from '../../../DateUtils';
export default createReactClass({
displayName: 'PinnedEventTile',
propTypes: {
export default class PinnedEventTile extends React.Component {
static propTypes = {
mxRoom: PropTypes.object.isRequired,
mxEvent: PropTypes.object.isRequired,
onUnpinned: PropTypes.func,
},
onTileClicked: function() {
};
onTileClicked = () => {
dis.dispatch({
action: 'view_room',
event_id: this.props.mxEvent.getId(),
highlighted: true,
room_id: this.props.mxEvent.getRoomId(),
});
},
onUnpinClicked: function() {
};
onUnpinClicked = () => {
const pinnedEvents = this.props.mxRoom.currentState.getStateEvents("m.room.pinned_events", "");
if (!pinnedEvents || !pinnedEvents.getContent().pinned) {
// Nothing to do: already unpinned
@ -56,11 +56,13 @@ export default createReactClass({
});
} else if (this.props.onUnpinned) this.props.onUnpinned();
}
},
_canUnpin: function() {
};
_canUnpin() {
return this.props.mxRoom.currentState.mayClientSendStateEvent('m.room.pinned_events', MatrixClientPeg.get());
},
render: function() {
}
render() {
const sender = this.props.mxEvent.getSender();
// Get the latest sender profile rather than historical
const senderProfile = this.props.mxRoom.getMember(sender);
@ -100,5 +102,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -17,46 +17,42 @@ limitations under the License.
import React from "react";
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import AccessibleButton from "../elements/AccessibleButton";
import PinnedEventTile from "./PinnedEventTile";
import { _t } from '../../../languageHandler';
import PinningUtils from "../../../utils/PinningUtils";
export default createReactClass({
displayName: 'PinnedEventsPanel',
propTypes: {
export default class PinnedEventsPanel extends React.Component {
static propTypes = {
// The Room from the js-sdk we're going to show pinned events for
room: PropTypes.object.isRequired,
onCancelClick: PropTypes.func,
},
};
getInitialState: function() {
return {
loading: true,
};
},
state = {
loading: true,
};
componentDidMount: function() {
componentDidMount() {
this._updatePinnedMessages();
MatrixClientPeg.get().on("RoomState.events", this._onStateEvent);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("RoomState.events", this._onStateEvent);
}
},
}
_onStateEvent: function(ev) {
_onStateEvent = ev => {
if (ev.getRoomId() === this.props.room.roomId && ev.getType() === "m.room.pinned_events") {
this._updatePinnedMessages();
}
},
};
_updatePinnedMessages: function() {
_updatePinnedMessages = () => {
const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", "");
if (!pinnedEvents || !pinnedEvents.getContent().pinned) {
this.setState({ loading: false, pinned: [] });
@ -85,9 +81,9 @@ export default createReactClass({
}
this._updateReadState();
},
};
_updateReadState: function() {
_updateReadState() {
const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", "");
if (!pinnedEvents) return; // nothing to read
@ -107,9 +103,9 @@ export default createReactClass({
event_ids: readStateEvents,
});
}
},
}
_getPinnedTiles: function() {
_getPinnedTiles() {
if (this.state.pinned.length === 0) {
return (<div>{ _t("No pinned messages.") }</div>);
}
@ -120,9 +116,9 @@ export default createReactClass({
mxEvent={context.event}
onUnpinned={this._updatePinnedMessages} />);
});
},
}
render: function() {
render() {
let tiles = <div>{ _t("Loading...") }</div>;
if (this.state && !this.state.loading) {
tiles = this._getPinnedTiles();
@ -139,5 +135,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -16,15 +16,12 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'PresenceLabel',
propTypes: {
export default class PresenceLabel extends React.Component {
static propTypes = {
// number of milliseconds ago this user was last active.
// zero = unknown
activeAgo: PropTypes.number,
@ -35,18 +32,16 @@ export default createReactClass({
// offline, online, etc
presenceState: PropTypes.string,
},
};
getDefaultProps: function() {
return {
ago: -1,
presenceState: null,
};
},
static defaultProps = {
activeAgo: -1,
presenceState: null,
};
// Return duration as a string using appropriate time units
// XXX: This would be better handled using a culture-aware library, but we don't use one yet.
getDuration: function(time) {
getDuration(time) {
if (!time) return;
const t = parseInt(time / 1000);
const s = t % 60;
@ -66,9 +61,9 @@ export default createReactClass({
return _t("%(duration)sh", {duration: h});
}
return _t("%(duration)sd", {duration: d});
},
}
getPrettyPresence: function(presence, activeAgo, currentlyActive) {
getPrettyPresence(presence, activeAgo, currentlyActive) {
if (!currentlyActive && activeAgo !== undefined && activeAgo > 0) {
const duration = this.getDuration(activeAgo);
if (presence === "online") return _t("Online for %(duration)s", { duration: duration });
@ -81,13 +76,13 @@ export default createReactClass({
if (presence === "offline") return _t("Offline");
return _t("Unknown");
}
},
}
render: function() {
render() {
return (
<div className="mx_PresenceLabel">
{ this.getPrettyPresence(this.props.presenceState, this.props.activeAgo, this.props.currentlyActive) }
</div>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import '../../../VelocityBounce';
import { _t } from '../../../languageHandler';
import {formatDate} from '../../../DateUtils';
@ -33,10 +32,8 @@ try {
} catch (e) {
}
export default createReactClass({
displayName: 'ReadReceiptMarker',
propTypes: {
export default class ReadReceiptMarker extends React.Component {
static propTypes = {
// the RoomMember to show the RR for
member: PropTypes.object,
// userId to fallback the avatar to
@ -70,30 +67,27 @@ export default createReactClass({
// True to show twelve hour format, false otherwise
showTwelveHour: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
leftOffset: 0,
};
},
static defaultProps = {
leftOffset: 0,
};
getInitialState: function() {
// if we are going to animate the RR, we don't show it on first render,
// and instead just add a placeholder to the DOM; once we've been
// mounted, we start an animation which moves the RR from its old
// position.
return {
constructor(props) {
super(props);
this._avatar = createRef();
this.state = {
// if we are going to animate the RR, we don't show it on first render,
// and instead just add a placeholder to the DOM; once we've been
// mounted, we start an animation which moves the RR from its old
// position.
suppressDisplay: !this.props.suppressAnimation,
};
},
}
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._avatar = createRef();
},
componentWillUnmount: function() {
componentWillUnmount() {
// before we remove the rr, store its location in the map, so that if
// it reappears, it can be animated from the right place.
const rrInfo = this.props.readReceiptInfo;
@ -112,9 +106,9 @@ export default createReactClass({
rrInfo.top = avatarNode.offsetTop;
rrInfo.left = avatarNode.offsetLeft;
rrInfo.parent = avatarNode.offsetParent;
},
}
componentDidMount: function() {
componentDidMount() {
if (!this.state.suppressDisplay) {
// we've already done our display - nothing more to do.
return;
@ -172,10 +166,9 @@ export default createReactClass({
startStyles: startStyles,
enterTransitionOpts: enterTransitionOpts,
});
},
}
render: function() {
render() {
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
if (this.state.suppressDisplay) {
return <div ref={this._avatar} />;
@ -222,5 +215,5 @@ export default createReactClass({
/>
</Velociraptor>
);
},
});
}
}

View file

@ -19,35 +19,32 @@ import dis from '../../../dispatcher/dispatcher';
import React from 'react';
import { _t } from '../../../languageHandler';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames';
import {roomShape} from './RoomDetailRow';
export default createReactClass({
displayName: 'RoomDetailList',
propTypes: {
export default class RoomDetailList extends React.Component {
static propTypes = {
rooms: PropTypes.arrayOf(roomShape),
className: PropTypes.string,
},
};
getRows: function() {
getRows() {
if (!this.props.rooms) return [];
const RoomDetailRow = sdk.getComponent('rooms.RoomDetailRow');
return this.props.rooms.map((room, index) => {
return <RoomDetailRow key={index} room={room} onClick={this.onDetailsClick} />;
});
},
}
onDetailsClick: function(ev, room) {
onDetailsClick = (ev, room) => {
dis.dispatch({
action: 'view_room',
room_id: room.roomId,
room_alias: room.canonicalAlias || (room.aliases || [])[0],
});
},
};
render() {
const rows = this.getRows();
@ -64,5 +61,5 @@ export default createReactClass({
return <div className={classNames("mx_RoomDetailList", this.props.className)}>
{ rooms }
</div>;
},
});
}
}

View file

@ -20,7 +20,6 @@ import { _t } from '../../../languageHandler';
import { linkifyElement } from '../../../HtmlUtils';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
export function getDisplayAliasForRoom(room) {
@ -40,47 +39,48 @@ export const roomShape = PropTypes.shape({
guestCanJoin: PropTypes.bool,
});
export default createReactClass({
propTypes: {
export default class RoomDetailRow extends React.Component {
static propTypes = {
room: roomShape,
// passes ev, room as args
onClick: PropTypes.func,
onMouseDown: PropTypes.func,
},
};
_linkifyTopic: function() {
constructor(props) {
super(props);
this._topic = createRef();
}
componentDidMount() {
this._linkifyTopic();
}
componentDidUpdate() {
this._linkifyTopic();
}
_linkifyTopic() {
if (this._topic.current) {
linkifyElement(this._topic.current);
}
},
}
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._topic = createRef();
},
componentDidMount: function() {
this._linkifyTopic();
},
componentDidUpdate: function() {
this._linkifyTopic();
},
onClick: function(ev) {
onClick = (ev) => {
ev.preventDefault();
if (this.props.onClick) {
this.props.onClick(ev, this.props.room);
}
},
};
onTopicClick: function(ev) {
onTopicClick = (ev) => {
// When clicking a link in the topic, prevent the event being propagated
// to `onClick`.
ev.stopPropagation();
},
};
render: function() {
render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const room = this.props.room;
@ -118,5 +118,5 @@ export default createReactClass({
{ room.numJoinedMembers }
</td>
</tr>;
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
@ -35,10 +34,8 @@ import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import {DefaultTagID} from "../../../stores/room-list/models";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
export default createReactClass({
displayName: 'RoomHeader',
propTypes: {
export default class RoomHeader extends React.Component {
static propTypes = {
room: PropTypes.object,
oobData: PropTypes.object,
inRoom: PropTypes.bool,
@ -48,22 +45,21 @@ export default createReactClass({
onLeaveClick: PropTypes.func,
onCancelClick: PropTypes.func,
e2eStatus: PropTypes.string,
},
};
getDefaultProps: function() {
return {
editing: false,
inRoom: false,
onCancelClick: null,
};
},
static defaultProps = {
editing: false,
inRoom: false,
onCancelClick: null,
};
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._topic = createRef();
},
}
componentDidMount: function() {
componentDidMount() {
const cli = MatrixClientPeg.get();
cli.on("RoomState.events", this._onRoomStateEvents);
cli.on("Room.accountData", this._onRoomAccountData);
@ -74,15 +70,15 @@ export default createReactClass({
if (this.props.room) {
this.props.room.on("Room.name", this._onRoomNameChange);
}
},
}
componentDidUpdate: function() {
componentDidUpdate() {
if (this._topic.current) {
linkifyElement(this._topic.current);
}
},
}
componentWillUnmount: function() {
componentWillUnmount() {
if (this.props.room) {
this.props.room.removeListener("Room.name", this._onRoomNameChange);
}
@ -91,41 +87,41 @@ export default createReactClass({
cli.removeListener("RoomState.events", this._onRoomStateEvents);
cli.removeListener("Room.accountData", this._onRoomAccountData);
}
},
}
_onRoomStateEvents: function(event, state) {
_onRoomStateEvents = (event, state) => {
if (!this.props.room || event.getRoomId() !== this.props.room.roomId) {
return;
}
// redisplay the room name, topic, etc.
this._rateLimitedUpdate();
},
};
_onRoomAccountData: function(event, room) {
_onRoomAccountData = (event, room) => {
if (!this.props.room || room.roomId !== this.props.room.roomId) return;
if (event.getType() !== "im.vector.room.read_pins") return;
this._rateLimitedUpdate();
},
};
_rateLimitedUpdate: new RateLimitedFunc(function() {
_rateLimitedUpdate = new RateLimitedFunc(function() {
/* eslint-disable babel/no-invalid-this */
this.forceUpdate();
}, 500),
}, 500);
_onRoomNameChange: function(room) {
_onRoomNameChange = (room) => {
this.forceUpdate();
},
};
onShareRoomClick: function(ev) {
onShareRoomClick = (ev) => {
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share room dialog', '', ShareDialog, {
target: this.props.room,
});
},
};
_hasUnreadPins: function() {
_hasUnreadPins() {
const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", '');
if (!currentPinEvent) return false;
if (currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0) {
@ -142,16 +138,16 @@ export default createReactClass({
// There's pins, and we haven't read any of them
return true;
},
}
_hasPins: function() {
_hasPins() {
const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", '');
if (!currentPinEvent) return false;
return !(currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0);
},
}
render: function() {
render() {
let searchStatus = null;
let cancelButton = null;
let settingsButton = null;
@ -301,5 +297,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -18,7 +18,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import dis from '../../../dispatcher/dispatcher';
@ -46,10 +45,8 @@ const MessageCase = Object.freeze({
OtherError: "OtherError",
});
export default createReactClass({
displayName: 'RoomPreviewBar',
propTypes: {
export default class RoomPreviewBar extends React.Component {
static propTypes = {
onJoinClick: PropTypes.func,
onRejectClick: PropTypes.func,
onRejectAndIgnoreClick: PropTypes.func,
@ -86,36 +83,32 @@ export default createReactClass({
// If given, this will be how the room is referred to (eg.
// in error messages).
roomAlias: PropTypes.string,
},
};
getDefaultProps: function() {
return {
onJoinClick: function() {},
};
},
static defaultProps = {
onJoinClick() {},
};
getInitialState: function() {
return {
busy: false,
};
},
state = {
busy: false,
};
componentDidMount: function() {
componentDidMount() {
this._checkInvitedEmail();
CommunityPrototypeStore.instance.on(UPDATE_EVENT, this._onCommunityUpdate);
},
}
componentDidUpdate: function(prevProps, prevState) {
componentDidUpdate(prevProps, prevState) {
if (this.props.invitedEmail !== prevProps.invitedEmail || this.props.inviterName !== prevProps.inviterName) {
this._checkInvitedEmail();
}
},
}
componentWillUnmount: function() {
componentWillUnmount() {
CommunityPrototypeStore.instance.off(UPDATE_EVENT, this._onCommunityUpdate);
},
}
_checkInvitedEmail: async function() {
async _checkInvitedEmail() {
// If this is an invite and we've been told what email address was
// invited, fetch the user's account emails and discovery bindings so we
// can check them against the email that was invited.
@ -148,14 +141,14 @@ export default createReactClass({
}
this.setState({busy: false});
}
},
}
_onCommunityUpdate: function (roomId) {
_onCommunityUpdate = (roomId) => {
if (this.props.room && this.props.room.roomId !== roomId) {
return;
}
this.forceUpdate(); // we have nothing to update
},
};
_getMessageCase() {
const isGuest = MatrixClientPeg.get().isGuest();
@ -207,7 +200,7 @@ export default createReactClass({
} else {
return MessageCase.ViewingRoom;
}
},
}
_getKickOrBanInfo() {
const myMember = this._getMyMember();
@ -221,9 +214,9 @@ export default createReactClass({
kickerMember.name : myMember.events.member.getSender();
const reason = myMember.events.member.getContent().reason;
return {memberName, reason};
},
}
_joinRule: function() {
_joinRule() {
const room = this.props.room;
if (room) {
const joinRules = room.currentState.getStateEvents('m.room.join_rules', '');
@ -231,14 +224,14 @@ export default createReactClass({
return joinRules.getContent().join_rule;
}
}
},
}
_communityProfile: function() {
_communityProfile() {
if (this.props.room) return CommunityPrototypeStore.instance.getInviteProfile(this.props.room.roomId);
return {displayName: null, avatarMxc: null};
},
}
_roomName: function(atStart = false) {
_roomName(atStart = false) {
let name = this.props.room ? this.props.room.name : this.props.roomAlias;
const profile = this._communityProfile();
if (profile.displayName) name = profile.displayName;
@ -249,16 +242,16 @@ export default createReactClass({
} else {
return _t("this room");
}
},
}
_getMyMember() {
return (
this.props.room &&
this.props.room.getMember(MatrixClientPeg.get().getUserId())
);
},
}
_getInviteMember: function() {
_getInviteMember() {
const {room} = this.props;
if (!room) {
return;
@ -270,7 +263,7 @@ export default createReactClass({
}
const inviterUserId = inviteEvent.events.member.getSender();
return room.currentState.getMember(inviterUserId);
},
}
_isDMInvite() {
const myMember = this._getMyMember();
@ -280,7 +273,7 @@ export default createReactClass({
const memberEvent = myMember.events.member;
const memberContent = memberEvent.getContent();
return memberContent.membership === "invite" && memberContent.is_direct;
},
}
_makeScreenAfterLogin() {
return {
@ -293,17 +286,17 @@ export default createReactClass({
inviter_name: this.props.oobData ? this.props.oobData.inviterName : null,
}
};
},
}
onLoginClick: function() {
onLoginClick = () => {
dis.dispatch({ action: 'start_login', screenAfterLogin: this._makeScreenAfterLogin() });
},
};
onRegisterClick: function() {
onRegisterClick = () => {
dis.dispatch({ action: 'start_registration', screenAfterLogin: this._makeScreenAfterLogin() });
},
};
render: function() {
render() {
const brand = SdkConfig.get().brand;
const Spinner = sdk.getComponent('elements.Spinner');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
@ -597,5 +590,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -16,29 +16,26 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
export default createReactClass({
displayName: 'RoomUpgradeWarningBar',
propTypes: {
export default class RoomUpgradeWarningBar extends React.Component {
static propTypes = {
room: PropTypes.object.isRequired,
recommendation: PropTypes.object.isRequired,
},
};
componentDidMount: function() {
componentDidMount() {
const tombstone = this.props.room.currentState.getStateEvents("m.room.tombstone", "");
this.setState({upgraded: tombstone && tombstone.getContent().replacement_room});
MatrixClientPeg.get().on("RoomState.events", this._onStateEvents);
},
}
_onStateEvents: function(event, state) {
_onStateEvents = (event, state) => {
if (!this.props.room || event.getRoomId() !== this.props.room.roomId) {
return;
}
@ -47,14 +44,14 @@ export default createReactClass({
const tombstone = this.props.room.currentState.getStateEvents("m.room.tombstone", "");
this.setState({upgraded: tombstone && tombstone.getContent().replacement_room});
},
};
onUpgradeClick: function() {
onUpgradeClick = () => {
const RoomUpgradeDialog = sdk.getComponent('dialogs.RoomUpgradeDialog');
Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, {room: this.props.room});
},
};
render: function() {
render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
let doUpgradeWarnings = (
@ -117,5 +114,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -15,35 +15,31 @@ limitations under the License.
*/
import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import AccessibleButton from "../elements/AccessibleButton";
import classNames from "classnames";
import { _t } from '../../../languageHandler';
import {Key} from "../../../Keyboard";
export default createReactClass({
displayName: 'SearchBar',
export default class SearchBar extends React.Component {
constructor(props) {
super(props);
getInitialState: function() {
return ({
scope: 'Room',
});
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._search_term = createRef();
},
onThisRoomClick: function() {
this.state = {
scope: 'Room',
};
}
onThisRoomClick = () => {
this.setState({ scope: 'Room' }, () => this._searchIfQuery());
},
};
onAllRoomsClick: function() {
onAllRoomsClick = () => {
this.setState({ scope: 'All' }, () => this._searchIfQuery());
},
};
onSearchChange: function(e) {
onSearchChange = (e) => {
switch (e.key) {
case Key.ENTER:
this.onSearch();
@ -52,19 +48,19 @@ export default createReactClass({
this.props.onCancelClick();
break;
}
},
};
_searchIfQuery: function() {
_searchIfQuery() {
if (this._search_term.current.value) {
this.onSearch();
}
},
}
onSearch: function() {
onSearch = () => {
this.props.onSearch(this._search_term.current.value, this.state.scope);
},
};
render: function() {
render() {
const searchButtonClasses = classNames("mx_SearchBar_searchButton", {
mx_SearchBar_searching: this.props.searchInProgress,
});
@ -92,5 +88,5 @@ export default createReactClass({
<AccessibleButton className="mx_SearchBar_cancel" onClick={this.props.onCancelClick} />
</div>
);
},
});
}
}

View file

@ -17,14 +17,11 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import {haveTileForEvent} from "./EventTile";
export default createReactClass({
displayName: 'SearchResult',
propTypes: {
export default class SearchResultTile extends React.Component {
static propTypes = {
// a matrix-js-sdk SearchResult containing the details of this result
searchResult: PropTypes.object.isRequired,
@ -35,9 +32,9 @@ export default createReactClass({
resultLink: PropTypes.string,
onHeightChanged: PropTypes.func,
},
};
render: function() {
render() {
const DateSeparator = sdk.getComponent('messages.DateSeparator');
const EventTile = sdk.getComponent('rooms.EventTile');
const result = this.props.searchResult;
@ -66,5 +63,5 @@ export default createReactClass({
<li data-scroll-tokens={eventId+"+"+j}>
{ ret }
</li>);
},
});
}
}

View file

@ -16,7 +16,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import AccessibleButton from '../elements/AccessibleButton';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
@ -37,18 +36,16 @@ export function CancelButton(props) {
* A stripped-down room header used for things like the user settings
* and room directory.
*/
export default createReactClass({
displayName: 'SimpleRoomHeader',
propTypes: {
export default class SimpleRoomHeader extends React.Component {
static propTypes = {
title: PropTypes.string,
onCancelClick: PropTypes.func,
// `src` to a TintableSvg. Optional.
icon: PropTypes.string,
},
};
render: function() {
render() {
let cancelButton;
let icon;
if (this.props.onCancelClick) {
@ -73,5 +70,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -18,19 +18,16 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
export default createReactClass({
displayName: 'TopUnreadMessagesBar',
propTypes: {
export default class TopUnreadMessagesBar extends React.Component {
static propTypes = {
onScrollUpClick: PropTypes.func,
onCloseClick: PropTypes.func,
},
};
render: function() {
render() {
return (
<div className="mx_TopUnreadMessagesBar">
<AccessibleButton className="mx_TopUnreadMessagesBar_scrollUp"
@ -43,5 +40,5 @@ export default createReactClass({
</AccessibleButton>
</div>
);
},
});
}
}

View file

@ -17,16 +17,13 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as WhoIsTyping from '../../../WhoIsTyping';
import Timer from '../../../utils/Timer';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import MemberAvatar from '../avatars/MemberAvatar';
export default createReactClass({
displayName: 'WhoIsTypingTile',
propTypes: {
export default class WhoIsTypingTile extends React.Component {
static propTypes = {
// the room this statusbar is representing.
room: PropTypes.object.isRequired,
onShown: PropTypes.func,
@ -34,32 +31,28 @@ export default createReactClass({
// Number of names to display in typing indication. E.g. set to 3, will
// result in "X, Y, Z and 100 others are typing."
whoIsTypingLimit: PropTypes.number,
},
};
getDefaultProps: function() {
return {
whoIsTypingLimit: 3,
};
},
static defaultProps = {
whoIsTypingLimit: 3,
};
getInitialState: function() {
return {
usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room),
// a map with userid => Timer to delay
// hiding the "x is typing" message for a
// user so hiding it can coincide
// with the sent message by the other side
// resulting in less timeline jumpiness
delayedStopTypingTimers: {},
};
},
state = {
usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room),
// a map with userid => Timer to delay
// hiding the "x is typing" message for a
// user so hiding it can coincide
// with the sent message by the other side
// resulting in less timeline jumpiness
delayedStopTypingTimers: {},
};
componentDidMount: function() {
componentDidMount() {
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
},
}
componentDidUpdate: function(_, prevState) {
componentDidUpdate(_, prevState) {
const wasVisible = this._isVisible(prevState);
const isVisible = this._isVisible(this.state);
if (this.props.onShown && !wasVisible && isVisible) {
@ -67,9 +60,9 @@ export default createReactClass({
} else if (this.props.onHidden && wasVisible && !isVisible) {
this.props.onHidden();
}
},
}
componentWillUnmount: function() {
componentWillUnmount() {
// we may have entirely lost our client as we're logging out before clicking login on the guest bar...
const client = MatrixClientPeg.get();
if (client) {
@ -77,17 +70,17 @@ export default createReactClass({
client.removeListener("Room.timeline", this.onRoomTimeline);
}
Object.values(this.state.delayedStopTypingTimers).forEach((t) => t.abort());
},
}
_isVisible: function(state) {
_isVisible(state) {
return state.usersTyping.length !== 0 || Object.keys(state.delayedStopTypingTimers).length !== 0;
},
}
isVisible: function() {
isVisible = () => {
return this._isVisible(this.state);
},
};
onRoomTimeline: function(event, room) {
onRoomTimeline = (event, room) => {
if (room && room.roomId === this.props.room.roomId) {
const userId = event.getSender();
// remove user from usersTyping
@ -96,15 +89,15 @@ export default createReactClass({
// abort timer if any
this._abortUserTimer(userId);
}
},
};
onRoomMemberTyping: function(ev, member) {
onRoomMemberTyping = (ev, member) => {
const usersTyping = WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room);
this.setState({
delayedStopTypingTimers: this._updateDelayedStopTypingTimers(usersTyping),
usersTyping,
});
},
};
_updateDelayedStopTypingTimers(usersTyping) {
const usersThatStoppedTyping = this.state.usersTyping.filter((a) => {
@ -142,26 +135,26 @@ export default createReactClass({
}, delayedStopTypingTimers);
return delayedStopTypingTimers;
},
}
_abortUserTimer: function(userId) {
_abortUserTimer(userId) {
const timer = this.state.delayedStopTypingTimers[userId];
if (timer) {
timer.abort();
this._removeUserTimer(userId);
}
},
}
_removeUserTimer: function(userId) {
_removeUserTimer(userId) {
const timer = this.state.delayedStopTypingTimers[userId];
if (timer) {
const delayedStopTypingTimers = Object.assign({}, this.state.delayedStopTypingTimers);
delete delayedStopTypingTimers[userId];
this.setState({delayedStopTypingTimers});
}
},
}
_renderTypingIndicatorAvatars: function(users, limit) {
_renderTypingIndicatorAvatars(users, limit) {
let othersCount = 0;
if (users.length > limit) {
othersCount = users.length - limit + 1;
@ -190,9 +183,9 @@ export default createReactClass({
}
return avatars;
},
}
render: function() {
render() {
let usersTyping = this.state.usersTyping;
const stoppedUsersOnTimer = Object.keys(this.state.delayedStopTypingTimers)
.map((userId) => this.props.room.getMember(userId));
@ -222,5 +215,5 @@ export default createReactClass({
</div>
</li>
);
},
});
}
}

View file

@ -16,14 +16,12 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'ChangeAvatar',
propTypes: {
export default class ChangeAvatar extends React.Component {
static propTypes = {
initialAvatarUrl: PropTypes.string,
room: PropTypes.object,
// if false, you need to call changeAvatar.onFileSelected yourself.
@ -31,36 +29,36 @@ export default createReactClass({
width: PropTypes.number,
height: PropTypes.number,
className: PropTypes.string,
},
};
Phases: {
static Phases = {
Display: "display",
Uploading: "uploading",
Error: "error",
},
};
getDefaultProps: function() {
return {
showUploadSection: true,
className: "",
width: 80,
height: 80,
};
},
static defaultProps = {
showUploadSection: true,
className: "",
width: 80,
height: 80,
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
avatarUrl: this.props.initialAvatarUrl,
phase: this.Phases.Display,
phase: ChangeAvatar.Phases.Display,
};
},
}
componentDidMount: function() {
componentDidMount() {
MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents);
},
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(newProps) {
UNSAFE_componentWillReceiveProps(newProps) {
if (this.avatarSet) {
// don't clobber what the user has just set
return;
@ -68,15 +66,15 @@ export default createReactClass({
this.setState({
avatarUrl: newProps.initialAvatarUrl,
});
},
}
componentWillUnmount: function() {
componentWillUnmount() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents);
}
},
}
onRoomStateEvents: function(ev) {
onRoomStateEvents = (ev) => {
if (!this.props.room) {
return;
}
@ -90,13 +88,13 @@ export default createReactClass({
this.avatarSet = false;
this.setState({}); // force update
}
},
};
setAvatarFromFile: function(file) {
setAvatarFromFile(file) {
let newUrl = null;
this.setState({
phase: this.Phases.Uploading,
phase: ChangeAvatar.Phases.Uploading,
});
const self = this;
const httpPromise = MatrixClientPeg.get().uploadContent(file).then(function(url) {
@ -115,31 +113,31 @@ export default createReactClass({
httpPromise.then(function() {
self.setState({
phase: self.Phases.Display,
phase: ChangeAvatar.Phases.Display,
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(newUrl),
});
}, function(error) {
self.setState({
phase: self.Phases.Error,
phase: ChangeAvatar.Phases.Error,
});
self.onError(error);
});
return httpPromise;
},
}
onFileSelected: function(ev) {
onFileSelected = (ev) => {
this.avatarSet = true;
return this.setAvatarFromFile(ev.target.files[0]);
},
};
onError: function(error) {
onError = (error) => {
this.setState({
errorText: _t("Failed to upload profile picture!"),
});
},
};
render: function() {
render() {
let avatarImg;
// Having just set an avatar we just display that since it will take a little
// time to propagate through to the RoomAvatar.
@ -165,8 +163,8 @@ export default createReactClass({
}
switch (this.state.phase) {
case this.Phases.Display:
case this.Phases.Error:
case ChangeAvatar.Phases.Display:
case ChangeAvatar.Phases.Error:
return (
<div>
<div className={this.props.className}>
@ -175,11 +173,11 @@ export default createReactClass({
{ uploadSection }
</div>
);
case this.Phases.Uploading:
case ChangeAvatar.Phases.Uploading:
var Loader = sdk.getComponent("elements.Spinner");
return (
<Loader />
);
}
},
});
}
}

View file

@ -17,15 +17,12 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'ChangeDisplayName',
_getDisplayName: async function() {
export default class ChangeDisplayName extends React.Component {
_getDisplayName = async () => {
const cli = MatrixClientPeg.get();
try {
const res = await cli.getProfileInfo(cli.getUserId());
@ -33,16 +30,16 @@ export default createReactClass({
} catch (e) {
throw new Error("Failed to fetch display name");
}
},
};
_changeDisplayName: function(newDisplayname) {
_changeDisplayName = (newDisplayname) => {
const cli = MatrixClientPeg.get();
return cli.setDisplayName(newDisplayname).catch(function(e) {
throw new Error("Failed to set display name", e);
});
},
};
render: function() {
render() {
const EditableTextContainer = sdk.getComponent('elements.EditableTextContainer');
return (
<EditableTextContainer
@ -51,5 +48,5 @@ export default createReactClass({
blurToSubmit={true}
onSubmit={this._changeDisplayName} />
);
},
});
}
}

View file

@ -18,7 +18,6 @@ limitations under the License.
import Field from "../elements/Field";
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import dis from "../../../dispatcher/dispatcher";
import AccessibleButton from '../elements/AccessibleButton';
@ -28,10 +27,8 @@ import Modal from "../../../Modal";
import sessionStore from '../../../stores/SessionStore';
export default createReactClass({
displayName: 'ChangePassword',
propTypes: {
export default class ChangePassword extends React.Component {
static propTypes = {
onFinished: PropTypes.func,
onError: PropTypes.func,
onCheckPassword: PropTypes.func,
@ -41,65 +38,61 @@ export default createReactClass({
confirm: PropTypes.bool,
// Whether to autoFocus the new password input
autoFocusNewPasswordInput: PropTypes.bool,
},
};
Phases: {
static Phases = {
Edit: "edit",
Uploading: "uploading",
Error: "error",
},
};
getDefaultProps: function() {
return {
onFinished: function() {},
onError: function() {},
onCheckPassword: function(oldPass, newPass, confirmPass) {
if (newPass !== confirmPass) {
return {
error: _t("New passwords don't match"),
};
} else if (!newPass || newPass.length === 0) {
return {
error: _t("Passwords can't be empty"),
};
}
},
confirm: true,
};
},
static defaultProps = {
onFinished() {},
onError() {},
onCheckPassword(oldPass, newPass, confirmPass) {
if (newPass !== confirmPass) {
return {
error: _t("New passwords don't match"),
};
} else if (!newPass || newPass.length === 0) {
return {
error: _t("Passwords can't be empty"),
};
}
},
confirm: true,
}
getInitialState: function() {
return {
phase: this.Phases.Edit,
cachedPassword: null,
oldPassword: "",
newPassword: "",
newPasswordConfirm: "",
};
},
state = {
phase: ChangePassword.Phases.Edit,
cachedPassword: null,
oldPassword: "",
newPassword: "",
newPasswordConfirm: "",
};
componentDidMount: function() {
componentDidMount() {
this._sessionStore = sessionStore;
this._sessionStoreToken = this._sessionStore.addListener(
this._setStateFromSessionStore,
);
this._setStateFromSessionStore();
},
}
componentWillUnmount: function() {
componentWillUnmount() {
if (this._sessionStoreToken) {
this._sessionStoreToken.remove();
}
},
}
_setStateFromSessionStore: function() {
_setStateFromSessionStore = () => {
this.setState({
cachedPassword: this._sessionStore.getCachedPassword(),
});
},
};
changePassword: function(oldPassword, newPassword) {
changePassword(oldPassword, newPassword) {
const cli = MatrixClientPeg.get();
if (!this.props.confirm) {
@ -136,9 +129,9 @@ export default createReactClass({
}
},
});
},
}
_changePassword: function(cli, oldPassword, newPassword) {
_changePassword(cli, oldPassword, newPassword) {
const authDict = {
type: 'm.login.password',
identifier: {
@ -152,7 +145,7 @@ export default createReactClass({
};
this.setState({
phase: this.Phases.Uploading,
phase: ChangePassword.Phases.Uploading,
});
cli.setPassword(authDict, newPassword).then(() => {
@ -172,51 +165,51 @@ export default createReactClass({
this.props.onError(err);
}).finally(() => {
this.setState({
phase: this.Phases.Edit,
phase: ChangePassword.Phases.Edit,
oldPassword: "",
newPassword: "",
newPasswordConfirm: "",
});
});
},
}
_optionallySetEmail: function() {
_optionallySetEmail() {
// Ask for an email otherwise the user has no way to reset their password
const SetEmailDialog = sdk.getComponent("dialogs.SetEmailDialog");
const modal = Modal.createTrackedDialog('Do you want to set an email address?', '', SetEmailDialog, {
title: _t('Do you want to set an email address?'),
});
return modal.finished.then(([confirmed]) => confirmed);
},
}
_onExportE2eKeysClicked: function() {
_onExportE2eKeysClicked = () => {
Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password',
import('../../../async-components/views/dialogs/ExportE2eKeysDialog'),
{
matrixClient: MatrixClientPeg.get(),
},
);
},
};
onChangeOldPassword(ev) {
onChangeOldPassword = (ev) => {
this.setState({
oldPassword: ev.target.value,
});
},
};
onChangeNewPassword(ev) {
onChangeNewPassword = (ev) => {
this.setState({
newPassword: ev.target.value,
});
},
};
onChangeNewPasswordConfirm(ev) {
onChangeNewPasswordConfirm = (ev) => {
this.setState({
newPasswordConfirm: ev.target.value,
});
},
};
onClickChange: function(ev) {
onClickChange = (ev) => {
ev.preventDefault();
const oldPassword = this.state.cachedPassword || this.state.oldPassword;
const newPassword = this.state.newPassword;
@ -229,9 +222,9 @@ export default createReactClass({
} else {
this.changePassword(oldPassword, newPassword);
}
},
};
render: function() {
render() {
// TODO: Live validation on `new pw == confirm pw`
const rowClassName = this.props.rowClassName;
@ -252,7 +245,7 @@ export default createReactClass({
}
switch (this.state.phase) {
case this.Phases.Edit:
case ChangePassword.Phases.Edit:
const passwordLabel = this.state.cachedPassword ?
_t('Password') : _t('New Password');
return (
@ -282,7 +275,7 @@ export default createReactClass({
</AccessibleButton>
</form>
);
case this.Phases.Uploading:
case ChangePassword.Phases.Uploading:
var Loader = sdk.getComponent("elements.Spinner");
return (
<div className="mx_Dialog_content">
@ -290,5 +283,5 @@ export default createReactClass({
</div>
);
}
},
});
}
}

View file

@ -16,7 +16,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
@ -65,46 +64,42 @@ function portLegacyActions(actions) {
}
}
export default createReactClass({
displayName: 'Notifications',
phases: {
export default class Notifications extends React.Component {
static phases = {
LOADING: "LOADING", // The component is loading or sending data to the hs
DISPLAY: "DISPLAY", // The component is ready and display data
ERROR: "ERROR", // There was an error
},
};
getInitialState: function() {
return {
phase: this.phases.LOADING,
masterPushRule: undefined, // The master rule ('.m.rule.master')
vectorPushRules: [], // HS default push rules displayed in Vector UI
vectorContentRules: { // Keyword push rules displayed in Vector UI
vectorState: PushRuleVectorState.ON,
rules: [],
},
externalPushRules: [], // Push rules (except content rule) that have been defined outside Vector UI
externalContentRules: [], // Keyword push rules that have been defined outside Vector UI
threepids: [], // used for email notifications
};
},
state = {
phase: Notifications.phases.LOADING,
masterPushRule: undefined, // The master rule ('.m.rule.master')
vectorPushRules: [], // HS default push rules displayed in Vector UI
vectorContentRules: { // Keyword push rules displayed in Vector UI
vectorState: PushRuleVectorState.ON,
rules: [],
},
externalPushRules: [], // Push rules (except content rule) that have been defined outside Vector UI
externalContentRules: [], // Keyword push rules that have been defined outside Vector UI
threepids: [], // used for email notifications
};
componentDidMount: function() {
componentDidMount() {
this._refreshFromServer();
},
}
onEnableNotificationsChange: function(checked) {
onEnableNotificationsChange = (checked) => {
const self = this;
this.setState({
phase: this.phases.LOADING,
phase: Notifications.phases.LOADING,
});
MatrixClientPeg.get().setPushRuleEnabled('global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !checked).then(function() {
self._refreshFromServer();
});
},
};
onEnableDesktopNotificationsChange: function(checked) {
onEnableDesktopNotificationsChange = (checked) => {
SettingsStore.setValue(
"notificationsEnabled", null,
SettingLevel.DEVICE,
@ -112,9 +107,9 @@ export default createReactClass({
).finally(() => {
this.forceUpdate();
});
},
};
onEnableDesktopNotificationBodyChange: function(checked) {
onEnableDesktopNotificationBodyChange = (checked) => {
SettingsStore.setValue(
"notificationBodyEnabled", null,
SettingLevel.DEVICE,
@ -122,9 +117,9 @@ export default createReactClass({
).finally(() => {
this.forceUpdate();
});
},
};
onEnableAudioNotificationsChange: function(checked) {
onEnableAudioNotificationsChange = (checked) => {
SettingsStore.setValue(
"audioNotificationsEnabled", null,
SettingLevel.DEVICE,
@ -132,7 +127,7 @@ export default createReactClass({
).finally(() => {
this.forceUpdate();
});
},
};
/*
* Returns the email pusher (pusher of type 'email') for a given
@ -140,7 +135,7 @@ export default createReactClass({
* pushers are unique over (app ID, pushkey), there will be at most
* one such pusher.
*/
getEmailPusher: function(pushers, address) {
getEmailPusher(pushers, address) {
if (pushers === undefined) {
return undefined;
}
@ -150,9 +145,9 @@ export default createReactClass({
}
}
return undefined;
},
}
onEnableEmailNotificationsChange: function(address, checked) {
onEnableEmailNotificationsChange = (address, checked) => {
let emailPusherPromise;
if (checked) {
const data = {};
@ -181,9 +176,9 @@ export default createReactClass({
description: _t('An error occurred whilst saving your email notification preferences.'),
});
});
},
};
onNotifStateButtonClicked: function(event) {
onNotifStateButtonClicked = (event) => {
// FIXME: use .bind() rather than className metadata here surely
const vectorRuleId = event.target.className.split("-")[0];
const newPushRuleVectorState = event.target.className.split("-")[1];
@ -196,11 +191,9 @@ export default createReactClass({
this._setPushRuleVectorState(rule, newPushRuleVectorState);
}
}
},
onKeywordsClicked: function(event) {
const self = this;
};
onKeywordsClicked = (event) => {
// Compute the keywords list to display
let keywords = [];
for (const i in this.state.vectorContentRules.rules) {
@ -223,7 +216,7 @@ export default createReactClass({
description: _t('Enter keywords separated by a comma:'),
button: _t('OK'),
value: keywords,
onFinished: function onFinished(should_leave, newValue) {
onFinished: (should_leave, newValue) => {
if (should_leave && newValue !== keywords) {
let newKeywords = newValue.split(',');
for (const i in newKeywords) {
@ -238,25 +231,25 @@ export default createReactClass({
return array;
}, []);
self._setKeywords(newKeywords);
this._setKeywords(newKeywords);
}
},
});
},
};
getRule: function(vectorRuleId) {
getRule(vectorRuleId) {
for (const i in this.state.vectorPushRules) {
const rule = this.state.vectorPushRules[i];
if (rule.vectorRuleId === vectorRuleId) {
return rule;
}
}
},
}
_setPushRuleVectorState: function(rule, newPushRuleVectorState) {
_setPushRuleVectorState(rule, newPushRuleVectorState) {
if (rule && rule.vectorState !== newPushRuleVectorState) {
this.setState({
phase: this.phases.LOADING,
phase: Notifications.phases.LOADING,
});
const self = this;
@ -288,9 +281,9 @@ export default createReactClass({
});
});
}
},
}
_setKeywordsPushRuleVectorState: function(newPushRuleVectorState) {
_setKeywordsPushRuleVectorState(newPushRuleVectorState) {
// Is there really a change?
if (this.state.vectorContentRules.vectorState === newPushRuleVectorState
|| this.state.vectorContentRules.rules.length === 0) {
@ -301,7 +294,7 @@ export default createReactClass({
const cli = MatrixClientPeg.get();
this.setState({
phase: this.phases.LOADING,
phase: Notifications.phases.LOADING,
});
// Update all rules in self.state.vectorContentRules
@ -356,11 +349,11 @@ export default createReactClass({
onFinished: self._refreshFromServer,
});
});
},
}
_setKeywords: function(newKeywords) {
_setKeywords(newKeywords) {
this.setState({
phase: this.phases.LOADING,
phase: Notifications.phases.LOADING,
});
const self = this;
@ -440,19 +433,19 @@ export default createReactClass({
self._refreshFromServer();
}, onError);
}, onError);
},
}
// Create a push rule but disabled
_addDisabledPushRule: function(scope, kind, ruleId, body) {
_addDisabledPushRule(scope, kind, ruleId, body) {
const cli = MatrixClientPeg.get();
return cli.addPushRule(scope, kind, ruleId, body).then(() =>
cli.setPushRuleEnabled(scope, kind, ruleId, false),
);
},
}
// Check if any legacy im.vector rules need to be ported to the new API
// for overriding the actions of default rules.
_portRulesToNewAPI: function(rulesets) {
_portRulesToNewAPI(rulesets) {
const needsUpdate = [];
const cli = MatrixClientPeg.get();
@ -485,9 +478,9 @@ export default createReactClass({
// Otherwise return the rules that we already have.
return rulesets;
}
},
}
_refreshFromServer: function() {
_refreshFromServer = () => {
const self = this;
const pushRulesPromise = MatrixClientPeg.get().getPushRules().then(self._portRulesToNewAPI).then(function(rulesets) {
/// XXX seriously? wtf is this?
@ -636,12 +629,12 @@ export default createReactClass({
Promise.all([pushRulesPromise, pushersPromise]).then(function() {
self.setState({
phase: self.phases.DISPLAY,
phase: Notifications.phases.DISPLAY,
});
}, function(error) {
console.error(error);
self.setState({
phase: self.phases.ERROR,
phase: Notifications.phases.ERROR,
});
}).finally(() => {
// actually explicitly update our state having been deep-manipulating it
@ -655,9 +648,9 @@ export default createReactClass({
});
MatrixClientPeg.get().getThreePids().then((r) => this.setState({threepids: r.threepids}));
},
};
_onClearNotifications: function() {
_onClearNotifications = () => {
const cli = MatrixClientPeg.get();
cli.getRooms().forEach(r => {
@ -666,9 +659,9 @@ export default createReactClass({
if (events.length) cli.sendReadReceipt(events.pop());
}
});
},
};
_updatePushRuleActions: function(rule, actions, enabled) {
_updatePushRuleActions(rule, actions, enabled) {
const cli = MatrixClientPeg.get();
return cli.setPushRuleActions(
@ -681,9 +674,9 @@ export default createReactClass({
);
}
});
},
}
renderNotifRulesTableRow: function(title, className, pushRuleVectorState) {
renderNotifRulesTableRow(title, className, pushRuleVectorState) {
return (
<tr key={ className }>
<th>
@ -712,9 +705,9 @@ export default createReactClass({
</th>
</tr>
);
},
}
renderNotifRulesTableRows: function() {
renderNotifRulesTableRows() {
const rows = [];
for (const i in this.state.vectorPushRules) {
const rule = this.state.vectorPushRules[i];
@ -726,9 +719,9 @@ export default createReactClass({
rows.push(this.renderNotifRulesTableRow(rule.description, rule.vectorRuleId, rule.vectorState));
}
return rows;
},
}
hasEmailPusher: function(pushers, address) {
hasEmailPusher(pushers, address) {
if (pushers === undefined) {
return false;
}
@ -738,17 +731,17 @@ export default createReactClass({
}
}
return false;
},
}
emailNotificationsRow: function(address, label) {
emailNotificationsRow(address, label) {
return <LabelledToggleSwitch value={this.hasEmailPusher(this.state.pushers, address)}
onChange={this.onEnableEmailNotificationsChange.bind(this, address)}
label={label} key={`emailNotif_${label}`} />;
},
}
render: function() {
render() {
let spinner;
if (this.state.phase === this.phases.LOADING) {
if (this.state.phase === Notifications.phases.LOADING) {
const Loader = sdk.getComponent("elements.Spinner");
spinner = <Loader />;
}
@ -910,5 +903,5 @@ export default createReactClass({
</div>
);
},
});
}
}

View file

@ -17,44 +17,42 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
export default createReactClass({
displayName: 'VideoFeed',
propTypes: {
export default class VideoFeed extends React.Component {
static propTypes = {
// maxHeight style attribute for the video element
maxHeight: PropTypes.number,
// a callback which is called when the video element is resized
// due to a change in video metadata
onResize: PropTypes.func,
},
};
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount() {
this._vid = createRef();
},
}
componentDidMount() {
this._vid.current.addEventListener('resize', this.onResize);
},
}
componentWillUnmount() {
this._vid.current.removeEventListener('resize', this.onResize);
},
}
onResize: function(e) {
onResize = (e) => {
if (this.props.onResize) {
this.props.onResize(e);
}
},
};
render: function() {
render() {
return (
<video ref={this._vid} style={{maxHeight: this.props.maxHeight}}>
</video>
);
},
});
}
}

View file

@ -18,7 +18,6 @@ limitations under the License.
import React, {createRef} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames';
import * as sdk from '../../../index';
@ -35,10 +34,8 @@ function getFullScreenElement() {
);
}
export default createReactClass({
displayName: 'VideoView',
propTypes: {
export default class VideoView extends React.Component {
static propTypes = {
// maxHeight style attribute for the video element
maxHeight: PropTypes.number,
@ -48,27 +45,28 @@ export default createReactClass({
// a callback which is called when the video element is resized due to
// a change in video metadata
onResize: PropTypes.func,
},
};
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._local = createRef();
this._remote = createRef();
},
}
componentDidMount: function() {
componentDidMount() {
this.dispatcherRef = dis.register(this.onAction);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
dis.unregister(this.dispatcherRef);
},
}
getRemoteVideoElement: function() {
getRemoteVideoElement = () => {
return ReactDOM.findDOMNode(this._remote.current);
},
};
getRemoteAudioElement: function() {
getRemoteAudioElement = () => {
// this needs to be somewhere at the top of the DOM which
// always exists to avoid audio interruptions.
// Might as well just use DOM.
@ -78,17 +76,17 @@ export default createReactClass({
+ "You need to add an <audio/> to the DOM.");
}
return remoteAudioElement;
},
};
getLocalVideoElement: function() {
getLocalVideoElement = () => {
return ReactDOM.findDOMNode(this._local.current);
},
};
setContainer: function(c) {
setContainer = (c) => {
this.container = c;
},
};
onAction: function(payload) {
onAction = (payload) => {
switch (payload.action) {
case 'video_fullscreen': {
if (!this.container) {
@ -117,9 +115,9 @@ export default createReactClass({
break;
}
}
},
};
render: function() {
render() {
const VideoFeed = sdk.getComponent('voip.VideoFeed');
// if we're fullscreen, we don't want to set a maxHeight on the video element.
@ -140,5 +138,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}