Commit 61493e5a authored by Florent Chehab's avatar Florent Chehab
Browse files

Merge branch 'frontend' into 'master'

Frontend update

See merge request chehabfl/outgoing_rex!29
parents 9936114a 5f18472b
...@@ -64,6 +64,6 @@ ...@@ -64,6 +64,6 @@
63,"Technical University Of Kosice","Kosice","SK","48.73280395","21.244194264458",,, 63,"Technical University Of Kosice","Kosice","SK","48.73280395","21.244194264458",,,
64,"Chalmers University Of Technology","Goteborg","SE","57.6896523","11.9766811023544",,, 64,"Chalmers University Of Technology","Goteborg","SE","57.6896523","11.9766811023544",,,
65,"Lulea University Of Technology","Lulea","SE","65.6170445","22.1370606335398",,, 65,"Lulea University Of Technology","Lulea","SE","65.6170445","22.1370606335398",,,
66,"Ecole Polytechnique Federale De Lausanne","Lausanne","CH","46.5186594","6.566561505148","EPFL","https://www.epfl.ch/","https://www.epfl.ch/img/epfl_small.png" 66,"École Polytechnique Fédérale De Lausanne","Lausanne","CH","46.5186594","6.566561505148","EPFL","https://www.epfl.ch/","https://upload.wikimedia.org/wikipedia/commons/f/f4/Logo_EPFL.svg"
67,"National Chiao Tung University","Hsinchu","TW","24.78676765","120.997244116807",,, 67,"National Chiao Tung University","Hsinchu","TW","24.78676765","120.997244116807",,,
68,"National Taiwan University Of Science And Technology","Taipei","TW","25.01350785","121.541707560048",,, 68,"National Taiwan University Of Science And Technology","Taipei","TW","25.01350785","121.541707560048",,,
...@@ -18,7 +18,7 @@ class LoadUniversities(LoadGeneric): ...@@ -18,7 +18,7 @@ class LoadUniversities(LoadGeneric):
destinations_path = os.path.abspath(tmp) destinations_path = os.path.abspath(tmp)
data = pd.read_csv(destinations_path, sep=',', header=0, data = pd.read_csv(destinations_path, sep=',', header=0,
dtype=object) dtype=object).fillna('')
for index, row in data.iterrows(): for index, row in data.iterrows():
utc_id, univ_name, city_name, country_code, lat, lon, acronym, website, logo = row utc_id, univ_name, city_name, country_code, lat, lon, acronym, website, logo = row
......
../../../node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css
\ No newline at end of file
../../../node_modules/leaflet.markercluster/dist/MarkerCluster.css
\ No newline at end of file
../../../node_modules/leaflet/dist/images
\ No newline at end of file
../../../node_modules/leaflet/dist/leaflet.js
\ No newline at end of file
../../../node_modules/leaflet.markercluster/dist/leaflet.markercluster.js
\ No newline at end of file
export const SAVE_MAIN_MAP_POSITION = 'SAVE_MAIN_MAP_POSITION'; export const SAVE_MAIN_MAP_POSITION = 'SAVE_MAIN_MAP_POSITION';
export const SAVE_SELECTED_UNIVERSITIES = 'SAVE_SELECTED_UNIVERSITIES';
export const SAVE_FILTER_CONFIG = 'SAVE_FILTER_CONFIG';
import {
SAVE_SELECTED_UNIVERSITIES,
SAVE_FILTER_CONFIG
} from "./action-types";
export function saveSelectedUniversities(new_selection) {
return {
type: SAVE_SELECTED_UNIVERSITIES,
new_selection
};
}
export function saveFilterConfig(config) {
return {
type: SAVE_FILTER_CONFIG,
config
};
}
...@@ -4,10 +4,10 @@ import { ...@@ -4,10 +4,10 @@ import {
} from "./action-types"; } from "./action-types";
export function saveMainMapPosition(new_position) { export function saveMainMapPosition(new_position) {
return { return {
type: SAVE_MAIN_MAP_POSITION, type: SAVE_MAIN_MAP_POSITION,
new_position new_position
}; };
} }
import {
SAVE_SELECTED_UNIVERSITIES,
SAVE_FILTER_CONFIG
} from "./action-types";
export function saveSelectedUniversities(new_selection) {
return {
type: SAVE_SELECTED_UNIVERSITIES,
new_selection
};
}
export function saveFilterConfig(config) {
return {
type: SAVE_FILTER_CONFIG,
config
};
}
...@@ -22,17 +22,19 @@ import MyComponent from './MyComponent' ...@@ -22,17 +22,19 @@ import MyComponent from './MyComponent'
// import route Components here // import route Components here
import { import {
BrowserRouter as Router,
Route, Route,
} from 'react-router-dom'; } from 'react-router-dom';
import { import {
countriesFetchData, countriesFetchData,
currenciesFetchData,
} from '../generated/actions'; } from '../generated/actions';
import PageMap from './pages/PageMap'; import PageMap from './pages/PageMap';
import PageHome from './pages/PageHome'; import PageHome from './pages/PageHome';
import PageFilter from './pages/PageFilter';
import PageSearch from './pages/PageSearch';
const drawerWidth = 240; const drawerWidth = 240;
...@@ -114,7 +116,7 @@ class App extends MyComponent { ...@@ -114,7 +116,7 @@ class App extends MyComponent {
}; };
myRender() { myRender() {
const { classes } = this.props; const { classes } = this.props;
return ( return (
...@@ -157,8 +159,9 @@ class App extends MyComponent { ...@@ -157,8 +159,9 @@ class App extends MyComponent {
<main className={classes.content}> <main className={classes.content}>
<Route path="/app/" exact={true} component={PageHome} /> <Route path="/app/" exact={true} component={PageHome} />
<Route path="/app/search" component={PageSearch} />
<Route path="/app/map" component={PageMap} /> <Route path="/app/map" component={PageMap} />
<Route path="/app/filter" component={PageFilter} />
</main> </main>
</div> </div>
...@@ -174,14 +177,16 @@ App.propTypes = { ...@@ -174,14 +177,16 @@ App.propTypes = {
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
return { return {
countries: state.countries countries: state.countries,
currencies: state.currencies
} }
}; };
const mapDispatchToProps = (dispatch) => { const mapDispatchToProps = (dispatch) => {
return { return {
fetchData: { fetchData: {
countries: () => dispatch(countriesFetchData()) countries: () => dispatch(countriesFetchData()),
currencies: () => dispatch(currenciesFetchData()),
} }
}; };
}; };
......
...@@ -3,6 +3,30 @@ import Loading from './other/Loading'; ...@@ -3,6 +3,30 @@ import Loading from './other/Loading';
class MyComponent extends Component { class MyComponent extends Component {
getFetchedData(prop) {
return this.props[prop].fetched.data;
}
getAllFetchedData() {
let out = Object()
for (let prop_key in this.props) {
let prop = this.props[prop_key];
if (prop && 'fetched' in prop) {
out[prop_key] = prop.fetched.data;
}
}
return out;
}
joinCampus(campus) {
const { universities, countries, cities } = this.getAllFetchedData();
let res = Object.assign({}, campus); //copy for safety
res.university = universities[campus.university];
res.city = cities[campus.city]
res.country = countries[res.city.country]
return res;
}
checkProps(val) { checkProps(val) {
for (let el in this.props) { for (let el in this.props) {
let prop = this.props[el]; let prop = this.props[el];
...@@ -25,12 +49,12 @@ class MyComponent extends Component { ...@@ -25,12 +49,12 @@ class MyComponent extends Component {
return this.checkProps('invalidated'); return this.checkProps('invalidated');
} }
loadPropsIfNeeded(){ loadPropsIfNeeded() {
for (let prop_key in this.props) { for (let prop_key in this.props) {
let prop = this.props[prop_key]; let prop = this.props[prop_key];
if (prop && 'fetched' in prop){ if (prop && 'fetched' in prop) {
if ( (!prop.fetched.fetchedAt) || prop.invalidated){ if ((!prop.fetched.fetchedAt) || prop.invalidated) {
if (!prop.isLoading){ if (!prop.isLoading) {
this.props.fetchData[prop_key](); this.props.fetchData[prop_key]();
} }
} }
...@@ -42,14 +66,14 @@ class MyComponent extends Component { ...@@ -42,14 +66,14 @@ class MyComponent extends Component {
this.loadPropsIfNeeded(); this.loadPropsIfNeeded();
this.myComponentDidMount(); this.myComponentDidMount();
} }
myComponentDidMount(){}; myComponentDidMount() { };
componentDidUpdate() { componentDidUpdate() {
// TODO ajouter expire date // TODO ajouter expire date
this.loadPropsIfNeeded(); this.loadPropsIfNeeded();
this.myComponentDidMount(); this.myComponentDidMount();
} }
myComponentDidMount(){}; myComponentDidMount() { };
render() { render() {
if (this.checkPropsHasError()) { if (this.checkPropsHasError()) {
......
// Inspired by : https://material-ui.com/demos/autocomplete/
import React from 'react';
import PropTypes from 'prop-types';
import keycode from 'keycode';
import Downshift from 'downshift';
import { withStyles } from '@material-ui/core/styles';
import TextField from '@material-ui/core/TextField';
import Paper from '@material-ui/core/Paper';
import MenuItem from '@material-ui/core/MenuItem';
import Chip from '@material-ui/core/Chip';
import _ from 'underscore';
import fuzzysort from 'fuzzysort';
function renderInput(inputProps) {
const { InputProps, classes, ref, ...other } = inputProps;
return (
<TextField
InputProps={{
inputRef: ref,
classes: {
root: classes.inputRoot,
},
...InputProps,
}}
{...other}
/>
);
}
function renderSuggestion({ suggestion, index, itemProps, highlightedIndex, selectedItem }) {
const isHighlighted = highlightedIndex === index;
const isSelected = (selectedItem || '').indexOf(suggestion) > -1;
return (
<MenuItem
{...itemProps}
key={suggestion.label}
selected={isHighlighted}
component="div"
style={{
fontWeight: isSelected ? 500 : 400,
}}
>
{suggestion.label}
</MenuItem>
);
}
renderSuggestion.propTypes = {
highlightedIndex: PropTypes.number,
index: PropTypes.number,
itemProps: PropTypes.object,
selectedItem: PropTypes.string,
suggestion: PropTypes.shape({ label: PropTypes.string, id: PropTypes.number }).isRequired,
};
class DownshiftMultiple extends React.Component {
state = this.props.config;
componentDidMount() {
this.setState(this.props.config);
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.state.selectedItems != prevState.selectedItems) {
this.props.onChange(this.state.selectedItems);
}
}
componentWillUnmount() {
if (this.props.onComponentUnmount) {
this.props.onComponentUnmount(this.state)
}
}
getSuggestions(value) {
const { options } = this.props;
const { selectedItems } = this.state;
let possible = _.difference(options, selectedItems);
const filter = fuzzysort.go(value, possible, { limit: 5, key: 'label' });
if (filter.length > 0){
return _.map(filter, (item) => item.obj)
} else {
return possible.slice(0,4)
}
}
handleKeyDown = event => {
const { inputValue, selectedItems } = this.state;
if (selectedItems.length && !inputValue.length && keycode(event) === 'backspace') {
this.setState({
selectedItems: selectedItems.slice(0, selectedItems.length - 1),
});
}
};
handleInputChange = event => {
this.setState({ inputValue: event.target.value });
};
handleChange = item => {
const { options } = this.props;
let { selectedItems } = this.state;
if (selectedItems.indexOf(item) === -1) {
for (let ind in options) {
let el = options[ind]
if (el.id == item) {
selectedItems = [...selectedItems, el];
break;
}
}
}
this.setState({
inputValue: '',
selectedItems,
});
};
handleDelete = item => () => {
this.setState(state => {
const selectedItems = [...state.selectedItems];
selectedItems.splice(selectedItems.indexOf(item), 1);
return { selectedItems };
});
};
render() {
const { classes, field_label, field_placeholder } = this.props;
const { inputValue, selectedItems } = this.state;
return (
<div className={classes.root}>
<Downshift
id="downshift-multiple"
inputValue={inputValue}
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,
}),
)}
</Paper>
) : null}
</div>
)}
</Downshift>
</div>
);
}
}
DownshiftMultiple.propTypes = {
classes: PropTypes.object.isRequired,
};
DownshiftMultiple.defaultProps = {
options: [
{ label: 'Item 1', id: 1 },
{ label: 'Item 2', id: 2 },
{ label: 'Item 3', id: 3 },
{ label: 'Item 4', id: 4 },
],
field_label: "label",
field_placeholder: "placeholder",
config: { selectedItems: [], inputValue: '' }
};
const styles = theme => ({
root: {
flexGrow: 1,
height: 250,
},
container: {
flexGrow: 1,
position: 'relative',
},
paper: {
position: 'absolute',
zIndex: 1,
marginTop: theme.spacing.unit,
left: 0,
right: 0,
},
chip: {
margin: `${theme.spacing.unit / 2}px ${theme.spacing.unit / 4}px`,
},
inputRoot: {
flexWrap: 'wrap',
}
});
export default withStyles(styles)(DownshiftMultiple);
import React from 'react';
import DownshiftMultiple from './DownshiftMultiple';
import MyComponent from '../MyComponent'
import { connect } from "react-redux";
import _ from 'underscore';
import { saveSelectedUniversities, saveFilterConfig } from '../../actions/filter';
import {
universitiesFetchData,
mainCampusesFetchData,
citiesFetchData,
countriesFetchData
} from '../../generated/actions';
class Filter extends MyComponent {
saveContriesFilterConfig(state) {
this.props.saveConfig({ contriesFilter: state })
}
// getUnivFromCampus(campus) {
// const { universities } = this.props;
// return universities[campus.univ]
// }
// getCountryFromUniversity(univ) {
// const { countries } = this.props;
// return countries[univ.country]
// }
// getCountryFromCampus(campus) {
// const univ = this.getUnivFromCampus(campus);
// return this.getCountryFromUniversity(univ);
// }
getCountriesWhereThereAreUniversities() {
const { mainCampuses } = this.getAllFetchedData();
let res = [];
_.each(mainCampuses, (campus) => {
const campusFull = this.joinCampus(campus)
res.push(campusFull.country)
});
return _.uniq(res, false, (c) => { return c.iso_alpha2_code });
}
updateSelectedUniversities(selection) {
const { mainCampuses } = this.getAllFetchedData();
const listOfCountries = _.map(selection, (s) => s.id);
let selected_universities = []
_.each(mainCampuses, (campus) => {
const campusFull = this.joinCampus(campus)
if (_.indexOf(listOfCountries, campusFull.country.iso_alpha2_code) > -1) {
selected_universities.push(campusFull.university.id);
}
})
this.props.saveSelection(selected_universities);
}
myRender() {
const options = _.map(this.getCountriesWhereThereAreUniversities(),
(c) => { return { id: c.iso_alpha2_code, label: c.name } })
return (
<DownshiftMultiple
options={options}
onChange={(selection) => this.updateSelectedUniversities(selection)}
onComponentUnmount={(state) => this.saveContriesFilterConfig(state)}
config={this.props.contriesFilterConfig}
/>
);
}
}