Changed how theme is set and stored on client

This commit is contained in:
Paweł Malak 2022-03-23 14:49:35 +01:00
parent e427fbf54c
commit 89bd921875
12 changed files with 92 additions and 191 deletions

View file

@ -10,7 +10,7 @@ import { actionCreators, store } from './store';
import { State } from './store/reducers';
// Utils
import { checkVersion, decodeToken } from './utility';
import { checkVersion, decodeToken, parsePABToTheme } from './utility';
// Routes
import { Home } from './components/Home/Home';
@ -31,7 +31,7 @@ export const App = (): JSX.Element => {
const { config, loading } = useSelector((state: State) => state.config);
const dispath = useDispatch();
const { fetchQueries, setTheme, logout, createNotification } =
const { fetchQueries, setTheme, logout, createNotification, fetchThemes } =
bindActionCreators(actionCreators, dispath);
useEffect(() => {
@ -51,9 +51,12 @@ export const App = (): JSX.Element => {
}
}, 1000);
// load themes
fetchThemes();
// set user theme if present
if (localStorage.theme) {
setTheme(localStorage.theme);
setTheme(parsePABToTheme(localStorage.theme));
}
// check for updated
@ -68,7 +71,7 @@ export const App = (): JSX.Element => {
// If there is no user theme, set the default one
useEffect(() => {
if (!loading && !localStorage.theme) {
setTheme(config.defaultTheme, false);
setTheme(parsePABToTheme(config.defaultTheme), false);
}
}, [loading]);

View file

@ -1,8 +1,3 @@
// Redux
import { useDispatch } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actionCreators } from '../../../../store';
// Components
import { ThemePreview } from '../ThemePreview/ThemePreview';
@ -15,14 +10,11 @@ interface Props {
}
export const ThemeGrid = ({ themes }: Props): JSX.Element => {
const dispatch = useDispatch();
const { setTheme } = bindActionCreators(actionCreators, dispatch);
return (
<div className={classes.ThemerGrid}>
{themes.map(
(theme: Theme, idx: number): JSX.Element => (
<ThemePreview key={idx} theme={theme} applyTheme={setTheme} />
<ThemePreview key={idx} theme={theme} />
)
)}
</div>

View file

@ -1,32 +1,38 @@
// Redux
import { useDispatch } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actionCreators } from '../../../../store';
// Other
import { Theme } from '../../../../interfaces/Theme';
import classes from './ThemePreview.module.css';
interface Props {
theme: Theme;
applyTheme: Function;
}
export const ThemePreview = (props: Props): JSX.Element => {
export const ThemePreview = ({
theme: { colors, name },
}: Props): JSX.Element => {
const { setTheme } = bindActionCreators(actionCreators, useDispatch());
return (
<div
className={classes.ThemePreview}
onClick={() => props.applyTheme(props.theme.name)}
>
<div className={classes.ThemePreview} onClick={() => setTheme(colors)}>
<div className={classes.ColorsPreview}>
<div
className={classes.ColorPreview}
style={{ backgroundColor: props.theme.colors.background }}
style={{ backgroundColor: colors.background }}
></div>
<div
className={classes.ColorPreview}
style={{ backgroundColor: props.theme.colors.primary }}
style={{ backgroundColor: colors.primary }}
></div>
<div
className={classes.ColorPreview}
style={{ backgroundColor: props.theme.colors.accent }}
style={{ backgroundColor: colors.accent }}
></div>
</div>
<p>{props.theme.name}</p>
<p>{name}</p>
</div>
);
};

View file

@ -9,12 +9,11 @@ import { actionCreators } from '../../../store';
import { Theme, ThemeSettingsForm } from '../../../interfaces';
// Components
import { Button, InputGroup, SettingsHeadline } from '../../UI';
import { Button, InputGroup, SettingsHeadline, Spinner } from '../../UI';
import { ThemeBuilder } from './ThemeBuilder/ThemeBuilder';
import { ThemeGrid } from './ThemeGrid/ThemeGrid';
// Other
import { themes } from './themes.json';
import { State } from '../../../store/reducers';
import { inputHandler, themeSettingsTemplate } from '../../../utility';
@ -22,6 +21,7 @@ export const Themer = (): JSX.Element => {
const {
auth: { isAuthenticated },
config: { loading, config },
theme: { themes },
} = useSelector((state: State) => state);
const dispatch = useDispatch();
@ -63,10 +63,10 @@ export const Themer = (): JSX.Element => {
return (
<Fragment>
<SettingsHeadline text="App themes" />
<ThemeGrid themes={themes} />
{!themes.length ? <Spinner /> : <ThemeGrid themes={themes} />}
<SettingsHeadline text="User themes" />
<ThemeBuilder />
{/* <SettingsHeadline text="User themes" />
<ThemeBuilder /> */}
{isAuthenticated && (
<form onSubmit={formSubmitHandler}>

View file

@ -1,124 +0,0 @@
{
"themes": [
{
"name": "blackboard",
"colors": {
"background": "#1a1a1a",
"primary": "#FFFDEA",
"accent": "#5c5c5c"
}
},
{
"name": "gazette",
"colors": {
"background": "#F2F7FF",
"primary": "#000000",
"accent": "#5c5c5c"
}
},
{
"name": "espresso",
"colors": {
"background": "#21211F",
"primary": "#D1B59A",
"accent": "#4E4E4E"
}
},
{
"name": "cab",
"colors": {
"background": "#F6D305",
"primary": "#1F1F1F",
"accent": "#424242"
}
},
{
"name": "cloud",
"colors": {
"background": "#f1f2f0",
"primary": "#35342f",
"accent": "#37bbe4"
}
},
{
"name": "lime",
"colors": {
"background": "#263238",
"primary": "#AABBC3",
"accent": "#aeea00"
}
},
{
"name": "white",
"colors": {
"background": "#ffffff",
"primary": "#222222",
"accent": "#dddddd"
}
},
{
"name": "tron",
"colors": {
"background": "#242B33",
"primary": "#EFFBFF",
"accent": "#6EE2FF"
}
},
{
"name": "blues",
"colors": {
"background": "#2B2C56",
"primary": "#EFF1FC",
"accent": "#6677EB"
}
},
{
"name": "passion",
"colors": {
"background": "#f5f5f5",
"primary": "#12005e",
"accent": "#8e24aa"
}
},
{
"name": "chalk",
"colors": {
"background": "#263238",
"primary": "#AABBC3",
"accent": "#FF869A"
}
},
{
"name": "paper",
"colors": {
"background": "#F8F6F1",
"primary": "#4C432E",
"accent": "#AA9A73"
}
},
{
"name": "neon",
"colors": {
"background": "#091833",
"primary": "#EFFBFF",
"accent": "#ea00d9"
}
},
{
"name": "pumpkin",
"colors": {
"background": "#2d3436",
"primary": "#EFFBFF",
"accent": "#ffa500"
}
},
{
"name": "onedark",
"colors": {
"background": "#282c34",
"primary": "#dfd9d6",
"accent": "#98c379"
}
}
]
}

View file

@ -1,8 +1,10 @@
export interface ThemeColors {
background: string;
primary: string;
accent: string;
}
export interface Theme {
name: string;
colors: {
background: string;
primary: string;
accent: string;
}
}
colors: ThemeColors;
}

View file

@ -1,30 +1,32 @@
import { Dispatch } from 'redux';
import { SetThemeAction } from '../actions/theme';
import { FetchThemesAction, SetThemeAction } from '../actions/theme';
import { ActionType } from '../action-types';
import { Theme } from '../../interfaces/Theme';
import { themes } from '../../components/Settings/Themer/themes.json';
import { Theme, ApiResponse, ThemeColors } from '../../interfaces';
import { parseThemeToPAB } from '../../utility';
import axios from 'axios';
export const setTheme =
(name: string, remeberTheme: boolean = true) =>
(colors: ThemeColors, remeberTheme: boolean = true) =>
(dispatch: Dispatch<SetThemeAction>) => {
const theme = themes.find((theme) => theme.name === name);
if (remeberTheme) {
localStorage.setItem('theme', parseThemeToPAB(colors));
}
if (theme) {
if (remeberTheme) {
localStorage.setItem('theme', name);
}
loadTheme(theme);
dispatch({
type: ActionType.setTheme,
payload: theme,
});
for (const [key, value] of Object.entries(colors)) {
document.body.style.setProperty(`--color-${key}`, value);
}
};
export const loadTheme = (theme: Theme): void => {
for (const [key, value] of Object.entries(theme.colors)) {
document.body.style.setProperty(`--color-${key}`, value);
}
};
export const fetchThemes =
() => async (dispatch: Dispatch<FetchThemesAction>) => {
try {
const res = await axios.get<ApiResponse<Theme[]>>('/api/themes');
dispatch({
type: ActionType.fetchThemes,
payload: res.data.data,
});
} catch (err) {
console.log(err);
}
};

View file

@ -1,6 +1,7 @@
export enum ActionType {
// THEME
setTheme = 'SET_THEME',
fetchThemes = 'FETCH_THEMES',
// CONFIG
getConfig = 'GET_CONFIG',
updateConfig = 'UPDATE_CONFIG',

View file

@ -3,5 +3,9 @@ import { Theme } from '../../interfaces';
export interface SetThemeAction {
type: ActionType.setTheme;
payload: Theme;
}
export interface FetchThemesAction {
type: ActionType.fetchThemes;
payload: Theme[];
}

View file

@ -3,18 +3,11 @@ import { ActionType } from '../action-types';
import { Theme } from '../../interfaces/Theme';
interface ThemeState {
theme: Theme;
themes: Theme[];
}
const initialState: ThemeState = {
theme: {
name: 'tron',
colors: {
background: '#242B33',
primary: '#EFFBFF',
accent: '#6EE2FF',
},
},
themes: [],
};
export const themeReducer = (
@ -22,8 +15,9 @@ export const themeReducer = (
action: Action
): ThemeState => {
switch (action.type) {
case ActionType.setTheme:
return { theme: action.payload };
case ActionType.fetchThemes: {
return { themes: action.payload };
}
default:
return state;

View file

@ -12,3 +12,4 @@ export * from './parseTime';
export * from './decodeToken';
export * from './applyAuth';
export * from './escapeRegex';
export * from './parseTheme';

View file

@ -0,0 +1,20 @@
import { ThemeColors } from '../interfaces';
// parse theme in PAB (primary;accent;background) format to theme colors object
export const parsePABToTheme = (themeStr: string): ThemeColors => {
const [primary, accent, background] = themeStr.split(';');
return {
primary,
accent,
background,
};
};
export const parseThemeToPAB = ({
primary: p,
accent: a,
background: b,
}: ThemeColors): string => {
return `${p};${a};${b}`;
};