Commit 7266140c authored by Florent Chehab's avatar Florent Chehab
Browse files

Merge branch 'cleaning' into 'master'

Cleaning

See merge request rex-dri/rex-dri!50
parents 0e42118e 4fe1bdf2
import Cookies from "js-cookie"; import Cookies from "js-cookie";
// TODO uodate parameters name to match new CRUD actions /**
* Class to perform network request and store the "status" in the redux store.
* The request are performed in a smart way to prevent multiple request to same endpoint at the same time.
*
* @export
* @class SmartActions
*/
export default class SmartActions { export default class SmartActions {
fetching = new Map(); fetching = new Map();
...@@ -22,51 +27,51 @@ export default class SmartActions { ...@@ -22,51 +27,51 @@ export default class SmartActions {
* Generic function for handling a GET request to the API * Generic function for handling a GET request to the API
* *
* @param {string} pk * @param {string} pk
* @param {string} api_end_point * @param {string} apiEndPoint
* @param {function} _IsLoading * @param {function} isReading
* @param {function} _FetchDataSuccess * @param {function} readDataSucceeded
* @param {function} _Invalidated * @param {function} invalidate
* @param {function} _HasError * @param {function} failed
* @param {boolean} [pk_required=false] * @param {boolean} [pkRequired=false]
* @returns {function} Function that takes the `dispatch` function of redux as argument. * @returns {function} Function that takes the `dispatch` function of redux as argument.
* @memberof SmartActions * @memberof SmartActions
*/ */
_FetchData(pk, api_end_point, _IsLoading, _FetchDataSuccess, _Invalidated, _HasError, pk_required = false) { _FetchData(pk, apiEndPoint, isReading, readDataSucceeded, invalidate, failed, pkRequired = false) {
if (pk_required && (typeof pk == "undefined")) { if (pkRequired && (typeof pk == "undefined")) {
throw "pk shouldn't be empty when requesting a specific element"; throw "pk shouldn't be empty when requesting a specific element";
} }
if (pk != "") { if (pk != "") {
api_end_point += pk + "/"; apiEndPoint += pk + "/";
} }
if (!this.shouldFetchEndPoint(api_end_point)) { if (!this.shouldFetchEndPoint(apiEndPoint)) {
return () => { }; return () => { };
} else { } else {
this.fetching.set(api_end_point, true); this.fetching.set(apiEndPoint, true);
} }
return (dispatch) => { return (dispatch) => {
dispatch(_IsLoading(true)); dispatch(isReading(true));
let token = Cookies.get("csrftoken"); let token = Cookies.get("csrftoken");
fetch(api_end_point, { credentials: "same-origin", headers: { "X-CSRFToken": token } }) fetch(apiEndPoint, { credentials: "same-origin", headers: { "X-CSRFToken": token } })
.then((response) => { .then((response) => {
if (!response.ok) { if (!response.ok) {
this.fetching.delete(api_end_point); this.fetching.delete(apiEndPoint);
throw Error(response.statusText); throw Error(response.statusText);
} }
return response; return response;
}) })
.then((response) => response.json()) .then((response) => response.json())
.then((obj) => { .then((obj) => {
dispatch(_Invalidated(false)); dispatch(invalidate(false));
dispatch(_FetchDataSuccess(obj)); dispatch(readDataSucceeded(obj));
dispatch(_IsLoading(false)); dispatch(isReading(false));
this.fetching.delete(api_end_point); this.fetching.delete(apiEndPoint);
}) })
.catch((e) => { .catch((e) => {
dispatch(_HasError(true, e)); dispatch(failed(true, e));
dispatch(_IsLoading(false)); dispatch(isReading(false));
this.fetching.delete(api_end_point); this.fetching.delete(apiEndPoint);
}); });
}; };
} }
...@@ -77,14 +82,14 @@ export default class SmartActions { ...@@ -77,14 +82,14 @@ export default class SmartActions {
* Generic function for handling a PUT and POST requests to the API * Generic function for handling a PUT and POST requests to the API
* *
* @param {object} data, to update the data, `data` should contains the `id` field. * @param {object} data, to update the data, `data` should contains the `id` field.
* @param {string} api_end_point * @param {string} apiEndPoint
* @param {function} _ElIsSaving * @param {function} isSavingSpecific
* @param {function} _ElFetchDataSuccess * @param {function} readSpecificSucceeded
* @param {function} _ElHasError * @param {function} failedSpecific
* @returns {function} Function that takes the `dispatch` function of redux as argument. * @returns {function} Function that takes the `dispatch` function of redux as argument.
* @memberof SmartActions * @memberof SmartActions
*/ */
_ElSaveData(data, api_end_point, _ElIsSaving, _ElFetchDataSuccess, _ElHasError) { _ElSaveData(data, apiEndPoint, isSavingSpecific, readSpecificSucceeded, failedSpecific) {
return (dispatch) => { return (dispatch) => {
let method = "POST"; let method = "POST";
let pk = ""; let pk = "";
...@@ -99,9 +104,9 @@ export default class SmartActions { ...@@ -99,9 +104,9 @@ export default class SmartActions {
let errorStatusText = ""; let errorStatusText = "";
dispatch(_ElIsSaving(true)); dispatch(isSavingSpecific(true));
let token = Cookies.get("csrftoken"); let token = Cookies.get("csrftoken");
fetch(api_end_point + __apiAttr + pk, { fetch(apiEndPoint + __apiAttr + pk, {
method: method, method: method,
credentials: "same-origin", credentials: "same-origin",
headers: { headers: {
...@@ -119,8 +124,8 @@ export default class SmartActions { ...@@ -119,8 +124,8 @@ export default class SmartActions {
return response.json(); return response.json();
}) })
.then((_El) => { .then((_El) => {
dispatch(_ElFetchDataSuccess(_El)); // we use the same here dispatch(readSpecificSucceeded(_El)); // we use the same here
dispatch(_ElIsSaving(false)); dispatch(isSavingSpecific(false));
}) })
.catch((e) => { .catch((e) => {
if (typeof e.json == "function") { if (typeof e.json == "function") {
...@@ -132,8 +137,8 @@ export default class SmartActions { ...@@ -132,8 +137,8 @@ export default class SmartActions {
}) })
.then((errorContent) => { .then((errorContent) => {
if (typeof errorContent != "undefined") { if (typeof errorContent != "undefined") {
dispatch(_ElHasError(true, { message: errorStatusText, content: errorContent })); dispatch(failedSpecific(true, { message: errorStatusText, content: errorContent }));
dispatch(_ElIsSaving(false)); dispatch(isSavingSpecific(false));
} }
}); });
}; };
......
/*
* This file dynamically create the redux actions and reducers related to the REST API and custom apis
*/
import API_CONFIG from "../../../shared/api_config.yml"; import API_CONFIG from "../../../shared/api_config.yml";
import CrudActions from "./CrudActions"; import CrudActions from "./CrudActions";
import CrudReducers from "./CrudReducers"; import CrudReducers from "./CrudReducers";
...@@ -6,7 +10,6 @@ import CrudReducers from "./CrudReducers"; ...@@ -6,7 +10,6 @@ import CrudReducers from "./CrudReducers";
let apiActionsTmp = {}; let apiActionsTmp = {};
let apiReducersTmp = {}; let apiReducersTmp = {};
// Simple APIs outside rest framework // Simple APIs outside rest framework
// TODO merge with the shared file // TODO merge with the shared file
const otherAPI = [ const otherAPI = [
...@@ -19,7 +22,7 @@ const otherAPI = [ ...@@ -19,7 +22,7 @@ const otherAPI = [
/** /**
* Create api actions and reducers for given the info in each arr obj * Create api actions and reducers for given the info in each arr obj
* Add those to apiActions and apiReducers objects * Add those to apiActions and apiReducers objects
* *
* @param {Array} arr * @param {Array} arr
*/ */
function addAPIs(arr) { function addAPIs(arr) {
...@@ -30,7 +33,6 @@ function addAPIs(arr) { ...@@ -30,7 +33,6 @@ function addAPIs(arr) {
// TODO better generality with /api add to a shared parameter file // TODO better generality with /api add to a shared parameter file
let apiEndPoint = `/api/${info.api_end_point}/`; let apiEndPoint = `/api/${info.api_end_point}/`;
let apiInfo = { let apiInfo = {
name: info.api_end_point, name: info.api_end_point,
readOnly: info.read_only, readOnly: info.read_only,
......
// Inspired by https://github.com/mui-org/material-ui/tree/master/docs/src/pages/page-layout-examples/dashboard /** General app JS entry
* Inspired by https://github.com/mui-org/material-ui/tree/master/docs/src/pages/page-layout-examples/dashboard
*/
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
...@@ -19,7 +21,6 @@ import { mainListItems, secondaryListItems, thirdListItems } from "./template/li ...@@ -19,7 +21,6 @@ import { mainListItems, secondaryListItems, thirdListItems } from "./template/li
import { connect } from "react-redux"; import { connect } from "react-redux";
import CustomComponentForAPI from "./CustomComponentForAPI"; import CustomComponentForAPI from "./CustomComponentForAPI";
// import route Components here
import { import {
Route, Route,
Redirect Redirect
...@@ -33,82 +34,13 @@ import PageUniversity from "./pages/PageUniversity"; ...@@ -33,82 +34,13 @@ import PageUniversity from "./pages/PageUniversity";
import PageSearch from "./pages/PageSearch"; import PageSearch from "./pages/PageSearch";
import PageSettings from "./pages/PageSettings"; import PageSettings from "./pages/PageSettings";
const drawerWidth = 240; const DRAWER_WIDTH = 240;
const styles = theme => ({
root: {
display: "flex",
},
toolbar: {
paddingRight: 24, // keep right padding when drawer closed
},
toolbarIcon: {
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
padding: "0 8px",
...theme.mixins.toolbar,
},
chip: {
margin: theme.spacing.unit,
},
menuButton: {
marginRight: 4,
},
hideIt: {
display: "none",
},
title: {
flexGrow: 1,
},
drawerPaper: {
position: "relative",
whiteSpace: "nowrap",
width: drawerWidth,
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawerPaperClose: {
overflowX: "hidden",
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
width: theme.spacing.unit * 7,
[theme.breakpoints.up("sm")]: {
width: theme.spacing.unit * 9,
},
},
content: {
flexGrow: 1,
padding: theme.spacing.unit * 3,
height: "100vh",
overflow: "auto",
paddingTop: "0px"
},
paddingTop: {
paddingTop: "24px"
},
chartContainer: {
marginLeft: -22,
},
tableContainer: {
height: 320,
},
myPaper: {
padding: 16
},
null: {}
});
class App extends CustomComponentForAPI { class App extends CustomComponentForAPI {
state = { state = {
open: true, open: true,
}; };
handleDrawerOpen = () => { handleDrawerOpen = () => {
this.setState({ open: true }); this.setState({ open: true });
}; };
...@@ -123,6 +55,7 @@ class App extends CustomComponentForAPI { ...@@ -123,6 +55,7 @@ class App extends CustomComponentForAPI {
return ( return (
<React.Fragment> <React.Fragment>
<CssBaseline /> <CssBaseline />
<div className={classes.root}> <div className={classes.root}>
<Drawer <Drawer
...@@ -132,8 +65,8 @@ class App extends CustomComponentForAPI { ...@@ -132,8 +65,8 @@ class App extends CustomComponentForAPI {
}} }}
open={this.state.open} open={this.state.open}
> >
<div className={classNames(classes.toolbarIcon)}> <div className={classes.toolbarIcon}>
<div className={classNames((!this.state.open) && classes.hideIt, classes.null)}> <div className={!this.state.open ? classes.hideIt : classes.null}>
<Chip <Chip
avatar={<Avatar> <SchoolIcon /> </Avatar>} avatar={<Avatar> <SchoolIcon /> </Avatar>}
label="Outgoing REX" label="Outgoing REX"
...@@ -141,12 +74,24 @@ class App extends CustomComponentForAPI { ...@@ -141,12 +74,24 @@ class App extends CustomComponentForAPI {
color="primary" color="primary"
/> />
</div> </div>
<IconButton onClick={this.handleDrawerOpen} className={classNames(classes.menuButton, <IconButton
this.state.open && classes.hideIt)}> onClick={this.handleDrawerOpen}
className={
classNames(
classes.menuButton,
this.state.open ? classes.hideIt : classes.null
)}
>
<MenuIcon /> <MenuIcon />
</IconButton> </IconButton>
<IconButton onClick={this.handleDrawerClose} className={classNames(classes.menuButton, <IconButton
(!this.state.open) && classes.hideIt)}> onClick={this.handleDrawerClose}
className={
classNames(
classes.menuButton,
!this.state.open ? classes.hideIt : classes.null
)}
>
<ChevronLeftIcon /> <ChevronLeftIcon />
</IconButton> </IconButton>
</div> </div>
...@@ -158,6 +103,7 @@ class App extends CustomComponentForAPI { ...@@ -158,6 +103,7 @@ class App extends CustomComponentForAPI {
<List>{secondaryListItems}</List> <List>{secondaryListItems}</List>
<Divider /> <Divider />
<List>{thirdListItems}</List> <List>{thirdListItems}</List>
</Drawer> </Drawer>
<main className={classNames(classes.content, classes.noPaddingTop)}> <main className={classNames(classes.content, classes.noPaddingTop)}>
...@@ -176,6 +122,7 @@ class App extends CustomComponentForAPI { ...@@ -176,6 +122,7 @@ class App extends CustomComponentForAPI {
<Route path="/app/university/:id" component={PageUniversity} /> <Route path="/app/university/:id" component={PageUniversity} />
</div> </div>
</main> </main>
</div> </div>
</React.Fragment> </React.Fragment>
); );
...@@ -203,4 +150,71 @@ const mapDispatchToProps = (dispatch) => { ...@@ -203,4 +150,71 @@ const mapDispatchToProps = (dispatch) => {
}; };
}; };
const styles = theme => ({
root: {
display: "flex",
},
toolbar: {
paddingRight: 24, // keep right padding when drawer closed
},
toolbarIcon: {
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
padding: "0 8px",
...theme.mixins.toolbar,
},
chip: {
margin: theme.spacing.unit,
},
menuButton: {
marginRight: 4,
},
hideIt: {
display: "none",
},
title: {
flexGrow: 1,
},
drawerPaper: {
position: "relative",
whiteSpace: "nowrap",
width: DRAWER_WIDTH,
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawerPaperClose: {
overflowX: "hidden",
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
width: theme.spacing.unit * 7,
[theme.breakpoints.up("sm")]: {
width: theme.spacing.unit * 9,
},
},
content: {
flexGrow: 1,
padding: theme.spacing.unit * 3,
height: "100vh",
overflow: "auto",
paddingTop: "0px"
},
paddingTop: {
paddingTop: "24px"
},
chartContainer: {
marginLeft: -22,
},
tableContainer: {
height: 320,
},
null: {}
});
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles, { withTheme: true })(App)); export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles, { withTheme: true })(App));
...@@ -192,7 +192,7 @@ class CustomComponentForAPI extends Component { ...@@ -192,7 +192,7 @@ class CustomComponentForAPI extends Component {
{ readAt: 0 }); { readAt: 0 });
if (!("data" in out)) { if (!("data" in out)) {
throw Error(`No read data from the api could be retreived for: ${propName}`); throw Error(`No read data from the api could be retrieved for: ${propName}`);
} else { } else {
return out; return out;
} }
...@@ -210,6 +210,17 @@ class CustomComponentForAPI extends Component { ...@@ -210,6 +210,17 @@ class CustomComponentForAPI extends Component {
return this.getReadDataAndTime(propName).data; return this.getReadDataAndTime(propName).data;
} }
/**
* Get the time at which the latest data from the api corresponding to `propName` was read
*
* @param {string} propName
* @returns {Any}
* @memberof CustomComponentForAPI
*/
getReadTime(propName) {
return this.getReadDataAndTime(propName).readAt;
}
/** /**
* Access to all the data loaded from the api given the props * Access to all the data loaded from the api given the props
* *
......
...@@ -14,6 +14,12 @@ import getActions from "../api/getActions"; ...@@ -14,6 +14,12 @@ import getActions from "../api/getActions";
import { saveAppTheme } from "../actions/theme"; import { saveAppTheme } from "../actions/theme";
/**
* Component that handles theme customization and saving at the scale of the entire app
*
* @class ThemeProvider
* @extends {CustomComponentForAPI}
*/
class ThemeProvider extends CustomComponentForAPI { class ThemeProvider extends CustomComponentForAPI {
state = { theme: this.props.themeSavedInTheApp.theme }; state = { theme: this.props.themeSavedInTheApp.theme };
...@@ -54,7 +60,10 @@ class ThemeProvider extends CustomComponentForAPI { ...@@ -54,7 +60,10 @@ class ThemeProvider extends CustomComponentForAPI {
fontSize: 14, fontSize: 14,
htmlFontSize: 14 htmlFontSize: 14
}, },
useNextVariants: true useNextVariants: true,
myPaper: {
padding: 16
}
}; };
const theme = Object.assign({}, this.state.theme, siteSettings); const theme = Object.assign({}, this.state.theme, siteSettings);
......
/**
*
* WARNING THIS FILE HAS NOT BEEN REVIEWED AS OF 22.02.2019
* THINGS MIGHT NOT BE SUPER CLEAR OR BROKEN
*
*
*
*
*
*
*
* TODO
*
*
*
*
*/
// Inspired by : https://material-ui.com/demos/autocomplete/ // Inspired by : https://material-ui.com/demos/autocomplete/