diff --git a/package.json b/package.json
index b57e05d..2799fb2 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
"react-markdown": "^8.0.0",
"react-markdown-editor-lite": "^1.3.2",
"react-mde": "^11.5.0",
+ "react-rating-stars-component": "^2.2.0",
"react-redux": "^7.2.4",
"react-router-dom": "^5.2.0",
"react-scripts": "^5.0.0",
diff --git a/src/App.css b/src/App.css
index 9903a22..8457170 100644
--- a/src/App.css
+++ b/src/App.css
@@ -43,6 +43,20 @@ blockquote p {
display: inline;
}
+.tutorial table,
+th,
+td {
+ border: 1px solid #ddd;
+}
+
+.tutorial th {
+ padding-top: 12px;
+ padding-bottom: 12px;
+ text-align: left;
+ background-color: #4eaf47;
+ color: white;
+}
+
.overlay {
display: flex;
flex-direction: column;
diff --git a/src/actions/tutorialActions.js b/src/actions/tutorialActions.js
index 372697a..226bba6 100644
--- a/src/actions/tutorialActions.js
+++ b/src/actions/tutorialActions.js
@@ -84,6 +84,77 @@ export const getTutorials = () => (dispatch, getState) => {
});
};
+export const getAllTutorials = () => (dispatch, getState) => {
+ axios
+ .get(`${process.env.REACT_APP_BLOCKLY_API}/tutorial/getAllTutorials`)
+ .then((res) => {
+ var tutorials = res.data.tutorials;
+ existingTutorials(tutorials, getState().tutorial.status).then(
+ (status) => {
+ dispatch({
+ type: TUTORIAL_SUCCESS,
+ payload: status,
+ });
+ dispatch(updateStatus(status));
+ dispatch({
+ type: GET_TUTORIALS,
+ payload: tutorials,
+ });
+ dispatch({ type: TUTORIAL_PROGRESS });
+ dispatch(returnSuccess(res.data.message, res.status));
+ }
+ );
+ })
+ .catch((err) => {
+ if (err.response) {
+ dispatch(
+ returnErrors(
+ err.response.data.message,
+ err.response.status,
+ "GET_TUTORIALS_FAIL"
+ )
+ );
+ }
+ dispatch({ type: TUTORIAL_PROGRESS });
+ });
+};
+
+export const getUserTutorials = () => (dispatch, getState) => {
+ axios
+ .get(`${process.env.REACT_APP_BLOCKLY_API}/tutorial/getUserTutorials`)
+ .then((res) => {
+ var tutorials = res.data.tutorials;
+ existingTutorials(tutorials, getState().tutorial.status).then(
+ (status) => {
+ dispatch({
+ type: TUTORIAL_SUCCESS,
+ payload: status,
+ });
+ dispatch(updateStatus(status));
+ dispatch({
+ type: GET_TUTORIALS,
+ payload: tutorials,
+ });
+ dispatch({ type: TUTORIAL_PROGRESS });
+ dispatch(returnSuccess(res.data.message, res.status));
+ }
+ );
+ })
+ .catch((err) => {
+ console.log(err);
+ if (err.response) {
+ dispatch(
+ returnErrors(
+ err.response.data.message,
+ err.response.status,
+ "GET_TUTORIALS_FAIL"
+ )
+ );
+ }
+ dispatch({ type: TUTORIAL_PROGRESS });
+ });
+};
+
export const updateStatus = (status) => (dispatch, getState) => {
if (getState().auth.isAuthenticated) {
// update user account in database - sync with redux store
diff --git a/src/actions/tutorialBuilderActions.js b/src/actions/tutorialBuilderActions.js
index 72d7f3e..cb4e798 100644
--- a/src/actions/tutorialBuilderActions.js
+++ b/src/actions/tutorialBuilderActions.js
@@ -4,6 +4,9 @@ import {
BUILDER_CHANGE,
BUILDER_ERROR,
BUILDER_TITLE,
+ BUILDER_PUBLIC,
+ BUILDER_DIFFICULTY,
+ BUILDER_REVIEW,
BUILDER_ID,
BUILDER_ADD_STEP,
BUILDER_DELETE_STEP,
@@ -35,6 +38,30 @@ export const tutorialTitle = (title) => (dispatch) => {
dispatch(changeTutorialBuilder());
};
+export const tutorialPublic = (pub) => (dispatch) => {
+ dispatch({
+ type: BUILDER_PUBLIC,
+ payload: pub,
+ });
+ dispatch(changeTutorialBuilder());
+};
+
+export const tutorialDifficulty = (difficulty) => (dispatch) => {
+ dispatch({
+ type: BUILDER_DIFFICULTY,
+ payload: difficulty,
+ });
+ dispatch(changeTutorialBuilder());
+};
+
+export const tutorialReview = (review) => (dispatch) => {
+ dispatch({
+ type: BUILDER_REVIEW,
+ payload: review,
+ });
+ dispatch(changeTutorialBuilder());
+};
+
export const tutorialSteps = (steps) => (dispatch) => {
dispatch({
type: BUILDER_ADD_STEP,
@@ -320,6 +347,7 @@ export const readJSON = (json) => (dispatch, getState) => {
return object;
});
dispatch(tutorialTitle(json.title));
+ dispatch(tutorialDifficulty(json.difficulty));
dispatch(tutorialSteps(steps));
dispatch(setSubmitError());
dispatch(progress(false));
diff --git a/src/actions/types.js b/src/actions/types.js
index 6a10987..84e2580 100644
--- a/src/actions/types.js
+++ b/src/actions/types.js
@@ -21,6 +21,7 @@ export const NAME = "NAME";
export const TUTORIAL_PROGRESS = "TUTORIAL_PROGRESS";
export const GET_TUTORIAL = "GET_TUTORIAL";
export const GET_TUTORIALS = "GET_TUTORIALS";
+export const GET_USERTUTORIALS = "GET_USERTUTORIALS";
export const GET_STATUS = "GET_STATUS";
export const TUTORIAL_SUCCESS = "TUTORIAL_SUCCESS";
export const TUTORIAL_ERROR = "TUTORIAL_ERROR";
@@ -32,6 +33,9 @@ export const JSON_STRING = "JSON_STRING";
export const BUILDER_CHANGE = "BUILDER_CHANGE";
export const BUILDER_TITLE = "BUILDER_TITLE";
+export const BUILDER_DIFFICULTY = "BUILDER_DIFFICULTY";
+export const BUILDER_PUBLIC = "BUILDER_PUBLIC";
+export const BUILDER_REVIEW = "BUILDER_REVIEW";
export const BUILDER_ID = "BUILDER_ID";
export const BUILDER_ADD_STEP = "BUILDER_ADD_STEP";
export const BUILDER_DELETE_STEP = "BUILDER_DELETE_STEP";
diff --git a/src/components/Blockly/msg/de/ui.js b/src/components/Blockly/msg/de/ui.js
index 901d495..7e598ad 100644
--- a/src/components/Blockly/msg/de/ui.js
+++ b/src/components/Blockly/msg/de/ui.js
@@ -228,6 +228,7 @@ export const UI = {
builder_requirements_head: "Voraussetzungen",
builder_requirements_order:
"Beachte, dass die Reihenfolge des Anhakens maßgebend ist.",
+ builder_difficulty: "Schwierigkeitsgrad",
/**
* Login
diff --git a/src/components/Tutorial/Builder/Builder.js b/src/components/Tutorial/Builder/Builder.js
index 26c53fd..a21738c 100644
--- a/src/components/Tutorial/Builder/Builder.js
+++ b/src/components/Tutorial/Builder/Builder.js
@@ -10,6 +10,8 @@ import {
resetTutorial as resetTutorialBuilder,
} from "../../../actions/tutorialBuilderActions";
import {
+ getAllTutorials,
+ getUserTutorials,
getTutorials,
resetTutorial,
deleteTutorial,
@@ -19,12 +21,17 @@ import { clearMessages } from "../../../actions/messageActions";
import axios from "axios";
import { withRouter } from "react-router-dom";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faEyeSlash, faUserCheck } from "@fortawesome/free-solid-svg-icons";
import Breadcrumbs from "../../Breadcrumbs";
import Textfield from "./Textfield";
+import Difficulty from "./Difficulty";
import Step from "./Step";
import Dialog from "../../Dialog";
import Snackbar from "../../Snackbar";
+import Public from "./Public";
+import Review from "./Review";
import { withStyles } from "@material-ui/core/styles";
import Button from "@material-ui/core/Button";
@@ -66,6 +73,8 @@ class Builder extends Component {
tutorial: "new",
open: false,
title: "",
+ public: false,
+ difficulty: "",
content: "",
string: false,
snackbar: false,
@@ -80,7 +89,11 @@ class Builder extends Component {
// retrieve tutorials only if a potential user is loaded - authentication
// is finished (success or failed)
if (!this.props.authProgress) {
- this.props.getTutorials();
+ if (this.props.user.role === "admin") {
+ this.props.getAllTutorials();
+ } else {
+ this.props.getUserTutorials();
+ }
}
}
@@ -89,8 +102,12 @@ class Builder extends Component {
props.authProgress !== this.props.authProgress &&
!this.props.authProgress
) {
- // authentication is completed
- this.props.getTutorials();
+ if (this.props.user.role === "admin") {
+ // authentication is completed
+ this.props.getAllTutorials();
+ } else {
+ this.props.getUserTutorials();
+ }
}
if (props.message !== this.props.message) {
if (this.props.message.id === "GET_TUTORIALS_FAIL") {
@@ -258,6 +275,9 @@ class Builder extends Component {
var steps = this.props.steps;
var newTutorial = new FormData();
newTutorial.append("title", this.props.title);
+ newTutorial.append("difficulty", this.props.difficulty);
+ newTutorial.append("public", this.props.public);
+ newTutorial.append("review", this.props.review);
steps.forEach((step, i) => {
if (step._id) {
newTutorial.append(`steps[${i}][_id]`, step._id);
@@ -284,6 +304,7 @@ class Builder extends Component {
newTutorial.append(`steps[${i}][xml]`, step.xml);
}
});
+ console.log(newTutorial);
return newTutorial;
}
};
@@ -362,6 +383,12 @@ class Builder extends Component {
(tutorial) => tutorial.creator === this.props.user.email
);
}
+
+ // } else {
+ // filteredTutorials = this.props.userTutorials.filter(
+ // (tutorial) => tutorial.creator === this.props.user.email
+ // );
+
return (
{filteredTutorials.map((tutorial) => (
- {tutorial.title}
+
+ {tutorial.title}{" "}
+ {tutorial.review && tutorial.public === false ? (
+
+
+
+
+ ) : tutorial.public === false ? (
+
+ ) : null}
+
+ /* ) : tutorial.public === false ? (
+
+ {tutorial.title}
+
+ ) : (
+ {tutorial.title}
+ )} */
))}
@@ -476,6 +520,45 @@ class Builder extends Component {
label={"Titel"}
error={this.props.error.title}
/>
+
+
+
+
+
+
+
+ {this.props.user.blocklyRole === "admin" ? (
+
+ ) : null}
+
{this.props.steps.map((step, i) => (
@@ -608,6 +691,8 @@ class Builder extends Component {
}
Builder.propTypes = {
+ getAllTutorials: PropTypes.func.isRequired,
+ getUserTutorials: PropTypes.func.isRequired,
getTutorials: PropTypes.func.isRequired,
resetTutorial: PropTypes.func.isRequired,
clearMessages: PropTypes.func.isRequired,
@@ -620,6 +705,9 @@ Builder.propTypes = {
resetTutorialBuilder: PropTypes.func.isRequired,
tutorialProgress: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
+ difficulty: PropTypes.number.isRequired,
+ public: PropTypes.bool.isRequired,
+ review: PropTypes.bool.isRequired,
id: PropTypes.string.isRequired,
steps: PropTypes.array.isRequired,
change: PropTypes.number.isRequired,
@@ -634,12 +722,16 @@ Builder.propTypes = {
const mapStateToProps = (state) => ({
title: state.builder.title,
+ difficulty: state.builder.difficulty,
+ review: state.builder.review,
+ public: state.builder.public,
id: state.builder.id,
steps: state.builder.steps,
change: state.builder.change,
error: state.builder.error,
json: state.builder.json,
isProgress: state.builder.progress,
+ userTutorials: state.tutorial.userTutorials,
tutorials: state.tutorial.tutorials,
message: state.message,
user: state.auth.user,
@@ -654,6 +746,8 @@ export default connect(mapStateToProps, {
tutorialId,
resetTutorialBuilder,
getTutorials,
+ getUserTutorials,
+ getAllTutorials,
resetTutorial,
tutorialProgress,
clearMessages,
diff --git a/src/components/Tutorial/Builder/Difficulty.js b/src/components/Tutorial/Builder/Difficulty.js
new file mode 100644
index 0000000..18111b9
--- /dev/null
+++ b/src/components/Tutorial/Builder/Difficulty.js
@@ -0,0 +1,101 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import {
+ tutorialDifficulty,
+ jsonString,
+ changeContent,
+ setError,
+ deleteError,
+} from "../../../actions/tutorialBuilderActions";
+
+import { withStyles } from "@material-ui/core/styles";
+import ReactStars from "react-rating-stars-component";
+import * as Blockly from "blockly";
+import FormGroup from "@material-ui/core/FormGroup";
+import FormControlLabel from "@material-ui/core/FormControlLabel";
+import FormControl from "@material-ui/core/FormControl";
+import FormLabel from "@material-ui/core/FormLabel";
+
+const styles = (theme) => ({
+ multiline: {
+ padding: "18.5px 14px 18.5px 24px",
+ },
+ errorColor: {
+ color: `${theme.palette.error.dark} !important`,
+ },
+ errorColorShrink: {
+ color: `rgba(0, 0, 0, 0.54) !important`,
+ },
+ errorBorder: {
+ borderColor: `${theme.palette.error.dark} !important`,
+ },
+});
+
+class Difficulty extends Component {
+ ratingChanged = (newRating) => {
+ console.log(newRating);
+ this.handleChange(newRating);
+ };
+
+ handleChange = (e) => {
+ var value = e;
+ console.log(value);
+ if (this.props.property === "difficulty") {
+ this.props.tutorialDifficulty(value);
+ } else if (this.props.property === "json") {
+ this.props.jsonString(value);
+ } else {
+ this.props.changeContent(
+ value,
+ this.props.index,
+ this.props.property,
+ this.props.property2
+ );
+ }
+ };
+
+ render() {
+ return (
+
+
+ {Blockly.Msg.builder_difficulty}
+
+
+ }
+ halfIcon={ }
+ fullIcon={ }
+ activeColor="#ffd700"
+ />
+ }
+ label="Schwierigkeitsgrad"
+ labelPlacement="start"
+ />
+
+
+ );
+ }
+}
+
+Difficulty.propTypes = {
+ tutorialDifficulty: PropTypes.func.isRequired,
+ jsonString: PropTypes.func.isRequired,
+ changeContent: PropTypes.func.isRequired,
+};
+
+export default connect(null, {
+ tutorialDifficulty,
+ jsonString,
+ changeContent,
+ setError,
+ deleteError,
+})(withStyles(styles, { withTheme: true })(Difficulty));
diff --git a/src/components/Tutorial/Builder/Public.js b/src/components/Tutorial/Builder/Public.js
new file mode 100644
index 0000000..ff8658e
--- /dev/null
+++ b/src/components/Tutorial/Builder/Public.js
@@ -0,0 +1,91 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import {
+ tutorialPublic,
+ jsonString,
+ changeContent,
+ setError,
+ deleteError,
+} from "../../../actions/tutorialBuilderActions";
+
+import { withStyles } from "@material-ui/core/styles";
+
+import * as Blockly from "blockly";
+import Checkbox from "@material-ui/core/Checkbox";
+import FormGroup from "@material-ui/core/FormGroup";
+import FormControlLabel from "@material-ui/core/FormControlLabel";
+import FormControl from "@material-ui/core/FormControl";
+import FormLabel from "@material-ui/core/FormLabel";
+
+const styles = (theme) => ({
+ multiline: {
+ padding: "18.5px 14px 18.5px 24px",
+ },
+ errorColor: {
+ color: `${theme.palette.error.dark} !important`,
+ },
+ errorColorShrink: {
+ color: `rgba(0, 0, 0, 0.54) !important`,
+ },
+ errorBorder: {
+ borderColor: `${theme.palette.error.dark} !important`,
+ },
+});
+
+class Public extends Component {
+ handleChange = (e) => {
+ var value = e.target.checked;
+ console.log(value);
+ if (this.props.property === "public") {
+ this.props.tutorialPublic(value);
+ } else if (this.props.property === "json") {
+ this.props.jsonString(value);
+ } else {
+ this.props.changeContent(
+ value,
+ this.props.index,
+ this.props.property,
+ this.props.property2
+ );
+ }
+ };
+
+ render() {
+ return (
+
+ Tutorial veröffentlichen
+
+
+ }
+ label="Tutorial veröffentlichen"
+ labelPlacement="start"
+ />
+
+
+ );
+ }
+}
+
+Public.propTypes = {
+ tutorialPublic: PropTypes.func.isRequired,
+ jsonString: PropTypes.func.isRequired,
+ changeContent: PropTypes.func.isRequired,
+};
+
+export default connect(null, {
+ tutorialPublic,
+ jsonString,
+ changeContent,
+ setError,
+ deleteError,
+})(withStyles(styles, { withTheme: true })(Public));
diff --git a/src/components/Tutorial/Builder/Review.js b/src/components/Tutorial/Builder/Review.js
new file mode 100644
index 0000000..1f51a1f
--- /dev/null
+++ b/src/components/Tutorial/Builder/Review.js
@@ -0,0 +1,97 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import {
+ tutorialReview,
+ jsonString,
+ changeContent,
+ setError,
+ deleteError,
+} from "../../../actions/tutorialBuilderActions";
+
+import { withStyles } from "@material-ui/core/styles";
+
+import * as Blockly from "blockly";
+import Checkbox from "@material-ui/core/Checkbox";
+import FormGroup from "@material-ui/core/FormGroup";
+import FormControlLabel from "@material-ui/core/FormControlLabel";
+import FormControl from "@material-ui/core/FormControl";
+import FormLabel from "@material-ui/core/FormLabel";
+
+const styles = (theme) => ({
+ multiline: {
+ padding: "18.5px 14px 18.5px 24px",
+ },
+ errorColor: {
+ color: `${theme.palette.error.dark} !important`,
+ },
+ errorColorShrink: {
+ color: `rgba(0, 0, 0, 0.54) !important`,
+ },
+ errorBorder: {
+ borderColor: `${theme.palette.error.dark} !important`,
+ },
+});
+
+class Review extends Component {
+ handleChange = (e) => {
+ var value = e.target.checked;
+ if (this.props.property === "review") {
+ this.props.tutorialReview(value);
+ } else if (this.props.property === "json") {
+ this.props.jsonString(value);
+ } else {
+ this.props.changeContent(
+ value,
+ this.props.index,
+ this.props.property,
+ this.props.property2
+ );
+ }
+ };
+
+ render() {
+ return (
+
+ Tutorial veröffentlichen
+
+ {" "}
+ Du kannst dein Tutorial direkt über den Link mit anderen Personen
+ teilen. Wenn du dein Tutorial für alle Nutzer:innen in der Überischt
+ veröffenltichen wollen kannst du es hier aktivieren. Ein Administrator
+ wird dein Tutorial ansehen und anschließend freischalten.
+
+
+
+ }
+ label="Ich möchte mein Tutorial öffentlich machen"
+ labelPlacement="start"
+ />
+
+
+ );
+ }
+}
+
+Review.propTypes = {
+ tutorialReview: PropTypes.func.isRequired,
+ jsonString: PropTypes.func.isRequired,
+ changeContent: PropTypes.func.isRequired,
+};
+
+export default connect(null, {
+ tutorialReview,
+ jsonString,
+ changeContent,
+ setError,
+ deleteError,
+})(withStyles(styles, { withTheme: true })(Review));
diff --git a/src/components/Tutorial/Requirement.js b/src/components/Tutorial/Requirement.js
index 87784cc..81584ad 100644
--- a/src/components/Tutorial/Requirement.js
+++ b/src/components/Tutorial/Requirement.js
@@ -1,124 +1,234 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
-import clsx from 'clsx';
-import { withRouter, Link } from 'react-router-dom';
+import clsx from "clsx";
+import { withRouter, Link } from "react-router-dom";
-import { fade } from '@material-ui/core/styles/colorManipulator';
-import { withStyles } from '@material-ui/core/styles';
-import Typography from '@material-ui/core/Typography';
-import List from '@material-ui/core/List';
-import Tooltip from '@material-ui/core/Tooltip';
+import { fade } from "@material-ui/core/styles/colorManipulator";
+import { withStyles } from "@material-ui/core/styles";
+import Typography from "@material-ui/core/Typography";
+import List from "@material-ui/core/List";
+import Tooltip from "@material-ui/core/Tooltip";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons";
-import * as Blockly from 'blockly'
+import * as Blockly from "blockly";
-const styles = theme => ({
+const styles = (theme) => ({
outerDiv: {
- width: '50px',
- height: '50px',
- position: 'absolute',
- color: fade(theme.palette.secondary.main, 0.6)
+ width: "50px",
+ height: "50px",
+ position: "absolute",
+ color: fade(theme.palette.secondary.main, 0.6),
},
outerDivError: {
stroke: fade(theme.palette.error.dark, 0.6),
- color: fade(theme.palette.error.dark, 0.6)
+ color: fade(theme.palette.error.dark, 0.6),
},
outerDivSuccess: {
stroke: fade(theme.palette.primary.main, 0.6),
- color: fade(theme.palette.primary.main, 0.6)
+ color: fade(theme.palette.primary.main, 0.6),
},
outerDivOther: {
- stroke: fade(theme.palette.secondary.main, 0.6)
+ stroke: fade(theme.palette.secondary.main, 0.6),
},
innerDiv: {
- width: 'inherit',
- height: 'inherit',
- display: 'table-cell',
- verticalAlign: 'middle',
- textAlign: 'center'
+ width: "inherit",
+ height: "inherit",
+ display: "table-cell",
+ verticalAlign: "middle",
+ textAlign: "center",
},
link: {
color: theme.palette.text.primary,
- position: 'relative',
- height: '50px',
- display: 'flex',
- margin: '5px 0 5px 10px',
- textDecoration: 'none'
+ position: "relative",
+ height: "50px",
+ display: "flex",
+ margin: "5px 0 5px 10px",
+ textDecoration: "none",
},
hoverLink: {
- '&:hover': {
+ "&:hover": {
background: fade(theme.palette.secondary.main, 0.5),
- borderRadius: '0 25px 25px 0 '
- }
- }
+ borderRadius: "0 25px 25px 0 ",
+ },
+ },
});
-
class Requirement extends Component {
-
render() {
var requirements = this.props.requirements;
- var tutorialIds = requirements.map(requirement => requirement._id);
+ var tutorialIds = requirements.map((requirement) => requirement._id);
return (
-
+
{Blockly.Msg.tutorials_requirements}
{tutorialIds.map((tutorialId, i) => {
- var title = requirements[i].title
- var status = this.props.status.filter(status => status._id === tutorialId)[0];
+ var title = requirements[i].title;
+ var status = this.props.status.filter(
+ (status) => status._id === tutorialId
+ )[0];
var tasks = status.tasks;
- var error = status.tasks.filter(task => task.type === 'error').length > 0;
- var success = status.tasks.filter(task => task.type === 'success').length / tasks.length
- var tutorialStatus = success === 1 ? 'Success' : error ? 'Error' : 'Other';
+ var error =
+ status.tasks.filter((task) => task.type === "error").length > 0;
+ var success =
+ status.tasks.filter((task) => task.type === "success").length /
+ tasks.length;
+ var tutorialStatus =
+ success === 1 ? "Success" : error ? "Error" : "Other";
return (
-
-
+
+
-
-
- {error || success === 1 ?
-
- : }
- {success < 1 && !error ?
-
-
- : null}
+
+
+ {error || success === 1 ? (
+
+ ) : (
+
+ )}
+ {success < 1 && !error ? (
+
+ ) : null}
-
+
- {error || success === 1 ?
-
- : 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success * 100)}%
- }
+ {error || success === 1 ? (
+
+ ) : (
+ 0
+ ? this.props.classes.outerDivSuccess
+ : {}
+ }
+ >
+ {Math.round(success * 100)}%
+
+ )}
-
-
{title}
+
+
+ {title}
+
- )
- }
- )}
+ );
+ })}
);
- };
+ }
}
Requirement.propTypes = {
status: PropTypes.array.isRequired,
- change: PropTypes.number.isRequired
+ change: PropTypes.number.isRequired,
};
-const mapStateToProps = state => ({
+const mapStateToProps = (state) => ({
change: state.tutorial.change,
status: state.tutorial.status,
});
-export default connect(mapStateToProps, null)(withStyles(styles, { withTheme: true })(withRouter(Requirement)));
+export default connect(
+ mapStateToProps,
+ null
+)(withStyles(styles, { withTheme: true })(withRouter(Requirement)));
diff --git a/src/components/Tutorial/TutorialHome.js b/src/components/Tutorial/TutorialHome.js
index ee91c30..9df9aef 100644
--- a/src/components/Tutorial/TutorialHome.js
+++ b/src/components/Tutorial/TutorialHome.js
@@ -3,9 +3,13 @@ import PropTypes from "prop-types";
import { connect } from "react-redux";
import {
getTutorials,
+ getAllTutorials,
+ getUserTutorials,
resetTutorial,
tutorialProgress,
} from "../../actions/tutorialActions";
+import { progress } from "../../actions/tutorialBuilderActions";
+
import { clearMessages } from "../../actions/messageActions";
import clsx from "clsx";
@@ -20,9 +24,20 @@ import Grid from "@material-ui/core/Grid";
import Paper from "@material-ui/core/Paper";
import Typography from "@material-ui/core/Typography";
-import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons";
+import {
+ faCheck,
+ faTimes,
+ faShareAlt,
+ faEye,
+ faUserCheck,
+} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as Blockly from "blockly";
+import ReactStars from "react-rating-stars-component";
+import Tooltip from "@material-ui/core/Tooltip";
+import IconButton from "@material-ui/core/IconButton";
+import Snackbar from "../Snackbar";
+import Divider from "@material-ui/core/Divider";
const styles = (theme) => ({
outerDiv: {
@@ -51,26 +66,89 @@ const styles = (theme) => ({
verticalAlign: "middle",
textAlign: "center",
},
+ button: {
+ backgroundColor: theme.palette.primary.main,
+ color: theme.palette.primary.contrastText,
+ width: "40px",
+ height: "40px",
+ "&:hover": {
+ backgroundColor: theme.palette.primary.main,
+ color: theme.palette.primary.contrastText,
+ },
+ },
+ link: {
+ color: theme.palette.primary.main,
+ textDecoration: "none",
+ "&:hover": {
+ color: theme.palette.primary.main,
+ textDecoration: "underline",
+ },
+ },
});
class TutorialHome extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ userTutorials: [],
+ tutorials: [],
+ snackbar: false,
+ };
+ }
+
componentDidMount() {
this.props.tutorialProgress();
// retrieve tutorials only if a potential user is loaded - authentication
// is finished (success or failed)
- if (!this.props.progress) {
- this.props.getTutorials();
+ // if (!this.props.progress) {
+ // if (this.props.user) {
+ // if (this.props.user.blocklyRole === "admin") {
+ // this.props.getAllTutorials();
+ // }
+ // if (this.props.user.blocklyRole === "creator") {
+ // this.props.getUserTutorials();
+ // this.props.getTutorials();
+ // console.log("get user tutorials");
+ // console.log(this.props.userTutorials);
+ // }
+ // } else {
+ // this.props.getTutorials();
+ // }
+ // }
+ if (!this.props.authProgress) {
+ if (this.props.user) {
+ if (this.props.user.role === "admin") {
+ this.props.getAllTutorials();
+ } else {
+ this.props.getUserTutorials();
+ //this.props.getTutorials();
+ }
+ } else {
+ this.props.getTutorials();
+ }
}
}
componentDidUpdate(props, state) {
- if (props.progress !== this.props.progress && !this.props.progress) {
- // authentication is completed
- this.props.getTutorials();
- }
+ if (
+ props.authProgress !== this.props.authProgress &&
+ !this.props.authProgress
+ )
+ if (this.props.user) {
+ if (this.props.user.role === "admin") {
+ // authentication is completed
+ this.props.getAllTutorials();
+ } else {
+ this.props.getUserTutorials();
+ }
+ } else {
+ this.props.getTutorials();
+ }
+
if (this.props.message.id === "GET_TUTORIALS_FAIL") {
alert(this.props.message.msg);
}
+ console.log(this.props.user);
}
componentWillUnmount() {
@@ -81,13 +159,25 @@ class TutorialHome extends Component {
}
render() {
+ var userTutorials = [];
+ const publicTutorials = this.props.tutorials.filter(
+ (tutorial) => tutorial.public === true
+ );
+ if (this.props.user && this.props.user.blocklyRole === "admin") {
+ userTutorials = this.props.tutorials;
+ }
+ if (this.props.user && this.props.user.blocklyRole === "creator") {
+ userTutorials = this.props.tutorials.filter(
+ (tutorial) => tutorial.creator === this.props.user.email
+ );
+ }
return this.props.isLoading ? null : (
-
{Blockly.Msg.tutorials_home_head}
+
Alle Tutorials
- {this.props.tutorials.map((tutorial, i) => {
+ {publicTutorials.map((tutorial, i) => {
var status = this.props.status.filter(
(status) => status._id === tutorial._id
)[0];
@@ -99,6 +189,12 @@ class TutorialHome extends Component {
tasks.length;
var tutorialStatus =
success === 1 ? "Success" : error ? "Error" : "Other";
+ const firstExample = {
+ size: 30,
+ value: tutorial.difficulty,
+ edit: false,
+ isHalf: true,
+ };
return (
{tutorial.title}
+
+ {this.props.user ? (
+
+
User Tutorials
+
+ {userTutorials.map((tutorial, i) => {
+ var status = this.props.status.filter(
+ (status) => status._id === tutorial._id
+ )[0];
+ var tasks = status.tasks;
+ var error =
+ status.tasks.filter((task) => task.type === "error").length >
+ 0;
+ var success =
+ status.tasks.filter((task) => task.type === "success")
+ .length / tasks.length;
+ var tutorialStatus =
+ success === 1 ? "Success" : error ? "Error" : "Other";
+ const firstExample = {
+ size: 30,
+ value: tutorial.difficulty,
+ edit: false,
+ isHalf: true,
+ };
+ return (
+
+
+
+ {tutorial.title}
+
+
+
+
+ Creator:{tutorial.creator}
+
+
+ {
+ navigator.clipboard.writeText(
+ `${window.location.origin}/tutorial/${tutorial._id}`
+ );
+ this.setState({
+ snackbar: true,
+ key: Date.now(),
+ message:
+ Blockly.Msg.messages_copylink_success,
+ type: "success",
+ });
+ }}
+ >
+
+
+
+
+
+
+
+
+ {tutorial.review ? (
+
+
+
+
+
+ ) : null}
+
+
+
+
+
+ );
+ })}
+
+
+ ) : null}
);
}
@@ -223,6 +433,8 @@ class TutorialHome extends Component {
TutorialHome.propTypes = {
getTutorials: PropTypes.func.isRequired,
+ getAllTutorials: PropTypes.func.isRequired,
+ getUserTutorials: PropTypes.func.isRequired,
resetTutorial: PropTypes.func.isRequired,
tutorialProgress: PropTypes.func.isRequired,
clearMessages: PropTypes.func.isRequired,
@@ -232,19 +444,27 @@ TutorialHome.propTypes = {
isLoading: PropTypes.bool.isRequired,
message: PropTypes.object.isRequired,
progress: PropTypes.bool.isRequired,
+ user: PropTypes.object.isRequired,
+ authProgress: PropTypes.bool.isRequired,
};
const mapStateToProps = (state) => ({
change: state.tutorial.change,
status: state.tutorial.status,
tutorials: state.tutorial.tutorials,
+ userTutorials: state.tutorial.userTutorials,
isLoading: state.tutorial.progress,
message: state.message,
progress: state.auth.progress,
+ user: state.auth.user,
+ authProgress: state.auth.progress,
});
export default connect(mapStateToProps, {
getTutorials,
+ progress,
+ getUserTutorials,
+ getAllTutorials,
resetTutorial,
clearMessages,
tutorialProgress,
diff --git a/src/components/Workspace/ShareProject.js b/src/components/Workspace/ShareProject.js
index 8b962cc..e86d817 100644
--- a/src/components/Workspace/ShareProject.js
+++ b/src/components/Workspace/ShareProject.js
@@ -1,72 +1,87 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import { shareProject } from '../../actions/projectActions';
-import { clearMessages } from '../../actions/messageActions';
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import { shareProject } from "../../actions/projectActions";
+import { clearMessages } from "../../actions/messageActions";
-import moment from 'moment';
+import moment from "moment";
-import Dialog from '../Dialog';
-import Snackbar from '../Snackbar';
+import Dialog from "../Dialog";
+import Snackbar from "../Snackbar";
-import { Link } from 'react-router-dom';
+import { Link } from "react-router-dom";
-import { withStyles } from '@material-ui/core/styles';
-import IconButton from '@material-ui/core/IconButton';
-import Tooltip from '@material-ui/core/Tooltip';
-import Typography from '@material-ui/core/Typography';
+import { withStyles } from "@material-ui/core/styles";
+import IconButton from "@material-ui/core/IconButton";
+import Tooltip from "@material-ui/core/Tooltip";
+import Typography from "@material-ui/core/Typography";
import { faShareAlt, faCopy } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import * as Blockly from 'blockly/core';
+import * as Blockly from "blockly/core";
const styles = (theme) => ({
button: {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
- width: '40px',
- height: '40px',
- '&:hover': {
+ width: "40px",
+ height: "40px",
+ "&:hover": {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
- }
+ },
},
link: {
color: theme.palette.primary.main,
- textDecoration: 'none',
- '&:hover': {
+ textDecoration: "none",
+ "&:hover": {
color: theme.palette.primary.main,
- textDecoration: 'underline'
- }
- }
+ textDecoration: "underline",
+ },
+ },
});
-
class WorkspaceFunc extends Component {
-
constructor(props) {
super(props);
this.inputRef = React.createRef();
this.state = {
snackbar: false,
- type: '',
- key: '',
- message: '',
- title: '',
- content: '',
+ type: "",
+ key: "",
+ message: "",
+ title: "",
+ content: "",
open: false,
- id: '',
+ id: "",
};
}
componentDidUpdate(props) {
if (this.props.message !== props.message) {
- if (this.props.message.id === 'SHARE_SUCCESS' && (!this.props.multiple || this.props.message.status === this.props.project._id)) {
- this.setState({ share: true, open: true, title: Blockly.Msg.messages_SHARE_SUCCESS, id: this.props.message.status });
- }
- else if (this.props.message.id === 'SHARE_FAIL' && (!this.props.multiple || this.props.message.status === this.props.project._id)) {
- this.setState({ snackbar: true, key: Date.now(), message: Blockly.Msg.messages_SHARE_FAIL, type: 'error' });
+ if (
+ this.props.message.id === "SHARE_SUCCESS" &&
+ (!this.props.multiple ||
+ this.props.message.status === this.props.project._id)
+ ) {
+ this.setState({
+ share: true,
+ open: true,
+ title: Blockly.Msg.messages_SHARE_SUCCESS,
+ id: this.props.message.status,
+ });
+ } else if (
+ this.props.message.id === "SHARE_FAIL" &&
+ (!this.props.multiple ||
+ this.props.message.status === this.props.project._id)
+ ) {
+ this.setState({
+ snackbar: true,
+ key: Date.now(),
+ message: Blockly.Msg.messages_SHARE_FAIL,
+ type: "error",
+ });
window.scrollTo(0, 0);
}
}
@@ -77,18 +92,25 @@ class WorkspaceFunc extends Component {
}
toggleDialog = () => {
- this.setState({ open: !this.state, title: '', content: '' });
- }
+ this.setState({ open: !this.state, title: "", content: "" });
+ };
shareBlocks = () => {
- if (this.props.projectType === 'project' && this.props.project.shared) {
+ if (this.props.projectType === "project" && this.props.project.shared) {
// project is already shared
- this.setState({ open: true, title: Blockly.Msg.messages_SHARE_SUCCESS, id: this.props.project._id });
+ this.setState({
+ open: true,
+ title: Blockly.Msg.messages_SHARE_SUCCESS,
+ id: this.props.project._id,
+ });
+ } else {
+ this.props.shareProject(
+ this.props.name || this.props.project.title,
+ this.props.projectType,
+ this.props.project ? this.props.project._id : undefined
+ );
}
- else {
- this.props.shareProject(this.props.name || this.props.project.title, this.props.projectType, this.props.project ? this.props.project._id : undefined);
- }
- }
+ };
render() {
return (
@@ -116,43 +138,89 @@ class WorkspaceFunc extends Component {
onClick={this.toggleDialog}
button={Blockly.Msg.button_close}
>
-
-
Über den folgenden Link kannst du dein Programm teilen:
-
this.toggleDialog()} className={this.props.classes.link}>{`${window.location.origin}/share/${this.state.id}`}
-
+
+
+ Über den folgenden Link kannst du dein Programm teilen:
+
+ this.toggleDialog()}
+ className={this.props.classes.link}
+ >{`${window.location.origin}/share/${this.state.id}`}
+
{
- navigator.clipboard.writeText(`${window.location.origin}/share/${this.state.id}`);
- this.setState({ snackbar: true, key: Date.now(), message: Blockly.Msg.messages_copylink_success, type: 'success' });
+ navigator.clipboard.writeText(
+ `${window.location.origin}/share/${this.state.id}`
+ );
+ this.setState({
+ snackbar: true,
+ key: Date.now(),
+ message: Blockly.Msg.messages_copylink_success,
+ type: "success",
+ });
}}
>
- {this.props.project && this.props.project.shared && this.props.message.id !== 'SHARE_SUCCESS' ?
- {`Das Projekt wurde bereits geteilt. Der Link ist noch mindestens ${moment(this.props.project.shared).diff(moment().utc(), 'days') === 0 ?
- moment(this.props.project.shared).diff(moment().utc(), 'hours') === 0 ?
- `${moment(this.props.project.shared).diff(moment().utc(), 'minutes')} Minuten`
- : `${moment(this.props.project.shared).diff(moment().utc(), 'hours')} Stunden`
- : `${moment(this.props.project.shared).diff(moment().utc(), 'days')} Tage`} gültig.`}
- : {`Der Link ist nun ${process.env.REACT_APP_SHARE_LINK_EXPIRES} Tage gültig.`} }
+ {this.props.project &&
+ this.props.project.shared &&
+ this.props.message.id !== "SHARE_SUCCESS" ? (
+ {`Das Projekt wurde bereits geteilt. Der Link ist noch mindestens ${
+ moment(this.props.project.shared).diff(
+ moment().utc(),
+ "days"
+ ) === 0
+ ? moment(this.props.project.shared).diff(
+ moment().utc(),
+ "hours"
+ ) === 0
+ ? `${moment(this.props.project.shared).diff(
+ moment().utc(),
+ "minutes"
+ )} Minuten`
+ : `${moment(this.props.project.shared).diff(
+ moment().utc(),
+ "hours"
+ )} Stunden`
+ : `${moment(this.props.project.shared).diff(
+ moment().utc(),
+ "days"
+ )} Tage`
+ } gültig.`}
+ ) : (
+ {`Der Link ist nun ${process.env.REACT_APP_SHARE_LINK_EXPIRES} Tage gültig.`}
+ )}
);
- };
+ }
}
WorkspaceFunc.propTypes = {
shareProject: PropTypes.func.isRequired,
clearMessages: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
- message: PropTypes.object.isRequired
+ message: PropTypes.object.isRequired,
};
-const mapStateToProps = state => ({
+const mapStateToProps = (state) => ({
name: state.workspace.name,
- message: state.message
+ message: state.message,
});
-export default connect(mapStateToProps, { shareProject, clearMessages })(withStyles(styles, { withTheme: true })(WorkspaceFunc));
+export default connect(mapStateToProps, { shareProject, clearMessages })(
+ withStyles(styles, { withTheme: true })(WorkspaceFunc)
+);
diff --git a/src/reducers/tutorialBuilderReducer.js b/src/reducers/tutorialBuilderReducer.js
index 1eb80f4..cb3a9dc 100644
--- a/src/reducers/tutorialBuilderReducer.js
+++ b/src/reducers/tutorialBuilderReducer.js
@@ -10,6 +10,9 @@ import {
BUILDER_CHANGE_STEP,
BUILDER_CHANGE_ORDER,
BUILDER_DELETE_PROPERTY,
+ BUILDER_DIFFICULTY,
+ BUILDER_PUBLIC,
+ BUILDER_REVIEW,
} from "../actions/types";
const initialState = {
@@ -17,6 +20,9 @@ const initialState = {
progress: false,
json: "",
title: "",
+ difficulty: 0,
+ public: false,
+ review: false,
id: "",
steps: [
{
@@ -45,6 +51,21 @@ export default function foo(state = initialState, action) {
...state,
title: action.payload,
};
+ case BUILDER_PUBLIC:
+ return {
+ ...state,
+ public: action.payload,
+ };
+ case BUILDER_DIFFICULTY:
+ return {
+ ...state,
+ difficulty: action.payload,
+ };
+ case BUILDER_REVIEW:
+ return {
+ ...state,
+ review: action.payload,
+ };
case BUILDER_ID:
return {
...state,
diff --git a/src/reducers/tutorialReducer.js b/src/reducers/tutorialReducer.js
index a44a31f..b60f7f7 100644
--- a/src/reducers/tutorialReducer.js
+++ b/src/reducers/tutorialReducer.js
@@ -1,24 +1,23 @@
-import { TUTORIAL_PROGRESS, GET_TUTORIAL, GET_TUTORIALS, GET_STATUS, TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_STEP } from '../actions/types';
-
-
-//
-// const initialStatus = () => {
-// if(store.getState().auth.user){
-// return store.getState().auth.user.status || []
-// }
-// else if (window.localStorage.getItem('status')) {
-// var status = JSON.parse(window.localStorage.getItem('status'));
-// return status;
-// }
-// return [];
-// };
+import {
+ TUTORIAL_PROGRESS,
+ GET_TUTORIAL,
+ GET_TUTORIALS,
+ GET_USERTUTORIALS,
+ GET_STATUS,
+ TUTORIAL_SUCCESS,
+ TUTORIAL_ERROR,
+ TUTORIAL_CHANGE,
+ TUTORIAL_XML,
+ TUTORIAL_STEP,
+} from "../actions/types";
const initialState = {
status: [],
activeStep: 0,
change: 0,
tutorials: [],
- progress: false
+ userTutorials: [],
+ progress: false,
};
export default function foo(state = initialState, action) {
@@ -26,18 +25,23 @@ export default function foo(state = initialState, action) {
case TUTORIAL_PROGRESS:
return {
...state,
- progress: !state.progress
- }
+ progress: !state.progress,
+ };
case GET_TUTORIALS:
return {
...state,
- tutorials: action.payload
+ tutorials: action.payload,
+ };
+ case GET_USERTUTORIALS:
+ return {
+ ...state,
+ tutorials: action.payload,
};
case GET_TUTORIAL:
return {
...state,
- tutorials: [action.payload]
- }
+ tutorials: [action.payload],
+ };
case TUTORIAL_SUCCESS:
case TUTORIAL_ERROR:
case TUTORIAL_XML:
@@ -46,23 +50,23 @@ export default function foo(state = initialState, action) {
// and 'TUTORIAL_XML' the function 'updateStatus' is called
return {
...state,
- status: action.payload
+ status: action.payload,
};
case GET_STATUS:
return {
...state,
- status: action.payload
+ status: action.payload,
};
case TUTORIAL_CHANGE:
return {
...state,
- change: state.change += 1
- }
+ change: (state.change += 1),
+ };
case TUTORIAL_STEP:
return {
...state,
- activeStep: action.payload
- }
+ activeStep: action.payload,
+ };
default:
return state;
}
diff --git a/src/store.js b/src/store.js
index bf6460e..3203857 100644
--- a/src/store.js
+++ b/src/store.js
@@ -1,6 +1,6 @@
-import { createStore, applyMiddleware, compose } from 'redux';
-import thunk from 'redux-thunk';
-import rootReducer from './reducers';
+import { createStore, applyMiddleware, compose } from "redux";
+import thunk from "redux-thunk";
+import rootReducer from "./reducers";
const initialState = {};
@@ -11,7 +11,7 @@ const store = createStore(
initialState,
compose(
applyMiddleware(...middleware),
- // window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
+ window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);