Merge pull request #624 from matrix-org/matthew/postcss
Implement theming via alternate stylesheets
This commit is contained in:
commit
4d7ed9a58f
5 changed files with 170 additions and 49 deletions
|
@ -42,6 +42,7 @@ var keyHex = [
|
||||||
"#76CFA6", // Vector Green
|
"#76CFA6", // Vector Green
|
||||||
"#EAF5F0", // Vector Light Green
|
"#EAF5F0", // Vector Light Green
|
||||||
"#D3EFE1", // BottomLeftMenu overlay (20% Vector Green overlaid on 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
|
// cache of our replacement colours
|
||||||
|
@ -50,6 +51,7 @@ var colors = [
|
||||||
keyHex[0],
|
keyHex[0],
|
||||||
keyHex[1],
|
keyHex[1],
|
||||||
keyHex[2],
|
keyHex[2],
|
||||||
|
keyHex[3],
|
||||||
];
|
];
|
||||||
|
|
||||||
var cssFixups = [
|
var cssFixups = [
|
||||||
|
@ -210,7 +212,9 @@ module.exports = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
colors = [primaryColor, secondaryColor, tertiaryColor];
|
colors[0] = primaryColor;
|
||||||
|
colors[1] = secondaryColor;
|
||||||
|
colors[2] = tertiaryColor;
|
||||||
|
|
||||||
if (DEBUG) console.log("Tinter.tint");
|
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
|
// 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)
|
// to the CSS fixup stuff in Tinter (just that the fixups are stored in TintableSvg)
|
||||||
// keeping it here for now.
|
// keeping it here for now.
|
||||||
|
|
|
@ -67,7 +67,7 @@ module.exports = {
|
||||||
chevronOffset.top = props.chevronOffset;
|
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 = "";
|
var chevronCSS = "";
|
||||||
if (props.menuColour) {
|
if (props.menuColour) {
|
||||||
chevronCSS = `
|
chevronCSS = `
|
||||||
|
|
|
@ -25,6 +25,7 @@ var SdkConfig = require("../../SdkConfig");
|
||||||
var ContextualMenu = require("./ContextualMenu");
|
var ContextualMenu = require("./ContextualMenu");
|
||||||
var RoomListSorter = require("../../RoomListSorter");
|
var RoomListSorter = require("../../RoomListSorter");
|
||||||
var UserActivity = require("../../UserActivity");
|
var UserActivity = require("../../UserActivity");
|
||||||
|
var UserSettingsStore = require('../../UserSettingsStore');
|
||||||
var Presence = require("../../Presence");
|
var Presence = require("../../Presence");
|
||||||
var dis = require("../../dispatcher");
|
var dis = require("../../dispatcher");
|
||||||
|
|
||||||
|
@ -456,6 +457,9 @@ module.exports = React.createClass({
|
||||||
middleOpacity: payload.middleOpacity,
|
middleOpacity: payload.middleOpacity,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'set_theme':
|
||||||
|
this._onSetTheme(payload.value);
|
||||||
|
break;
|
||||||
case 'on_logged_in':
|
case 'on_logged_in':
|
||||||
this._onLoggedIn();
|
this._onLoggedIn();
|
||||||
break;
|
break;
|
||||||
|
@ -584,6 +588,45 @@ module.exports = React.createClass({
|
||||||
_onLoadCompleted: function() {
|
_onLoadCompleted: function() {
|
||||||
this.props.onLoadCompleted();
|
this.props.onLoadCompleted();
|
||||||
this.setState({loading: false});
|
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');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -32,6 +32,53 @@ var AddThreepid = require('../../AddThreepid');
|
||||||
const REACT_SDK_VERSION =
|
const REACT_SDK_VERSION =
|
||||||
'dist' in package_json ? package_json.version : package_json.gitHead || "<local>";
|
'dist' in package_json ? package_json.version : package_json.gitHead || "<local>";
|
||||||
|
|
||||||
|
|
||||||
|
// 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({
|
module.exports = React.createClass({
|
||||||
displayName: 'UserSettings',
|
displayName: 'UserSettings',
|
||||||
|
|
||||||
|
@ -93,6 +140,12 @@ module.exports = React.createClass({
|
||||||
middleOpacity: 0.3,
|
middleOpacity: 0.3,
|
||||||
});
|
});
|
||||||
this._refreshFromServer();
|
this._refreshFromServer();
|
||||||
|
|
||||||
|
var syncedSettings = UserSettingsStore.getSyncedSettings();
|
||||||
|
if (!syncedSettings.theme) {
|
||||||
|
syncedSettings.theme = 'light';
|
||||||
|
}
|
||||||
|
this._syncedSettings = syncedSettings;
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
|
@ -342,60 +395,68 @@ module.exports = React.createClass({
|
||||||
_renderUserInterfaceSettings: function() {
|
_renderUserInterfaceSettings: function() {
|
||||||
var client = MatrixClientPeg.get();
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3>User Interface</h3>
|
<h3>User Interface</h3>
|
||||||
<div className="mx_UserSettings_section">
|
<div className="mx_UserSettings_section">
|
||||||
<div className="mx_UserSettings_toggle">
|
{ this._renderUrlPreviewSelector() }
|
||||||
<input id="urlPreviewsDisabled"
|
{ SETTINGS_LABELS.map( this._renderSyncedSetting ) }
|
||||||
type="checkbox"
|
{ THEMES.map( this._renderThemeSelector ) }
|
||||||
defaultChecked={ UserSettingsStore.getUrlPreviewsDisabled() }
|
|
||||||
onChange={ e => UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) }
|
|
||||||
/>
|
|
||||||
<label htmlFor="urlPreviewsDisabled">
|
|
||||||
Disable inline URL previews by default
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{ settingsLabels.forEach( setting => {
|
|
||||||
<div className="mx_UserSettings_toggle">
|
|
||||||
<input id={ setting.id }
|
|
||||||
type="checkbox"
|
|
||||||
defaultChecked={ syncedSettings[setting.id] }
|
|
||||||
onChange={ e => UserSettingsStore.setSyncedSetting(setting.id, e.target.checked) }
|
|
||||||
/>
|
|
||||||
<label htmlFor={ setting.id }>
|
|
||||||
{ settings.label }
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_renderUrlPreviewSelector: function() {
|
||||||
|
return <div className="mx_UserSettings_toggle">
|
||||||
|
<input id="urlPreviewsDisabled"
|
||||||
|
type="checkbox"
|
||||||
|
defaultChecked={ UserSettingsStore.getUrlPreviewsDisabled() }
|
||||||
|
onChange={ e => UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) }
|
||||||
|
/>
|
||||||
|
<label htmlFor="urlPreviewsDisabled">
|
||||||
|
Disable inline URL previews by default
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderSyncedSetting: function(setting) {
|
||||||
|
return <div className="mx_UserSettings_toggle" key={ setting.id }>
|
||||||
|
<input id={ setting.id }
|
||||||
|
type="checkbox"
|
||||||
|
defaultChecked={ this._syncedSettings[setting.id] }
|
||||||
|
onChange={ e => UserSettingsStore.setSyncedSetting(setting.id, e.target.checked) }
|
||||||
|
/>
|
||||||
|
<label htmlFor={ setting.id }>
|
||||||
|
{ setting.label }
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderThemeSelector: function(setting) {
|
||||||
|
return <div className="mx_UserSettings_toggle" key={ setting.id + "_" + setting.value }>
|
||||||
|
<input id={ setting.id + "_" + setting.value }
|
||||||
|
type="radio"
|
||||||
|
name={ setting.id }
|
||||||
|
value={ setting.value }
|
||||||
|
defaultChecked={ this._syncedSettings[setting.id] === setting.value }
|
||||||
|
onChange={ e => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
UserSettingsStore.setSyncedSetting(setting.id, setting.value)
|
||||||
|
}
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'set_theme',
|
||||||
|
value: setting.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label htmlFor={ setting.id + "_" + setting.value }>
|
||||||
|
{ setting.label }
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
_renderCryptoInfo: function() {
|
_renderCryptoInfo: function() {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const deviceId = client.deviceId;
|
const deviceId = client.deviceId;
|
||||||
|
|
|
@ -263,7 +263,7 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
|
|
||||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||||
var x = buttonRect.right + window.pageXOffset;
|
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;
|
var self = this;
|
||||||
ContextualMenu.createMenu(MessageContextMenu, {
|
ContextualMenu.createMenu(MessageContextMenu, {
|
||||||
chevronOffset: 10,
|
chevronOffset: 10,
|
||||||
|
@ -465,7 +465,7 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
var editButton = (
|
var editButton = (
|
||||||
<img className="mx_EventTile_editButton" src="img/icon_context_message.svg" width="19" height="19" alt="Options" title="Options" onClick={this.onEditClicked} />
|
<span className="mx_EventTile_editButton" title="Options" onClick={this.onEditClicked} />
|
||||||
);
|
);
|
||||||
|
|
||||||
var e2e;
|
var e2e;
|
||||||
|
|
Loading…
Reference in a new issue