Merge pull request #4668 from matrix-org/t3chguy/toasts6

Give contextual feedback for manual update check instead of banner
This commit is contained in:
Michael Telatynski 2020-05-30 12:46:53 +01:00 committed by GitHub
commit 097f27c822
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 225 additions and 212 deletions

View file

@ -14,9 +14,7 @@ src/components/views/elements/AddressSelector.js
src/components/views/elements/DirectorySearchBox.js
src/components/views/elements/MemberEventListSummary.js
src/components/views/elements/UserSelector.js
src/components/views/globals/MatrixToolbar.js
src/components/views/globals/NewVersionBar.js
src/components/views/globals/UpdateCheckBar.js
src/components/views/messages/MFileBody.js
src/components/views/messages/TextualBody.js
src/components/views/room_settings/ColorSettings.js

View file

@ -123,7 +123,6 @@
@import "./views/elements/_TooltipButton.scss";
@import "./views/elements/_Validation.scss";
@import "./views/emojipicker/_EmojiPicker.scss";
@import "./views/globals/_MatrixToolbar.scss";
@import "./views/groups/_GroupPublicityToggle.scss";
@import "./views/groups/_GroupRoomList.scss";
@import "./views/groups/_GroupUserSettings.scss";
@ -202,6 +201,7 @@
@import "./views/settings/_ProfileSettings.scss";
@import "./views/settings/_SetIdServer.scss";
@import "./views/settings/_SetIntegrationManager.scss";
@import "./views/settings/_UpdateCheckButton.scss";
@import "./views/settings/tabs/_SettingsTab.scss";
@import "./views/settings/tabs/room/_GeneralRoomSettingsTab.scss";
@import "./views/settings/tabs/room/_RolesRoomSettingsTab.scss";

View file

@ -41,10 +41,6 @@ limitations under the License.
height: 40px;
}
.mx_MatrixChat_toolbarShowing {
height: auto;
}
.mx_MatrixChat {
width: 100%;
height: 100%;

View file

@ -1,69 +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.
*/
.mx_MatrixToolbar {
background-color: $accent-color;
color: $accent-fg-color;
display: flex;
align-items: center;
}
.mx_MatrixToolbar_warning {
margin-left: 16px;
margin-right: 8px;
margin-top: -2px;
}
.mx_MatrixToolbar_info {
padding-left: 16px;
padding-right: 8px;
background-color: $info-bg-color;
}
.mx_MatrixToolbar_error {
padding-left: 16px;
padding-right: 8px;
background-color: $warning-bg-color;
}
.mx_MatrixToolbar_content {
flex: 1;
}
.mx_MatrixToolbar_link {
color: $accent-fg-color !important;
text-decoration: underline !important;
cursor: pointer;
}
.mx_MatrixToolbar_clickable {
cursor: pointer;
}
.mx_MatrixToolbar_close {
cursor: pointer;
}
.mx_MatrixToolbar_close img {
display: block;
float: right;
margin-right: 10px;
}
.mx_MatrixToolbar_action {
margin-right: 16px;
}

View file

@ -0,0 +1,23 @@
/*
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.
*/
.mx_UpdateCheckButton_summary {
margin-left: 16px;
.mx_AccessibleButton_kind_link {
padding: 0;
}
}

View file

@ -21,6 +21,16 @@ import {MatrixClient} from "matrix-js-sdk/src/client";
import dis from './dispatcher/dispatcher';
import BaseEventIndexManager from './indexing/BaseEventIndexManager';
import {ActionPayload} from "./dispatcher/payloads";
import {CheckUpdatesPayload} from "./dispatcher/payloads/CheckUpdatesPayload";
import {Action} from "./dispatcher/actions";
export enum UpdateCheckStatus {
Checking = "CHECKING",
Error = "ERROR",
NotAvailable = "NOTAVAILABLE",
Downloading = "DOWNLOADING",
Ready = "READY",
}
/**
* Base class for classes that provide platform-specific functionality
@ -56,6 +66,20 @@ export default abstract class BasePlatform {
this.errorDidOccur = errorDidOccur;
}
/**
* Whether we can call checkForUpdate on this platform build
*/
async canSelfUpdate(): Promise<boolean> {
return false;
}
startUpdateCheck() {
dis.dispatch<CheckUpdatesPayload>({
action: Action.CheckUpdates,
status: UpdateCheckStatus.Checking,
});
}
/**
* Returns true if the platform supports displaying
* notifications, otherwise false.

View file

@ -81,7 +81,6 @@ interface IProps {
currentRoomId: string;
ConferenceHandler?: object;
collapseLhs: boolean;
checkingForUpdate: boolean;
config: {
piwik: {
policyUrl: string;
@ -177,15 +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.checkingForUpdate !== this.props.checkingForUpdate)
) {
this.props.resizeNotifier.notifyBannersChanged();
}
}
componentWillUnmount() {
document.removeEventListener('keydown', this._onNativeKeyDown, false);
this._matrixClient.removeListener("accountData", this.onAccountData);
@ -617,7 +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 UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar');
let pageElement;
@ -661,15 +650,7 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
break;
}
let topBar;
if (this.props.checkingForUpdate) {
topBar = <UpdateCheckBar {...this.props.checkingForUpdate} />;
}
let bodyClasses = 'mx_MatrixChat';
if (topBar) {
bodyClasses += ' mx_MatrixChat_toolbarShowing';
}
if (this.state.useCompactLayout) {
bodyClasses += ' mx_MatrixChat_useCompactLayout';
}
@ -684,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

@ -173,7 +173,6 @@ interface IState {
leftDisabled: boolean;
middleDisabled: boolean;
// the right panel's disabled state is tracked in its store.
checkingForUpdate?: string; // updateCheckStatusEnum
// Parameters used in the registration dance with the IS
register_client_secret?: string;
register_session_id?: string;
@ -226,8 +225,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
leftDisabled: false,
middleDisabled: false,
checkingForUpdate: null,
hideToSRUsers: false,
syncError: null, // If the current syncing status is ERROR, the error object, otherwise null.
@ -720,9 +717,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
case 'client_started':
this.onClientStarted();
break;
case 'check_updates':
this.setState({ checkingForUpdate: payload.value });
break;
case 'send_event':
this.onSendEvent(payload.room_id, payload.event);
break;

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

@ -0,0 +1,90 @@
/*
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 {hideToast as hideUpdateToast} from "../../../toasts/UpdateToast";
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();
hideUpdateToast();
};
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

@ -45,8 +45,12 @@ export enum Action {
ViewTooltip = "view_tooltip",
/**
* Forces the theme to reload. No additional payload information required.
* Forces the theme to reload. No additional payload information required.
*/
RecheckTheme = "recheck_theme",
}
/**
* Provide status information for an ongoing update check. Should be used with a CheckUpdatesPayload.
*/
CheckUpdates = "check_updates",
}

View file

@ -0,0 +1,33 @@
/*
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 { ActionPayload } from "../payloads";
import { Action } from "../actions";
import {UpdateCheckStatus} from "../../BasePlatform";
export interface CheckUpdatesPayload extends ActionPayload {
action: Action.CheckUpdates,
/**
* The current phase of the manual update check.
*/
status: UpdateCheckStatus;
/**
* Detail string relating to the current status, typically for error details.
*/
detail?: string;
}

View file

@ -0,0 +1,40 @@
/*
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 {useEffect, useRef} from "react";
import {ActionPayload} from "../dispatcher/payloads";
import {Dispatcher} from "flux";
// Hook to simplify listening to flux dispatches
export const useDispatcher = (dispatcher: Dispatcher<ActionPayload>, handler: (payload: ActionPayload) => void) => {
// Create a ref that stores handler
const savedHandler = useRef((payload: ActionPayload) => {});
// Update ref.current value if handler changes.
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(() => {
// Create event listener that calls handler function stored in ref
const ref = dispatcher.register((payload) => savedHandler.current(payload));
// Remove event listener on cleanup
return () => {
dispatcher.unregister(ref);
};
}, [dispatcher]);
};

View file

@ -772,6 +772,12 @@
"Use an Integration Manager to manage bots, widgets, and sticker packs.": "Use an Integration Manager to manage bots, widgets, and sticker packs.",
"Manage integrations": "Manage integrations",
"Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.",
"Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).",
"Checking for an update...": "Checking for an update...",
"No update available.": "No update available.",
"Downloading update...": "Downloading update...",
"New version available. <a>Update now.</a>": "New version available. <a>Update now.</a>",
"Check for update": "Check for update",
"Size must be a number": "Size must be a number",
"Custom font size can only be between %(min)s pt and %(max)s pt": "Custom font size can only be between %(min)s pt and %(max)s pt",
"Use between %(min)s pt and %(max)s pt": "Use between %(min)s pt and %(max)s pt",
@ -804,7 +810,6 @@
"For help with using Riot, click <a>here</a>.": "For help with using Riot, click <a>here</a>.",
"For help with using Riot, click <a>here</a> or start a chat with our bot using the button below.": "For help with using Riot, click <a>here</a> or start a chat with our bot using the button below.",
"Chat with Riot Bot": "Chat with Riot Bot",
"Check for update": "Check for update",
"Help & About": "Help & About",
"Bug reporting": "Bug reporting",
"If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.",
@ -1397,10 +1402,6 @@
"Something went wrong when trying to get your communities.": "Something went wrong when trying to get your communities.",
"Display your community flair in rooms configured to show it.": "Display your community flair in rooms configured to show it.",
"You're not currently a member of any communities.": "You're not currently a member of any communities.",
"Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).",
"Checking for an update...": "Checking for an update...",
"No update available.": "No update available.",
"Downloading update...": "Downloading update...",
"Frequently Used": "Frequently Used",
"Smileys & People": "Smileys & People",
"Animals & Nature": "Animals & Nature",

View file

@ -29,11 +29,6 @@ export default class ResizeNotifier extends EventEmitter {
this._throttledMiddlePanel = throttle(() => this.emit("middlePanelResized"), 200);
}
notifyBannersChanged() {
this.emit("leftPanelResized");
this.emit("middlePanelResized");
}
// can be called in quick succession
notifyLeftHandleResized() {
// don't emit event for own region

View file

@ -16,7 +16,6 @@ const components = {};
components['structures.LeftPanel'] = stubComponent();
components['structures.RightPanel'] = stubComponent();
components['structures.RoomDirectory'] = stubComponent();
components['views.globals.MatrixToolbar'] = stubComponent();
components['views.globals.GuestWarningBar'] = stubComponent();
components['views.globals.NewVersionBar'] = stubComponent();
components['views.elements.Spinner'] = stubComponent({displayName: 'Spinner'});