diff --git a/src/Tinter.js b/src/Tinter.js index 534a1d810b..4a5e4e453c 100644 --- a/src/Tinter.js +++ b/src/Tinter.js @@ -42,6 +42,7 @@ var keyHex = [ "#76CFA6", // Vector Green "#EAF5F0", // Vector Light Green "#D3EFE1", // BottomLeftMenu overlay (20% Vector Green overlaid on Vector Light Green) + "#FFFFFF", // white highlights of the SVGs (for switching to dark theme) ]; // cache of our replacement colours @@ -50,6 +51,7 @@ var colors = [ keyHex[0], keyHex[1], keyHex[2], + keyHex[3], ]; var cssFixups = [ @@ -210,7 +212,9 @@ module.exports = { return; } - colors = [primaryColor, secondaryColor, tertiaryColor]; + colors[0] = primaryColor; + colors[1] = secondaryColor; + colors[2] = tertiaryColor; if (DEBUG) console.log("Tinter.tint"); @@ -224,6 +228,19 @@ module.exports = { }); }, + tintSvgWhite: function(whiteColor) { + if (!whiteColor) { + whiteColor = colors[3]; + } + if (colors[3] === whiteColor) { + return; + } + colors[3] = whiteColor; + tintables.forEach(function(tintable) { + tintable(); + }); + }, + // XXX: we could just move this all into TintableSvg, but as it's so similar // to the CSS fixup stuff in Tinter (just that the fixups are stored in TintableSvg) // keeping it here for now. diff --git a/src/components/structures/ContextualMenu.js b/src/components/structures/ContextualMenu.js index fecb2a1841..da419897dc 100644 --- a/src/components/structures/ContextualMenu.js +++ b/src/components/structures/ContextualMenu.js @@ -67,7 +67,7 @@ module.exports = { chevronOffset.top = props.chevronOffset; } - // To overide the deafult chevron colour, if it's been set + // To override the default chevron colour, if it's been set var chevronCSS = ""; if (props.menuColour) { chevronCSS = ` diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 4d98e3f09e..8917f0535e 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -25,6 +25,7 @@ var SdkConfig = require("../../SdkConfig"); var ContextualMenu = require("./ContextualMenu"); var RoomListSorter = require("../../RoomListSorter"); var UserActivity = require("../../UserActivity"); +var UserSettingsStore = require('../../UserSettingsStore'); var Presence = require("../../Presence"); var dis = require("../../dispatcher"); @@ -456,6 +457,9 @@ module.exports = React.createClass({ middleOpacity: payload.middleOpacity, }); break; + case 'set_theme': + this._onSetTheme(payload.value); + break; case 'on_logged_in': this._onLoggedIn(); break; @@ -584,6 +588,45 @@ module.exports = React.createClass({ _onLoadCompleted: function() { this.props.onLoadCompleted(); this.setState({loading: false}); + + // set up the right theme. + // XXX: this will temporarily flicker the wrong CSS. + dis.dispatch({ + action: 'set_theme', + value: UserSettingsStore.getSyncedSetting('theme') + }); + }, + + /** + * Called whenever someone changes the theme + */ + _onSetTheme: function(theme) { + if (!theme) { + theme = 'light'; + } + + var i, a; + for (i = 0; (a = document.getElementsByTagName("link")[i]); i++) { + var href = a.getAttribute("href"); + var match = href.match(/^bundles\/.*\/theme-(.*)\.css$/); + if (match) { + if (match[1] === theme) { + a.disabled = false; + } + else { + a.disabled = true; + } + } + } + + if (theme === 'dark') { + // abuse the tinter to change all the SVG's #fff to #2d2d2d + // XXX: obviously this shouldn't be hardcoded here. + Tinter.tintSvgWhite('#2d2d2d'); + } + else { + Tinter.tintSvgWhite('#ffffff'); + } }, /** diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index a41eab3a76..5ce9ab1a15 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -32,6 +32,53 @@ var AddThreepid = require('../../AddThreepid'); const REACT_SDK_VERSION = 'dist' in package_json ? package_json.version : package_json.gitHead || ""; + +// Enumerate some simple 'flip a bit' UI settings (if any). +// 'id' gives the key name in the im.vector.web.settings account data event +// 'label' is how we describe it in the UI. +const SETTINGS_LABELS = [ +/* + { + id: 'alwaysShowTimestamps', + label: 'Always show message timestamps', + }, + { + id: 'showTwelveHourTimestamps', + label: 'Show timestamps in 12 hour format (e.g. 2:30pm)', + }, + { + id: 'useCompactLayout', + label: 'Use compact timeline layout', + }, + { + id: 'useFixedWidthFont', + label: 'Use fixed width font', + }, +*/ +]; + +// Enumerate the available themes, with a nice human text label. +// 'id' gives the key name in the im.vector.web.settings account data event +// 'value' is the value for that key in the event +// 'label' is how we describe it in the UI. +// +// XXX: Ideally we would have a theme manifest or something and they'd be nicely +// packaged up in a single directory, and/or located at the application layer. +// But for now for expedience we just hardcode them here. +const THEMES = [ + { + id: 'theme', + label: 'Light theme', + value: 'light', + }, + { + id: 'theme', + label: 'Dark theme', + value: 'dark', + } +]; + + module.exports = React.createClass({ displayName: 'UserSettings', @@ -93,6 +140,12 @@ module.exports = React.createClass({ middleOpacity: 0.3, }); this._refreshFromServer(); + + var syncedSettings = UserSettingsStore.getSyncedSettings(); + if (!syncedSettings.theme) { + syncedSettings.theme = 'light'; + } + this._syncedSettings = syncedSettings; }, componentDidMount: function() { @@ -342,60 +395,68 @@ module.exports = React.createClass({ _renderUserInterfaceSettings: function() { var client = MatrixClientPeg.get(); - var settingsLabels = [ - /* - { - id: 'alwaysShowTimestamps', - label: 'Always show message timestamps', - }, - { - id: 'showTwelveHourTimestamps', - label: 'Show timestamps in 12 hour format (e.g. 2:30pm)', - }, - { - id: 'useCompactLayout', - label: 'Use compact timeline layout', - }, - { - id: 'useFixedWidthFont', - label: 'Use fixed width font', - }, - */ - ]; - - var syncedSettings = UserSettingsStore.getSyncedSettings(); - return (

User Interface

-
- UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) } - /> - -
+ { this._renderUrlPreviewSelector() } + { SETTINGS_LABELS.map( this._renderSyncedSetting ) } + { THEMES.map( this._renderThemeSelector ) }
- { settingsLabels.forEach( setting => { -
- UserSettingsStore.setSyncedSetting(setting.id, e.target.checked) } - /> - -
- })}
); }, + _renderUrlPreviewSelector: function() { + return
+ UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) } + /> + +
+ }, + + _renderSyncedSetting: function(setting) { + return
+ UserSettingsStore.setSyncedSetting(setting.id, e.target.checked) } + /> + +
+ }, + + _renderThemeSelector: function(setting) { + return
+ { + if (e.target.checked) { + UserSettingsStore.setSyncedSetting(setting.id, setting.value) + } + dis.dispatch({ + action: 'set_theme', + value: setting.value, + }); + } + } + /> + +
+ }, + _renderCryptoInfo: function() { const client = MatrixClientPeg.get(); const deviceId = client.deviceId; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 42dbe78630..ef578d47db 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -263,7 +263,7 @@ module.exports = WithMatrixClient(React.createClass({ // The window X and Y offsets are to adjust position when zoomed in to page var x = buttonRect.right + window.pageXOffset; - var y = (buttonRect.top + (e.target.height / 2) + window.pageYOffset) - 19; + var y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19; var self = this; ContextualMenu.createMenu(MessageContextMenu, { chevronOffset: 10, @@ -465,7 +465,7 @@ module.exports = WithMatrixClient(React.createClass({ } var editButton = ( - Options + ); var e2e;