mirror of
https://github.com/Sonarr/Sonarr.git
synced 2024-10-29 23:12:39 +01:00
parent
f81bb3ec19
commit
cae134ec7b
@ -12,11 +12,10 @@ function App({ store, history }) {
|
||||
<DocumentTitle title={window.Sonarr.instanceName}>
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<ApplyTheme>
|
||||
<PageConnector>
|
||||
<AppRoutes app={App} />
|
||||
</PageConnector>
|
||||
</ApplyTheme>
|
||||
<ApplyTheme />
|
||||
<PageConnector>
|
||||
<AppRoutes app={App} />
|
||||
</PageConnector>
|
||||
</ConnectedRouter>
|
||||
</Provider>
|
||||
</DocumentTitle>
|
||||
|
@ -1,50 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Fragment, useCallback, 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.Sonarr.theme,
|
||||
(
|
||||
theme
|
||||
) => {
|
||||
return {
|
||||
theme
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function ApplyTheme({ theme, children }) {
|
||||
// Update the CSS Variables
|
||||
|
||||
const updateCSSVariables = useCallback(() => {
|
||||
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]
|
||||
);
|
||||
});
|
||||
}, [theme]);
|
||||
|
||||
// On Component Mount and Component Update
|
||||
useEffect(() => {
|
||||
updateCSSVariables(theme);
|
||||
}, [updateCSSVariables, theme]);
|
||||
|
||||
return <Fragment>{children}</Fragment>;
|
||||
}
|
||||
|
||||
ApplyTheme.propTypes = {
|
||||
theme: PropTypes.string.isRequired,
|
||||
children: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps)(ApplyTheme);
|
37
frontend/src/App/ApplyTheme.tsx
Normal file
37
frontend/src/App/ApplyTheme.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import React, { Fragment, ReactNode, useCallback, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import themes from 'Styles/Themes';
|
||||
import AppState from './State/AppState';
|
||||
|
||||
interface ApplyThemeProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
function createThemeSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.settings.ui.item.theme || window.Sonarr.theme,
|
||||
(theme) => {
|
||||
return theme;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function ApplyTheme({ children }: ApplyThemeProps) {
|
||||
const theme = useSelector(createThemeSelector());
|
||||
|
||||
const updateCSSVariables = useCallback(() => {
|
||||
Object.entries(themes[theme]).forEach(([key, value]) => {
|
||||
document.documentElement.style.setProperty(`--${key}`, value);
|
||||
});
|
||||
}, [theme]);
|
||||
|
||||
// On Component Mount and Component Update
|
||||
useEffect(() => {
|
||||
updateCSSVariables();
|
||||
}, [updateCSSVariables, theme]);
|
||||
|
||||
return <Fragment>{children}</Fragment>;
|
||||
}
|
||||
|
||||
export default ApplyTheme;
|
@ -2,7 +2,7 @@ import * as dark from './dark';
|
||||
import * as light from './light';
|
||||
|
||||
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const auto = defaultDark ? { ...dark } : { ...light };
|
||||
const auto = defaultDark ? dark : light;
|
||||
|
||||
export default {
|
||||
auto,
|
||||
|
@ -57,8 +57,8 @@
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: #f5f7fa;
|
||||
color: #656565;
|
||||
background-color: var(--pageBackground);
|
||||
color: var(--textColor);
|
||||
font-family: "Roboto", "open sans", "Helvetica Neue", Helvetica, Arial,
|
||||
sans-serif;
|
||||
}
|
||||
@ -88,14 +88,14 @@
|
||||
padding: 10px;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
background-color: #3a3f51;
|
||||
background-color: var(--themeDarkColor);
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
padding: 20px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
background-color: #fff;
|
||||
background-color: var(--panelBackground);
|
||||
}
|
||||
|
||||
.sign-in {
|
||||
@ -112,16 +112,17 @@
|
||||
padding: 6px 16px;
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
border: 1px solid #dde6e9;
|
||||
background-color: var(--inputBackgroundColor);
|
||||
border: 1px solid var(--inputBorderColor);
|
||||
border-radius: 4px;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
box-shadow: inset 0 1px 1px var(--inputBoxShadowColor);
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
outline: 0;
|
||||
border-color: #66afe9;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
|
||||
0 0 8px rgba(102, 175, 233, 0.6);
|
||||
border-color: var(--inputFocusBorderColor);
|
||||
box-shadow: inset 0 1px 1px var(--inputBoxShadowColor),
|
||||
0 0 8px var(--inputFocusBoxShadowColor);
|
||||
}
|
||||
|
||||
.button {
|
||||
@ -130,10 +131,10 @@
|
||||
padding: 10px 0;
|
||||
width: 100%;
|
||||
border: 1px solid;
|
||||
border-color: #5899eb;
|
||||
border-color: var(--primaryBorderColor);
|
||||
border-radius: 4px;
|
||||
background-color: #5d9cec;
|
||||
color: #fff;
|
||||
background-color: var(--primaryBackgroundColor);
|
||||
color: var(--white);
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
@ -141,9 +142,9 @@
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
border-color: #3483e7;
|
||||
background-color: #4b91ea;
|
||||
color: #fff;
|
||||
border-color: var(--primaryHoverBorderColor);
|
||||
background-color: var(--primaryHoverBackgroundColor);
|
||||
color: var(--white);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@ -165,24 +166,24 @@
|
||||
|
||||
.forgot-password {
|
||||
margin-left: auto;
|
||||
color: #909fa7;
|
||||
color: var(--forgotPasswordColor);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.forgot-password:focus,
|
||||
.forgot-password:hover {
|
||||
color: #748690;
|
||||
color: var(--forgotPasswordAltColor);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.forgot-password:visited {
|
||||
color: #748690;
|
||||
color: var(--forgotPasswordAltColor);
|
||||
}
|
||||
|
||||
.login-failed {
|
||||
margin-top: 20px;
|
||||
color: #f05050;
|
||||
color: var(--failedColor);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@ -291,5 +292,59 @@
|
||||
|
||||
loginFailedDiv.classList.remove("hidden");
|
||||
}
|
||||
|
||||
var light = {
|
||||
white: '#fff',
|
||||
pageBackground: '#f5f7fa',
|
||||
textColor: '#656565',
|
||||
themeDarkColor: '#3a3f51',
|
||||
panelBackground: '#fff',
|
||||
inputBackgroundColor: '#fff',
|
||||
inputBorderColor: '#dde6e9',
|
||||
inputBoxShadowColor: 'rgba(0, 0, 0, 0.075)',
|
||||
inputFocusBorderColor: '#66afe9',
|
||||
inputFocusBoxShadowColor: 'rgba(102, 175, 233, 0.6)',
|
||||
primaryBackgroundColor: '#5d9cec',
|
||||
primaryBorderColor: '#5899eb',
|
||||
primaryHoverBackgroundColor: '#4b91ea',
|
||||
primaryHoverBorderColor: '#3483e7',
|
||||
failedColor: '#f05050',
|
||||
forgotPasswordColor: '#909fa7',
|
||||
forgotPasswordAltColor: '#748690'
|
||||
};
|
||||
|
||||
var dark = {
|
||||
white: '#fff',
|
||||
pageBackground: '#202020',
|
||||
textColor: '#656565',
|
||||
themeDarkColor: '#494949',
|
||||
panelBackground: '#111',
|
||||
inputBackgroundColor: '#333',
|
||||
inputBorderColor: '#dde6e9',
|
||||
inputBoxShadowColor: 'rgba(0, 0, 0, 0.075)',
|
||||
inputFocusBorderColor: '#66afe9',
|
||||
inputFocusBoxShadowColor: 'rgba(102, 175, 233, 0.6)',
|
||||
primaryBackgroundColor: '#5d9cec',
|
||||
primaryBorderColor: '#5899eb',
|
||||
primaryHoverBackgroundColor: '#4b91ea',
|
||||
primaryHoverBorderColor: '#3483e7',
|
||||
failedColor: '#f05050',
|
||||
forgotPasswordColor: '#737d83',
|
||||
forgotPasswordAltColor: '#546067'
|
||||
};
|
||||
|
||||
var theme = "_THEME_";
|
||||
var defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
var finalTheme = theme === 'dark' || (theme === 'auto' && defaultDark) ?
|
||||
dark :
|
||||
light;
|
||||
|
||||
Object.entries(finalTheme).forEach(([key, value]) => {
|
||||
document.documentElement.style.setProperty(
|
||||
`--${key}`,
|
||||
value
|
||||
);
|
||||
});
|
||||
|
||||
</script>
|
||||
</html>
|
||||
|
@ -1,5 +1,5 @@
|
||||
export interface UiSettings {
|
||||
theme: string;
|
||||
theme: 'auto' | 'dark' | 'light';
|
||||
showRelativeDates: boolean;
|
||||
shortDateFormat: string;
|
||||
longDateFormat: string;
|
||||
|
@ -39,7 +39,7 @@ namespace Sonarr.Http.Frontend.Mappers
|
||||
return stream;
|
||||
}
|
||||
|
||||
protected string GetHtmlText()
|
||||
protected virtual string GetHtmlText()
|
||||
{
|
||||
if (RuntimeInfo.IsProduction && _generatedContent != null)
|
||||
{
|
||||
|
@ -9,6 +9,8 @@ namespace Sonarr.Http.Frontend.Mappers
|
||||
{
|
||||
public class LoginHtmlMapper : HtmlMapperBase
|
||||
{
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
|
||||
public LoginHtmlMapper(IAppFolderInfo appFolderInfo,
|
||||
IDiskProvider diskProvider,
|
||||
Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
|
||||
@ -16,6 +18,7 @@ namespace Sonarr.Http.Frontend.Mappers
|
||||
Logger logger)
|
||||
: base(diskProvider, cacheBreakProviderFactory, logger)
|
||||
{
|
||||
_configFileProvider = configFileProvider;
|
||||
HtmlPath = Path.Combine(appFolderInfo.StartUpFolder, configFileProvider.UiFolder, "login.html");
|
||||
UrlBase = configFileProvider.UrlBase;
|
||||
}
|
||||
@ -29,5 +32,15 @@ namespace Sonarr.Http.Frontend.Mappers
|
||||
{
|
||||
return resourceUrl.StartsWith("/login");
|
||||
}
|
||||
|
||||
protected override string GetHtmlText()
|
||||
{
|
||||
var html = base.GetHtmlText();
|
||||
var theme = _configFileProvider.Theme;
|
||||
|
||||
html = html.Replace("_THEME_", theme);
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user