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 304bb6449f..e9b30430a3 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: "", + customThemeMessage: {isError: false, text: ""}, }; this.dispatcherRef = dis.register(this._onAction); @@ -274,6 +276,41 @@ 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 (
@@ -368,6 +405,45 @@ 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 = ( +
+
+ + {_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")} @@ -376,10 +452,11 @@ 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}
); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d92cf41f5b..42120588c3 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -385,6 +385,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", @@ -708,12 +709,17 @@ "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", 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: [],