Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Julien Jerphanion
Rex Dri
Commits
8647ed5c
Commit
8647ed5c
authored
Feb 08, 2019
by
Florent Chehab
Browse files
Added js linting settings and fixed linting issues
Added editor config
parent
7c509208
Changes
112
Expand all
Show whitespace changes
Inline
Side-by-side
.editorconfig
0 → 100644
View file @
8647ed5c
# editorconfig.org
root = true
[*]
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.py]
indent_size = 4
[*.js]
indent_size = 2
[*.json]
indent_size = 2
\ No newline at end of file
frontend/.eslintrc.js
0 → 100644
View file @
8647ed5c
module
.
exports
=
{
"
env
"
:
{
"
browser
"
:
true
,
"
es6
"
:
true
,
},
"
extends
"
:
[
"
eslint:recommended
"
,
"
plugin:react/recommended
"
],
"
parser
"
:
"
babel-eslint
"
,
"
parserOptions
"
:
{
"
ecmaVersion
"
:
2018
,
"
sourceType
"
:
"
module
"
,
"
ecmaFeatures
"
:
{
"
jsx
"
:
true
}
},
"
plugins
"
:
[
"
react
"
],
"
rules
"
:
{
"
indent
"
:
[
"
error
"
,
2
],
"
linebreak-style
"
:
[
"
error
"
,
"
unix
"
],
"
quotes
"
:
[
"
error
"
,
"
double
"
],
"
semi
"
:
[
"
error
"
,
"
always
"
],
"
react/no-unescaped-entities
"
:
"
warn
"
,
"
react/prop-types
"
:
"
warn
"
,
"
react/no-deprecated
"
:
"
warn
"
}
};
frontend/.eslintrc.yml
deleted
100644 → 0
View file @
7c509208
extends
:
google
frontend/package-lock.json
View file @
8647ed5c
This diff is collapsed.
Click to expand it.
frontend/package.json
View file @
8647ed5c
...
...
@@ -4,6 +4,8 @@
"description"
:
"[](https://gitlab.utc.fr/chehabfl/outgoing_rex/pipelines) [](https://chehabfl.gitlab.utc.fr/outgoing_rex/) [](https://opensource.org/licenses/BSD-2-Clause)"
,
"main"
:
"manage.py"
,
"scripts"
:
{
"lint"
:
"eslint
\"
./src/**/*.js
\"
"
,
"lint-fix"
:
"eslint
\"
./src/**/*.js
\"
--fix"
,
"dev"
:
"webpack --mode development"
,
"build"
:
"webpack --mode production"
,
"test"
:
"echo
\"
Error: no test specified
\"
&& exit 1"
,
...
...
@@ -45,6 +47,7 @@
"devDependencies"
:
{
"autoprefixer"
:
"^9.1.5"
,
"babel-core"
:
"^6.26.3"
,
"babel-eslint"
:
"^10.0.1"
,
"babel-loader"
:
"^7.1.5"
,
"babel-plugin-transform-class-properties"
:
"^6.24.1"
,
"babel-preset-env"
:
"^1.7.0"
,
...
...
@@ -52,8 +55,9 @@
"babel-preset-react"
:
"^6.24.1"
,
"babel-preset-stage-1"
:
"^6.24.1"
,
"css-loader"
:
"^1.0.0"
,
"eslint"
:
"^5.
4
.0"
,
"eslint"
:
"^5.
13
.0"
,
"eslint-config-google"
:
"^0.9.1"
,
"eslint-plugin-react"
:
"^7.12.4"
,
"file-loader"
:
"^2.0.0"
,
"js-yaml-loader"
:
"^1.0.1"
,
"mini-css-extract-plugin"
:
"^0.4.2"
,
...
...
frontend/src/actions/action-types.js
View file @
8647ed5c
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
'
;
export
const
SAVE_APP_THEME
=
'
SAVE_APP_THEME
'
;
export
const
SAVE_APP_COLOR_PICKER
=
'
SAVE_APP_COLOR_PICKER
'
;
export
const
SAVE_UNIVERSITY_BEING_VIEWED
=
'
SAVE_UNIVERSITY_BEING_VIEWED
'
;
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
"
;
export
const
SAVE_APP_THEME
=
"
SAVE_APP_THEME
"
;
export
const
SAVE_APP_COLOR_PICKER
=
"
SAVE_APP_COLOR_PICKER
"
;
export
const
SAVE_UNIVERSITY_BEING_VIEWED
=
"
SAVE_UNIVERSITY_BEING_VIEWED
"
;
frontend/src/actions/filter.js
View file @
8647ed5c
frontend/src/actions/map.js
View file @
8647ed5c
frontend/src/actions/search.js
View file @
8647ed5c
frontend/src/actions/theme.js
View file @
8647ed5c
frontend/src/actions/universityPage.js
View file @
8647ed5c
frontend/src/api/CrudActions.js
View file @
8647ed5c
...
...
@@ -9,7 +9,7 @@ function _isReading(status, type) {
return
{
type
,
status
}
}
;
}
function
_readFailed
(
failed
,
error
,
type
)
{
...
...
@@ -17,7 +17,7 @@ function _readFailed(failed, error, type) {
type
,
failed
,
error
}
}
;
}
function
_readSucceeded
(
data
,
type
)
{
...
...
@@ -82,7 +82,7 @@ export default class CrudActions {
readAllSucceeded
,
this
.
setInvalidatedAll
.
bind
(
this
),
readAllFailed
)
)
;
}
...
...
@@ -116,7 +116,7 @@ export default class CrudActions {
readSpecificSucceeded
,
this
.
setInvalidatedSpecific
.
bind
(
this
),
readSpecificFailed
)
)
;
}
...
...
@@ -182,7 +182,7 @@ export default class CrudActions {
isCreating
,
createSucceeded
,
createFailed
)
)
;
}
/**
...
...
@@ -218,7 +218,7 @@ export default class CrudActions {
}
self
.
_checkNotReadonly
();
if
(
!
'
id
'
in
data
)
{
if
(
!
(
"
id
"
in
data
)
)
{
throw
Error
(
"
When updating an object, an id is expected in the data
"
);
}
...
...
@@ -228,7 +228,7 @@ export default class CrudActions {
isUpdating
,
updateSucceeded
,
updateFailed
)
)
;
}
///////////
...
...
@@ -246,7 +246,7 @@ export default class CrudActions {
return
{
type
:
this
.
types
.
isInvalidatedAll
,
isInvalidated
:
bool
}
}
;
}
/**
...
...
@@ -260,6 +260,6 @@ export default class CrudActions {
return
{
type
:
this
.
types
.
isInvalidatedSpecific
,
isInvalidated
:
bool
}
}
;
}
}
\ No newline at end of file
frontend/src/api/CrudReducers.js
View file @
8647ed5c
import
{
combineReducers
}
from
'
redux
'
;
import
{
combineReducers
}
from
"
redux
"
;
import
getCrudActionTypes
from
"
./getCrudActionTypes
"
;
import
SmartActions
from
"
./SmartActions
"
;
...
...
@@ -10,7 +10,7 @@ function failed(state, action, type) {
return
{
failed
:
action
.
failed
,
error
:
action
.
error
}
}
;
default
:
return
state
;
...
...
@@ -23,7 +23,7 @@ function succeeded(state, action, type) {
return
{
data
:
action
.
data
,
readAt
:
(
new
Date
()).
getTime
()
}
}
;
default
:
return
state
;
...
...
@@ -150,7 +150,7 @@ export default class CrudReducers {
readSucceeded
:
readSpecificSucceeded
,
readFailed
:
readSpecificFailed
,
isInvalidated
:
isInvalidatedSpecific
,
}
}
;
// Add only appropriate reducers
if
(
this
.
readOnly
!==
true
)
{
...
...
frontend/src/api/SmartActions.js
View file @
8647ed5c
import
Cookies
from
'
js-cookie
'
;
import
Cookies
from
"
js-cookie
"
;
// TODO uodate parameters name to match new CRUD actions
...
...
@@ -32,11 +32,11 @@ export default class SmartActions {
* @memberof SmartActions
*/
_FetchData
(
pk
,
api_end_point
,
_IsLoading
,
_FetchDataSuccess
,
_Invalidated
,
_HasError
,
pk_required
=
false
)
{
if
(
pk_required
&&
(
typeof
pk
==
'
undefined
'
))
{
if
(
pk_required
&&
(
typeof
pk
==
"
undefined
"
))
{
throw
"
pk shouldn't be empty when requesting a specific element
"
;
}
if
(
pk
!=
""
)
{
api_end_point
+=
pk
+
'
/
'
;
api_end_point
+=
pk
+
"
/
"
;
}
if
(
!
this
.
shouldFetchEndPoint
(
api_end_point
))
{
...
...
@@ -47,8 +47,8 @@ export default class SmartActions {
return
(
dispatch
)
=>
{
dispatch
(
_IsLoading
(
true
));
let
token
=
Cookies
.
get
(
'
csrftoken
'
);
fetch
(
api_end_point
,
{
credentials
:
'
same-origin
'
,
headers
:
{
'
X-CSRFToken
'
:
token
}
})
let
token
=
Cookies
.
get
(
"
csrftoken
"
);
fetch
(
api_end_point
,
{
credentials
:
"
same-origin
"
,
headers
:
{
"
X-CSRFToken
"
:
token
}
})
.
then
((
response
)
=>
{
if
(
!
response
.
ok
)
{
this
.
fetching
.
delete
(
api_end_point
);
...
...
@@ -88,26 +88,26 @@ export default class SmartActions {
return
(
dispatch
)
=>
{
let
method
=
"
POST
"
;
let
pk
=
""
;
if
(
'
id
'
in
data
)
{
if
(
"
id
"
in
data
)
{
method
=
"
PUT
"
;
pk
=
data
.
id
+
'
/
'
;
pk
=
data
.
id
+
"
/
"
;
}
let
__apiAttr
=
''
;
if
(
'
__apiAttr
'
in
data
&&
data
.
__apiAttr
!=
''
)
{
__apiAttr
=
data
.
__apiAttr
+
'
/
'
;
let
__apiAttr
=
""
;
if
(
"
__apiAttr
"
in
data
&&
data
.
__apiAttr
!=
""
)
{
__apiAttr
=
data
.
__apiAttr
+
"
/
"
;
}
let
errorStatusText
=
''
;
let
errorStatusText
=
""
;
dispatch
(
_ElIsSaving
(
true
));
let
token
=
Cookies
.
get
(
'
csrftoken
'
);
let
token
=
Cookies
.
get
(
"
csrftoken
"
);
fetch
(
api_end_point
+
__apiAttr
+
pk
,
{
method
:
method
,
credentials
:
'
same-origin
'
,
credentials
:
"
same-origin
"
,
headers
:
{
'
Accept
'
:
'
application/json
'
,
'
Content-Type
'
:
'
application/json
'
,
'
X-CSRFToken
'
:
token
"
Accept
"
:
"
application/json
"
,
"
Content-Type
"
:
"
application/json
"
,
"
X-CSRFToken
"
:
token
},
body
:
JSON
.
stringify
(
data
)
})
...
...
@@ -123,18 +123,19 @@ export default class SmartActions {
dispatch
(
_ElIsSaving
(
false
));
})
.
catch
((
e
)
=>
{
if
(
typeof
e
.
json
==
'
function
'
)
{
return
e
.
json
()
if
(
typeof
e
.
json
==
"
function
"
)
{
return
e
.
json
()
;
}
else
{
return
new
Promise
(
function
(
resolve
,
reject
)
{
resolve
(
e
)
});
// eslint-disable-next-line no-unused-vars
return
new
Promise
(
function
(
resolve
,
reject
)
{
resolve
(
e
);
});
}
})
.
then
((
errorContent
)
=>
{
if
(
typeof
errorContent
!=
'
undefined
'
)
{
if
(
typeof
errorContent
!=
"
undefined
"
)
{
dispatch
(
_ElHasError
(
true
,
{
message
:
errorStatusText
,
content
:
errorContent
}));
dispatch
(
_ElIsSaving
(
false
));
}
})
})
;
};
}
}
frontend/src/api/buildApiActionsAndReducers.js
View file @
8647ed5c
...
...
@@ -14,7 +14,7 @@ const otherAPI = [
name
:
"
serverModerationStatus
"
,
api_end_point
:
"
serverModerationStatus
"
}
]
]
;
/**
* Create api actions and reducers for given the info in each arr obj
...
...
@@ -43,7 +43,7 @@ function addAPIs(arr) {
const
reducers
=
new
CrudReducers
(
apiInfo
);
apiReducersTmp
[
`
${
apiInfo
.
name
}
All`
]
=
reducers
.
getCombinedAll
();
apiReducersTmp
[
`
${
apiInfo
.
name
}
Specific`
]
=
reducers
.
getCombinedSpecific
();
})
})
;
}
addAPIs
(
otherAPI
);
...
...
frontend/src/api/getActions.js
View file @
8647ed5c
import
{
apiActions
}
from
"
./buildApiActionsAndReducers
"
;
import
CrudActions
from
"
./CrudActions
"
;
/**
* Function to get the the actions corresponding to an API end point.
...
...
@@ -10,7 +9,7 @@ import CrudActions from "./CrudActions";
*/
export
default
function
getActions
(
name
)
{
if
(
!
(
name
in
apiActions
))
{
console
.
error
(
"
available actions
"
,
apiActions
);
console
.
error
(
"
available actions
"
,
apiActions
);
// eslint-disable-line no-console
throw
Error
(
`Requested api action is not defined, check the name:
${
name
}
`
);
}
return
apiActions
[
name
];
...
...
frontend/src/api/getCrudActionTypes.js
View file @
8647ed5c
...
...
@@ -30,5 +30,5 @@ export default function getCrudActionTypes(name) {
// Not directly a CRUD action but needed for in app behavior
isInvalidatedAll
:
`API_
${
name
}
_ALL_INVALIDATED`
,
isInvalidatedSpecific
:
`API_
${
name
}
_SPECIFIC_INVALIDATED`
,
}
}
;
}
\ No newline at end of file
frontend/src/components/App.js
View file @
8647ed5c
// 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
'
;
import
classNames
from
'
classnames
'
;
import
withStyles
from
'
@material-ui/core/styles/withStyles
'
;
import
CssBaseline
from
'
@material-ui/core/CssBaseline
'
;
import
Drawer
from
'
@material-ui/core/Drawer
'
;
import
List
from
'
@material-ui/core/List
'
;
import
Divider
from
'
@material-ui/core/Divider
'
;
import
IconButton
from
'
@material-ui/core/IconButton
'
;
import
MenuIcon
from
'
@material-ui/icons/Menu
'
;
import
Chip
from
'
@material-ui/core/Chip
'
;
import
Avatar
from
'
@material-ui/core/Avatar
'
;
import
ChevronLeftIcon
from
'
@material-ui/icons/ChevronLeft
'
;
import
SchoolIcon
from
'
@material-ui/icons/School
'
;
import
{
mainListItems
,
secondaryListItems
,
thirdListItems
}
from
'
./template/listItems
'
;
import
React
from
"
react
"
;
import
PropTypes
from
"
prop-types
"
;
import
classNames
from
"
classnames
"
;
import
withStyles
from
"
@material-ui/core/styles/withStyles
"
;
import
CssBaseline
from
"
@material-ui/core/CssBaseline
"
;
import
Drawer
from
"
@material-ui/core/Drawer
"
;
import
List
from
"
@material-ui/core/List
"
;
import
Divider
from
"
@material-ui/core/Divider
"
;
import
IconButton
from
"
@material-ui/core/IconButton
"
;
import
MenuIcon
from
"
@material-ui/icons/Menu
"
;
import
Chip
from
"
@material-ui/core/Chip
"
;
import
Avatar
from
"
@material-ui/core/Avatar
"
;
import
ChevronLeftIcon
from
"
@material-ui/icons/ChevronLeft
"
;
import
SchoolIcon
from
"
@material-ui/icons/School
"
;
import
{
mainListItems
,
secondaryListItems
,
thirdListItems
}
from
"
./template/listItems
"
;
import
{
connect
}
from
"
react-redux
"
;
import
CustomComponentForAPI
from
'
./CustomComponentForAPI
'
import
CustomComponentForAPI
from
"
./CustomComponentForAPI
"
;
// import route Components here
import
{
Route
,
Redirect
}
from
'
react-router-dom
'
;
}
from
"
react-router-dom
"
;
import
getActions
from
"
../api/getActions
"
;
import
PageMap
from
'
./pages/PageMap
'
;
import
PageHome
from
'
./pages/PageHome
'
;
import
PageUniversity
from
'
./pages/PageUniversity
'
;
import
PageSearch
from
'
./pages/PageSearch
'
;
import
PageSettings
from
'
./pages/PageSettings
'
;
import
PageMap
from
"
./pages/PageMap
"
;
import
PageHome
from
"
./pages/PageHome
"
;
import
PageUniversity
from
"
./pages/PageUniversity
"
;
import
PageSearch
from
"
./pages/PageSearch
"
;
import
PageSettings
from
"
./pages/PageSettings
"
;
const
drawerWidth
=
240
;
const
styles
=
theme
=>
({
root
:
{
display
:
'
flex
'
,
display
:
"
flex
"
,
},
toolbar
:
{
paddingRight
:
24
,
// keep right padding when drawer closed
},
toolbarIcon
:
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
flex-end
'
,
padding
:
'
0 8px
'
,
display
:
"
flex
"
,
alignItems
:
"
center
"
,
justifyContent
:
"
flex-end
"
,
padding
:
"
0 8px
"
,
...
theme
.
mixins
.
toolbar
,
},
chip
:
{
...
...
@@ -56,36 +56,36 @@ const styles = theme => ({
marginRight
:
4
,
},
hideIt
:
{
display
:
'
none
'
,
display
:
"
none
"
,
},
title
:
{
flexGrow
:
1
,
},
drawerPaper
:
{
position
:
'
relative
'
,
whiteSpace
:
'
nowrap
'
,
position
:
"
relative
"
,
whiteSpace
:
"
nowrap
"
,
width
:
drawerWidth
,
transition
:
theme
.
transitions
.
create
(
'
width
'
,
{
transition
:
theme
.
transitions
.
create
(
"
width
"
,
{
easing
:
theme
.
transitions
.
easing
.
sharp
,
duration
:
theme
.
transitions
.
duration
.
enteringScreen
,
}),
},
drawerPaperClose
:
{
overflowX
:
'
hidden
'
,
transition
:
theme
.
transitions
.
create
(
'
width
'
,
{
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
'
)]:
{
[
theme
.
breakpoints
.
up
(
"
sm
"
)]:
{
width
:
theme
.
spacing
.
unit
*
9
,
},
},
content
:
{
flexGrow
:
1
,
padding
:
theme
.
spacing
.
unit
*
3
,
height
:
'
100vh
'
,
overflow
:
'
auto
'
,
height
:
"
100vh
"
,
overflow
:
"
auto
"
,
paddingTop
:
"
0px
"
},
paddingTop
:
{
...
...
@@ -191,7 +191,7 @@ const mapStateToProps = (state) => {
return
{
countries
:
state
.
api
.
countriesAll
,
currencies
:
state
.
api
.
currenciesAll
,
}
}
;
};
const
mapDispatchToProps
=
(
dispatch
)
=>
{
...
...
frontend/src/components/CustomComponentForAPI.js
View file @
8647ed5c
import
React
,
{
Component
}
from
'
react
'
;
import
Loading
from
'
./other/Loading
'
;
import
React
,
{
Component
}
from
"
react
"
;
import
Loading
from
"
./other/Loading
"
;
// Stores the name of the reducers/actions that result in read data
const
successActionsWithReads
=
[
"
readSucceeded
"
,
"
createSucceeded
"
,
"
updateSucceeded
"
];
...
...
@@ -21,7 +21,7 @@ class CustomComponentForAPI extends Component {
// a little bit of optimization
// Stores the list of props that use the API
this
.
apiProps
=
Array
();
if
(
typeof
props
===
'
object
'
&&
'
api
'
in
props
)
{
if
(
typeof
props
===
"
object
"
&&
"
api
"
in
props
)
{
const
{
api
}
=
props
;
this
.
apiProps
=
Object
.
keys
(
api
);
}
...
...
@@ -35,7 +35,7 @@ class CustomComponentForAPI extends Component {
this
.
customComponentDidMount
();
this
.
forceUpdate
();
// bug otherwise
}
customComponentDidMount
()
{
}
;
customComponentDidMount
()
{
}
shouldComponentUpdate
(
nextProps
,
nextState
)
{
// Below is buggy with redux connect
...
...
@@ -53,6 +53,7 @@ class CustomComponentForAPI extends Component {
// // }
return
this
.
customShouldComponentUpdate
(
nextProps
,
nextState
);
}
// eslint-disable-next-line no-unused-vars
customShouldComponentUpdate
(
nextProps
,
nextState
)
{
return
true
;
}
componentDidUpdate
(
prevProps
,
prevState
,
snapshot
)
{
...
...
@@ -60,7 +61,7 @@ class CustomComponentForAPI extends Component {
this
.
readPropsIfNeeded
();
this
.
customComponentDidUpdate
(
prevProps
,
prevState
,
snapshot
);
}
customComponentDidUpdate
()
{
}
;
customComponentDidUpdate
()
{
}
// Override of the default render behaviour to wait for data to arrive.
...
...
@@ -89,12 +90,13 @@ class CustomComponentForAPI extends Component {
checkPropsFailed() {
return this.apiProps.some((propName) => {
if (typeof this.props[propName] === "undefined") {
// eslint-disable-next-line no-console
console.error(this.props, propName);
throw Error(`${propName} is not in the class props ! Dev, check what your are doing`);
}
const prop = this.props[propName];
return prop.readFailed.failed; // general handling of all types of API reducers
})
})
;
}
...
...
@@ -141,7 +143,7 @@ class CustomComponentForAPI extends Component {
readPropsIfNeeded() {
this.getListPropsNeedRead().map((propName) => {
this.performReadFromApi(propName);
})
})
;
}
/**
...
...
@@ -185,10 +187,10 @@ class CustomComponentForAPI extends Component {
.map(action => prop[action])
.reduce(
(prev, curr) => prev.readAt < curr.readAt ? curr : prev,
{ readAt: 0 })
{ readAt: 0 })
;
if (!"data" in out) {
throw Error(`No read data from the api could be retreived for: ${propName}`)
if (!
(
"data" in out)
)
{
throw Error(`No read data from the api could be retreived for: ${propName}`)
;
} else {
return out;
}
...
...
@@ -213,7 +215,7 @@ class CustomComponentForAPI extends Component {
* @memberof CustomComponentForAPI
*/
getAllReadData() {
let out = Object()
let out = Object()
;
this.apiProps.forEach((propName) => {
out[propName] = this.getReadData(propName);
});
...
...
@@ -226,8 +228,8 @@ class CustomComponentForAPI extends Component {
const { universities, countries, cities } = this.getAllReadData();
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.city = cities[campus.city]
;
res.country = countries[res.city.country]
;
return res;
}
...
...
@@ -237,13 +239,13 @@ class CustomComponentForAPI extends Component {
const countries = this.getReadData("countries");
const city = cities[univMainCampus.city];
const country = countries[city.country];
return { city, country }
return { city, country }
;
}
findMainCampus(univId) {
const mainCampuses = this.getReadData(
'
mainCampuses
'
);
const mainCampuses = this.getReadData(
"
mainCampuses
"
);
for (let mainCampusPk in mainCampuses) {
const campus = mainCampuses[mainCampusPk]
const campus = mainCampuses[mainCampusPk]
;
if (campus.university == univId) {
return campus;
}
...
...
frontend/src/components/ThemeProvider.js