diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index b26312cebf0..d07840f0130 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -602,6 +602,8 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { .mx_EventTile .mx_SenderProfile { font-size: $font-13px; + font-size: 13px; + padding-left: 10px; } .mx_EventTile.mx_EventTile_emote { @@ -627,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 { @@ -654,6 +657,15 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { margin-top: 6px; } + .mx_ReplyThread { + margin-bottom: 5px; + padding-left: 0; + } + + .mx_ReplyThread_show { + padding-left: 10px; + } + .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 = (
diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 801062bff49..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,77 +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"); @@ -547,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 bdb2a9ffc4a..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 = [ @@ -51,6 +54,7 @@ export default class PreferencesUserSettingsTab extends React.Component { 'showAvatarChanges', 'showDisplaynameChanges', 'showImages', + 'useCompactLayout', ]; static ADVANCED_SETTINGS = [ @@ -78,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(), }; } @@ -112,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})); }; @@ -139,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) { @@ -173,6 +342,8 @@ export default class PreferencesUserSettingsTab extends React.Component {
{_t("Preferences")}
+ {this._renderThemeSection()} +
{_t("Room list")} {this._renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)} 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",