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

Merge branch 'cleaning' into 'master'

Cleaning

See merge request !50
parents 0e42118e 4fe1bdf2
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 {
fetching = new Map();
......@@ -22,51 +27,51 @@ export default class SmartActions {
* Generic function for handling a GET request to the API
*
* @param {string} pk
* @param {string} api_end_point
* @param {function} _IsLoading
* @param {function} _FetchDataSuccess
* @param {function} _Invalidated
* @param {function} _HasError
* @param {boolean} [pk_required=false]
* @param {string} apiEndPoint
* @param {function} isReading
* @param {function} readDataSucceeded
* @param {function} invalidate
* @param {function} failed
* @param {boolean} [pkRequired=false]
* @returns {function} Function that takes the `dispatch` function of redux as argument.
* @memberof SmartActions
*/
_FetchData(pk, api_end_point, _IsLoading, _FetchDataSuccess, _Invalidated, _HasError, pk_required = false) {
if (pk_required && (typeof pk == "undefined")) {
_FetchData(pk, apiEndPoint, isReading, readDataSucceeded, invalidate, failed, pkRequired = false) {
if (pkRequired && (typeof pk == "undefined")) {
throw "pk shouldn't be empty when requesting a specific element";
}
if (pk != "") {
api_end_point += pk + "/";
apiEndPoint += pk + "/";
}
if (!this.shouldFetchEndPoint(api_end_point)) {
if (!this.shouldFetchEndPoint(apiEndPoint)) {
return () => { };
} else {
this.fetching.set(api_end_point, true);
this.fetching.set(apiEndPoint, true);
}
return (dispatch) => {
dispatch(_IsLoading(true));
dispatch(isReading(true));
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) => {
if (!response.ok) {
this.fetching.delete(api_end_point);
this.fetching.delete(apiEndPoint);
throw Error(response.statusText);
}
return response;
})
.then((response) => response.json())
.then((obj) => {
dispatch(_Invalidated(false));
dispatch(_FetchDataSuccess(obj));
dispatch(_IsLoading(false));
this.fetching.delete(api_end_point);
dispatch(invalidate(false));
dispatch(readDataSucceeded(obj));
dispatch(isReading(false));
this.fetching.delete(apiEndPoint);
})
.catch((e) => {
dispatch(_HasError(true, e));
dispatch(_IsLoading(false));
this.fetching.delete(api_end_point);
dispatch(failed(true, e));
dispatch(isReading(false));
this.fetching.delete(apiEndPoint);
});
};
}
......@@ -77,14 +82,14 @@ export default class SmartActions {
* 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 {string} api_end_point
* @param {function} _ElIsSaving
* @param {function} _ElFetchDataSuccess
* @param {function} _ElHasError
* @param {string} apiEndPoint
* @param {function} isSavingSpecific
* @param {function} readSpecificSucceeded
* @param {function} failedSpecific
* @returns {function} Function that takes the `dispatch` function of redux as argument.
* @memberof SmartActions
*/
_ElSaveData(data, api_end_point, _ElIsSaving, _ElFetchDataSuccess, _ElHasError) {
_ElSaveData(data, apiEndPoint, isSavingSpecific, readSpecificSucceeded, failedSpecific) {
return (dispatch) => {
let method = "POST";
let pk = "";
......@@ -99,9 +104,9 @@ export default class SmartActions {
let errorStatusText = "";
dispatch(_ElIsSaving(true));
dispatch(isSavingSpecific(true));
let token = Cookies.get("csrftoken");
fetch(api_end_point + __apiAttr + pk, {
fetch(apiEndPoint + __apiAttr + pk, {
method: method,
credentials: "same-origin",
headers: {
......@@ -119,8 +124,8 @@ export default class SmartActions {
return response.json();
})
.then((_El) => {
dispatch(_ElFetchDataSuccess(_El)); // we use the same here
dispatch(_ElIsSaving(false));
dispatch(readSpecificSucceeded(_El)); // we use the same here
dispatch(isSavingSpecific(false));
})
.catch((e) => {
if (typeof e.json == "function") {
......@@ -132,8 +137,8 @@ export default class SmartActions {
})
.then((errorContent) => {
if (typeof errorContent != "undefined") {
dispatch(_ElHasError(true, { message: errorStatusText, content: errorContent }));
dispatch(_ElIsSaving(false));
dispatch(failedSpecific(true, { message: errorStatusText, content: errorContent }));
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 CrudActions from "./CrudActions";
import CrudReducers from "./CrudReducers";
......@@ -6,7 +10,6 @@ import CrudReducers from "./CrudReducers";
let apiActionsTmp = {};
let apiReducersTmp = {};
// Simple APIs outside rest framework
// TODO merge with the shared file
const otherAPI = [
......@@ -19,7 +22,7 @@ const otherAPI = [
/**
* Create api actions and reducers for given the info in each arr obj
* Add those to apiActions and apiReducers objects
*
*
* @param {Array} arr
*/
function addAPIs(arr) {
......@@ -30,7 +33,6 @@ function addAPIs(arr) {
// TODO better generality with /api add to a shared parameter file
let apiEndPoint = `/api/${info.api_end_point}/`;
let apiInfo = {
name: info.api_end_point,
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 PropTypes from "prop-types";
......@@ -19,7 +21,6 @@ import { mainListItems, secondaryListItems, thirdListItems } from "./template/li
import { connect } from "react-redux";
import CustomComponentForAPI from "./CustomComponentForAPI";
// import route Components here
import {
Route,
Redirect
......@@ -33,82 +34,13 @@ import PageUniversity from "./pages/PageUniversity";
import PageSearch from "./pages/PageSearch";
import PageSettings from "./pages/PageSettings";
const drawerWidth = 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: {}
});
const DRAWER_WIDTH = 240;
class App extends CustomComponentForAPI {
state = {
open: true,
};
handleDrawerOpen = () => {
this.setState({ open: true });
};
......@@ -123,6 +55,7 @@ class App extends CustomComponentForAPI {
return (
<React.Fragment>
<CssBaseline />
<div className={classes.root}>
<Drawer
......@@ -132,8 +65,8 @@ class App extends CustomComponentForAPI {
}}
open={this.state.open}
>
<div className={classNames(classes.toolbarIcon)}>
<div className={classNames((!this.state.open) && classes.hideIt, classes.null)}>
<div className={classes.toolbarIcon}>
<div className={!this.state.open ? classes.hideIt : classes.null}>
<Chip
avatar={<Avatar> <SchoolIcon /> </Avatar>}
label="Outgoing REX"
......@@ -141,12 +74,24 @@ class App extends CustomComponentForAPI {
color="primary"
/>
</div>
<IconButton onClick={this.handleDrawerOpen} className={classNames(classes.menuButton,
this.state.open && classes.hideIt)}>
<IconButton
onClick={this.handleDrawerOpen}
className={
classNames(
classes.menuButton,
this.state.open ? classes.hideIt : classes.null
)}
>
<MenuIcon />
</IconButton>
<IconButton onClick={this.handleDrawerClose} className={classNames(classes.menuButton,
(!this.state.open) && classes.hideIt)}>
<IconButton
onClick={this.handleDrawerClose}
className={
classNames(
classes.menuButton,
!this.state.open ? classes.hideIt : classes.null
)}
>
<ChevronLeftIcon />
</IconButton>
</div>
......@@ -158,6 +103,7 @@ class App extends CustomComponentForAPI {
<List>{secondaryListItems}</List>
<Divider />
<List>{thirdListItems}</List>
</Drawer>
<main className={classNames(classes.content, classes.noPaddingTop)}>
......@@ -176,6 +122,7 @@ class App extends CustomComponentForAPI {
<Route path="/app/university/:id" component={PageUniversity} />
</div>
</main>
</div>
</React.Fragment>
);
......@@ -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));
......@@ -192,7 +192,7 @@ class CustomComponentForAPI extends Component {
{ readAt: 0 });
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 {
return out;
}
......@@ -210,6 +210,17 @@ class CustomComponentForAPI extends Component {
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
*
......
......@@ -14,6 +14,12 @@ import getActions from "../api/getActions";
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 {
state = { theme: this.props.themeSavedInTheApp.theme };
......@@ -54,7 +60,10 @@ class ThemeProvider extends CustomComponentForAPI {
fontSize: 14,
htmlFontSize: 14
},
useNextVariants: true
useNextVariants: true,
myPaper: {
padding: 16
}
};
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/
import React from "react";
......@@ -21,9 +42,7 @@ function renderInput(inputProps) {
<TextField
InputProps={{
inputRef: ref,
classes: {
root: classes.inputRoot,
},
classes: { root: classes.inputRoot },
...InputProps,
}}
{...other}
......
/**
*
* WARNING THIS FILE HAS NOT BEEN REVIEWED AS OF 22.02.2019
* THINGS MIGHT NOT BE SUPER CLEAR OR BROKEN
*
*
*
*
*
*
*
* TODO
*
*
*
*
*/
import React from "react";
import DownshiftMultiple from "./DownshiftMultiple";
......@@ -15,18 +37,13 @@ import withStyles from "@material-ui/core/styles/withStyles";
import getActions from "../../api/getActions";
const styles = theme => ({
root: {
width: "100%",
},
heading: {
fontSize: theme.typography.pxToRem(15),
fontWeight: theme.typography.fontWeightRegular,
},
});
/**
* Implementation of a filter component
*
* @class Filter
* @extends {CustomComponentForAPI}
*/
class Filter extends CustomComponentForAPI {
saveCountriesFilterConfig(state) {
......@@ -55,6 +72,7 @@ class Filter extends CustomComponentForAPI {
return [...res.values()];
}
updateSelectedUniversities(selection) {
const mainCampuses = this.getReadData("mainCampuses"),
listOfCountries = __map(selection, (s) => s.id);
......@@ -92,7 +110,6 @@ class Filter extends CustomComponentForAPI {
}
const mapStateToProps = (state) => {
return {
universities: state.api.universitiesAll,
......@@ -117,4 +134,14 @@ const mapDispatchToProps = (dispatch) => {
};
const styles = theme => ({
root: {
width: "100%",
},
heading: {
fontSize: theme.typography.pxToRem(15),
fontWeight: theme.typography.fontWeightRegular,
},
});
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(Filter));
......@@ -2,13 +2,19 @@ import React, { Component } from "react";
import PropTypes from "prop-types";
import CardMedia from "@material-ui/core/CardMedia";
/**
* Custom MUI Card Media Component that renders a single div if no URL is provided
*
* @class MyCardMedia
* @extends {Component}
*/
class MyCardMedia extends Component {
render() {
const { title, height, url } = this.props;
if (url == "") {
return (<div />);
}
return (
<CardMedia
component="img"
......@@ -16,7 +22,6 @@ class MyCardMedia extends Component {
image={url}
title={title}
/>
);
}
}
......
......@@ -2,7 +2,6 @@ import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { Marker, Popup } from "react-leaflet";
// import MarkerClusterGroup from 'react-leaflet-markercluster';
import UnivPopupContent from "./UnivPopupContent";
import