From 8d39d5c6bb2420168215b2e13c3746bce48199b7 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sat, 20 Nov 2021 17:15:30 -0600 Subject: [PATCH] New: Native Theme Engine --- frontend/src/App/App.js | 9 +- frontend/src/App/ApplyTheme.js | 49 +++++ frontend/src/Settings/UI/UISettings.js | 16 ++ frontend/src/Styles/Themes/index.js | 5 + frontend/src/Styles/Themes/light.js | 191 ++++++++++++++++++ .../Configuration/ConfigFileProvider.cs | 2 + src/NzbDrone.Core/Localization/Core/en.json | 1 + .../Config/UiConfigController.cs | 24 ++- .../Config/UiConfigResource.cs | 6 +- .../Frontend/InitializeJsController.cs | 1 + 10 files changed, 297 insertions(+), 7 deletions(-) create mode 100644 frontend/src/App/ApplyTheme.js create mode 100644 frontend/src/Styles/Themes/index.js create mode 100644 frontend/src/Styles/Themes/light.js diff --git a/frontend/src/App/App.js b/frontend/src/App/App.js index 37c453f45..1eea6e082 100644 --- a/frontend/src/App/App.js +++ b/frontend/src/App/App.js @@ -4,6 +4,7 @@ import React from 'react'; import DocumentTitle from 'react-document-title'; import { Provider } from 'react-redux'; import PageConnector from 'Components/Page/PageConnector'; +import ApplyTheme from './ApplyTheme'; import AppRoutes from './AppRoutes'; function App({ store, history }) { @@ -11,9 +12,11 @@ function App({ store, history }) { - - - + + + + + diff --git a/frontend/src/App/ApplyTheme.js b/frontend/src/App/ApplyTheme.js new file mode 100644 index 000000000..339032d4a --- /dev/null +++ b/frontend/src/App/ApplyTheme.js @@ -0,0 +1,49 @@ +import PropTypes from 'prop-types'; +import React, { Fragment, useEffect } from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import themes from 'Styles/Themes'; + +function createMapStateToProps() { + return createSelector( + (state) => state.settings.ui.item.theme || window.Prowlarr.theme, + ( + theme + ) => { + return { + theme + }; + } + ); +} + +function ApplyTheme({ theme, children }) { + // Update the CSS Variables + function updateCSSVariables() { + const arrayOfVariableKeys = Object.keys(themes[theme]); + const arrayOfVariableValues = Object.values(themes[theme]); + + // Loop through each array key and set the CSS Variables + arrayOfVariableKeys.forEach((cssVariableKey, index) => { + // Based on our snippet from MDN + document.documentElement.style.setProperty( + `--${cssVariableKey}`, + arrayOfVariableValues[index] + ); + }); + } + + // On Component Mount and Component Update + useEffect(() => { + updateCSSVariables(theme); + }, [theme]); + + return {children}; +} + +ApplyTheme.propTypes = { + theme: PropTypes.string.isRequired, + children: PropTypes.object.isRequired +}; + +export default connect(createMapStateToProps)(ApplyTheme); diff --git a/frontend/src/Settings/UI/UISettings.js b/frontend/src/Settings/UI/UISettings.js index 1b9be3964..7b395225b 100644 --- a/frontend/src/Settings/UI/UISettings.js +++ b/frontend/src/Settings/UI/UISettings.js @@ -10,6 +10,8 @@ import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; import { inputTypes } from 'Helpers/Props'; import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import themes from 'Styles/Themes'; +import titleCase from 'Utilities/String/titleCase'; import translate from 'Utilities/String/translate'; export const firstDayOfWeekOptions = [ @@ -62,6 +64,9 @@ class UISettings extends Component { const uiLanguages = languages.filter((item) => item.value !== 'Original'); + const themeOptions = Object.keys(themes) + .map((theme) => ({ key: theme, value: titleCase(theme) })); + return (
+ + {translate('Theme')} + + {translate('SettingsEnableColorImpairedMode')} GetValue("PostgresPassword", string.Empty, persist: false); public string PostgresMainDb => GetValue("PostgresMainDb", "prowlarr-main", persist: false); public string PostgresLogDb => GetValue("PostgresLogDb", "prowlarr-log", persist: false); + public string Theme => GetValue("Theme", "light", persist: false); public int PostgresPort => GetValueInt("PostgresPort", 5432, persist: false); public bool LogSql => GetValueBoolean("LogSql", false, persist: false); public int LogRotate => GetValueInt("LogRotate", 50, persist: false); diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 2d2616bce..dc6dfb8ba 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -391,6 +391,7 @@ "TestAllApps": "Test All Apps", "TestAllClients": "Test All Clients", "TestAllIndexers": "Test All Indexers", + "ThemeHelpText": "Change Prowlarr UI theme, inspired by {0}", "Time": "Time", "Title": "Title", "Today": "Today", diff --git a/src/Prowlarr.Api.V1/Config/UiConfigController.cs b/src/Prowlarr.Api.V1/Config/UiConfigController.cs index 436b5717f..eda73e967 100644 --- a/src/Prowlarr.Api.V1/Config/UiConfigController.cs +++ b/src/Prowlarr.Api.V1/Config/UiConfigController.cs @@ -1,4 +1,8 @@ +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; using NzbDrone.Core.Configuration; +using NzbDrone.Http.REST.Attributes; using Prowlarr.Http; namespace Prowlarr.Api.V1.Config @@ -6,14 +10,30 @@ namespace Prowlarr.Api.V1.Config [V1ApiController("config/ui")] public class UiConfigController : ConfigController { - public UiConfigController(IConfigService configService) + private readonly IConfigFileProvider _configFileProvider; + + public UiConfigController(IConfigFileProvider configFileProvider, IConfigService configService) : base(configService) { + _configFileProvider = configFileProvider; + } + + [RestPutById] + public override ActionResult SaveConfig(UiConfigResource resource) + { + var dictionary = resource.GetType() + .GetProperties(BindingFlags.Instance | BindingFlags.Public) + .ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null)); + + _configFileProvider.SaveConfigDictionary(dictionary); + _configService.SaveConfigDictionary(dictionary); + + return Accepted(resource.Id); } protected override UiConfigResource ToResource(IConfigService model) { - return UiConfigResourceMapper.ToResource(model); + return UiConfigResourceMapper.ToResource(_configFileProvider, model); } } } diff --git a/src/Prowlarr.Api.V1/Config/UiConfigResource.cs b/src/Prowlarr.Api.V1/Config/UiConfigResource.cs index d2e9e786b..ae63a6d43 100644 --- a/src/Prowlarr.Api.V1/Config/UiConfigResource.cs +++ b/src/Prowlarr.Api.V1/Config/UiConfigResource.cs @@ -17,11 +17,12 @@ namespace Prowlarr.Api.V1.Config public bool EnableColorImpairedMode { get; set; } public int UILanguage { get; set; } + public string Theme { get; set; } } public static class UiConfigResourceMapper { - public static UiConfigResource ToResource(IConfigService model) + public static UiConfigResource ToResource(IConfigFileProvider config, IConfigService model) { return new UiConfigResource { @@ -34,7 +35,8 @@ namespace Prowlarr.Api.V1.Config ShowRelativeDates = model.ShowRelativeDates, EnableColorImpairedMode = model.EnableColorImpairedMode, - UILanguage = model.UILanguage + UILanguage = model.UILanguage, + Theme = config.Theme }; } } diff --git a/src/Prowlarr.Http/Frontend/InitializeJsController.cs b/src/Prowlarr.Http/Frontend/InitializeJsController.cs index fc3dcf302..c979d7525 100644 --- a/src/Prowlarr.Http/Frontend/InitializeJsController.cs +++ b/src/Prowlarr.Http/Frontend/InitializeJsController.cs @@ -50,6 +50,7 @@ namespace Prowlarr.Http.Frontend builder.AppendLine($" release: '{BuildInfo.Release}',"); builder.AppendLine($" version: '{BuildInfo.Version.ToString()}',"); builder.AppendLine($" instanceName: '{_configFileProvider.InstanceName.ToString()}',"); + builder.AppendLine($" theme: '{_configFileProvider.Theme.ToString()}',"); builder.AppendLine($" branch: '{_configFileProvider.Branch.ToLower()}',"); builder.AppendLine($" analytics: {_analyticsService.IsEnabled.ToString().ToLowerInvariant()},"); builder.AppendLine($" userHash: '{HashUtil.AnonymousToken()}',");