From 8520310e6e3e97a554c737952d9649d0be53e2b4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 28 Feb 2020 11:42:16 -0700 Subject: [PATCH 1/3] Proof of concept for custom theme adding For https://github.com/vector-im/riot-web/issues/12517 --- .../tabs/user/GeneralUserSettingsTab.js | 52 +++++++++++++++++++ src/i18n/strings/en_EN.json | 5 ++ src/settings/Settings.js | 6 +++ 3 files changed, 63 insertions(+) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 304bb6449f..7af9aae146 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -61,6 +61,8 @@ export default class GeneralUserSettingsTab extends React.Component { emails: [], msisdns: [], ...this._calculateThemeState(), + customThemeUrl: "", + customThemeError: "", }; this.dispatcherRef = dis.register(this._onAction); @@ -274,6 +276,33 @@ 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 + + try { + const r = await fetch(this.state.customThemeUrl); + const themeInfo = await r.json(); + if (!themeInfo || typeof(themeInfo['name']) !== 'string' || typeof(themeInfo['colors']) !== 'object') { + console.log(themeInfo); + this.setState({customThemeError: _t("Invalid theme schema.")}); + return; + } + currentThemes.push(themeInfo); + } catch (e) { + console.error(e); + this.setState({customThemeError: _t("Error downloading theme information.")}); + } + + await SettingsStore.setValue("custom_themes", null, SettingLevel.ACCOUNT, currentThemes); + this.setState({customThemeUrl: "", customThemeError: ""}); + }; + + _onCustomThemeChange = (e) => { + this.setState({customThemeUrl: e.target.value}); + }; + _renderProfileSection() { return (
@@ -368,6 +397,28 @@ export default class GeneralUserSettingsTab extends React.Component { />
; } + + let customThemeForm; + if (SettingsStore.isFeatureEnabled("feature_custom_themes")) { + customThemeForm = ( +
+ +
{this.state.customThemeError}
+ {_t("Add theme")} + + ); + } + return (
{_t("Theme")} @@ -380,6 +431,7 @@ export default class GeneralUserSettingsTab extends React.Component { return ; })} + {customThemeForm}
); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ba48f7cd37..6984e21f06 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -379,6 +379,7 @@ "Multiple integration managers": "Multiple integration managers", "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)", "Show a presence dot next to DMs in the room list": "Show a presence dot next to DMs in the room list", + "Support adding custom themes": "Support adding custom themes", "Enable cross-signing to verify per-user instead of per-session (in development)": "Enable cross-signing to verify per-user instead of per-session (in development)", "Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)", "Show info about bridges in room settings": "Show info about bridges in room settings", @@ -702,12 +703,16 @@ "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.", "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", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index b77fb392e9..ba6df47a04 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -134,6 +134,12 @@ export const SETTINGS = { supportedLevels: LEVELS_FEATURE, default: false, }, + "feature_custom_themes": { + isFeature: true, + displayName: _td("Support adding custom themes"), + supportedLevels: LEVELS_FEATURE, + default: false, + }, "mjolnirRooms": { supportedLevels: ['account'], default: [], From 6aeca7b8533806c6d859339c1dfd0be0c5eda424 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 6 Mar 2020 17:43:10 -0700 Subject: [PATCH 2/3] UX polish for custom themes --- res/css/_common.scss | 7 ++- .../tabs/user/GeneralUserSettingsTab.js | 59 ++++++++++++------- src/i18n/strings/en_EN.json | 1 + 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index e062e0bd73..a4ef603242 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -42,10 +42,15 @@ pre, code { font-size: 100% !important; } -.error, .warning { +.error, .warning, +.text-error, .text-warning { color: $warning-color; } +.text-success { + color: $accent-color; +} + b { // On Firefox, the default weight for `` is `bolder` which results in no bold // effect since we only have specific weights of our fonts available. diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 7af9aae146..d0509644b5 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -62,7 +62,7 @@ export default class GeneralUserSettingsTab extends React.Component { msisdns: [], ...this._calculateThemeState(), customThemeUrl: "", - customThemeError: "", + customThemeMessage: {isError: false, text: ""}, }; this.dispatcherRef = dis.register(this._onAction); @@ -281,22 +281,30 @@ export default class GeneralUserSettingsTab extends React.Component { 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') { - console.log(themeInfo); - this.setState({customThemeError: _t("Invalid theme schema.")}); + this.setState({customThemeMessage: {text: _t("Invalid theme schema."), isError: true}}); return; } currentThemes.push(themeInfo); } catch (e) { console.error(e); - this.setState({customThemeError: _t("Error downloading theme information.")}); + 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: "", customThemeError: ""}); + this.setState({customThemeUrl: "", customThemeMessage: {text: _t("Theme added!"), isError: false}}); + + this._themeTimer = setTimeout(() => { + this.setState({customThemeMessage: {text: "", isError: false}}); + }, 3000); }; _onCustomThemeChange = (e) => { @@ -400,22 +408,33 @@ export default class GeneralUserSettingsTab extends React.Component { 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 = ( -
- -
{this.state.customThemeError}
- {_t("Add theme")} - +
+
+ + {_t("Add theme")} + {messageElement} + +
); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 57e4bcb1cc..6463c7474f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -706,6 +706,7 @@ "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", From 661508ddf8323d4fc8daad9663376bc9e203965a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 6 Mar 2020 17:52:16 -0700 Subject: [PATCH 3/3] Order theme names in the dropdown --- .../views/settings/tabs/user/GeneralUserSettingsTab.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index d0509644b5..e9b30430a3 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -438,6 +438,12 @@ export default class GeneralUserSettingsTab extends React.Component { ); } + 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")} @@ -446,8 +452,8 @@ export default class GeneralUserSettingsTab extends React.Component { value={this.state.theme} onChange={this._onThemeChange} disabled={this.state.useSystemTheme} > - {Object.entries(enumerateThemes()).map(([theme, text]) => { - return ; + {orderedThemes.map(theme => { + return ; })} {customThemeForm}