Commit ccd63b4c authored by Florent Chehab's avatar Florent Chehab

Comments added in all but university generally speaking + code cleaning

parent ebe42bc4
Pipeline #35761 passed with stages
in 9 minutes and 32 seconds
......@@ -60,8 +60,7 @@ class App extends CustomComponentForAPI {
const { classes } = this.props;
return (
<React.Fragment>
<>
<CssBaseline />
<div className={classes.root}>
<Drawer
......@@ -133,7 +132,7 @@ class App extends CustomComponentForAPI {
</main>
</div>
</React.Fragment>
</>
);
}
}
......
......@@ -6,7 +6,7 @@ import { connect } from "react-redux";
/**
* Function to enable the FullScreenDialog to have nice transitions
* Component to enable the FullScreenDialog to have nice transitions
* @returns
*/
function Transition(props) {
......@@ -37,8 +37,8 @@ class FullScreenDialog extends Component {
FullScreenDialog.propTypes = {
open: PropTypes.bool.isRequired,
innerNodes: PropTypes.node.isRequired,
open: PropTypes.bool.isRequired, // Is the full screen dialog open
innerNodes: PropTypes.node.isRequired, // content of the fullScreen Dialog
};
// Get the props from the redux store.
......
/**
* This file contains the general site template
* This file contains elements of the site template
*/
import React from "react";
......
......@@ -11,53 +11,61 @@ import withStyles from "@material-ui/core/styles/withStyles";
import compose from "recompose/compose";
/**
* Component to render an "alert" that prevents all other interaction on the site.
*
* @class Alert
* @extends {React.Component}
*/
class Alert extends React.Component {
render() {
const { classes } = this.props;
if (!this.props.open) {
return <></>;
}
return (
<Dialog
open={this.props.open}
open={true}
>
{
this.props.open ?
<>
<DialogTitle>{this.props.title}</DialogTitle>
<DialogContent>
<DialogContentText style={{ whiteSpace: "pre-wrap" }}>
{this.props.description}
</DialogContentText>
</DialogContent>
<DialogActions>
{
this.props.info ?
<Button onClick={() => { this.props.handleClose(); this.props.handleResponse(); }} color="primary">
{this.props.infoText}
<>
<DialogTitle>{this.props.title}</DialogTitle>
<DialogContent>
<DialogContentText style={{ whiteSpace: "pre-wrap" }}>
{this.props.description}
</DialogContentText>
</DialogContent>
<DialogActions>
{
this.props.info ?
<Button onClick={() => { this.props.handleClose(); this.props.handleResponse(); }} color="primary">
{this.props.infoText}
</Button>
:
<div>
<Button
onClick={() => { this.props.handleClose(); this.props.handleResponse(false); }}
color='secondary'
className={this.props.multilineButtons ? classes.multilineButton : classes.button}
>
{this.props.disagreeText}
</Button>
<Button
onClick={() => { this.props.handleClose(); this.props.handleResponse(true); }}
className={this.props.multilineButtons ? classes.multilineButton : classes.button}
variant='outlined'
color="primary"
autoFocus
>
{this.props.agreeText}
</Button>
:
<div>
<Button
onClick={() => { this.props.handleClose(); this.props.handleResponse(false); }}
color='secondary'
className={this.props.multilineButtons ? classes.multilineButton : classes.button}
>
{this.props.disagreeText}
</Button>
<Button
onClick={() => { this.props.handleClose(); this.props.handleResponse(true); }}
className={this.props.multilineButtons ? classes.multilineButton : classes.button}
variant='outlined'
color="primary"
autoFocus
>
{this.props.agreeText}
</Button>
</div>
}
</DialogActions>
</>
:
<></>
</div>
}
</DialogActions>
</>
}
</Dialog >
);
......@@ -66,16 +74,16 @@ class Alert extends React.Component {
Alert.propTypes = {
open: PropTypes.bool.isRequired,
info: PropTypes.bool.isRequired,
title: PropTypes.string.isRequired,
agreeText: PropTypes.string.isRequired,
disagreeText: PropTypes.string.isRequired,
infoText: PropTypes.string.isRequired,
description: PropTypes.string,
handleClose: PropTypes.func.isRequired,
handleResponse: PropTypes.func.isRequired,
multilineButtons: PropTypes.bool.isRequired,
open: PropTypes.bool.isRequired, // is the alert open
title: PropTypes.string.isRequired, // Title display on the vindow
description: PropTypes.string, // textual description (below the titile)
info: PropTypes.bool.isRequired, // If it's just un info alert with one button
infoText: PropTypes.string.isRequired, // content of the alert when it's an info
agreeText: PropTypes.string.isRequired, // Text display in the "agree" button
disagreeText: PropTypes.string.isRequired, // Text display in the "disagree" button
multilineButtons: PropTypes.bool.isRequired, // should the agree/disagree button displayed on multiple lines
handleClose: PropTypes.func.isRequired, // function called when the alert is closed
handleResponse: PropTypes.func.isRequired, // function called allong the previous one with false if the user disagreed and true otherwise. Or no parameters if info.
classes: PropTypes.object.isRequired
};
......
......@@ -3,6 +3,44 @@ import Loading from "./Loading";
import PropTypes from "prop-types";
import { successActionsWithReads, getLatestRead } from "../../redux/api/utils";
/**
* Custom react component to be used when called to the api are required to display data of the component.
*
* When extending, you shouldn't use the `render` function but the `customRender` (that will be called once all the data is ready)
*
* Also, when connecting your component to redux, you should use a `mapDispatchToProps` such as this one.
* What is important is that "api" related elements are under the `api` key for optimization purposes.
* NB: if you don't do it this way, the data won't be fetched.
*
```JS
const mapDispatchToProps = (dispatch) => {
return {
api: {
universities: () => dispatch(getActions("universities").readAll()),
},
saveUniversityInView: (univId) => dispatch(saveUniversityBeingViewed(univId))
};
};
```
*
* IMPORTANT: to use some of the function contained in this class, you need to have a matching `mapStateToProps` function for read
* elements: the key of the object should match the one in `api`.
*
* Here `universities` is both in the `api` of `mapDispatchToProps` and in `mapStateToProps`.
```JS
const mapStateToProps = (state) => {
return {
universities: state.api.universitiesAll,
universityBeingViewed: state.app.universityBeingViewed
};
};
```
*
*
* @abstract
* @class CustomComponentForAPI
* @extends {Component}
*/
class CustomComponentForAPI extends Component {
customErrorHandlers = {}
// __apiAttr should be an object
......@@ -52,11 +90,12 @@ class CustomComponentForAPI extends Component {
/**
* Function to use instead of `render`.
*
* @virtual
* @memberof CustomComponentForAPI
*/
customRender() {
// eslint-disable-next-line no-console
console.error("Dev: you forget to define the `customRender` function that is used when rendering within a subClass of CustomComponentForAPI");
throw new Error("Dev: you forget to define the `customRender` function that is used when rendering within a subClass of CustomComponentForAPI");
}
// End of react functions override
......@@ -270,7 +309,7 @@ class CustomComponentForAPI extends Component {
}
CustomComponentForAPI.propTypes = {
api: PropTypes.object.isRequired
api: PropTypes.object.isRequired // should be given through redux connect of `mapDispatchToProps`
};
export default CustomComponentForAPI;
......@@ -188,8 +188,10 @@ const renderers = {
/**
* Custom Markdown component renderer to make use of material UI
*
* We don't make use of Custom Component for API since currencies should be loaded at app startup.
* We don't need to fetch them here
* A custom tag for currency was adde: `:100.2CHF:` would be understood as `100 CHF (~88€)` for instance.
*
* NB: We don't make use of Custom Component for API since currencies should be loaded at app startup.
* We don't need to fetch them here.
*
* @class Markdown
* @extends {Component}
......
......@@ -3,7 +3,7 @@ import Badge from "@material-ui/core/Badge";
/**
* Custom component to render a badge only if there is something interesting to display
* Custom component to render a badge (little circle with a number in it) only if there is something interesting to display
*
* @export
* @param {object} props
......
......@@ -22,7 +22,7 @@ const styles = theme => {
/**
* Component to render the links in custom manner
*
*/
*/
export default withStyles(styles, { withTheme: true })(({ classes, ...props }) => (
<a href={props.href} className={classes.link} target="_blank" rel="noopener noreferrer">
{props.children}
......
......@@ -34,12 +34,12 @@ class ThemeProvider extends CustomComponentForAPI {
return;
}
const currentTheme = this.state.theme;
const themeSavedInTheApp = this.props.themeSavedInTheApp.theme;
const themeSavedInTheAppAt = this.props.themeSavedInTheApp.savedAt;
const userDataAndTime = this.getReadDataAndTime("userData");
const themeOnServer = userDataAndTime.data.config.theme;
const themeOnServerRetrievedAt = userDataAndTime.readAt;
const currentTheme = this.state.theme,
themeSavedInTheApp = this.props.themeSavedInTheApp.theme,
themeSavedInTheAppAt = this.props.themeSavedInTheApp.savedAt,
userDataAndTime = this.getReadDataAndTime("userData"),
themeOnServer = userDataAndTime.data.config.theme,
themeOnServerRetrievedAt = userDataAndTime.readAt;
if (!themeOnServer) {
// config on server is raw, we need to add the default one.
......
/**
* React context to hold visibility information for optimization purposes.
*/
import React from "react";
const VisibilityContext = React.createContext();
export default VisibilityContext;
\ No newline at end of file
export default VisibilityContext;
......@@ -105,11 +105,13 @@ class Form extends Component {
.combine(this.hasFormLevelErrors());
}
/**
* Function to check for errors in the form
*
* Can be override in sub classes
*
* @virtual
* @returns {CustomError}
* @memberof Form
*/
......
......@@ -26,12 +26,29 @@ class LocalizedUtils extends DateFnsUtils {
}
}
/**
* Form field for dates
*
* @class DateField
* @extends {Field}
*/
class DateField extends Field {
/**
* @override
* @returns
* @memberof DateField
*/
serializeFromField() {
return dateToDateStr(this.state.value);
}
/**
* Specific error detection for this field
* @override
* @returns {CustomError}
* @memberof MarkdownField
*/
getError() {
const date = this.state.value;
let messages = Array();
......@@ -42,12 +59,20 @@ class DateField extends Field {
return new CustomError(messages);
}
handleDateChange = (date) => {
/**
* @param {Date} date
* @memberof DateField
*/
handleDateChange(date) {
this.setState({
value: date,
});
}
/**
* @override
* @memberof DateField
*/
renderField() {
return (
<MuiPickersUtilsProvider utils={LocalizedUtils} locale={frLocale}>
......@@ -55,7 +80,7 @@ class DateField extends Field {
clearable
format="d MMM yyyy"
value={this.state.value}
onChange={this.handleDateChange}
onChange={(d) => this.handleDateChange(d)}
clearLabel="vider"
cancelLabel="annuler"
leftArrowIcon={<KeyboardArrowLeftIcon />}
......
......@@ -36,6 +36,13 @@ class Field extends PureComponent {
};
}
/**
* Sets the new state. Before it's done it replaces the value by null if appropriate.
*
* @override
* @param {object} newState
* @memberof Field
*/
setState(newState) {
let state = Object.assign({}, newState);
if (typeof this.defaultNullValue !== "undefined") {
......@@ -45,6 +52,9 @@ class Field extends PureComponent {
}
/**
* What is error state of the field.
*
* @virtual
* @returns {CustomError}
* @memberof Field
*/
......@@ -79,12 +89,19 @@ class Field extends PureComponent {
*
* MUST BE OVERRIDE
*
* @virtual
* @memberof Field
*/
renderField() {
throw new Error("This method should be override in subclass of Field");
}
/**
* Default react render function
*
* @returns
* @memberof Field
*/
render() {
return (
<FieldWrapper
......@@ -105,11 +122,11 @@ Field.defaultProps = {
};
Field.propTypes = {
required: PropTypes.bool.isRequired,
label: PropTypes.string,
value: PropTypes.isRequired,
form: PropTypes.oneOf([Form]).isRequired,
fieldMapping: PropTypes.string.isRequired,
required: PropTypes.bool.isRequired, // is the field required ?
label: PropTypes.string, // text to go along the field
value: PropTypes.isRequired, // value of the field
form: PropTypes.instanceOf(Form).isRequired, // (reference of to the) form containing the field
fieldMapping: PropTypes.string.isRequired, // name of the field in the data
};
export default Field;
......@@ -11,7 +11,7 @@ import CustomError from "../../common/CustomError";
/**
* Wrapper for fields in form to handle labels and visual feedback
* Wrapper for fields in forms to handle labels and visual feedback
*
* @class FieldWrapper
* @extends {PureComponent}
......@@ -42,10 +42,10 @@ FieldWrapper.defaultProps = {
FieldWrapper.propTypes = {
label: PropTypes.string.isRequired,
errorObj: PropTypes.instanceOf(CustomError).isRequired,
classes: PropTypes.object.isRequired,
required: PropTypes.bool.isRequired,
errorObj: PropTypes.instanceOf(CustomError).isRequired, // Error state of the field
children: PropTypes.node.isRequired,
classes: PropTypes.object.isRequired,
};
......
......@@ -13,9 +13,24 @@ import TextLink from "../../common/TextLink";
import CustomError from "../../common/CustomError";
/**
* Form field for markdown elements.
*
* A `maxLength` can be set to limit the length of the text.
*
* @class MarkdownField
* @extends {Field}
*/
class MarkdownField extends Field {
defaultNullValue = "";
/**
* Specific error detection for this field
*
* @override
* @returns {CustomError}
* @memberof MarkdownField
*/
getError() {
const { value } = this.state;
let messages = Array();
......@@ -29,6 +44,9 @@ class MarkdownField extends Field {
return new CustomError(messages);
}
/**
* @memberof MarkdownField
*/
handleChangeValue = (val) => {
let value = val;
const { maxLength } = this.props;
......@@ -40,6 +58,11 @@ class MarkdownField extends Field {
}
/**
* @override
* @returns
* @memberof MarkdownField
*/
renderField() {
const valFromState = this.state.value,
......
......@@ -11,6 +11,13 @@ import Field from "./Field";
import CustomError from "../../common/CustomError";
/**
* Form field for multi selects.
*
*
* @class MultiSelectField
* @extends {Field}
*/
class MultiSelectField extends Field {
constructor(props) {
......@@ -19,6 +26,11 @@ class MultiSelectField extends Field {
props.options.map((opt) => this.optionsByValue[opt.value] = opt.label);
}
/**
* @override
* @returns
* @memberof MultiSelectField
*/
getError() {
const { value } = this.state;
let messages = Array();
......@@ -28,10 +40,15 @@ class MultiSelectField extends Field {
return new CustomError(messages);
}
handleChangeValue = (value) => {
handleChangeValue(value) {
this.setState({ value });
}
/**
* @override
* @returns
* @memberof MultiSelectField
*/
renderField() {
return (
<Select
......
......@@ -8,9 +8,21 @@ import Field from "./Field";
import CustomError from "../../common/CustomError";
/**
* Form field for numbers.
* You can specify a `minValue` and a `maxValue` through the props.
*
* @class NumberField
* @extends {Field}
*/
class NumberField extends Field {
defaultNullValue = "";
/**
* @override
* @returns {CustomError}
* @memberof NumberField
*/
getError() {
const { value } = this.state;
let messages = Array();
......@@ -26,11 +38,18 @@ class NumberField extends Field {
return new CustomError(messages);
}
handleChangeValue = (val) => {
var regex = /[^\d.]*/gi;
const value = val.replace(regex, "");
handleChangeValue(val) {
const regex = /[^\d.]*/gi,
value = val.replace(regex, "");
this.setState({ value });
}
/**
* Render information about the constraints applied to the field.
*
* @returns
* @memberof NumberField
*/
renderInfo() {
const { minValue, maxValue } = this.props;
if (maxValue !== null && minValue !== null) {
......@@ -44,6 +63,11 @@ class NumberField extends Field {
}
}
/**
* @override
* @returns
* @memberof NumberField
*/
renderField() {
const valFromState = this.state.value,
value = valFromState === null ? this.defaultNullValue : valFromState;
......
......@@ -14,6 +14,12 @@ import withStyles from "@material-ui/core/styles/withStyles";
// create a nasty unique variable
const NULL_OPTION_VALUE = `NULL_OPTION_VALUE___${Math.random()}`;
/**
* Form field for select (not multiple, use the other one for such thing).
*
* @class SelectField
* @extends {Field}
*/
class SelectField extends Field {
defaultNullValue = NULL_OPTION_VALUE;
......@@ -41,6 +47,11 @@ class SelectField extends Field {
super(props, customStateAttrs);
}
/**
* @override
* @returns
* @memberof SelectField
*/
getError() {
const { value } = this.state;
let messages = Array();
......@@ -51,10 +62,15 @@ class SelectField extends Field {
return new CustomError(messages);
}
handleChangeValue = (value) => {
handleChangeValue(value) {
this.setState({ value });
}
/**
* @override
* @returns
* @memberof SelectField
*/
renderField() {
const valFromState = this.state.value,
value = valFromState === null ? this.defaultNullValue : valFromState;
......
......@@ -10,9 +10,21 @@ import stringHasExtension from "../../../utils/stringHasExtension";
import CustomError from "../../common/CustomError";
/**
* Form field for a simple textField (with no markdown)
*
*
* @class TextField
* @extends {Field}
*/
class TextField extends Field {
defaultNullValue = "";
/**
* @override
* @returns
* @memberof TextField
*/
getError() {
const { value } = this.state;
let messages = Array();
......@@ -33,7 +45,8 @@ class TextField extends Field {
return new CustomError(messages);
}
handleChangeValue = (val) => {
handleChangeValue(val){
let value = val;
const { maxLength } = this.props;
......@@ -44,6 +57,11 @@ class TextField extends Field {
}
/**
* @override
* @returns
* @memberof TextField
*/
renderField() {
const valFromState = this.state.value,
value = valFromState === null ? this.defaultNullValue : valFromState;
......
......@@ -20,8 +20,21 @@ import CustomError from "../../common/CustomError";
/**
* Form field to render the useFullLinks field that is present in many models.
*
* Has to props parameters: `urlMaxLength` and `descriptionMaxLength`
*
* @class UsefulLinksField
* @extends {Field}
*/
class UsefulLinksField extends Field {
/**
* @override
* @returns