New: Native Theme Engine

instance-name-ordering
Qstick 3 years ago
parent beabd10f06
commit 8d39d5c6bb

@ -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 }) {
<DocumentTitle title={window.Prowlarr.instanceName}>
<Provider store={store}>
<ConnectedRouter history={history}>
<PageConnector>
<AppRoutes app={App} />
</PageConnector>
<ApplyTheme>
<PageConnector>
<AppRoutes app={App} />
</PageConnector>
</ApplyTheme>
</ConnectedRouter>
</Provider>
</DocumentTitle>

@ -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 <Fragment>{children}</Fragment>;
}
ApplyTheme.propTypes = {
theme: PropTypes.string.isRequired,
children: PropTypes.object.isRequired
};
export default connect(createMapStateToProps)(ApplyTheme);

@ -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 (
<PageContent title={translate('UISettings')}>
<SettingsToolbarConnector
@ -138,6 +143,17 @@ class UISettings extends Component {
</FieldSet>
<FieldSet legend={translate('Style')}>
<FormGroup>
<FormLabel>{translate('Theme')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="theme"
helpText={translate('ThemeHelpText', ['Theme.Park'])}
values={themeOptions}
onChange={onInputChange}
{...settings.theme}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('SettingsEnableColorImpairedMode')}</FormLabel>
<FormInputGroup

@ -0,0 +1,5 @@
import * as light from './light';
export default {
light
};

@ -0,0 +1,191 @@
const prowlarrOrange = '#e66000';
module.exports = {
textColor: '#515253',
defaultColor: '#333',
disabledColor: '#999',
dimColor: '#555',
black: '#000',
white: '#fff',
offWhite: '#f5f7fa',
primaryColor: '#5d9cec',
selectedColor: '#f9be03',
successColor: '#27c24c',
dangerColor: '#f05050',
warningColor: '#ffa500',
infoColor: '#5d9cec',
queueColor: '#7a43b6',
purple: '#7a43b6',
pink: '#ff69b4',
prowlarrOrange,
helpTextColor: '#909293',
darkGray: '#888',
gray: '#adadad',
lightGray: '#ddd',
disabledInputColor: '#808080',
// Theme Colors
themeBlue: prowlarrOrange,
themeRed: '#c4273c',
themeDarkColor: '#595959',
themeLightColor: '#707070',
torrentColor: '#00853d',
usenetColor: '#17b1d9',
// Links
defaultLinkHoverColor: '#fff',
linkColor: '#5d9cec',
linkHoverColor: '#1b72e2',
// Sidebar
sidebarColor: '#e1e2e3',
sidebarBackgroundColor: '#595959',
sidebarActiveBackgroundColor: '#333333',
// Toolbar
toolbarColor: '#e1e2e3',
toolbarBackgroundColor: '#707070',
toolbarMenuItemBackgroundColor: '#606060',
toolbarMenuItemHoverBackgroundColor: '#515151',
toolbarLabelColor: '#e1e2e3',
// Accents
borderColor: '#e5e5e5',
inputBorderColor: '#dde6e9',
inputBoxShadowColor: 'rgba(0, 0, 0, 0.075)',
inputFocusBorderColor: '#66afe9',
inputFocusBoxShadowColor: 'rgba(102, 175, 233, 0.6)',
inputErrorBorderColor: '#f05050',
inputErrorBoxShadowColor: 'rgba(240, 80, 80, 0.6)',
inputWarningBorderColor: '#ffa500',
inputWarningBoxShadowColor: 'rgba(255, 165, 0, 0.6)',
colorImpairedGradient: '#ffffff',
colorImpairedGradientDark: '#f4f5f6',
//
// Buttons
defaultBackgroundColor: '#fff',
defaultBorderColor: '#eaeaea',
defaultHoverBackgroundColor: '#f5f5f5',
defaultHoverBorderColor: '#d6d6d6;',
primaryBackgroundColor: '#5d9cec',
primaryBorderColor: '#5899eb',
primaryHoverBackgroundColor: '#4b91ea',
primaryHoverBorderColor: '#3483e7;',
successBackgroundColor: '#27c24c',
successBorderColor: '#26be4a',
successHoverBackgroundColor: '#24b145',
successHoverBorderColor: '#1f9c3d;',
warningBackgroundColor: '#ff902b',
warningBorderColor: '#ff8d26',
warningHoverBackgroundColor: '#ff8517',
warningHoverBorderColor: '#fc7800;',
dangerBackgroundColor: '#f05050',
dangerBorderColor: '#f04b4b',
dangerHoverBackgroundColor: '#ee3d3d',
dangerHoverBorderColor: '#ec2626;',
iconButtonDisabledColor: '#7a7a7a',
iconButtonHoverColor: '#666',
iconButtonHoverLightColor: '#ccc',
//
// Modal
modalBackdropBackgroundColor: 'rgba(0, 0, 0, 0.6)',
modalBackgroundColor: '#fff',
modalCloseButtonHoverColor: '#888',
//
// Menu
menuItemColor: '#e1e2e3',
menuItemHoverColor: '#fbfcfc',
menuItemHoverBackgroundColor: '#f5f7fa',
//
// Toolbar
toobarButtonHoverColor: '#e66000',
toobarButtonSelectedColor: '#e66000',
//
// Scroller
scrollbarBackgroundColor: '#9ea4b9',
scrollbarHoverBackgroundColor: '#656d8c',
//
// Card
cardShadowColor: '#e1e1e1',
cardAlternateBackgroundColor: '#f5f5f5',
//
// Alert
alertDangerBorderColor: '#ebccd1',
alertDangerBackgroundColor: '#f2dede',
alertDangerColor: '#a94442',
alertInfoBorderColor: '#bce8f1',
alertInfoBackgroundColor: '#d9edf7',
alertInfoColor: '#31708f',
alertSuccessBorderColor: '#d6e9c6',
alertSuccessBackgroundColor: '#dff0d8',
alertSuccessColor: '#3c763d',
alertWarningBorderColor: '#faebcc',
alertWarningBackgroundColor: '#fcf8e3',
alertWarningColor: '#8a6d3b',
//
// Slider
sliderAccentColor: '#5d9cec',
//
// Form
advancedFormLabelColor: '#ff902b',
disabledCheckInputColor: '#ddd',
//
// Popover
popoverTitleBackgroundColor: '#f7f7f7',
popoverTitleBorderColor: '#ebebeb',
popoverShadowColor: 'rgba(0, 0, 0, 0.2)',
popoverArrowBorderColor: '#fff',
popoverTitleBackgroundInverseColor: '#595959',
popoverTitleBorderInverseColor: '#707070',
popoverShadowInverseColor: 'rgba(0, 0, 0, 0.2)',
popoverArrowBorderInverseColor: 'rgba(58, 63, 81, 0.75)',
//
// Calendar
calendarTodayBackgroundColor: '#ddd',
calendarBorderColor: '#cecece',
calendarTextDim: '#666',
//
// Table
tableRowHoverBackgroundColor: '#fafbfc',
//
// Charts
failedColors: ['#ffbeb2', '#feb4a6', '#fdab9b', '#fca290', '#fb9984', '#fa8f79', '#f9856e', '#f77b66', '#f5715d', '#f36754', '#f05c4d', '#ec5049', '#e74545', '#e13b42', '#da323f', '#d3293d', '#ca223c', '#c11a3b', '#b8163a', '#ae123a'],
chartColors: ['#f4d166', '#f6c760', '#f8bc58', '#f8b252', '#f7a84a', '#f69e41', '#f49538', '#f38b2f', '#f28026', '#f0751e', '#eb6c1c', '#e4641e', '#de5d1f', '#d75521', '#cf4f22', '#c64a22', '#bc4623', '#b24223', '#a83e24', '#9e3a26']
};

@ -55,6 +55,7 @@ namespace NzbDrone.Core.Configuration
string PostgresPassword { get; }
string PostgresMainDb { get; }
string PostgresLogDb { get; }
string Theme { get; }
}
public class ConfigFileProvider : IConfigFileProvider
@ -199,6 +200,7 @@ namespace NzbDrone.Core.Configuration
public string PostgresPassword => 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);

@ -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",

@ -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<UiConfigResource>
{
public UiConfigController(IConfigService configService)
private readonly IConfigFileProvider _configFileProvider;
public UiConfigController(IConfigFileProvider configFileProvider, IConfigService configService)
: base(configService)
{
_configFileProvider = configFileProvider;
}
[RestPutById]
public override ActionResult<UiConfigResource> 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);
}
}
}

@ -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
};
}
}

@ -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()}',");

Loading…
Cancel
Save