From bc953c236889d8709082005715cf1718b3bb0834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Thu, 2 Apr 2020 13:30:41 +0200 Subject: [PATCH 1/6] Move useCompactLayout flag to Preferences tab --- .../views/settings/tabs/user/GeneralUserSettingsTab.js | 1 - .../views/settings/tabs/user/PreferencesUserSettingsTab.js | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 801062bff49..771dc2c9544 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -457,7 +457,6 @@ export default class GeneralUserSettingsTab extends React.Component { })} {customThemeForm} - ); } diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index bdb2a9ffc4a..9789ead14ab 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -51,6 +51,7 @@ export default class PreferencesUserSettingsTab extends React.Component { 'showAvatarChanges', 'showDisplaynameChanges', 'showImages', + 'useCompactLayout', ]; static ADVANCED_SETTINGS = [ From 43efbc64ebfc4b069295e722e6abb94c14ce667c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Thu, 2 Apr 2020 14:16:35 +0200 Subject: [PATCH 2/6] Remove avatar from compact timeline layout --- res/css/views/rooms/_EventTile.scss | 9 +++++++++ src/components/views/rooms/EventTile.js | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index b26312cebf0..e3fcd11d3cf 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -602,6 +602,7 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { .mx_EventTile .mx_SenderProfile { font-size: $font-13px; + padding-left: 0; } .mx_EventTile.mx_EventTile_emote { @@ -654,6 +655,14 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { margin-top: 6px; } + .mx_EventTile_content, .mx_ReplyThread, .mx_TextualEvent { + margin-left: -20px; + } + + .mx_ReplyThread { + margin-bottom: 5px; + } + .mx_EventTile_content .markdown-body { p, ul, ol, dl, blockquote, pre, table { margin-bottom: 4px; // 1/4 of the non-compact margin-bottom diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index aed2d4b25ba..48e59986357 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -703,6 +703,14 @@ export default createReactClass({ needsSenderProfile = true; } + const useCompactLayout = SettingsStore.getValue("useCompactLayout"); + + // Do not display avatar in compact layout. We do not touch to the + // SenderProfile since it depends on message groups. + if (useCompactLayout) { + avatarSize = 0; + } + if (this.props.mxEvent.sender && avatarSize) { avatar = (
From ef901d98ea989a58b38cc1ff922f8fd032e7cc82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Fri, 3 Apr 2020 09:37:27 +0200 Subject: [PATCH 3/6] Compact timeline: align SenderProfile with message TimeeStamp --- res/css/views/rooms/_EventTile.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index e3fcd11d3cf..8ea4eefd9ef 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -602,7 +602,8 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { .mx_EventTile .mx_SenderProfile { font-size: $font-13px; - padding-left: 0; + font-size: 13px; + padding-left: 10px; } .mx_EventTile.mx_EventTile_emote { From d7b2ef4065db521072d0eb8c1a8521f520810bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Fri, 3 Apr 2020 09:51:38 +0200 Subject: [PATCH 4/6] Move Theme selector from General to Preferences settings tab --- .../tabs/user/GeneralUserSettingsTab.js | 169 ----------------- .../tabs/user/PreferencesUserSettingsTab.js | 170 ++++++++++++++++++ 2 files changed, 170 insertions(+), 169 deletions(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 771dc2c9544..c7c9143efcd 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -19,7 +19,6 @@ limitations under the License. import React from 'react'; import {_t} from "../../../../../languageHandler"; import ProfileSettings from "../../ProfileSettings"; -import Field from "../../../elements/Field"; import * as languageHandler from "../../../../../languageHandler"; import {SettingLevel} from "../../../../../settings/SettingsStore"; import SettingsStore from "../../../../../settings/SettingsStore"; @@ -27,7 +26,6 @@ import LanguageDropdown from "../../../elements/LanguageDropdown"; import AccessibleButton from "../../../elements/AccessibleButton"; import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog"; import PropTypes from "prop-types"; -import {enumerateThemes, ThemeWatcher} from "../../../../../theme"; import PlatformPeg from "../../../../../PlatformPeg"; import {MatrixClientPeg} from "../../../../../MatrixClientPeg"; import * as sdk from "../../../../.."; @@ -60,7 +58,6 @@ export default class GeneralUserSettingsTab extends React.Component { }, emails: [], msisdns: [], - ...this._calculateThemeState(), customThemeUrl: "", customThemeMessage: {isError: false, text: ""}, }; @@ -91,39 +88,6 @@ export default class GeneralUserSettingsTab extends React.Component { dis.unregister(this.dispatcherRef); } - _calculateThemeState() { - // We have to mirror the logic from ThemeWatcher.getEffectiveTheme so we - // show the right values for things. - - const themeChoice = SettingsStore.getValueAt(SettingLevel.ACCOUNT, "theme"); - const systemThemeExplicit = SettingsStore.getValueAt( - SettingLevel.DEVICE, "use_system_theme", null, false, true); - const themeExplicit = SettingsStore.getValueAt( - SettingLevel.DEVICE, "theme", null, false, true); - - // If the user has enabled system theme matching, use that. - if (systemThemeExplicit) { - return { - theme: themeChoice, - useSystemTheme: true, - }; - } - - // If the user has set a theme explicitly, use that (no system theme matching) - if (themeExplicit) { - return { - theme: themeChoice, - useSystemTheme: false, - }; - } - - // Otherwise assume the defaults for the settings - return { - theme: themeChoice, - useSystemTheme: SettingsStore.getValueAt(SettingLevel.DEVICE, "use_system_theme"), - }; - } - _onAction = (payload) => { if (payload.action === 'id_server_changed') { this.setState({haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl())}); @@ -214,33 +178,6 @@ export default class GeneralUserSettingsTab extends React.Component { PlatformPeg.get().reload(); }; - _onThemeChange = (e) => { - const newTheme = e.target.value; - if (this.state.theme === newTheme) return; - - // doing getValue in the .catch will still return the value we failed to set, - // so remember what the value was before we tried to set it so we can revert - const oldTheme = SettingsStore.getValue('theme'); - SettingsStore.setValue("theme", null, SettingLevel.ACCOUNT, newTheme).catch(() => { - dis.dispatch({action: 'recheck_theme'}); - this.setState({theme: oldTheme}); - }); - this.setState({theme: newTheme}); - // The settings watcher doesn't fire until the echo comes back from the - // server, so to make the theme change immediately we need to manually - // do the dispatch now - // XXX: The local echoed value appears to be unreliable, in particular - // when settings custom themes(!) so adding forceTheme to override - // the value from settings. - dis.dispatch({action: 'recheck_theme', forceTheme: newTheme}); - }; - - _onUseSystemThemeChanged = (checked) => { - this.setState({useSystemTheme: checked}); - SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, checked); - dis.dispatch({action: 'recheck_theme'}); - }; - _onPasswordChangeError = (err) => { // TODO: Figure out a design that doesn't involve replacing the current dialog let errMsg = err.error || ""; @@ -277,41 +214,6 @@ export default class GeneralUserSettingsTab extends React.Component { }); }; - _onAddCustomTheme = async () => { - let currentThemes = SettingsStore.getValue("custom_themes"); - if (!currentThemes) currentThemes = []; - currentThemes = currentThemes.map(c => c); // cheap clone - - if (this._themeTimer) { - clearTimeout(this._themeTimer); - } - - try { - const r = await fetch(this.state.customThemeUrl); - const themeInfo = await r.json(); - if (!themeInfo || typeof(themeInfo['name']) !== 'string' || typeof(themeInfo['colors']) !== 'object') { - this.setState({customThemeMessage: {text: _t("Invalid theme schema."), isError: true}}); - return; - } - currentThemes.push(themeInfo); - } catch (e) { - console.error(e); - this.setState({customThemeMessage: {text: _t("Error downloading theme information."), isError: true}}); - return; // Don't continue on error - } - - await SettingsStore.setValue("custom_themes", null, SettingLevel.ACCOUNT, currentThemes); - this.setState({customThemeUrl: "", customThemeMessage: {text: _t("Theme added!"), isError: false}}); - - this._themeTimer = setTimeout(() => { - this.setState({customThemeMessage: {text: "", isError: false}}); - }, 3000); - }; - - _onCustomThemeChange = (e) => { - this.setState({customThemeUrl: e.target.value}); - }; - _renderProfileSection() { return (
@@ -391,76 +293,6 @@ export default class GeneralUserSettingsTab extends React.Component { ); } - _renderThemeSection() { - const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag"); - const LabelledToggleSwitch = sdk.getComponent("views.elements.LabelledToggleSwitch"); - - const themeWatcher = new ThemeWatcher(); - let systemThemeSection; - if (themeWatcher.isSystemThemeSupported()) { - systemThemeSection =
- -
; - } - - let customThemeForm; - if (SettingsStore.isFeatureEnabled("feature_custom_themes")) { - let messageElement = null; - if (this.state.customThemeMessage.text) { - if (this.state.customThemeMessage.isError) { - messageElement =
{this.state.customThemeMessage.text}
; - } else { - messageElement =
{this.state.customThemeMessage.text}
; - } - } - customThemeForm = ( -
-
- - {_t("Add theme")} - {messageElement} - -
- ); - } - - const themes = Object.entries(enumerateThemes()) - .map(p => ({id: p[0], name: p[1]})); // convert pairs to objects for code readability - const builtInThemes = themes.filter(p => !p.id.startsWith("custom-")); - const customThemes = themes.filter(p => !builtInThemes.includes(p)) - .sort((a, b) => a.name.localeCompare(b.name)); - const orderedThemes = [...builtInThemes, ...customThemes]; - return ( -
- {_t("Theme")} - {systemThemeSection} - - {orderedThemes.map(theme => { - return ; - })} - - {customThemeForm} -
- ); - } - _renderDiscoverySection() { const SetIdServer = sdk.getComponent("views.settings.SetIdServer"); @@ -546,7 +378,6 @@ export default class GeneralUserSettingsTab extends React.Component { {this._renderProfileSection()} {this._renderAccountSection()} {this._renderLanguageSection()} - {this._renderThemeSection()}
{discoWarning} {_t("Discovery")}
{this._renderDiscoverySection()} {this._renderIntegrationManagerSection() /* Has its own title */} diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 9789ead14ab..0cba3bb55bc 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -21,8 +21,11 @@ import {SettingLevel} from "../../../../../settings/SettingsStore"; import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; import SettingsStore from "../../../../../settings/SettingsStore"; import Field from "../../../elements/Field"; +import AccessibleButton from "../../../elements/AccessibleButton"; import * as sdk from "../../../../.."; import PlatformPeg from "../../../../../PlatformPeg"; +import {enumerateThemes, ThemeWatcher} from "../../../../../theme"; +import dis from "../../../../../dispatcher"; export default class PreferencesUserSettingsTab extends React.Component { static ROOM_LIST_SETTINGS = [ @@ -79,6 +82,7 @@ export default class PreferencesUserSettingsTab extends React.Component { SettingsStore.getValueAt(SettingLevel.DEVICE, 'readMarkerInViewThresholdMs').toString(10), readMarkerOutOfViewThresholdMs: SettingsStore.getValueAt(SettingLevel.DEVICE, 'readMarkerOutOfViewThresholdMs').toString(10), + ...this._calculateThemeState(), }; } @@ -113,6 +117,39 @@ export default class PreferencesUserSettingsTab extends React.Component { }); } + _calculateThemeState() { + // We have to mirror the logic from ThemeWatcher.getEffectiveTheme so we + // show the right values for things. + + const themeChoice = SettingsStore.getValueAt(SettingLevel.ACCOUNT, "theme"); + const systemThemeExplicit = SettingsStore.getValueAt( + SettingLevel.DEVICE, "use_system_theme", null, false, true); + const themeExplicit = SettingsStore.getValueAt( + SettingLevel.DEVICE, "theme", null, false, true); + + // If the user has enabled system theme matching, use that. + if (systemThemeExplicit) { + return { + theme: themeChoice, + useSystemTheme: true, + }; + } + + // If the user has set a theme explicitly, use that (no system theme matching) + if (themeExplicit) { + return { + theme: themeChoice, + useSystemTheme: false, + }; + } + + // Otherwise assume the defaults for the settings + return { + theme: themeChoice, + useSystemTheme: SettingsStore.getValueAt(SettingLevel.DEVICE, "use_system_theme"), + }; + } + _onAutoLaunchChange = (checked) => { PlatformPeg.get().setAutoLaunchEnabled(checked).then(() => this.setState({autoLaunch: checked})); }; @@ -140,11 +177,142 @@ export default class PreferencesUserSettingsTab extends React.Component { SettingsStore.setValue("readMarkerOutOfViewThresholdMs", null, SettingLevel.DEVICE, e.target.value); }; + _onThemeChange = (e) => { + const newTheme = e.target.value; + if (this.state.theme === newTheme) return; + + // doing getValue in the .catch will still return the value we failed to set, + // so remember what the value was before we tried to set it so we can revert + const oldTheme = SettingsStore.getValue('theme'); + SettingsStore.setValue("theme", null, SettingLevel.ACCOUNT, newTheme).catch(() => { + dis.dispatch({action: 'recheck_theme'}); + this.setState({theme: oldTheme}); + }); + this.setState({theme: newTheme}); + // The settings watcher doesn't fire until the echo comes back from the + // server, so to make the theme change immediately we need to manually + // do the dispatch now + // XXX: The local echoed value appears to be unreliable, in particular + // when settings custom themes(!) so adding forceTheme to override + // the value from settings. + dis.dispatch({action: 'recheck_theme', forceTheme: newTheme}); + }; + + _onAddCustomTheme = async () => { + let currentThemes = SettingsStore.getValue("custom_themes"); + if (!currentThemes) currentThemes = []; + currentThemes = currentThemes.map(c => c); // cheap clone + + if (this._themeTimer) { + clearTimeout(this._themeTimer); + } + + try { + const r = await fetch(this.state.customThemeUrl); + const themeInfo = await r.json(); + if (!themeInfo || typeof(themeInfo['name']) !== 'string' || typeof(themeInfo['colors']) !== 'object') { + this.setState({customThemeMessage: {text: _t("Invalid theme schema."), isError: true}}); + return; + } + currentThemes.push(themeInfo); + } catch (e) { + console.error(e); + this.setState({customThemeMessage: {text: _t("Error downloading theme information."), isError: true}}); + return; // Don't continue on error + } + + await SettingsStore.setValue("custom_themes", null, SettingLevel.ACCOUNT, currentThemes); + this.setState({customThemeUrl: "", customThemeMessage: {text: _t("Theme added!"), isError: false}}); + + this._themeTimer = setTimeout(() => { + this.setState({customThemeMessage: {text: "", isError: false}}); + }, 3000); + }; + + _onCustomThemeChange = (e) => { + this.setState({customThemeUrl: e.target.value}); + }; + + _onUseSystemThemeChanged = (checked) => { + this.setState({useSystemTheme: checked}); + SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, checked); + dis.dispatch({action: 'recheck_theme'}); + }; + _renderGroup(settingIds) { const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag"); return settingIds.map(i => ); } + _renderThemeSection() { + const LabelledToggleSwitch = sdk.getComponent("views.elements.LabelledToggleSwitch"); + + const themeWatcher = new ThemeWatcher(); + let systemThemeSection; + if (themeWatcher.isSystemThemeSupported()) { + systemThemeSection =
+ +
; + } + + let customThemeForm; + if (SettingsStore.isFeatureEnabled("feature_custom_themes")) { + let messageElement = null; + if (this.state.customThemeMessage.text) { + if (this.state.customThemeMessage.isError) { + messageElement =
{this.state.customThemeMessage.text}
; + } else { + messageElement =
{this.state.customThemeMessage.text}
; + } + } + customThemeForm = ( +
+
+ + {_t("Add theme")} + {messageElement} + +
+ ); + } + + const themes = Object.entries(enumerateThemes()) + .map(p => ({id: p[0], name: p[1]})); // convert pairs to objects for code readability + const builtInThemes = themes.filter(p => !p.id.startsWith("custom-")); + const customThemes = themes.filter(p => !builtInThemes.includes(p)) + .sort((a, b) => a.name.localeCompare(b.name)); + const orderedThemes = [...builtInThemes, ...customThemes]; + return ( +
+ {_t("Theme")} + {systemThemeSection} + + {orderedThemes.map(theme => { + return ; + })} + + {customThemeForm} +
+ ); + } + render() { let autoLaunchOption = null; if (this.state.autoLaunchSupported) { @@ -174,6 +342,8 @@ export default class PreferencesUserSettingsTab extends React.Component {
{_t("Preferences")}
+ {this._renderThemeSection()} +
{_t("Room list")} {this._renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)} From 677a8982343251498ca41cad2c9645b684ebb220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Fri, 3 Apr 2020 10:31:17 +0200 Subject: [PATCH 5/6] Sync i18n strings with latest changes --- src/i18n/strings/en_EN.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 24a6568d823..36b5ecc4d42 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -733,18 +733,12 @@ "Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?", "Success": "Success", "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them": "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them", - "Invalid theme schema.": "Invalid theme schema.", - "Error downloading theme information.": "Error downloading theme information.", - "Theme added!": "Theme added!", "Profile": "Profile", "Email addresses": "Email addresses", "Phone numbers": "Phone numbers", "Set a new account password...": "Set a new account password...", "Account": "Account", "Language and region": "Language and region", - "Custom theme URL": "Custom theme URL", - "Add theme": "Add theme", - "Theme": "Theme", "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.", "Account management": "Account management", "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", @@ -810,6 +804,12 @@ "Room ID or alias of ban list": "Room ID or alias of ban list", "Subscribe": "Subscribe", "Notifications": "Notifications", + "Invalid theme schema.": "Invalid theme schema.", + "Error downloading theme information.": "Error downloading theme information.", + "Theme added!": "Theme added!", + "Custom theme URL": "Custom theme URL", + "Add theme": "Add theme", + "Theme": "Theme", "Start automatically after system login": "Start automatically after system login", "Always show the window menu bar": "Always show the window menu bar", "Show tray icon and minimize window to it on close": "Show tray icon and minimize window to it on close", From 9422f7019187927066c2aef9419b7131191f56e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Fri, 3 Apr 2020 10:55:56 +0200 Subject: [PATCH 6/6] Compact mode: fix alignment of textual events --- res/css/views/rooms/_EventTile.scss | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 8ea4eefd9ef..d07840f0130 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -629,6 +629,7 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { .mx_EventTile_line, .mx_EventTile_reply { padding-top: 0px; padding-bottom: 0px; + padding-left: 45px; } .mx_EventTile_avatar { @@ -656,12 +657,13 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { margin-top: 6px; } - .mx_EventTile_content, .mx_ReplyThread, .mx_TextualEvent { - margin-left: -20px; - } - .mx_ReplyThread { margin-bottom: 5px; + padding-left: 0; + } + + .mx_ReplyThread_show { + padding-left: 10px; } .mx_EventTile_content .markdown-body {