mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-17 17:14:18 +02:00
New: Native Theme Engine
This commit is contained in:
@@ -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>
|
||||
|
49
frontend/src/App/ApplyTheme.js
Normal file
49
frontend/src/App/ApplyTheme.js
Normal file
@@ -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
|
||||
|
5
frontend/src/Styles/Themes/index.js
Normal file
5
frontend/src/Styles/Themes/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as light from './light';
|
||||
|
||||
export default {
|
||||
light
|
||||
};
|
191
frontend/src/Styles/Themes/light.js
Normal file
191
frontend/src/Styles/Themes/light.js
Normal file
@@ -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()}',");
|
||||
|
Reference in New Issue
Block a user