Moved Themer to Settings. Added option to set default theme

This commit is contained in:
Paweł Malak 2021-11-18 13:47:27 +01:00
parent 426766225b
commit e2285e2deb
20 changed files with 156 additions and 54 deletions

View file

@ -1,4 +1,5 @@
### v2.0.1 (TBA) ### v2.0.1 (TBA)
- Added option to set default theme for all new users ([#165](https://github.com/pawelmalak/flame/issues/165))
- Fixed bug with custom icons not working with apps when "pin by default" was disabled - Fixed bug with custom icons not working with apps when "pin by default" was disabled
### v2.0.0 (2021-11-15) ### v2.0.0 (2021-11-15)

View file

@ -1,7 +1,13 @@
import { useEffect } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { BrowserRouter, Route, Switch } from 'react-router-dom';
import 'external-svg-loader';
// Redux
import { useDispatch, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { autoLogin, getConfig } from './store/action-creators'; import { autoLogin, getConfig } from './store/action-creators';
import { actionCreators, store } from './store'; import { actionCreators, store } from './store';
import 'external-svg-loader'; import { State } from './store/reducers';
// Utils // Utils
import { checkVersion, decodeToken } from './utility'; import { checkVersion, decodeToken } from './utility';
@ -12,9 +18,6 @@ import { Apps } from './components/Apps/Apps';
import { Settings } from './components/Settings/Settings'; import { Settings } from './components/Settings/Settings';
import { Bookmarks } from './components/Bookmarks/Bookmarks'; import { Bookmarks } from './components/Bookmarks/Bookmarks';
import { NotificationCenter } from './components/NotificationCenter/NotificationCenter'; import { NotificationCenter } from './components/NotificationCenter/NotificationCenter';
import { useDispatch } from 'react-redux';
import { bindActionCreators } from 'redux';
import { useEffect } from 'react';
// Get config // Get config
store.dispatch<any>(getConfig()); store.dispatch<any>(getConfig());
@ -25,6 +28,8 @@ if (localStorage.token) {
} }
export const App = (): JSX.Element => { export const App = (): JSX.Element => {
const { config, loading } = useSelector((state: State) => state.config);
const dispath = useDispatch(); const dispath = useDispatch();
const { fetchQueries, setTheme, logout, createNotification } = const { fetchQueries, setTheme, logout, createNotification } =
bindActionCreators(actionCreators, dispath); bindActionCreators(actionCreators, dispath);
@ -46,7 +51,7 @@ export const App = (): JSX.Element => {
} }
}, 1000); }, 1000);
// set theme // set user theme if present
if (localStorage.theme) { if (localStorage.theme) {
setTheme(localStorage.theme); setTheme(localStorage.theme);
} }
@ -60,6 +65,13 @@ export const App = (): JSX.Element => {
return () => window.clearInterval(tokenIsValid); return () => window.clearInterval(tokenIsValid);
}, []); }, []);
// If there is no user theme, set the default one
useEffect(() => {
if (!loading && !localStorage.theme) {
setTheme(config.defaultTheme);
}
}, [loading]);
return ( return (
<> <>
<BrowserRouter> <BrowserRouter>

View file

@ -59,7 +59,7 @@ export const DockerSettings = (): JSX.Element => {
<SettingsHeadline text="Docker" /> <SettingsHeadline text="Docker" />
{/* CUSTOM DOCKER SOCKET HOST */} {/* CUSTOM DOCKER SOCKET HOST */}
<InputGroup> <InputGroup>
<label htmlFor="dockerHost">Docker Host</label> <label htmlFor="dockerHost">Docker host</label>
<input <input
type="text" type="text"
id="dockerHost" id="dockerHost"

View file

@ -70,7 +70,7 @@ export const SearchSettings = (): JSX.Element => {
> >
<SettingsHeadline text="General" /> <SettingsHeadline text="General" />
<InputGroup> <InputGroup>
<label htmlFor="defaultSearchProvider">Default Search Provider</label> <label htmlFor="defaultSearchProvider">Default search provider</label>
<select <select
id="defaultSearchProvider" id="defaultSearchProvider"
name="defaultSearchProvider" name="defaultSearchProvider"

View file

@ -11,7 +11,7 @@ import { Route as SettingsRoute } from '../../interfaces';
import classes from './Settings.module.css'; import classes from './Settings.module.css';
// Components // Components
import { Themer } from '../Themer/Themer'; import { Themer } from './Themer/Themer';
import { WeatherSettings } from './WeatherSettings/WeatherSettings'; import { WeatherSettings } from './WeatherSettings/WeatherSettings';
import { UISettings } from './UISettings/UISettings'; import { UISettings } from './UISettings/UISettings';
import { AppDetails } from './AppDetails/AppDetails'; import { AppDetails } from './AppDetails/AppDetails';

View file

@ -14,7 +14,6 @@
text-transform: capitalize; text-transform: capitalize;
margin: 8px 0; margin: 8px 0;
color: var(--color-primary); color: var(--color-primary);
/* align-self: flex-start; */
} }
.ColorsPreview { .ColorsPreview {
@ -32,4 +31,4 @@
width: 40px; width: 40px;
height: 40px; height: 40px;
} }
} }

View file

@ -1,4 +1,4 @@
import { Theme } from '../../interfaces/Theme'; import { Theme } from '../../../interfaces/Theme';
import classes from './ThemePreview.module.css'; import classes from './ThemePreview.module.css';
interface Props { interface Props {

View file

@ -0,0 +1,101 @@
import { ChangeEvent, FormEvent, Fragment, useEffect, useState } from 'react';
// Redux
import { useDispatch, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actionCreators } from '../../../store';
// Typescript
import { Theme, ThemeSettingsForm } from '../../../interfaces';
// Components
import { ThemePreview } from './ThemePreview';
import { Button, InputGroup, SettingsHeadline } from '../../UI';
// Other
import classes from './Themer.module.css';
import { themes } from './themes.json';
import { State } from '../../../store/reducers';
import { inputHandler, themeSettingsTemplate } from '../../../utility';
export const Themer = (): JSX.Element => {
const {
auth: { isAuthenticated },
config: { loading, config },
} = useSelector((state: State) => state);
const dispatch = useDispatch();
const { setTheme, updateConfig } = bindActionCreators(
actionCreators,
dispatch
);
// Initial state
const [formData, setFormData] = useState<ThemeSettingsForm>(
themeSettingsTemplate
);
// Get config
useEffect(() => {
setFormData({
...config,
});
}, [loading]);
// Form handler
const formSubmitHandler = async (e: FormEvent) => {
e.preventDefault();
// Save settings
await updateConfig(formData);
};
// Input handler
const inputChangeHandler = (
e: ChangeEvent<HTMLInputElement | HTMLSelectElement>,
options?: { isNumber?: boolean; isBool?: boolean }
) => {
inputHandler<ThemeSettingsForm>({
e,
options,
setStateHandler: setFormData,
state: formData,
});
};
return (
<Fragment>
<SettingsHeadline text="Set theme" />
<div className={classes.ThemerGrid}>
{themes.map(
(theme: Theme, idx: number): JSX.Element => (
<ThemePreview key={idx} theme={theme} applyTheme={setTheme} />
)
)}
</div>
{isAuthenticated && (
<form onSubmit={formSubmitHandler}>
<SettingsHeadline text="Other settings" />
<InputGroup>
<label htmlFor="defaultTheme">Default theme (for new users)</label>
<select
id="defaultTheme"
name="defaultTheme"
value={formData.defaultTheme}
onChange={(e) => inputChangeHandler(e)}
>
{themes.map((theme: Theme, idx) => (
<option key={idx} value={theme.name}>
{theme.name}
</option>
))}
</select>
</InputGroup>
<Button>Save changes</Button>
</form>
)}
</Fragment>
);
};

View file

@ -1,29 +0,0 @@
import { Fragment } from 'react';
import { useDispatch } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actionCreators } from '../../store';
import classes from './Themer.module.css';
import { themes } from './themes.json';
import { Theme } from '../../interfaces/Theme';
import { ThemePreview } from './ThemePreview';
export const Themer = (): JSX.Element => {
const dispatch = useDispatch();
const { setTheme } = bindActionCreators(actionCreators, dispatch);
return (
<Fragment>
<div>
<div className={classes.ThemerGrid}>
{themes.map(
(theme: Theme, idx: number): JSX.Element => (
<ThemePreview key={idx} theme={theme} applyTheme={setTheme} />
)
)}
</div>
</div>
</Fragment>
);
};

View file

@ -25,4 +25,5 @@ export interface Config {
daySchema: string; daySchema: string;
monthSchema: string; monthSchema: string;
showTime: boolean; showTime: boolean;
defaultTheme: string;
} }

View file

@ -35,3 +35,7 @@ export interface DockerSettingsForm {
kubernetesApps: boolean; kubernetesApps: boolean;
unpinStoppedApps: boolean; unpinStoppedApps: boolean;
} }
export interface ThemeSettingsForm {
defaultTheme: string;
}

View file

@ -8,17 +8,10 @@ import {
UpdateQueryAction, UpdateQueryAction,
} from '../actions/config'; } from '../actions/config';
import axios from 'axios'; import axios from 'axios';
import { import { ApiResponse, Config, Query } from '../../interfaces';
ApiResponse,
Config,
DockerSettingsForm,
OtherSettingsForm,
Query,
SearchForm,
WeatherForm,
} from '../../interfaces';
import { ActionType } from '../action-types'; import { ActionType } from '../action-types';
import { storeUIConfig, applyAuth } from '../../utility'; import { storeUIConfig, applyAuth } from '../../utility';
import { ConfigFormData } from '../../types';
const keys: (keyof Config)[] = [ const keys: (keyof Config)[] = [
'useAmericanDate', 'useAmericanDate',
@ -50,9 +43,7 @@ export const getConfig = () => async (dispatch: Dispatch<GetConfigAction>) => {
}; };
export const updateConfig = export const updateConfig =
( (formData: ConfigFormData) =>
formData: WeatherForm | OtherSettingsForm | SearchForm | DockerSettingsForm
) =>
async (dispatch: Dispatch<UpdateConfigAction>) => { async (dispatch: Dispatch<UpdateConfigAction>) => {
try { try {
const res = await axios.put<ApiResponse<Config>>( const res = await axios.put<ApiResponse<Config>>(

View file

@ -2,7 +2,7 @@ import { Dispatch } from 'redux';
import { SetThemeAction } from '../actions/theme'; import { SetThemeAction } from '../actions/theme';
import { ActionType } from '../action-types'; import { ActionType } from '../action-types';
import { Theme } from '../../interfaces/Theme'; import { Theme } from '../../interfaces/Theme';
import { themes } from '../../components/Themer/themes.json'; import { themes } from '../../components/Settings/Themer/themes.json';
export const setTheme = export const setTheme =
(name: string) => (dispatch: Dispatch<SetThemeAction>) => { (name: string) => (dispatch: Dispatch<SetThemeAction>) => {

View file

@ -0,0 +1,14 @@
import {
DockerSettingsForm,
OtherSettingsForm,
SearchForm,
ThemeSettingsForm,
WeatherForm,
} from '../interfaces';
export type ConfigFormData =
| WeatherForm
| SearchForm
| DockerSettingsForm
| OtherSettingsForm
| ThemeSettingsForm;

View file

@ -0,0 +1 @@
export * from './ConfigFormData';

View file

@ -28,4 +28,5 @@ export const configTemplate: Config = {
monthSchema: monthSchema:
'January;February;March;April;May;June;July;August;September;October;November;December', 'January;February;March;April;May;June;July;August;September;October;November;December',
showTime: false, showTime: false,
defaultTheme: 'tron',
}; };

View file

@ -2,6 +2,7 @@ import {
DockerSettingsForm, DockerSettingsForm,
OtherSettingsForm, OtherSettingsForm,
SearchForm, SearchForm,
ThemeSettingsForm,
WeatherForm, WeatherForm,
} from '../../interfaces'; } from '../../interfaces';
@ -43,3 +44,7 @@ export const dockerSettingsTemplate: DockerSettingsForm = {
kubernetesApps: true, kubernetesApps: true,
unpinStoppedApps: true, unpinStoppedApps: true,
}; };
export const themeSettingsTemplate: ThemeSettingsForm = {
defaultTheme: 'tron',
};

View file

@ -24,5 +24,6 @@
"greetingsSchema": "Good evening!;Good afternoon!;Good morning!;Good night!", "greetingsSchema": "Good evening!;Good afternoon!;Good morning!;Good night!",
"daySchema": "Sunday;Monday;Tuesday;Wednesday;Thursday;Friday;Saturday", "daySchema": "Sunday;Monday;Tuesday;Wednesday;Thursday;Friday;Saturday",
"monthSchema": "January;February;March;April;May;June;July;August;September;October;November;December", "monthSchema": "January;February;March;April;May;June;July;August;September;October;November;December",
"showTime": false "showTime": false,
"defaultTheme": "tron"
} }