Commit 51913498 authored by Nicolas Frandeboeuf's avatar Nicolas Frandeboeuf

Simplify themes: keep only light/dark and add a switch

parent 901d141d
......@@ -9,52 +9,25 @@ import LocaleSelector from './locale-selector';
const CustomizationOptions = connect(
state => {
return {
themes: get.themes(state),
currentTheme: get.setting(state, 'theme'),
isDarkMode: get.boolSetting(state, 'dark-mode'),
isDiscoveryModeEnabled: get.boolSetting(state, 'discovery-mode')
};
},
dispatch => {
return {
changeTheme(theme) {
actions.setTheme(dispatch, theme);
setDarkModeStatus: enabled => {
actions.setDarkMode(dispatch, enabled);
},
setDiscoverySetting(value) {
actions.setBoolSetting(dispatch, 'discovery-mode', value);
}
};
}
)(props => {
let handleThemeChange = event => props.changeTheme(event.target.value);
let handleDarkModeToggle = event => props.setDarkModeStatus(event.target.checked);
let handleDiscoveryCHange = event => props.setDiscoverySetting(event.target.checked);
let themes = null;
if (props.themes.length < 2) {
themes = <p className="alerts warning">{$t('client.settings.customization.no_themes')}</p>;
} else {
let options = props.themes.map(t => {
return (
<option value={t} key={t}>
{t}
</option>
);
});
themes = (
<p className="wrap-on-mobile">
<label htmlFor="theme-selector">
{$t('client.settings.customization.choose_theme')}
</label>
<select
id="theme-selector"
className="form-element-block"
defaultValue={props.currentTheme}
onChange={handleThemeChange}>
{options}
</select>
</p>
);
}
return (
<form className="settings-form settings-container">
<p className="wrap-on-mobile">
......@@ -64,7 +37,17 @@ const CustomizationOptions = connect(
<LocaleSelector className="form-element-block" id="locale-selector" />
</p>
{themes}
<p className="wrap-on-mobile">
<label htmlFor="theme-selector">
{$t('client.settings.customization.dark_mode')}
</label>
<input
type="checkbox"
name="dark-mode"
onChange={handleDarkModeToggle}
checked={props.isDarkMode}
/>
</p>
<p>
<label htmlFor="discovery-mode">
......
......@@ -34,7 +34,7 @@ class ThemeLink extends React.Component {
onLoadHandler = () => {
this.clearTimers();
this.props.setThemeLoaded(this.props.theme, true);
this.props.setThemeLoaded(true);
if (this.props.onLoad) {
this.props.onLoad(true);
}
......@@ -43,7 +43,7 @@ class ThemeLink extends React.Component {
onErrorHandler = () => {
this.clearTimers();
this.props.setThemeLoaded(this.props.theme, false);
this.props.setThemeLoaded(false);
if (this.props.onLoad) {
this.props.onLoad(false);
}
......@@ -103,13 +103,13 @@ ThemeLink.propTypes = {
const ThemeLoaderTag = connect(
state => {
return {
theme: get.setting(state, 'theme')
theme: get.boolSetting(state, 'dark-mode') ? 'dark' : 'light'
};
},
dispatch => {
return {
setThemeLoaded: (theme, loaded) => {
actions.finishThemeLoad(dispatch, theme, loaded);
setThemeLoaded: loaded => {
actions.finishThemeLoad(dispatch, loaded);
}
};
}
......
import { assert, translate as $t } from '../helpers';
import { translate as $t } from '../helpers';
import { checkExactFields, checkAllowedFields } from '../../shared/validators';
const API_VERSION = 'v1';
......@@ -77,15 +77,7 @@ function buildFetchPromise(url, options = {}) {
}
export function init() {
let all = buildFetchPromise(`api/${API_VERSION}/all/`, { cache: 'no-cache' });
let themes = buildFetchPromise('themes.json');
return Promise.all([all, themes]).then(([world, jsonThemes]) => {
assert(jsonThemes.themes instanceof Array, 'JSON themes must be an array');
world.themes = jsonThemes.themes;
return world;
});
return buildFetchPromise(`api/${API_VERSION}/all/`, { cache: 'no-cache' });
}
export function deleteOperation(opId) {
......
......@@ -44,8 +44,7 @@ const rootReducer = combineReducers({
settings: augmentReducer(Settings.reducer, 'settings'),
ui: augmentReducer(Ui.reducer, 'ui'),
// Static information
types: (state = {}) => state,
themes: (state = {}) => state
types: (state = {}) => state
});
// A simple middleware to log which action is called, and its status if applicable.
......@@ -327,12 +326,6 @@ export const get = {
return Bank.alertPairsByType(state.banks, type);
},
// *** Themes *************************************************************
themes(state) {
assertDefined(state);
return state.themes;
},
// *** Logs ***************************************************************
logs(state) {
assertDefined(state);
......@@ -439,20 +432,15 @@ export const actions = {
dispatch(Ui.toggleSearchDetails(show));
},
setTheme(dispatch, theme) {
assertDefined(dispatch);
setDarkMode(dispatch, enabled) {
assert(typeof enabled === 'boolean', 'enabled must be a boolean');
dispatch(Ui.startThemeLoad());
dispatch(Settings.set('theme', theme));
dispatch(Settings.set('dark-mode', enabled.toString()));
},
finishThemeLoad(dispatch, theme, loaded) {
finishThemeLoad(dispatch, loaded) {
assertDefined(dispatch);
if (!loaded && theme !== 'default') {
debug('Could not load theme, revert to default theme.');
dispatch(Settings.set('theme', 'default'));
} else {
dispatch(Ui.finishThemeLoad(loaded));
}
dispatch(Ui.finishThemeLoad(loaded));
},
setIsSmallScreen(dispatch, isSmall) {
......@@ -674,9 +662,6 @@ export function init() {
state.types = OperationType.initialState();
assertHas(world, 'themes');
state.themes = world.themes;
// The UI must be computed at the end.
state.ui = Ui.initialState(get.boolSetting(state, 'demo-mode'));
......
......@@ -29,7 +29,7 @@ import {
import Errors, { genericErrorHandler } from '../errors';
/* Those settings are stored in the browser local storage only. */
const localSettings = ['theme'];
const localSettings = ['dark-mode'];
function getLocalSettings() {
if (window && window.localStorage) {
......
......@@ -313,7 +313,8 @@ export function initialState(isDemoEnabled) {
slug: null,
state: null
},
isMenuHidden: computeIsSmallScreen()
isMenuHidden: computeIsSmallScreen(),
isDarkMode: false
},
{}
);
......@@ -372,3 +373,7 @@ export function isMenuHidden(state) {
export function isDemoMode(state) {
return state.isDemoMode;
}
export function isDarkMode(state) {
return state.isDarkMode;
}
/* Banks list component and account list component */
main nav .bank-details .icon {
border-radius: 5%;
border: 3px solid #FFFFFF;
}
main nav .bank-details .bank-name {
color: #FFFFFF;
}
main nav .bank-details .bank-name button {
color: inherit;
text-align: left;
}
main nav .bank-details > li.active .bank-name,
main nav .bank-details > li .bank-name button:hover,
main nav .bank-details > li .bank-name button:focus {
font-weight: bold;
}
main nav .bank-details .bank-name .bank-sum {
color: #AFB0B3;
}
main nav .bank-details > li {
border-left: 4px solid grey;
color: #494F59;
}
main nav .bank-details > li.active {
border-color: #D0E9C6;
}
main nav .bank-details a {
color: #FFFFFF;
}
main nav .bank-details .amount.positive {
color: #00A651;
}
main nav .bank-details .amount.negative {
color: #F26C4F;
}
main nav .bank-details > li > .accounts > li > a:hover,
main nav .bank-details > li > .accounts > li > a:focus {
border-left: 4px solid #82D8AC;
background-color: rgba(255, 255, 255, 0.25);
color: #FFFFFF;
}
main nav .bank-details > li > .accounts > li a.active {
border-left: 4px solid #00A651;
color: #FFFFFF;
}
main nav .bank-details > li > .accounts > li a {
color: #AFB0B3;
border-left: 4px solid transparent;
}
/* Sidebar list of sections (operations, settings, etc.) */
main nav .sidebar-section-list li span.badge {
background-color: white;
color: #2B303A;
}
.sidebar-section-list li {
border-bottom: 2px solid #343A45;
background: #303641;
}
.sidebar-section-list li > a {
color: #AFB0B3;
}
.sidebar-section-list li > a:hover,
.sidebar-section-list li > a:focus {
background-color: rgba(255, 255, 255, 0.25);
color: #FFFFFF;
}
.sidebar-section-list li > a.active {
background: #2B303A;
color: #FFFFFF;
}
/* Operations list component */
.operation-toolbar {
border: none;
}
.operation-toolbar ul {
background-color: transparent;
}
.operation-toolbar > form {
border: none;
}
/* Main panel, with form and operations */
.operation-table caption {
color: #777777;
}
.operation-table > tbody > tr {
border-right-color: #EAEAEA;
}
.operation-table td.amount {
font-weight: bold;
}
.operation-table > tbody > tr.income td.amount {
color: #00A651;
}
.operation-table td.modale-button > button {
color: #337AB7;
}
@import '~../light/fonts.css';
@import '~../light/icons.css';
@import '~../light/forms.css';
@import '~../light/loading.css';
@import '~../light/error-reporter.css';
@import '~../light/header.css';
@import '~../light/settings-menu.css';
@import '~../light/menu/about.css';
@import '~../light/modals.css';
@import '~../light/sections/onboarding.css';
@import '~../light/sections/categories.css';
@import '~../light/sections/duplicates.css';
@import '~../light/sections/about.css';
@import '~../light/sections/reports/account-summary.css';
@import '~../light/sections/reports/search-summary.css';
@import '~./menu/banks.css';
@import '~./menu/sections.css';
@import '~./sections/reports/reports.css';
body {
color: #717171;
background: #3D424C;
}
a,
a:hover,
a:focus {
color: #337AB7;
text-decoration: none;
outline: 0;
}
#content {
background: #FFFFFF;
}
main nav {
background-color: #2B303A;
color: white;
}
.account-summary {
color: white;
background-color: var(--wells-balance-color);
border-radius: 3px;
border: none;
padding-left: 1em;
box-shadow: none;
}
.account-summary .icon {
position: static;
border-radius: 100%;
background-color: rgba(255, 255, 255, 0.2);
text-align: center;
width: 2em;
display: inline-block;
height: 2em;
line-height: 2em;
margin-right: 1em;
}
@media (max-width: 768px) {
.account-summary {
display: block;
padding: 0.5em;
}
.account-summary .icon {
position: absolute;
margin-top: 20px;
margin-left: 10px;
}
}
.search-summary li.received,
.search-summary li.spent,
.search-summary li.saved {
border: none;
color: white;
}
.search-summary li.received {
background-color: var(--wells-received-color);
}
.search-summary li.spent {
background-color: var(--wells-spent-color);
}
.search-summary li.saved {
background-color: var(--wells-saved-color);
}
......@@ -22,12 +22,12 @@ DefaultSettings.set('duplicate-threshold', '24');
DefaultSettings.set('email-recipient', '');
DefaultSettings.set('locale', 'en');
DefaultSettings.set('migration-version', '0');
DefaultSettings.set('theme', 'default');
DefaultSettings.set('weboob-auto-merge-accounts', 'true');
DefaultSettings.set('weboob-auto-update', 'true');
DefaultSettings.set('weboob-enable-debug', 'false');
DefaultSettings.set('weboob-fetch-threshold', '1');
DefaultSettings.set('demo-mode', 'false');
DefaultSettings.set('discovery-mode', 'true');
DefaultSettings.set('dark-mode', 'false');
export default DefaultSettings;
......@@ -392,8 +392,7 @@
"limit_is_empty": "Please enter a threshold value."
},
"customization": {
"choose_theme": "Theme",
"no_themes": "No additional themes available. Ask your administrator to install one!",
"dark_mode": "Dark mode",
"locale": "Language",
"discovery": "Discovery (help) messages",
"discovery_label": "Display discovery (help) messages",
......
......@@ -392,8 +392,7 @@
"limit_is_empty": "La valeur de seuil est obligatoire."
},
"customization": {
"choose_theme": "Thème",
"no_themes": "Aucun thème additionnel disponible. Demandez à votre administrateur d'en installer !",
"dark_mode": "Mode sombre",
"locale": "Langue",
"discovery": "Messages de découverte (aide)",
"discovery_label": "Afficher les messages de découverte (aide)",
......
......@@ -5,7 +5,6 @@ const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CssExtractPlugin = require('mini-css-extract-plugin');
const SpritesmithPlugin = require('webpack-spritesmith');
const GenerateJsonPlugin = require('generate-json-webpack-plugin');
// List available locales, to fetch only the required locales from Moment.JS:
// Build a regexp that selects the locale's name without the JS extension (due
......@@ -32,7 +31,9 @@ let entry = {
'./node_modules/react-toastify/dist/ReactToastify.min.css',
'./client/css/base.css',
'./client/init.js'
]
],
'themes-light-bundle': './client/themes/light/style.css',
'themes-dark-bundle': './client/themes/dark/style.css'
};
// These extra locales should be put after the main client entrypoint to ensure
......@@ -45,14 +46,6 @@ locales.forEach(locale => {
}
});
const themes = fs.readdirSync('client/themes').filter(
f => fs.statSync(`client/themes/${f}`).isDirectory()
);
themes.forEach(theme => {
entry[`themes-${theme}-bundle`] = `./client/themes/${theme}/style.css`;
});
// Webpack config
const config = {
mode: process.env.NODE_ENV === "production" ? "production" : "development",
......@@ -216,10 +209,7 @@ const config = {
}),
// Only keep the useful locales from Moment.
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, localesRegex),
// Generate a themes.json file with the list of themes.
new GenerateJsonPlugin('themes.json', {themes: themes})
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, localesRegex)
]
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment