From efd0da44a1b1fb8f58e8bdac4ea66fa77af2cc32 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 29 May 2020 18:24:45 +0100 Subject: [PATCH] Give contextual feedback for manual update check instead of banner Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .eslintignore.errorfiles | 1 - res/css/_components.scss | 2 +- res/css/structures/_MatrixChat.scss | 4 - res/css/views/globals/_MatrixToolbar.scss | 69 -------------- .../views/settings/_UpdateCheckButton.scss | 23 +++++ src/BasePlatform.ts | 24 +++++ src/components/structures/LoggedInView.tsx | 20 ---- src/components/structures/MatrixChat.tsx | 6 -- .../views/globals/UpdateCheckBar.js | 91 ------------------- .../views/settings/UpdateCheckButton.tsx | 90 ++++++++++++++++++ .../settings/tabs/user/HelpUserSettingsTab.js | 8 +- src/dispatcher/actions.ts | 7 +- .../payloads/CheckUpdatesPayload.ts | 33 +++++++ src/hooks/useDispatcher.ts | 40 ++++++++ src/i18n/strings/en_EN.json | 11 ++- src/utils/ResizeNotifier.js | 5 - 16 files changed, 225 insertions(+), 209 deletions(-) delete mode 100644 res/css/views/globals/_MatrixToolbar.scss create mode 100644 res/css/views/settings/_UpdateCheckButton.scss delete mode 100644 src/components/views/globals/UpdateCheckBar.js create mode 100644 src/components/views/settings/UpdateCheckButton.tsx create mode 100644 src/dispatcher/payloads/CheckUpdatesPayload.ts create mode 100644 src/hooks/useDispatcher.ts diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index ffc3b21181..7e88ebff7f 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -16,7 +16,6 @@ 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 diff --git a/res/css/_components.scss b/res/css/_components.scss index 44c63b9df7..bcf7b97683 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -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"; diff --git a/res/css/structures/_MatrixChat.scss b/res/css/structures/_MatrixChat.scss index c5a5d50068..05c703ab6d 100644 --- a/res/css/structures/_MatrixChat.scss +++ b/res/css/structures/_MatrixChat.scss @@ -41,10 +41,6 @@ limitations under the License. height: 40px; } -.mx_MatrixChat_toolbarShowing { - height: auto; -} - .mx_MatrixChat { width: 100%; height: 100%; diff --git a/res/css/views/globals/_MatrixToolbar.scss b/res/css/views/globals/_MatrixToolbar.scss deleted file mode 100644 index 07b92a7235..0000000000 --- a/res/css/views/globals/_MatrixToolbar.scss +++ /dev/null @@ -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; -} diff --git a/res/css/views/settings/_UpdateCheckButton.scss b/res/css/views/settings/_UpdateCheckButton.scss new file mode 100644 index 0000000000..f35a023ac1 --- /dev/null +++ b/res/css/views/settings/_UpdateCheckButton.scss @@ -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; + } +} diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index ed04c67803..0465b434d0 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -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 { + return false; + } + + startUpdateCheck() { + dis.dispatch({ + action: Action.CheckUpdates, + status: UpdateCheckStatus.Checking, + }); + } + /** * Returns true if the platform supports displaying * notifications, otherwise false. diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 06ba3e49c9..1ad38c6f04 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -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 { 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 { 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 { break; } - let topBar; - if (this.props.checkingForUpdate) { - topBar = ; - } - 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 { onMouseDown={this._onMouseDown} onMouseUp={this._onMouseUp} > - { topBar }
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index b70d6ed3eb..058a7ba50b 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -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 { 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 { 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; diff --git a/src/components/views/globals/UpdateCheckBar.js b/src/components/views/globals/UpdateCheckBar.js deleted file mode 100644 index 32b38ff5b0..0000000000 --- a/src/components/views/globals/UpdateCheckBar.js +++ /dev/null @@ -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
; - } - - const updateCheckStatusEnum = PlatformPeg.get().getUpdateCheckStatusEnum(); - const doneStatuses = [ - updateCheckStatusEnum.ERROR, - updateCheckStatusEnum.NOTAVAILABLE, - ]; - - let image; - if (doneStatuses.includes(this.props.status)) { - image = ; - } else { - image = ; - } - - return ( -
- {image} -
- {message} -
- - {_t('Close')} - -
- ); - }, -}); diff --git a/src/components/views/settings/UpdateCheckButton.tsx b/src/components/views/settings/UpdateCheckButton.tsx new file mode 100644 index 0000000000..3f7c11dfc8 --- /dev/null +++ b/src/components/views/settings/UpdateCheckButton.tsx @@ -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. Update now.", {}, { + a: sub => {sub} + }); + } +} + +const doneStatuses = [ + UpdateCheckStatus.Ready, + UpdateCheckStatus.Error, + UpdateCheckStatus.NotAvailable, +]; + +const UpdateCheckButton = () => { + const [state, setState] = useState(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 = + {getStatusText(state.status, state.detail)} + {busy && } + ; + } + + return + + {_t("Check for update")} + + { suffix } + ; +}; + +export default UpdateCheckButton; diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js index 146d841d58..bec79b97c4 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js @@ -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 = ( - - {_t('Check for update')} - - ); + updateButton = ; } return ( diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index ba72daa00d..c1d6fd5c54 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -45,8 +45,13 @@ 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", + + /** + * Response to manual update check, will fire multiple times during the update check. + */ + CheckUpdates = "check_updates", } diff --git a/src/dispatcher/payloads/CheckUpdatesPayload.ts b/src/dispatcher/payloads/CheckUpdatesPayload.ts new file mode 100644 index 0000000000..0f0f9a01e5 --- /dev/null +++ b/src/dispatcher/payloads/CheckUpdatesPayload.ts @@ -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; +} diff --git a/src/hooks/useDispatcher.ts b/src/hooks/useDispatcher.ts new file mode 100644 index 0000000000..004b15fcef --- /dev/null +++ b/src/hooks/useDispatcher.ts @@ -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, 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]); +}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ba55ee9d64..8f7e8ea6b6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -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. Update now.": "New version available. Update now.", + "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 here.": "For help with using Riot, click here.", "For help with using Riot, click here or start a chat with our bot using the button below.": "For help with using Riot, click here 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", diff --git a/src/utils/ResizeNotifier.js b/src/utils/ResizeNotifier.js index 35ec1a0269..d65bc4bd07 100644 --- a/src/utils/ResizeNotifier.js +++ b/src/utils/ResizeNotifier.js @@ -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