Commit 8f668e77 authored by Florent Chehab's avatar Florent Chehab

feat(frontend): general improvements

* Final touch to truncated markdown
* A bit of reorganization and hookification
* Fixed tabbar position on university page
* Change mobile phone notification bar color
* WIP responsiveness in course feedback
* Shared parameters and HOC for pages / styles
parent 2e1e841b
......@@ -9,6 +9,9 @@
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
/>
<!-- Custom colors for phone status bar -->
<meta name="theme-color" content="#9c27b0">
<meta name="apple-mobile-web-app-status-bar-style" content="#9c27b0">
<!-- Favicon hell -->
<link rel="apple-touch-icon-precomposed" sizes="57x57" href="{% static '/base_app/favicon/apple-touch-icon-57x57.png' %}" />
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="{% static '/base_app/favicon/apple-touch-icon-114x114.png' %}" />
......
......@@ -2,8 +2,6 @@
*/
import React from "react";
import PropTypes from "prop-types";
import {withStyles} from "@material-ui/styles";
import FullScreenDialog from "./FullScreenDialog";
import {connect} from "react-redux";
......@@ -33,12 +31,11 @@ import PageAboutConditions from "../pages/PageAboutConditions";
*/
class App extends CustomComponentForAPI {
customRender() {
const {classes} = this.props;
return (
<>
<AppFrame>
<FullScreenDialog/>
<main className={classes.content}>
<main>
<Route exact path={APP_ROUTES.base} component={PageHome}/>
<Route path={APP_ROUTES.search} component={PageSearch}/>
<Route path={APP_ROUTES.map} component={PageMap}/>
......@@ -55,9 +52,7 @@ class App extends CustomComponentForAPI {
}
}
App.propTypes = {
classes: PropTypes.object.isRequired,
};
App.propTypes = {};
// Already load some of the data even if it's not use here.
// /!\ Don't delete it
......@@ -81,20 +76,7 @@ const mapDispatchToProps = (dispatch) => {
};
};
const styles = theme => ({
content: {
[theme.breakpoints.up("md")]: {
padding: theme.spacing(3),
},
[theme.breakpoints.down("sm")]: {
padding: 0,
},
},
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withStyles(styles, {withTheme: true}),
withErrorBoundary(),
)(App);
......@@ -18,6 +18,7 @@ import DrawerMenu from "./DrawerMenu";
import {APP_ROUTES} from "../../config/appRoutes";
import CustomNavLink from "../common/CustomNavLink";
import {classNames} from "../../utils/classNames";
import {appBarHeight, siteMaxWidth} from "../../config/sharedStyles";
const styles = theme => ({
root: {
......@@ -25,14 +26,15 @@ const styles = theme => ({
},
appBar: {
width: "100%",
height: appBarHeight(theme),
},
toolBar: {
width: "100%",
maxWidth: 1920,
maxWidth: siteMaxWidth(),
display: "flex",
},
content: {
maxWidth: 1920,
maxWidth: siteMaxWidth(),
},
siteName: {
fontWeight: 900,
......
......@@ -222,12 +222,16 @@ class Markdown extends Component {
render() {
const compiledSource = this.compileSource(this.props.source);
return <ReactMarkdown
renderers={renderers}
allowedTypes={[...Object.keys(renderers), "text", "emphasis", "root", "strong"]} // Only allow custom nodes and basic ones
mode={"escape"}
source={compiledSource}
/>;
return (
<div style={{wordBreak: "break-word"}}>
<ReactMarkdown
renderers={renderers}
allowedTypes={[...Object.keys(renderers), "text", "emphasis", "root", "strong"]} // Only allow custom nodes and basic ones
mode={"escape"}
source={compiledSource}
/>
</div>
);
}
}
......
......@@ -2,7 +2,6 @@ import React from "react";
import PropTypes from "prop-types";
import MuiThemeProvider from "@material-ui/core/styles/MuiThemeProvider";
import createMuiTheme from "@material-ui/core/styles/createMuiTheme";
import {connect} from "react-redux";
import {BrowserRouter as Router} from "react-router-dom";
import CustomComponentForAPI from "./CustomComponentForAPI";
......@@ -10,7 +9,8 @@ import "typeface-roboto";
import getActions from "../../redux/api/getActions";
import {RequestParams} from "../../redux/api/RequestParams";
import {responsiveFontSizes} from "@material-ui/core/styles";
import {responsiveFontSizes, rgbToHex} from "@material-ui/core/styles";
import {createMuiTheme} from "@material-ui/core";
const siteSettings = {
typography: {
......@@ -22,6 +22,26 @@ const siteSettings = {
}
};
/**
* method to set the correct meta tags on the HTML page so that
* the status bar on phone is of a matching color as the theme.
*
* @param mainColor
*/
function updatePhoneStatusBarColor(mainColor) {
let color = rgbToHex(mainColor);
try {
for (let el of document.getElementsByTagName("meta")) {
if (el.name === "theme-color" || el.name === "apple-mobile-web-app-status-bar-style") {
el.content = color;
}
}
} catch (e) {
// nothing, yes.
}
}
/**
* Method to generate a full site theme based on an object themeData
* @param themeData Should be an object like: src/config/defaultTheme.json
......@@ -51,10 +71,13 @@ export function getTheme(themeData) {
class ThemeProvider extends CustomComponentForAPI {
customRender() {
const {userData} = this.getAllLatestReadData("userData"),
themeData = userData.theme;
themeData = userData.theme,
theme = getTheme(themeData);
updatePhoneStatusBarColor(theme.palette.primary.main);
return (
<MuiThemeProvider theme={getTheme(themeData)}>
<MuiThemeProvider theme={theme}>
<Router>
{this.props.children}
</Router>
......
......@@ -4,32 +4,32 @@ import PropTypes from "prop-types";
import {compose} from "recompose";
import withStyles from "@material-ui/core/styles/withStyles";
import Button from "@material-ui/core/Button";
import Collapse from "@material-ui/core/Collapse";
function TruncatedMarkdown(props) {
const [truncated, setTruncated] = useState(false),
{comment, truncateFromLength, classes} = props,
truncatable = comment.length > truncateFromLength;
return (
<div>
<div className={truncatable && !truncated ? classes.truncated : ""}>
<Markdown source={comment}/>
</div>
{
truncatable ?
<>
{!truncated ? <div className={classes.gradientBorder}/> : <></>}
<Button variant={"contained"}
className={classes.moreButton}
onClick={() => setTruncated(!truncated)}>
{!truncated ? "En lire plus..." : "En lire moins..."}
</Button>
</>
:
<></>
}
</div>
);
if (!truncatable) {
return <Markdown source={comment}/>;
} else {
return (
<>
<Collapse in={truncated} collapsedHeight={"4rem"}>
<Markdown source={comment}/>
</Collapse>
{!truncated ? <div className={classes.gradientBorder}/> : <></>}
<Button variant={"contained"}
className={classes.moreButton}
onClick={() => setTruncated(!truncated)}>
{!truncated ? "En lire plus..." : "En lire moins..."}
</Button>
</>
);
}
}
TruncatedMarkdown.propTypes = {
......@@ -49,7 +49,7 @@ const styles = theme => ({
},
gradientBorder: {
position: "relative",
marginTop: "-3rem",
marginTop: "-2rem",
bottom: 0,
width: "100%",
height: "3rem",
......
import {useTheme} from "@material-ui/styles";
import useMediaQuery from "@material-ui/core/useMediaQuery";
// Small hack to keep track of the last width there seem to be bug sometimes
let lastWidth = "xs";
export function useWindowWidth() {
const theme = useTheme();
lastWidth = [...theme.breakpoints.keys].reverse().reduce((output, key) => {
const matches = useMediaQuery(theme.breakpoints.only(key));
return !output && matches ? key : output;
}, null) || lastWidth;
return lastWidth;
}
import React from "react";
import PropTypes from "prop-types";
import withStyles from "@material-ui/core/styles/withStyles";
import Typography from "@material-ui/core/Typography";
import Paper from "@material-ui/core/Paper";
import Markdown from "../common/Markdown";
import {compose} from "recompose";
import {withErrorBoundary} from "../common/ErrorBoundary";
import {withPaddedPaper} from "./shared";
const source = `
......@@ -19,33 +18,23 @@ const source = `
/**
* Component corresponding to page about use conditions.
*
* @class PageAboutConditions
* @extends {React.Component}
*/
class PageAboutConditions extends React.Component {
render() {
const {theme} = this.props;
return (
<Paper style={theme.myPaper}>
<Typography variant="h3">
Le projet <em><b>REX-DRI</b></em>
</Typography>
<Markdown source={source}/>
</Paper>
);
}
function PageAboutConditions() {
return (
<>
<Typography variant="h3">
Le projet <em><b>REX-DRI</b></em>
</Typography>
<Markdown source={source}/>
</>
);
}
PageAboutConditions.propTypes = {
theme: PropTypes.object.isRequired,
};
const styles = {};
export default compose(
withStyles(styles, {withTheme: true}),
withPaddedPaper(),
withErrorBoundary()
)(PageAboutConditions);
import React from "react";
import PropTypes from "prop-types";
import withStyles from "@material-ui/core/styles/withStyles";
import Typography from "@material-ui/core/Typography";
import Paper from "@material-ui/core/Paper";
import Markdown from "../common/Markdown";
import {compose} from "recompose";
import {withErrorBoundary} from "../common/ErrorBoundary";
import {withPaddedPaper} from "./shared";
const source = `
......@@ -17,28 +16,18 @@ Rendez vous sur [le GitLab de l'UTC](https://gitlab.utc.fr/rex-dri/rex-dri) !
`;
/**
* Component corresponding to page about the project.
*
* @class PageAboutProject
* @extends {React.Component}
*/
class PageAboutProject extends React.Component {
render() {
const { theme } = this.props;
return (
<Paper style={theme.myPaper}>
<Typography variant="h3">
Le projet <em><b>REX-DRI</b></em>
</Typography>
<Markdown source={source} />
</Paper>
);
}
function PageAboutProject() {
return (
<>
<Typography variant="h3">
Le projet <em><b>REX-DRI</b></em>
</Typography>
<Markdown source={source}/>
</>
);
}
PageAboutProject.propTypes = {
......@@ -46,9 +35,7 @@ PageAboutProject.propTypes = {
};
const styles = {};
export default compose(
withStyles(styles, { withTheme: true }),
withPaddedPaper(),
withErrorBoundary()
)(PageAboutProject);
import React from "react";
import PropTypes from "prop-types";
import withStyles from "@material-ui/core/styles/withStyles";
import Paper from "@material-ui/core/Paper";
import {compose} from "recompose";
import {withErrorBoundary} from "../common/ErrorBoundary";
import Typography from "@material-ui/core/Typography";
import Markdown from "../common/Markdown";
import {withPaddedPaper} from "./shared";
const source = `
......@@ -62,34 +60,21 @@ You can format money: \`:100LSD:\` => :100LSD:
/**
* Component corresponding to the landing page of the site
*
* @class PageHome
* @extends {React.Component}
*/
class PageHome extends React.Component {
render() {
const {theme} = this.props;
return (
<Paper style={theme.myPaper}>
<Typography variant="h3">
Bienvenue sur <em><b>REX-DRI</b></em> !
</Typography>
<Markdown source={source}/>
</Paper>
);
}
function PageHome() {
return (
<>
<Typography variant="h3">
Bienvenue sur <em><b>REX-DRI</b></em> !
</Typography>
<Markdown source={source}/>
</>
);
}
PageHome.propTypes = {
theme: PropTypes.object.isRequired,
};
const styles = {};
PageHome.propTypes = {};
export default compose(
withStyles(styles, {withTheme: true}),
withPaddedPaper(),
withErrorBoundary()
)(PageHome);
......@@ -7,6 +7,8 @@ import ViewList from "../recommendation/ViewListSubPage";
import {makeStyles} from "@material-ui/styles";
import PropTypes from "prop-types";
import {withPaddedPaper} from "./shared";
const useStyles = makeStyles(theme => ({
paper: theme.myPaper,
}));
......@@ -37,5 +39,6 @@ PageLists.propTypes = {
};
export default compose(
withPaddedPaper(),
withErrorBoundary(),
)(PageLists);
......@@ -5,24 +5,23 @@ import {compose} from "recompose";
import {withErrorBoundary} from "../common/ErrorBoundary";
import Filter from "../filter/Filter";
import MainMap from "../map/MainMap";
import Paper from "@material-ui/core/Paper";
import {withPaddedPaper} from "./shared";
const useStyles = makeStyles(theme => ({
filter: {
marginBottom: theme.spacing(2),
},
paper: theme.myPaper,
}));
/**
* Component corresponding to the page with the map of the universities
*/
// eslint-disable-next-line no-unused-vars
function PageMap(props) {
function PageMap() {
const classes = useStyles();
return (
<Paper className={classes.paper}>
<>
<Typography variant="h4" gutterBottom>
Exploration Cartographique
</Typography>
......@@ -30,10 +29,11 @@ function PageMap(props) {
<Filter/>
</div>
<MainMap/>
</Paper>
</>
);
}
export default compose(
withPaddedPaper(),
withErrorBoundary()
)(PageMap);
import React from "react";
import PropTypes from "prop-types";
import withStyles from "@material-ui/core/styles/withStyles";
import Typography from "@material-ui/core/Typography";
import Search from "../search/Search";
import Paper from "@material-ui/core/Paper";
import {withErrorBoundary} from "../common/ErrorBoundary";
import {compose} from "recompose";
import Filter from "../filter/Filter";
import {withPaddedPaper} from "./shared";
/**
* Component corresponding to the page with the search university capabilities
*
* @class PageSearch
* @extends {React.Component}
*/
class PageSearch extends React.Component {
state = {
selectedOption: null,
};
render() {
const {theme} = this.props;
return (
<Paper style={theme.myPaper}>
<Typography variant="h4" gutterBottom>
Recherche d'une université
</Typography>
<Filter/>
<Search/>
</Paper>
);
}
function PageSearch() {
return (
<>
<Typography variant="h4" gutterBottom>
Recherche d'une université
</Typography>
<Filter/>
<Search/>
</>
);
}
PageSearch.propTypes = {
theme: PropTypes.object.isRequired,
};
const styles = {};
export default compose(
withStyles(styles, {withTheme: true}),
withPaddedPaper(),
withErrorBoundary(),
)(PageSearch);
import React from "react";
import PropTypes from "prop-types";
import withStyles from "@material-ui/core/styles/withStyles";
import Paper from "@material-ui/core/Paper";
import {withErrorBoundary} from "../common/ErrorBoundary";
import {compose} from "recompose";
import ColorTool from "../settings/theme/ColorTools";
import {withPaddedPaper} from "./shared";
/**
* Component corresponding to the site settings page
*
* @class PageThemeSettings
* @extends {React.Component}
*/
class PageThemeSettings extends React.Component {
render() {
const {theme} = this.props;
return (
<Paper style={theme.myPaper}>
<ColorTool/>
</Paper>
);
}
function PageThemeSettings() {
return (
<>
<ColorTool/>
</>
);
}
PageThemeSettings.propTypes = {
theme: PropTypes.object.isRequired,
};
PageThemeSettings.propTypes = {};
const styles = {};
export default compose(
withStyles(styles, {withTheme: true}),
withErrorBoundary()
withPaddedPaper(),
withErrorBoundary(),
)(PageThemeSettings);
import React from "react";
import PropTypes from "prop-types";
import Typography from "@material-ui/core/Typography";
import Paper from "@material-ui/core/Paper";
import CustomComponentForAPI from "../common/CustomComponentForAPI";
import {connect} from "react-redux";
import Dialog from "@material-ui/core/Dialog";
......@@ -22,6 +21,7 @@ import {withErrorBoundary} from "../common/ErrorBoundary";
import {APP_ROUTES} from "../../config/appRoutes";
import CustomNavLink from "../common/CustomNavLink";
import CustomLink from "../common/CustomLink";
import {PaddedPaper} from "./shared";
let previousUnivId = -1;
......@@ -79,13 +79,13 @@ class PageUniversity extends CustomComponentForAPI {
renderFirstTimeHere() {
return (
<Paper className={this.props.classes.paper}>
<PaddedPaper>
<Typography>
C'est la première fois que vous consulter cet onglet. Nous vous invitons à <CustomLink to={APP_ROUTES.map}>parcourir
les universités</CustomLink> dans un
premier temps. 😁
</Typography>
</Paper>
</PaddedPaper>
);
}
......@@ -145,6 +145,7 @@ const styles = theme => ({
});
export default compose(
// volontarly no withPaddedPaper(), here
withStyles(styles, {withTheme: true}),
connect(mapStateToProps, mapDispatchToProps, null, {
pure: false
......
import React from "react";
import PropTypes from "prop-types";
import withStyles from "@material-ui/core/styles/withStyles";
import Typography from "@material-ui/core/Typography";
import Paper from "@material-ui/core/Paper";
import CancelIcon from "@material-ui/icons/Cancel";
import CheckCircleIcon from "@material-ui/icons/CheckCircle";
import Fab from "@material-ui/core/Fab";
......@@ -11,96 +9,85 @@ import Divider from "@material-ui/core/Divider";
import UserInfo from "../user/UserInfo";
import {compose} from "recompose";
import {withErrorBoundary} from "../common/ErrorBoundary";
import {withPaddedPaper} from "./shared";
import {makeStyles} from "@material-ui/styles";
const useStyle = makeStyles(theme => ({
header: {
textAlign: "right",
},
spacer: {
marginTop: theme.spacing(2),
marginBottom: theme.spacing(1),
},
inlineIcon: {
fontSize: "1em",
position: "relative",
top: ".125em",
}
}));
/**
* Component corresponding to the page with user information
*
* @class PageUser
* @extends {React.Component}
*/
class PageUser extends React.Component {
getUserIdFromUrl() {
return this.props.match.params.userId;
function PageUser(props) {
function getUserIdFromUrl() {
return props.match.params.userId;
}
render() {
const {theme, classes} = this.props,
requestedUserId = this.getUserIdFromUrl();
return (
<>
<Paper style={theme.myPaper}>
<Grid container
direction="row"
justify="flex-end"
alignItems="flex-start"
>
<Fab variant={"round"} size={"large"} color={"primary"} className={classes.header}>
#{requestedUserId}
</Fab>
</Grid>
<Typography variant={"body2"}>
Les éléments marqués d'un « tick »
(<CheckCircleIcon color="primary" className={classes.inlineIcon}/>)
sont visibles par les autres utilisateurs.
<br/>
Les éléments marqués d'une croix
(<CancelIcon color="disabled" className={classes.inlineIcon}/>)
ne sont jamais visibles par les autres utilisateurs, sauf administrateurs.
<br/>
Ces réglages peuvent être changés lors de l'édition: <b>toutefois, le pseudo et l'identifiant unique seront