Commit 1237854b authored by Florent Chehab's avatar Florent Chehab
Browse files

Merge branch 'cleaning' into 'master'

Cleaning

Closes #48 and #47

See merge request !48
parents 909dae0d 827fa5b8
......@@ -71,7 +71,7 @@ export default class CrudReducers {
return is(state, action, self.types.isReadingAll);
}
function readAllSucceeded(state = { data: Object(), readAt: 0 }, action) {
function readAllSucceeded(state = { data: Array(), readAt: 0 }, action) {
return succeeded(state, action, self.types.readAllSucceeded);
}
......@@ -152,7 +152,7 @@ export default class CrudReducers {
isInvalidated: isInvalidatedSpecific,
};
// Add only appropriate reducers
// Add only appropriate reducers
if (this.readOnly !== true) {
// Creation
out.isCreating = isCreating;
......@@ -168,4 +168,4 @@ export default class CrudReducers {
return combineReducers(out);
}
}
\ No newline at end of file
}
......@@ -186,7 +186,7 @@ App.propTypes = {
classes: PropTypes.object.isRequired,
};
// Already load some of the data even if it's not use here.
const mapStateToProps = (state) => {
return {
countries: state.api.countriesAll,
......
import React, { Component } from "react";
import Loading from "./other/Loading";
import PropTypes from "prop-types";
// Stores the name of the reducers/actions that result in read data
const successActionsWithReads = ["readSucceeded", "createSucceeded", "updateSucceeded"];
......@@ -222,36 +224,71 @@ class CustomComponentForAPI extends Component {
return out;
}
/**
* Access to the read data from the propNames array
* This should be used instead of getAllReadData to optimize things
*
* @param {array} propNames Array of the prop names you want to read data from
* @returns {object}
* @memberof CustomComponentForAPI
*/
getReadDataFor(propNames) {
let out = Object();
propNames.forEach(propName => out[propName] = this.getReadData(propName));
return out;
}
// utilities functions shared by subclasses
/**
* Function to retrieve all the information relative to a campus
*
* @param {object} campus instance of Campus
* @returns {object} Campus with replaced university, city and country with the matching instance
* @memberof CustomComponentForAPI
*/
joinCampus(campus) {
const { universities, countries, cities } = this.getAllReadData();
const { universities, countries, cities } = this.getReadDataFor(["universities", "countries", "cities"]);
let res = Object.assign({}, campus); //copy for safety
res.university = universities[campus.university];
res.city = cities[campus.city];
res.country = countries[res.city.country];
res.university = universities.find(univ => univ.id = campus.university);
res.city = cities.find(city => city.id = campus.city);
res.country = countries.find(country => country.id = res.city.country);
return res;
}
/**
* Funciton to get the city and the country of a university given a university id
*
* @param {number} univId
* @returns {object} Object with city and country instance of the university (main campus)
* @memberof CustomComponentForAPI
*/
getUnivCityAndCountry(univId) {
const univMainCampus = this.findMainCampus(univId);
const cities = this.getReadData("cities");
const countries = this.getReadData("countries");
const city = cities[univMainCampus.city];
const country = countries[city.country];
const univMainCampus = this.findMainCampus(univId),
{ countries, cities } = this.getReadDataFor(["countries", "cities"]),
city = cities.find(city => city.id == univMainCampus.city),
country = countries.find(country => country.id == city.country);
return { city, country };
}
/**
* Function that returns the main campus instance associated with a university
* identified by its id.
*
* @param {number} univId
* @returns {object}
* @memberof CustomComponentForAPI
*/
findMainCampus(univId) {
const mainCampuses = this.getReadData("mainCampuses");
for (let mainCampusPk in mainCampuses) {
const campus = mainCampuses[mainCampusPk];
if (campus.university == univId) {
return campus;
}
}
return null;
return mainCampuses.find(campus => campus.university == univId);
}
}
CustomComponentForAPI.propTypes = {
api: PropTypes.object
};
export default CustomComponentForAPI;
......@@ -49,13 +49,15 @@ class ThemeProvider extends CustomComponentForAPI {
customRender() {
const font = {
const siteSettings = {
typography: {
fontSize: 14,
htmlFontSize: 14
}
},
useNextVariants: true
};
const theme = Object.assign({}, this.state.theme, font);
const theme = Object.assign({}, this.state.theme, siteSettings);
return (
<div>
<MuiThemeProvider theme={createMuiTheme(theme)}>
......
......@@ -84,10 +84,10 @@ class DownshiftMultiple extends React.Component {
const { selectedItems } = this.state;
let possible = __difference(options, selectedItems);
const filter = fuzzysort.go(value, possible, { limit: 5, key: "label" });
if (filter.length > 0){
if (filter.length > 0) {
return __map(filter, (item) => item.obj);
} else {
return possible.slice(0,4);
return possible.slice(0, 4);
}
}
......@@ -142,51 +142,54 @@ class DownshiftMultiple extends React.Component {
onChange={this.handleChange}
selectedItem={selectedItems}
>
{({
getInputProps,
getItemProps,
isOpen,
inputValue: inputValue2,
selectedItem: selectedItem2,
highlightedIndex,
}) => (
<div className={classes.container}>
{renderInput({
fullWidth: true,
classes,
InputProps: getInputProps({
startAdornment: selectedItems.map(item => (
<Chip
key={item.id}
tabIndex={-1}
label={item.label}
className={classes.chip}
onDelete={this.handleDelete(item.id)}
variant="outlined"
color="primary"
/>
)),
onChange: this.handleInputChange,
onKeyDown: this.handleKeyDown,
placeholder: field_placeholder,
}),
label: field_label,
})}
{isOpen ? (
<Paper className={classes.paper} square>
{this.getSuggestions(inputValue2).map((suggestion, index) =>
renderSuggestion({
suggestion,
index,
itemProps: getItemProps({ item: suggestion.id }),
highlightedIndex,
selectedItem: selectedItem2,
{
({
getInputProps,
getItemProps,
isOpen,
inputValue: inputValue2,
selectedItem: selectedItem2,
highlightedIndex,
}) =>
(
<div className={classes.container}>
{renderInput({
fullWidth: true,
classes,
InputProps: getInputProps({
startAdornment: selectedItems.map(item => (
<Chip
key={item.id}
tabIndex={-1}
label={item.label}
className={classes.chip}
onDelete={this.handleDelete(item.id)}
variant="outlined"
color="primary"
/>
)),
onChange: this.handleInputChange,
onKeyDown: this.handleKeyDown,
placeholder: field_placeholder,
}),
)}
</Paper>
) : null}
</div>
)}
label: field_label,
})}
{isOpen ? (
<Paper className={classes.paper} square>
{this.getSuggestions(inputValue2).map((suggestion, index) =>
renderSuggestion({
suggestion,
index,
itemProps: getItemProps({ item: suggestion.id }),
highlightedIndex,
selectedItem: selectedItem2,
}),
)}
</Paper>
) : null}
</div>
)
}
</Downshift>
</div>
......@@ -196,6 +199,12 @@ class DownshiftMultiple extends React.Component {
DownshiftMultiple.propTypes = {
classes: PropTypes.object.isRequired,
onComponentUnmount: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
config: PropTypes.object.isRequired,
field_label: PropTypes.string.isRequired,
field_placeholder: PropTypes.string.isRequired,
options: PropTypes.array.isRequired,
};
DownshiftMultiple.defaultProps = {
......
......@@ -3,8 +3,6 @@ import DownshiftMultiple from "./DownshiftMultiple";
import CustomComponentForAPI from "../CustomComponentForAPI";
import { connect } from "react-redux";
import __each from "lodash/each";
import __uniq from "lodash/uniq";
import __map from "lodash/map";
import __indexOf from "lodash/indexOf";
import { saveSelectedUniversities, saveFilterConfig } from "../../actions/filter";
......@@ -31,34 +29,44 @@ const styles = theme => ({
class Filter extends CustomComponentForAPI {
saveContriesFilterConfig(state) {
this.props.saveConfig({ contriesFilter: state });
saveCountriesFilterConfig(state) {
this.props.saveConfig({ countriesFilter: state });
}
/**
* Function to get the list of countries where there are universities.
*
* @returns {Array} of the countries instances
* @memberof Filter
*/
getCountriesWhereThereAreUniversities() {
const { mainCampuses } = this.getAllReadData();
const mainCampuses = this.getReadData("mainCampuses");
let res = [];
__each(mainCampuses, (campus) => {
const campusFull = this.joinCampus(campus);
res.push(campusFull.country);
// use of map to get only each country once
let res = new Map();
// TODO optimize: use maps instead of joinCampus
mainCampuses.forEach(campus => {
const country = this.joinCampus(campus).country,
code = country.iso_alpha2_code;
res.set(code, country);
});
return __uniq(res, false, (c) => { return c.iso_alpha2_code; });
return [...res.values()];
}
updateSelectedUniversities(selection) {
const { mainCampuses } = this.getAllReadData();
const listOfCountries = __map(selection, (s) => s.id);
const mainCampuses = this.getReadData("mainCampuses"),
listOfCountries = __map(selection, (s) => s.id);
let selected_universities = [];
__each(mainCampuses, (campus) => {
let selectedUniversities = [];
mainCampuses.forEach(campus => {
const campusFull = this.joinCampus(campus);
if (__indexOf(listOfCountries, campusFull.country.iso_alpha2_code) > -1) {
selected_universities.push(campusFull.university.id);
selectedUniversities.push(campusFull.university.id);
}
});
this.props.saveSelection(selected_universities);
this.props.saveSelection(selectedUniversities);
}
customRender() {
......@@ -74,8 +82,8 @@ class Filter extends CustomComponentForAPI {
<DownshiftMultiple
options={options}
onChange={(selection) => this.updateSelectedUniversities(selection)}
onComponentUnmount={(state) => this.saveContriesFilterConfig(state)}
config={this.props.contriesFilterConfig}
onComponentUnmount={(state) => this.saveCountriesFilterConfig(state)}
config={this.props.countriesFilterConfig}
/>
</ExpansionPanelDetails>
</ExpansionPanel>
......@@ -91,7 +99,7 @@ const mapStateToProps = (state) => {
mainCampuses: state.api.mainCampusesAll,
cities: state.api.citiesAll,
countries: state.api.countriesAll,
contriesFilterConfig: state.app.filter.contriesFilter
countriesFilterConfig: state.app.filter.countriesFilter
};
};
......
import React, { Component } from "react";
import PropTypes from "prop-types";
import CardMedia from "@material-ui/core/CardMedia";
class MyCardMedia extends Component {
......@@ -6,7 +7,7 @@ class MyCardMedia extends Component {
render() {
const { title, height, url } = this.props;
if (url == "") {
return (<div/>);
return (<div />);
}
return (
<CardMedia
......@@ -20,5 +21,10 @@ class MyCardMedia extends Component {
}
}
MyCardMedia.propTypes = {
title: PropTypes.string.isRequired,
height: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
};
export default MyCardMedia;
\ No newline at end of file
export default MyCardMedia;
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';
......@@ -7,44 +8,61 @@ import UnivPopupContent from "./UnivPopupContent";
import getActions from "../../api/getActions";
import CustomComponentForAPI from "../CustomComponentForAPI";
import arrayOfInstancesToMap from "../../utils/arrayOfInstancesToMap";
/**
* Class that renders the markers for the map of the universities.
*
* Each main campus and associated relevant information is added to the map.
*
* @class UnivMarkers
* @extends {CustomComponentForAPI}
*/
class UnivMarkers extends CustomComponentForAPI {
customRender() {
let { universities,
const { universities,
mainCampuses,
countries,
cities } = this.getAllReadData();
let selected_main_campus = [];
for (let main_campus_pk in mainCampuses) {
let campus = mainCampuses[main_campus_pk];
let univ = universities[campus.university];
if (univ && campus) {
let city = cities[campus.city];
let country = countries[city.country];
selected_main_campus.push({
univ_name: univ.name,
univ_logo: univ.logo,
univ_city: city.name,
univ_country: country.name,
// some conversions for optimization (faster search of element in a map)
const universitiesMap = arrayOfInstancesToMap(universities),
countriesMap = arrayOfInstancesToMap(countries),
citiesMap = arrayOfInstancesToMap(cities);
let mainCampusesSelection = [];
// Merge the data and add it to the selection
mainCampuses.forEach(campus => {
const univ = universitiesMap.get(campus.university);
if (campus && univ) {
const city = citiesMap.get(campus.city),
country = countriesMap.get(city.country);
mainCampusesSelection.push({
univName: univ.name,
univLogo: univ.logo,
univCity: city.name,
univCountry: country.name,
lat: campus.lat,
lon: campus.lon,
id: univ.id
});
}
}
});
return (
selected_main_campus.map((el) => (
<Marker key={el.id} position={[el.lat, el.lon]}>
mainCampusesSelection.map((el, idx) => (
<Marker key={idx} position={[el.lat, el.lon]}>
<Popup closeButton={false} >
<UnivPopupContent
name={el.univ_name}
logo={el.univ_logo}
city={el.univ_city}
country={el.univ_country}
name={el.univName}
logo={el.univLogo}
city={el.univCity}
country={el.univCountry}
univId={el.id}
/>
</Popup>
......@@ -55,6 +73,12 @@ class UnivMarkers extends CustomComponentForAPI {
}
}
UnivMarkers.propTypes = {
universities: PropTypes.object.isRequired,
mainCampuses: PropTypes.object.isRequired,
cities: PropTypes.object.isRequired,
countries: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => {
return {
......
......@@ -46,11 +46,11 @@ class UnivPopupContent extends Component {
/>
<Divider />
<CardContent>
<Typography variant="subheading">
<Typography variant="subtitle1">
{city}, {country}
</Typography>
<Divider />
<Typography gutterBottom variant="headline">
<Typography gutterBottom variant="h5">
{name}
</Typography>
</CardContent>
......@@ -74,6 +74,12 @@ class UnivPopupContent extends Component {
UnivPopupContent.propTypes = {
classes: PropTypes.object.isRequired,
logo: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
city: PropTypes.string.isRequired,
country: PropTypes.string.isRequired,
univId: PropTypes.number.isRequired,
leaflet: PropTypes.object.isRequired,
};
export default withLeaflet(withStyles(styles)(UnivPopupContent));
\ No newline at end of file
export default withLeaflet(withStyles(styles)(UnivPopupContent));
......@@ -63,7 +63,7 @@ class PageHome extends React.Component {
return (
<Paper className={classes.myPaper}>
<Typography variant="display2">
<Typography variant="h3">
Bienvenue sur <em>Outgoing REX</em>
</Typography>
<Markdown source={source} />
......
......@@ -20,7 +20,7 @@ class PageMap extends React.Component {
<Paper className={classes.myPaper}>
<Grid container spacing={24}>
<Grid item xs={11}>
<Typography variant="display1" gutterBottom>
<Typography variant="h4" gutterBottom>
Exploration Cartographique
</Typography>
</Grid>
......
......@@ -20,7 +20,7 @@ class PageSearch extends React.Component {
<Paper className={classes.myPaper}>
<Grid container spacing={24}>
<Grid item xs={11}>
<Typography variant="display1" gutterBottom>
<Typography variant="h4" gutterBottom>
Recherche d'une université
</Typography>
</Grid>
......
......@@ -20,7 +20,7 @@ class PageSettings extends React.Component {
<Paper className={classes.myPaper}>
<Grid container spacing={24}>
<Grid item xs={11}>
<Typography variant="display1" gutterBottom>
<Typography variant="h4" gutterBottom>
Paramètres
</Typography>
</Grid>
......
......@@ -74,10 +74,11 @@ class PageUniversity extends CustomComponentForAPI {
customComponentDidUpdate() {
if (this.props.universities.readSucceeded.readAt) {
// we have the university data
const { universities } = this.getAllReadData();
const { match, universityBeingViewed } = this.props;
const requestedUniversity = match.params.id;
if (requestedUniversity != "undefined" && requestedUniversity in universities && requestedUniversity != universityBeingViewed) {
const universities = this.getReadData("universities"),
{ match, universityBeingViewed } = this.props,
requestedUniversity = match.params.id;
if (requestedUniversity != "undefined" && universities.find(univ => univ.id == requestedUniversity) && requestedUniversity != universityBeingViewed) {
this.props.saveUniversityInView(requestedUniversity);
}
}
......@@ -85,9 +86,9 @@ class PageUniversity extends CustomComponentForAPI {
}
customRender() {
const { match, universityBeingViewed } = this.props;
const requestedUnivId = match.params.id;
const { universities } = this.getAllReadData();
const { match, universityBeingViewed } = this.props,
requestedUnivId = match.params.id,
universities = this.getReadData("universities");
if (requestedUnivId == "undefined") {
if (universityBeingViewed != null) {
......@@ -96,7 +97,7 @@ class PageUniversity extends CustomComponentForAPI {
return renderFirstTimeHere();
}
} else {
if (requestedUnivId in universities) {
if (universities.find(univ => univ.id == requestedUnivId)) {
return renderDefaultView(requestedUnivId);
} else {