Added redux. Moved theme loading/setting to redux actions. Added automatic theme loading based on localStorage value.

This commit is contained in:
unknown 2021-05-08 18:49:08 +02:00
parent 765418d3d2
commit 7199e296b8
14 changed files with 203 additions and 39 deletions

View file

@ -2290,6 +2290,15 @@
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz", "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz",
"integrity": "sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==" "integrity": "sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA=="
}, },
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"@types/html-minifier-terser": { "@types/html-minifier-terser": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz",
@ -2388,6 +2397,17 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"@types/react-redux": {
"version": "7.1.16",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.16.tgz",
"integrity": "sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==",
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"@types/react-router": { "@types/react-router": {
"version": "5.1.14", "version": "5.1.14",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.14.tgz", "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.14.tgz",
@ -12541,6 +12561,19 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"react-redux": {
"version": "7.2.4",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz",
"integrity": "sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==",
"requires": {
"@babel/runtime": "^7.12.1",
"@types/react-redux": "^7.1.16",
"hoist-non-react-statics": "^3.3.2",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
"react-is": "^16.13.1"
}
},
"react-refresh": { "react-refresh": {
"version": "0.8.3", "version": "0.8.3",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
@ -12773,6 +12806,24 @@
"strip-indent": "^3.0.0" "strip-indent": "^3.0.0"
} }
}, },
"redux": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.1.0.tgz",
"integrity": "sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==",
"requires": {
"@babel/runtime": "^7.9.2"
}
},
"redux-devtools-extension": {
"version": "2.13.9",
"resolved": "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz",
"integrity": "sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A=="
},
"redux-thunk": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
"integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw=="
},
"regenerate": { "regenerate": {
"version": "1.4.2", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",

View file

@ -12,11 +12,16 @@
"@types/node": "^12.20.12", "@types/node": "^12.20.12",
"@types/react": "^17.0.5", "@types/react": "^17.0.5",
"@types/react-dom": "^17.0.3", "@types/react-dom": "^17.0.3",
"@types/react-redux": "^7.1.16",
"@types/react-router-dom": "^5.1.7", "@types/react-router-dom": "^5.1.7",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-redux": "^7.2.4",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",
"redux": "^4.1.0",
"redux-devtools-extension": "^2.13.9",
"redux-thunk": "^2.3.0",
"typescript": "^4.2.4", "typescript": "^4.2.4",
"web-vitals": "^1.1.2" "web-vitals": "^1.1.2"
}, },

View file

@ -1,19 +0,0 @@
.SettingsButton {
width: 35px;
height: 35px;
background-color: var(--color-accent);
border-radius: 50%;
position: absolute;
bottom: 10px;
left: 10px;
display: flex;
justify-content: center;
align-items: center;
opacity: 0.25;
transition: all 0.3s;
}
.SettingsButton:hover {
cursor: pointer;
opacity: 1;
}

View file

@ -1,23 +1,30 @@
import { BrowserRouter, Route, Switch, Link } from 'react-router-dom'; import { BrowserRouter, Route, Switch } from 'react-router-dom';
import { setTheme } from './store/actions';
// Redux
import store from './store/store';
import { Provider } from 'react-redux';
import classes from './App.module.css'; import classes from './App.module.css';
import Home from './components/Home/Home';
import Apps from './components/Apps/Apps'; import Apps from './components/Apps/Apps';
import Settings from './components/Settings/Settings'; import Settings from './components/Settings/Settings';
import Icon from './components/UI/Icon/Icon'; if (localStorage.theme) {
store.dispatch(setTheme(localStorage.theme));
}
const App = (): JSX.Element => { const App = (): JSX.Element => {
return ( return (
<BrowserRouter> <Provider store={store}>
<Switch> <BrowserRouter>
<Route exact path='/' component={Apps} /> <Switch>
<Route exact path='/settings' component={Settings} /> <Route exact path='/' component={Home} />
</Switch> <Route exact path='/settings' component={Settings} />
<Link to='/settings' className={classes.SettingsButton}> </Switch>
<Icon icon='mdiCog' /> </BrowserRouter>
</Link> </Provider>
</BrowserRouter>
); );
} }

View file

@ -0,0 +1,19 @@
.SettingsButton {
width: 35px;
height: 35px;
background-color: var(--color-accent);
border-radius: 50%;
position: absolute;
bottom: 10px;
left: 10px;
display: flex;
justify-content: center;
align-items: center;
opacity: 0.25;
transition: all 0.3s;
}
.SettingsButton:hover {
cursor: pointer;
opacity: 1;
}

View file

@ -0,0 +1,29 @@
import { Link } from 'react-router-dom';
import Icon from '../UI/Icon/Icon';
import classes from './Home.module.css';
import { Container } from '../UI/Layout/Layout';
import Headline from '../UI/Headline/Headline';
const Home = (): JSX.Element => {
const dateAndTime = (): string => {
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const date = new Date();
return `${days[date.getDay()]}, ${date.getDate()} of ${months[date.getMonth()]} ${date.getFullYear()}`;
}
return (
<Container>
<Headline title='Home' subtitle={dateAndTime()} />
<Link to='/settings' className={classes.SettingsButton}>
<Icon icon='mdiCog' />
</Link>
</Container>
)
}
export default Home;

View file

@ -3,7 +3,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: flex-start;
} }
.ThemePreview:hover { .ThemePreview:hover {
@ -13,6 +13,7 @@
.ThemePreview p { .ThemePreview p {
text-transform: capitalize; text-transform: capitalize;
margin: 8px 0; margin: 8px 0;
color: var(--color-primary);
/* align-self: flex-start; */ /* align-self: flex-start; */
} }

View file

@ -1,4 +1,5 @@
import { useEffect } from 'react'; import { useEffect, Fragment } from 'react';
import { connect } from 'react-redux';
import classes from './Themer.module.css'; import classes from './Themer.module.css';
@ -8,7 +9,13 @@ import ThemePreview from './ThemePreview';
import { Container } from '../UI/Layout/Layout'; import { Container } from '../UI/Layout/Layout';
import Headline from '../UI/Headline/Headline'; import Headline from '../UI/Headline/Headline';
const Themer = (): JSX.Element => { import { setTheme } from '../../store/actions';
interface ComponentProps {
setTheme: Function;
}
const Themer = (props: ComponentProps): JSX.Element => {
useEffect((): void => { useEffect((): void => {
if (localStorage.theme) { if (localStorage.theme) {
applyTheme(localStorage.theme); applyTheme(localStorage.theme);
@ -27,19 +34,20 @@ const Themer = (): JSX.Element => {
} }
return ( return (
<Container> <Fragment>
<div> <div>
<Headline {/* <Headline
title='Themes' title='Themes'
subtitle='Select new theme by clicking on it' subtitle='Select new theme by clicking on it'
/> /> */}
<div className={classes.ThemerGrid}> <div className={classes.ThemerGrid}>
{themes.map((theme: Theme): JSX.Element => <ThemePreview theme={theme} applyTheme={applyTheme} />)} {themes.map((theme: Theme): JSX.Element => <ThemePreview theme={theme} applyTheme={props.setTheme} />)}
</div> </div>
</div> </div>
</Container> </Fragment>
) )
} }
export default Themer;
export default connect(null, { setTheme })(Themer);

View file

@ -0,0 +1 @@
export const SET_THEME = 'SET_THEME';

View file

@ -0,0 +1,2 @@
export * from './theme';
export * from './actionTypes';

View file

@ -0,0 +1,24 @@
import { Dispatch } from 'redux';
import { themes } from '../../components/Themer/themes.json';
import { Theme } from '../../interfaces/Theme';
import { SET_THEME } from './actionTypes';
export const setTheme = (themeName: string) => (dispatch: Dispatch) => {
const theme = themes.find((theme: Theme) => theme.name === themeName);
if (theme) {
localStorage.setItem('theme', themeName);
loadTheme(theme);
dispatch({
type: SET_THEME,
payload: themeName
})
}
}
export const loadTheme = (theme: Theme) => {
for (const [key, value] of Object.entries(theme.colors)) {
document.body.style.setProperty(`--color-${key}`, value);
}
}

View file

@ -0,0 +1,9 @@
import { combineReducers } from 'redux';
import themeReducer from './theme';
const rootReducer = combineReducers({
theme: themeReducer
})
export default rootReducer;

View file

@ -0,0 +1,18 @@
import { SET_THEME } from '../actions/actionTypes';
const initialState = {
theme: 'blues'
}
const themeReducer = (state = initialState, action: any) => {
switch (action.type) {
case SET_THEME:
return {
theme: action.payload
};
default:
return state;
}
}
export default themeReducer;

View file

@ -0,0 +1,9 @@
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const store = createStore(rootReducer, initialState, composeWithDevTools(applyMiddleware(thunk)));
export default store;