Merge branch 'develop' of https://github.com/matrix-org/matrix-react-sdk into joriks/appearance-tab

This commit is contained in:
Jorik Schellekens 2020-06-01 14:32:31 +01:00
commit 69747b6114
55 changed files with 902 additions and 870 deletions

View file

@ -43,6 +43,15 @@ import ResizeNotifier from "../../utils/ResizeNotifier";
import PlatformPeg from "../../PlatformPeg";
import { RoomListStoreTempProxy } from "../../stores/room-list/RoomListStoreTempProxy";
import { DefaultTagID } from "../../stores/room-list/models";
import {
showToast as showSetPasswordToast,
hideToast as hideSetPasswordToast
} from "../../toasts/SetPasswordToast";
import {
showToast as showServerLimitToast,
hideToast as hideServerLimitToast
} from "../../toasts/ServerLimitToast";
// We need to fetch each pinned message individually (if we don't already have it)
// so each pinned message may trigger a request. Limit the number per room for sanity.
// NB. this is just for server notices rather than pinned messages in general.
@ -65,10 +74,6 @@ interface IProps {
initialEventPixelOffset: number;
leftDisabled: boolean;
rightDisabled: boolean;
showCookieBar: boolean;
hasNewVersion: boolean;
userHasGeneratedPassword: boolean;
showNotifierToolbar: boolean;
page_type: string;
autoJoin: boolean;
thirdPartyInvite?: object;
@ -76,7 +81,6 @@ interface IProps {
currentRoomId: string;
ConferenceHandler?: object;
collapseLhs: boolean;
checkingForUpdate: boolean;
config: {
piwik: {
policyUrl: string;
@ -86,10 +90,8 @@ interface IProps {
currentUserId?: string;
currentGroupId?: string;
currentGroupIsNew?: boolean;
version?: string;
newVersion?: string;
newVersionReleaseNotes?: string;
}
interface IState {
mouseDown?: {
x: number;
@ -97,8 +99,6 @@ interface IState {
};
syncErrorData: any;
useCompactLayout: boolean;
serverNoticeEvents: MatrixEvent[];
userHasGeneratedPassword: boolean;
}
/**
@ -141,11 +141,8 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
this.state = {
mouseDown: undefined,
syncErrorData: undefined,
userHasGeneratedPassword: false,
// use compact timeline view
useCompactLayout: SettingsStore.getValue('useCompactLayout'),
// any currently active server notice events
serverNoticeEvents: [],
};
// stash the MatrixClient in case we log out before we are unmounted
@ -179,18 +176,6 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
this._loadResizerPreferences();
}
componentDidUpdate(prevProps, prevState) {
// attempt to guess when a banner was opened or closed
if (
(prevProps.showCookieBar !== this.props.showCookieBar) ||
(prevProps.hasNewVersion !== this.props.hasNewVersion) ||
(prevState.userHasGeneratedPassword !== this.state.userHasGeneratedPassword) ||
(prevProps.showNotifierToolbar !== this.props.showNotifierToolbar)
) {
this.props.resizeNotifier.notifyBannersChanged();
}
}
componentWillUnmount() {
document.removeEventListener('keydown', this._onNativeKeyDown, false);
this._matrixClient.removeListener("accountData", this.onAccountData);
@ -220,9 +205,11 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
};
_setStateFromSessionStore = () => {
this.setState({
userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()),
});
if (this._sessionStore.getCachedPassword()) {
showSetPasswordToast();
} else {
hideSetPasswordToast();
}
};
_createResizer() {
@ -294,6 +281,8 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
if (oldSyncState === 'PREPARED' && syncState === 'SYNCING') {
this._updateServerNoticeEvents();
} else {
this._calculateServerLimitToast(data);
}
};
@ -304,11 +293,24 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
}
};
_calculateServerLimitToast(syncErrorData, usageLimitEventContent?) {
const error = syncErrorData && syncErrorData.error && syncErrorData.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED";
if (error) {
usageLimitEventContent = syncErrorData.error.data;
}
if (usageLimitEventContent) {
showServerLimitToast(usageLimitEventContent.limit_type, usageLimitEventContent.admin_contact, error);
} else {
hideServerLimitToast();
}
}
_updateServerNoticeEvents = async () => {
const roomLists = RoomListStoreTempProxy.getRoomLists();
if (!roomLists[DefaultTagID.ServerNotice]) return [];
const pinnedEvents = [];
const events = [];
for (const room of roomLists[DefaultTagID.ServerNotice]) {
const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", "");
@ -318,12 +320,18 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
for (const eventId of pinnedEventIds) {
const timeline = await this._matrixClient.getEventTimeline(room.getUnfilteredTimelineSet(), eventId, 0);
const event = timeline.getEvents().find(ev => ev.getId() === eventId);
if (event) pinnedEvents.push(event);
if (event) events.push(event);
}
}
this.setState({
serverNoticeEvents: pinnedEvents,
const usageLimitEvent = events.find((e) => {
return (
e && e.getType() === 'm.room.message' &&
e.getContent()['server_notice_type'] === 'm.server_notice.usage_limit_reached'
);
});
this._calculateServerLimitToast(this.state.syncErrorData, usageLimitEvent && usageLimitEvent.getContent());
};
_onPaste = (ev) => {
@ -599,12 +607,6 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
const GroupView = sdk.getComponent('structures.GroupView');
const MyGroups = sdk.getComponent('structures.MyGroups');
const ToastContainer = sdk.getComponent('structures.ToastContainer');
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
const CookieBar = sdk.getComponent('globals.CookieBar');
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar');
const PasswordNagBar = sdk.getComponent('globals.PasswordNagBar');
const ServerLimitBar = sdk.getComponent('globals.ServerLimitBar');
let pageElement;
@ -648,46 +650,7 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
break;
}
const usageLimitEvent = this.state.serverNoticeEvents.find((e) => {
return (
e && e.getType() === 'm.room.message' &&
e.getContent()['server_notice_type'] === 'm.server_notice.usage_limit_reached'
);
});
let topBar;
if (this.state.syncErrorData && this.state.syncErrorData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') {
topBar = <ServerLimitBar kind='hard'
adminContact={this.state.syncErrorData.error.data.admin_contact}
limitType={this.state.syncErrorData.error.data.limit_type}
/>;
} else if (usageLimitEvent) {
topBar = <ServerLimitBar kind='soft'
adminContact={usageLimitEvent.getContent().admin_contact}
limitType={usageLimitEvent.getContent().limit_type}
/>;
} else if (this.props.showCookieBar &&
this.props.config.piwik &&
navigator.doNotTrack !== "1"
) {
const policyUrl = this.props.config.piwik.policyUrl || null;
topBar = <CookieBar policyUrl={policyUrl} />;
} else if (this.props.hasNewVersion) {
topBar = <NewVersionBar version={this.props.version} newVersion={this.props.newVersion}
releaseNotes={this.props.newVersionReleaseNotes}
/>;
} else if (this.props.checkingForUpdate) {
topBar = <UpdateCheckBar {...this.props.checkingForUpdate} />;
} else if (this.state.userHasGeneratedPassword) {
topBar = <PasswordNagBar />;
} else if (this.props.showNotifierToolbar) {
topBar = <MatrixToolbar />;
}
let bodyClasses = 'mx_MatrixChat';
if (topBar) {
bodyClasses += ' mx_MatrixChat_toolbarShowing';
}
if (this.state.useCompactLayout) {
bodyClasses += ' mx_MatrixChat_useCompactLayout';
}
@ -702,7 +665,6 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
onMouseDown={this._onMouseDown}
onMouseUp={this._onMouseUp}
>
{ topBar }
<ToastContainer />
<DragDropContext onDragEnd={this._onDragEnd}>
<div ref={this._resizeContainer} className={bodyClasses}>

View file

@ -67,6 +67,11 @@ import * as StorageManager from "../../utils/StorageManager";
import type LoggedInViewType from "./LoggedInView";
import { ViewUserPayload } from "../../dispatcher/payloads/ViewUserPayload";
import { Action } from "../../dispatcher/actions";
import {
showToast as showAnalyticsToast,
hideToast as hideAnalyticsToast
} from "../../toasts/AnalyticsToast";
import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast";
/** constants for MatrixChat.state.view */
export enum Views {
@ -168,12 +173,6 @@ interface IState {
leftDisabled: boolean;
middleDisabled: boolean;
// the right panel's disabled state is tracked in its store.
version?: string;
newVersion?: string;
hasNewVersion: boolean;
newVersionReleaseNotes?: string;
checkingForUpdate?: string; // updateCheckStatusEnum
showCookieBar: boolean;
// Parameters used in the registration dance with the IS
register_client_secret?: string;
register_session_id?: string;
@ -183,7 +182,6 @@ interface IState {
hideToSRUsers: boolean;
syncError?: Error;
resizeNotifier: ResizeNotifier;
showNotifierToolbar: boolean;
serverConfig?: ValidatedServerConfig;
ready: boolean;
thirdPartyInvite?: object;
@ -227,17 +225,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
leftDisabled: false,
middleDisabled: false,
hasNewVersion: false,
newVersionReleaseNotes: null,
checkingForUpdate: null,
showCookieBar: false,
hideToSRUsers: false,
syncError: null, // If the current syncing status is ERROR, the error object, otherwise null.
resizeNotifier: new ResizeNotifier(),
showNotifierToolbar: false,
ready: false,
};
@ -338,12 +329,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
});
}
if (SettingsStore.getValue("showCookieBar")) {
this.setState({
showCookieBar: true,
});
}
if (SettingsStore.getValue("analyticsOptIn")) {
Analytics.enable();
}
@ -685,9 +670,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
dis.dispatch({action: 'view_my_groups'});
}
break;
case 'notifier_enabled':
this.setState({showNotifierToolbar: Notifier.shouldShowToolbar()});
break;
case 'hide_left_panel':
this.setState({
collapseLhs: true,
@ -735,15 +717,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
case 'client_started':
this.onClientStarted();
break;
case 'new_version':
this.onVersion(
payload.currentVersion, payload.newVersion,
payload.releaseNotes,
);
break;
case 'check_updates':
this.setState({ checkingForUpdate: payload.value });
break;
case 'send_event':
this.onSendEvent(payload.room_id, payload.event);
break;
@ -760,19 +733,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
case 'accept_cookies':
SettingsStore.setValue("analyticsOptIn", null, SettingLevel.DEVICE, true);
SettingsStore.setValue("showCookieBar", null, SettingLevel.DEVICE, false);
this.setState({
showCookieBar: false,
});
hideAnalyticsToast();
Analytics.enable();
break;
case 'reject_cookies':
SettingsStore.setValue("analyticsOptIn", null, SettingLevel.DEVICE, false);
SettingsStore.setValue("showCookieBar", null, SettingLevel.DEVICE, false);
this.setState({
showCookieBar: false,
});
hideAnalyticsToast();
break;
}
};
@ -1261,6 +1228,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
StorageManager.tryPersistStorage();
if (SettingsStore.getValue("showCookieBar") && this.props.config.piwik && navigator.doNotTrack !== "1") {
showAnalyticsToast(this.props.config.piwik && this.props.config.piwik.policyUrl);
}
}
private showScreenAfterLogin() {
@ -1388,10 +1359,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.firstSyncComplete = true;
this.firstSyncPromise.resolve();
if (Notifier.shouldShowToolbar()) {
showNotificationsToast();
}
dis.dispatch({action: 'focus_composer'});
this.setState({
ready: true,
showNotifierToolbar: Notifier.shouldShowToolbar(),
});
});
cli.on('Call.incoming', function(call) {
@ -1833,16 +1807,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.showScreen("settings");
};
onVersion(current: string, latest: string, releaseNotes?: string) {
this.setState({
version: current,
newVersion: latest,
hasNewVersion: current !== latest,
newVersionReleaseNotes: releaseNotes,
checkingForUpdate: null,
});
}
onSendEvent(roomId: string, event: MatrixEvent) {
const cli = MatrixClientPeg.get();
if (!cli) {
@ -2037,7 +2001,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
onCloseAllSettings={this.onCloseAllSettings}
onRegistered={this.onRegistered}
currentRoomId={this.state.currentRoomId}
showCookieBar={this.state.showCookieBar}
/>
);
} else {

View file

@ -108,6 +108,9 @@ export default class MessagePanel extends React.Component {
// whether to show reactions for an event
showReactions: PropTypes.bool,
// whether to use the irc layout
useIRCLayout: PropTypes.bool,
};
// Force props to be loaded for useIRCLayout
@ -119,7 +122,6 @@ export default class MessagePanel extends React.Component {
// display 'ghost' read markers that are animating away
ghostReadMarkers: [],
showTypingNotifications: SettingsStore.getValue("showTypingNotifications"),
useIRCLayout: this.useIRCLayout(SettingsStore.getValue("feature_irc_ui")),
};
// opaque readreceipt info for each userId; used by ReadReceiptMarker
@ -172,8 +174,6 @@ export default class MessagePanel extends React.Component {
this._showTypingNotificationsWatcherRef =
SettingsStore.watchSetting("showTypingNotifications", null, this.onShowTypingNotificationsChange);
this._layoutWatcherRef = SettingsStore.watchSetting("feature_irc_ui", null, this.onLayoutChange);
}
componentDidMount() {
@ -183,7 +183,6 @@ export default class MessagePanel extends React.Component {
componentWillUnmount() {
this._isMounted = false;
SettingsStore.unwatchSetting(this._showTypingNotificationsWatcherRef);
SettingsStore.unwatchSetting(this._layoutWatcherRef);
}
componentDidUpdate(prevProps, prevState) {
@ -202,17 +201,6 @@ export default class MessagePanel extends React.Component {
});
};
onLayoutChange = () => {
this.setState({
useIRCLayout: this.useIRCLayout(SettingsStore.getValue("feature_irc_ui")),
});
}
useIRCLayout(ircLayoutSelected) {
// if room is null we are not in a normal room list
return ircLayoutSelected && this.props.room;
}
/* get the DOM node representing the given event */
getNodeForEventId(eventId) {
if (!this.eventNodes) {
@ -614,7 +602,7 @@ export default class MessagePanel extends React.Component {
isSelectedEvent={highlight}
getRelationsForEvent={this.props.getRelationsForEvent}
showReactions={this.props.showReactions}
useIRCLayout={this.state.useIRCLayout}
useIRCLayout={this.props.useIRCLayout}
/>
</TileErrorBoundary>
</li>,
@ -797,8 +785,6 @@ export default class MessagePanel extends React.Component {
this.props.className,
{
"mx_MessagePanel_alwaysShowTimestamps": this.props.alwaysShowTimestamps,
"mx_IRCLayout": this.state.useIRCLayout,
"mx_GroupLayout": !this.state.useIRCLayout,
},
);

View file

@ -165,6 +165,8 @@ export default createReactClass({
canReact: false,
canReply: false,
useIRCLayout: SettingsStore.getValue("feature_irc_ui"),
matrixClientIsReady: this.context && this.context.isInitialSyncComplete(),
};
},
@ -195,6 +197,8 @@ export default createReactClass({
this._roomView = createRef();
this._searchResultsPanel = createRef();
this._layoutWatcherRef = SettingsStore.watchSetting("feature_irc_ui", null, this.onLayoutChange);
},
_onReadReceiptsChange: function() {
@ -535,6 +539,14 @@ export default createReactClass({
// no need to do this as Dir & Settings are now overlays. It just burnt CPU.
// console.log("Tinter.tint from RoomView.unmount");
// Tinter.tint(); // reset colourscheme
SettingsStore.unwatchSetting(this._layoutWatcherRef);
},
onLayoutChange: function() {
this.setState({
useIRCLayout: SettingsStore.getValue("feature_irc_ui"),
});
},
_onRightPanelStoreUpdate: function() {
@ -1996,6 +2008,13 @@ export default createReactClass({
highlightedEventId = this.state.initialEventId;
}
const messagePanelClassNames = classNames(
"mx_RoomView_messagePanel",
{
"mx_IRCLayout": this.state.useIRCLayout,
"mx_GroupLayout": !this.state.useIRCLayout,
});
// console.info("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
const messagePanel = (
<TimelinePanel
@ -2011,11 +2030,12 @@ export default createReactClass({
onScroll={this.onMessageListScroll}
onReadMarkerUpdated={this._updateTopUnreadMessagesBar}
showUrlPreview = {this.state.showUrlPreview}
className="mx_RoomView_messagePanel"
className={messagePanelClassNames}
membersLoaded={this.state.membersLoaded}
permalinkCreator={this._getPermalinkCreatorForRoom(this.state.room)}
resizeNotifier={this.props.resizeNotifier}
showReactions={true}
useIRCLayout={this.state.useIRCLayout}
/>);
let topUnreadMessagesBar = null;

View file

@ -112,6 +112,9 @@ const TimelinePanel = createReactClass({
// whether to show reactions for an event
showReactions: PropTypes.bool,
// whether to use the irc layout
useIRCLayout: PropTypes.bool,
},
statics: {
@ -1447,6 +1450,7 @@ const TimelinePanel = createReactClass({
getRelationsForEvent={this.getRelationsForEvent}
editState={this.state.editState}
showReactions={this.props.showReactions}
useIRCLayout={this.props.useIRCLayout}
/>
);
},

View file

@ -539,6 +539,7 @@ export const MsisdnAuthEntry = createReactClass({
type: MsisdnAuthEntry.LOGIN_TYPE,
// TODO: Remove `threepid_creds` once servers support proper UIA
// See https://github.com/vector-im/riot-web/issues/10312
// See https://github.com/matrix-org/matrix-doc/issues/2220
threepid_creds: creds,
threepidCreds: creds,
});

View file

@ -23,7 +23,8 @@ import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
import * as ServerType from '../../views/auth/ServerTypeSelector';
import ServerConfig from "./ServerConfig";
const MODULAR_URL = 'https://modular.im/?utm_source=riot-web&utm_medium=web&utm_campaign=riot-web-authentication';
const MODULAR_URL = 'https://modular.im/services/matrix-hosting-riot' +
'?utm_source=riot-web&utm_medium=web&utm_campaign=riot-web-authentication';
// TODO: TravisR - Can this extend ServerConfig for most things?

View file

@ -238,7 +238,7 @@ export default class PasswordLogin extends React.Component {
type="text"
label={_t("Phone")}
value={this.state.phoneNumber}
prefix={phoneCountry}
prefixComponent={phoneCountry}
onChange={this.onPhoneNumberChanged}
onBlur={this.onPhoneNumberBlur}
disabled={this.props.disableSubmit}

View file

@ -473,7 +473,7 @@ export default createReactClass({
type="text"
label={phoneLabel}
value={this.state.phoneNumber}
prefix={phoneCountry}
prefixComponent={phoneCountry}
onChange={this.onPhoneNumberChange}
onValidate={this.onPhoneNumberValidate}
/>;

View file

@ -22,7 +22,8 @@ import classnames from 'classnames';
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
import {makeType} from "../../../utils/TypeUtils";
const MODULAR_URL = 'https://modular.im/?utm_source=riot-web&utm_medium=web&utm_campaign=riot-web-authentication';
const MODULAR_URL = 'https://modular.im/services/matrix-hosting-riot' +
'?utm_source=riot-web&utm_medium=web&utm_campaign=riot-web-authentication';
export const FREE = 'Free';
export const PREMIUM = 'Premium';

View file

@ -144,6 +144,7 @@ export default createReactClass({
>
<div className={classNames('mx_Dialog_header', {
'mx_Dialog_headerWithButton': !!this.props.headerButton,
'mx_Dialog_headerWithCancel': !!cancelButton,
})}>
<div className={classNames('mx_Dialog_title', this.props.titleClass)} id='mx_BaseDialog_title'>
{headerImage}

View file

@ -47,8 +47,8 @@ export default class RoomAliasField extends React.PureComponent {
<Field
label={_t("Room address")}
className="mx_RoomAliasField"
prefix={poundSign}
postfix={domain}
prefixComponent={poundSign}
postfixComponent={domain}
ref={ref => this._fieldRef = ref}
onValidate={this._onValidate}
placeholder={_t("e.g. my-room")}

View file

@ -1,103 +0,0 @@
/*
Copyright 2018 New Vector Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
import Analytics from '../../../Analytics';
export default class CookieBar extends React.Component {
static propTypes = {
policyUrl: PropTypes.string,
}
constructor() {
super();
}
onUsageDataClicked(e) {
e.stopPropagation();
e.preventDefault();
Analytics.showDetailsModal();
}
onAccept() {
dis.dispatch({
action: 'accept_cookies',
});
}
onReject() {
dis.dispatch({
action: 'reject_cookies',
});
}
render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const toolbarClasses = "mx_MatrixToolbar";
return (
<div className={toolbarClasses}>
<img className="mx_MatrixToolbar_warning" src={require("../../../../res/img/warning.svg")} width="24" height="23" alt="" />
<div className="mx_MatrixToolbar_content">
{ this.props.policyUrl ? _t(
"Please help improve Riot.im by sending <UsageDataLink>anonymous usage data</UsageDataLink>. " +
"This will use a cookie " +
"(please see our <PolicyLink>Cookie Policy</PolicyLink>).",
{},
{
'UsageDataLink': (sub) => <a
className="mx_MatrixToolbar_link"
onClick={this.onUsageDataClicked}
>
{ sub }
</a>,
// XXX: We need to link to the page that explains our cookies
'PolicyLink': (sub) => <a
className="mx_MatrixToolbar_link"
target="_blank"
href={this.props.policyUrl}
>
{ sub }
</a>
,
},
) : _t(
"Please help improve Riot.im by sending <UsageDataLink>anonymous usage data</UsageDataLink>. " +
"This will use a cookie.",
{},
{
'UsageDataLink': (sub) => <a
className="mx_MatrixToolbar_link"
onClick={this.onUsageDataClicked}
>
{ sub }
</a>,
},
) }
</div>
<AccessibleButton element='button' className="mx_MatrixToolbar_action" onClick={this.onAccept}>
{ _t("Yes, I want to help!") }
</AccessibleButton>
<AccessibleButton className="mx_MatrixToolbar_close" onClick={this.onReject}>
<img src={require("../../../../res/img/cancel.svg")} width="18" height="18" alt={_t('Close')} />
</AccessibleButton>
</div>
);
}
}

View file

@ -1,45 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
import Notifier from '../../../Notifier';
import AccessibleButton from '../../../components/views/elements/AccessibleButton';
export default createReactClass({
displayName: 'MatrixToolbar',
hideToolbar: function() {
Notifier.setToolbarHidden(true);
},
onClick: function() {
Notifier.setEnabled(true);
},
render: function() {
return (
<div className="mx_MatrixToolbar">
<img className="mx_MatrixToolbar_warning" src={require("../../../../res/img/warning.svg")} width="24" height="23" alt="" />
<div className="mx_MatrixToolbar_content">
{ _t('You are not receiving desktop notifications') } <a className="mx_MatrixToolbar_link" onClick={ this.onClick }> { _t('Enable them now') }</a>
</div>
<AccessibleButton className="mx_MatrixToolbar_close" onClick={ this.hideToolbar } ><img src={require("../../../../res/img/cancel.svg")} width="18" height="18" alt={_t('Close')} /></AccessibleButton>
</div>
);
},
});

View file

@ -1,108 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import PlatformPeg from '../../../PlatformPeg';
import { _t } from '../../../languageHandler';
/**
* Check a version string is compatible with the Changelog
* dialog ([vectorversion]-react-[react-sdk-version]-js-[js-sdk-version])
*/
function checkVersion(ver) {
const parts = ver.split('-');
return parts.length == 5 && parts[1] == 'react' && parts[3] == 'js';
}
export default createReactClass({
propTypes: {
version: PropTypes.string.isRequired,
newVersion: PropTypes.string.isRequired,
releaseNotes: PropTypes.string,
},
displayReleaseNotes: function(releaseNotes) {
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
Modal.createTrackedDialog('Display release notes', '', QuestionDialog, {
title: _t("What's New"),
description: <div className="mx_MatrixToolbar_changelog">{releaseNotes}</div>,
button: _t("Update"),
onFinished: (update) => {
if (update && PlatformPeg.get()) {
PlatformPeg.get().installUpdate();
}
},
});
},
displayChangelog: function() {
const ChangelogDialog = sdk.getComponent('dialogs.ChangelogDialog');
Modal.createTrackedDialog('Display Changelog', '', ChangelogDialog, {
version: this.props.version,
newVersion: this.props.newVersion,
onFinished: (update) => {
if (update && PlatformPeg.get()) {
PlatformPeg.get().installUpdate();
}
},
});
},
onUpdateClicked: function() {
PlatformPeg.get().installUpdate();
},
render: function() {
let action_button;
// If we have release notes to display, we display them. Otherwise,
// we display the Changelog Dialog which takes two versions and
// automatically tells you what's changed (provided the versions
// are in the right format)
if (this.props.releaseNotes) {
action_button = (
<button className="mx_MatrixToolbar_action" onClick={this.displayReleaseNotes}>
{ _t("What's new?") }
</button>
);
} else if (checkVersion(this.props.version) && checkVersion(this.props.newVersion)) {
action_button = (
<button className="mx_MatrixToolbar_action" onClick={this.displayChangelog}>
{ _t("What's new?") }
</button>
);
} else if (PlatformPeg.get()) {
action_button = (
<button className="mx_MatrixToolbar_action" onClick={this.onUpdateClicked}>
{ _t("Update") }
</button>
);
}
return (
<div className="mx_MatrixToolbar">
<img className="mx_MatrixToolbar_warning" src={require("../../../../res/img/warning.svg")} width="24" height="23" alt="" />
<div className="mx_MatrixToolbar_content">
{_t("A new version of Riot is available.")}
</div>
{action_button}
</div>
);
},
});

View file

@ -1,53 +0,0 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
export default createReactClass({
onUpdateClicked: function() {
const SetPasswordDialog = sdk.getComponent('dialogs.SetPasswordDialog');
Modal.createTrackedDialog('Set Password Dialog', 'Password Nag Bar', SetPasswordDialog);
},
render: function() {
const toolbarClasses = "mx_MatrixToolbar mx_MatrixToolbar_clickable";
return (
<div className={toolbarClasses} onClick={this.onUpdateClicked}>
<img className="mx_MatrixToolbar_warning"
src={require("../../../../res/img/warning.svg")}
width="24"
height="23"
alt=""
/>
<div className="mx_MatrixToolbar_content">
{ _t(
"To return to your account in future you need to <u>set a password</u>",
{},
{ 'u': (sub) => <u>{ sub }</u> },
) }
</div>
<button className="mx_MatrixToolbar_action">
{ _t("Set Password") }
</button>
</div>
);
},
});

View file

@ -1,99 +0,0 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames';
import { _td } from '../../../languageHandler';
import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
export default createReactClass({
propTypes: {
// 'hard' if the logged in user has been locked out, 'soft' if they haven't
kind: PropTypes.string,
adminContact: PropTypes.string,
// The type of limit that has been hit.
limitType: PropTypes.string.isRequired,
},
getDefaultProps: function() {
return {
kind: 'hard',
};
},
render: function() {
const toolbarClasses = {
'mx_MatrixToolbar': true,
};
let adminContact;
let limitError;
if (this.props.kind === 'hard') {
toolbarClasses['mx_MatrixToolbar_error'] = true;
adminContact = messageForResourceLimitError(
this.props.limitType,
this.props.adminContact,
{
'': _td("Please <a>contact your service administrator</a> to continue using the service."),
},
);
limitError = messageForResourceLimitError(
this.props.limitType,
this.props.adminContact,
{
'monthly_active_user': _td("This homeserver has hit its Monthly Active User limit."),
'': _td("This homeserver has exceeded one of its resource limits."),
},
);
} else {
toolbarClasses['mx_MatrixToolbar_info'] = true;
adminContact = messageForResourceLimitError(
this.props.limitType,
this.props.adminContact,
{
'': _td("Please <a>contact your service administrator</a> to get this limit increased."),
},
);
limitError = messageForResourceLimitError(
this.props.limitType,
this.props.adminContact,
{
'monthly_active_user': _td(
"This homeserver has hit its Monthly Active User limit so " +
"<b>some users will not be able to log in</b>.",
),
'': _td(
"This homeserver has exceeded one of its resource limits so " +
"<b>some users will not be able to log in</b>.",
),
},
{'b': sub => <b>{sub}</b>},
);
}
return (
<div className={classNames(toolbarClasses)}>
<div className="mx_MatrixToolbar_content">
{limitError}
{' '}
{adminContact}
</div>
</div>
);
},
});

View file

@ -1,91 +0,0 @@
/*
Copyright 2017, 2019 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
import PlatformPeg from '../../../PlatformPeg';
import AccessibleButton from '../../../components/views/elements/AccessibleButton';
export default createReactClass({
propTypes: {
status: PropTypes.string.isRequired,
// Currently for error detail but will be usable for download progress
// once that is a thing that squirrel passes through electron.
detail: PropTypes.string,
},
getDefaultProps: function() {
return {
detail: '',
};
},
getStatusText: function() {
// we can't import the enum from riot-web as we don't want matrix-react-sdk
// to depend on riot-web. so we grab it as a normal object via API instead.
const updateCheckStatusEnum = PlatformPeg.get().getUpdateCheckStatusEnum();
switch (this.props.status) {
case updateCheckStatusEnum.ERROR:
return _t('Error encountered (%(errorDetail)s).', { errorDetail: this.props.detail });
case updateCheckStatusEnum.CHECKING:
return _t('Checking for an update...');
case updateCheckStatusEnum.NOTAVAILABLE:
return _t('No update available.');
case updateCheckStatusEnum.DOWNLOADING:
return _t('Downloading update...');
}
},
hideToolbar: function() {
PlatformPeg.get().stopUpdateCheck();
},
render: function() {
const message = this.getStatusText();
const warning = _t('Warning');
if (!('getUpdateCheckStatusEnum' in PlatformPeg.get())) {
return <div></div>;
}
const updateCheckStatusEnum = PlatformPeg.get().getUpdateCheckStatusEnum();
const doneStatuses = [
updateCheckStatusEnum.ERROR,
updateCheckStatusEnum.NOTAVAILABLE,
];
let image;
if (doneStatuses.includes(this.props.status)) {
image = <img className="mx_MatrixToolbar_warning" src={require("../../../../res/img/warning.svg")} width="24" height="23" alt="" />;
} else {
image = <img className="mx_MatrixToolbar_warning" src={require("../../../../res/img/spinner.gif")} width="24" height="23" alt="" />;
}
return (
<div className="mx_MatrixToolbar">
{image}
<div className="mx_MatrixToolbar_content">
{message}
</div>
<AccessibleButton className="mx_MatrixToolbar_close" onClick={this.hideToolbar}>
<img src={require("../../../../res/img/cancel.svg")} width="18" height="18" alt={_t('Close')} />
</AccessibleButton>
</div>
);
},
});

View file

@ -141,6 +141,12 @@ export default createReactClass({
_changePassword: function(cli, oldPassword, newPassword) {
const authDict = {
type: 'm.login.password',
identifier: {
type: 'm.id.user',
user: cli.credentials.userId,
},
// TODO: Remove `user` once servers support proper UIA
// See https://github.com/matrix-org/synapse/issues/5665
user: cli.credentials.userId,
password: oldPassword,
};

View file

@ -1,75 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import createReactClass from 'create-react-class';
import Notifier from "../../../Notifier";
import dis from "../../../dispatcher/dispatcher";
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'EnableNotificationsButton',
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
},
onAction: function(payload) {
if (payload.action !== "notifier_enabled") {
return;
}
this.forceUpdate();
},
enabled: function() {
return Notifier.isEnabled();
},
onClick: function() {
const self = this;
if (!Notifier.supportsDesktopNotifications()) {
return;
}
if (!Notifier.isEnabled()) {
Notifier.setEnabled(true, function() {
self.forceUpdate();
});
} else {
Notifier.setEnabled(false);
}
this.forceUpdate();
},
render: function() {
if (this.enabled()) {
return (
<button className="mx_EnableNotificationsButton" onClick={this.onClick}>
{ _t("Disable Notifications") }
</button>
);
} else {
return (
<button className="mx_EnableNotificationsButton" onClick={this.onClick}>
{ _t("Enable Notifications") }
</button>
);
}
},
});

View file

@ -0,0 +1,88 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {useState} from "react";
import {UpdateCheckStatus} from "../../../BasePlatform";
import PlatformPeg from "../../../PlatformPeg";
import {useDispatcher} from "../../../hooks/useDispatcher";
import dis from "../../../dispatcher/dispatcher";
import {Action} from "../../../dispatcher/actions";
import {_t} from "../../../languageHandler";
import InlineSpinner from "../../../components/views/elements/InlineSpinner";
import AccessibleButton from "../../../components/views/elements/AccessibleButton";
import {CheckUpdatesPayload} from "../../../dispatcher/payloads/CheckUpdatesPayload";
function installUpdate() {
PlatformPeg.get().installUpdate();
}
function getStatusText(status: UpdateCheckStatus, errorDetail?: string) {
switch (status) {
case UpdateCheckStatus.Error:
return _t('Error encountered (%(errorDetail)s).', { errorDetail });
case UpdateCheckStatus.Checking:
return _t('Checking for an update...');
case UpdateCheckStatus.NotAvailable:
return _t('No update available.');
case UpdateCheckStatus.Downloading:
return _t('Downloading update...');
case UpdateCheckStatus.Ready:
return _t("New version available. <a>Update now.</a>", {}, {
a: sub => <AccessibleButton kind="link" onClick={installUpdate}>{sub}</AccessibleButton>
});
}
}
const doneStatuses = [
UpdateCheckStatus.Ready,
UpdateCheckStatus.Error,
UpdateCheckStatus.NotAvailable,
];
const UpdateCheckButton = () => {
const [state, setState] = useState<CheckUpdatesPayload>(null);
const onCheckForUpdateClick = () => {
setState(null);
PlatformPeg.get().startUpdateCheck();
};
useDispatcher(dis, ({action, ...params}) => {
if (action === Action.CheckUpdates) {
setState(params as CheckUpdatesPayload);
}
});
const busy = state && !doneStatuses.includes(state.status);
let suffix;
if (state) {
suffix = <span className="mx_UpdateCheckButton_summary">
{getStatusText(state.status, state.detail)}
{busy && <InlineSpinner />}
</span>;
}
return <React.Fragment>
<AccessibleButton onClick={onCheckForUpdateClick} kind="primary" disabled={busy}>
{_t("Check for update")}
</AccessibleButton>
{ suffix }
</React.Fragment>;
};
export default UpdateCheckButton;

View file

@ -25,6 +25,7 @@ import Modal from "../../../../../Modal";
import * as sdk from "../../../../../";
import PlatformPeg from "../../../../../PlatformPeg";
import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts";
import UpdateCheckButton from "../../UpdateCheckButton";
export default class HelpUserSettingsTab extends React.Component {
static propTypes = {
@ -177,12 +178,7 @@ export default class HelpUserSettingsTab extends React.Component {
let updateButton = null;
if (this.state.canUpdate) {
const platform = PlatformPeg.get();
updateButton = (
<AccessibleButton onClick={platform.startUpdateCheck} kind='primary'>
{_t('Check for update')}
</AccessibleButton>
);
updateButton = <UpdateCheckButton />;
}
return (

View file

@ -17,17 +17,21 @@ limitations under the License.
import React, {ReactChild} from "react";
import FormButton from "../elements/FormButton";
import {XOR} from "../../../@types/common";
interface IProps {
description: ReactChild;
acceptLabel: string;
rejectLabel?: string;
onAccept();
onReject?();
}
const GenericToast: React.FC<IProps> = ({description, acceptLabel, rejectLabel, onAccept, onReject}) => {
interface IPropsExtended extends IProps {
rejectLabel: string;
onReject();
}
const GenericToast: React.FC<XOR<IPropsExtended, IProps>> = ({description, acceptLabel, rejectLabel, onAccept, onReject}) => {
return <div>
<div className="mx_Toast_description">
{ description }