Changed how theme is set and stored on client
This commit is contained in:
parent
e427fbf54c
commit
89bd921875
12 changed files with 92 additions and 191 deletions
|
@ -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]);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
export interface Theme {
|
||||
name: string;
|
||||
colors: {
|
||||
export interface ThemeColors {
|
||||
background: string;
|
||||
primary: string;
|
||||
accent: string;
|
||||
}
|
||||
|
||||
export interface Theme {
|
||||
name: string;
|
||||
colors: ThemeColors;
|
||||
}
|
|
@ -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 (theme) {
|
||||
if (remeberTheme) {
|
||||
localStorage.setItem('theme', name);
|
||||
localStorage.setItem('theme', parseThemeToPAB(colors));
|
||||
}
|
||||
|
||||
loadTheme(theme);
|
||||
|
||||
dispatch({
|
||||
type: ActionType.setTheme,
|
||||
payload: theme,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const loadTheme = (theme: Theme): void => {
|
||||
for (const [key, value] of Object.entries(theme.colors)) {
|
||||
for (const [key, value] of Object.entries(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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export enum ActionType {
|
||||
// THEME
|
||||
setTheme = 'SET_THEME',
|
||||
fetchThemes = 'FETCH_THEMES',
|
||||
// CONFIG
|
||||
getConfig = 'GET_CONFIG',
|
||||
updateConfig = 'UPDATE_CONFIG',
|
||||
|
|
|
@ -3,5 +3,9 @@ import { Theme } from '../../interfaces';
|
|||
|
||||
export interface SetThemeAction {
|
||||
type: ActionType.setTheme;
|
||||
payload: Theme;
|
||||
}
|
||||
|
||||
export interface FetchThemesAction {
|
||||
type: ActionType.fetchThemes;
|
||||
payload: Theme[];
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -12,3 +12,4 @@ export * from './parseTime';
|
|||
export * from './decodeToken';
|
||||
export * from './applyAuth';
|
||||
export * from './escapeRegex';
|
||||
export * from './parseTheme';
|
||||
|
|
20
client/src/utility/parseTheme.ts
Normal file
20
client/src/utility/parseTheme.ts
Normal 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}`;
|
||||
};
|
Loading…
Reference in a new issue