Merge pull request #156 from sensebox/update/tutorial-builder
update tutorial builder
This commit is contained in:
commit
3e9d55719b
@ -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",
|
||||
|
14
src/App.css
14
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;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { Component } from "react";
|
||||
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
import { Router } from "react-router-dom";
|
||||
import { createBrowserHistory } from "history";
|
||||
|
||||
import { Provider } from "react-redux";
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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";
|
||||
|
@ -1,34 +1,29 @@
|
||||
import React, { Component } from 'react';
|
||||
import React, { Component } from "react";
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { fade } from '@material-ui/core/styles/colorManipulator';
|
||||
import { withStyles } from "@material-ui/core/styles";
|
||||
import { alpha } from "@material-ui/core/styles";
|
||||
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
|
||||
const styles = (theme) => ({
|
||||
alert: {
|
||||
marginBottom: '20px',
|
||||
marginBottom: "20px",
|
||||
border: `1px solid ${theme.palette.primary.main}`,
|
||||
padding: '10px 20px',
|
||||
borderRadius: '4px',
|
||||
background: fade(theme.palette.primary.main, 0.3),
|
||||
color: 'rgb(70,70,70)'
|
||||
}
|
||||
padding: "10px 20px",
|
||||
borderRadius: "4px",
|
||||
background: alpha(theme.palette.primary.main, 0.3),
|
||||
color: "rgb(70,70,70)",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export class Alert extends Component {
|
||||
|
||||
render(){
|
||||
return(
|
||||
render() {
|
||||
return (
|
||||
<div className={this.props.classes.alert}>
|
||||
<Typography>
|
||||
{this.props.children}
|
||||
</Typography>
|
||||
<Typography>{this.props.children}</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default withStyles(styles, { withTheme: true })(Alert);
|
||||
|
@ -128,7 +128,7 @@ BlocklyWindow.propTypes = {
|
||||
onChangeWorkspace: PropTypes.func.isRequired,
|
||||
clearStats: PropTypes.func.isRequired,
|
||||
renderer: PropTypes.string.isRequired,
|
||||
sounds: PropTypes.string.isRequired,
|
||||
sounds: PropTypes.bool.isRequired,
|
||||
language: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
|
@ -228,6 +228,12 @@ export const UI = {
|
||||
builder_requirements_head: "Voraussetzungen",
|
||||
builder_requirements_order:
|
||||
"Beachte, dass die Reihenfolge des Anhakens maßgebend ist.",
|
||||
builder_difficulty: "Schwierigkeitsgrad",
|
||||
builder_public_head: "Tutorial veröffentlichen",
|
||||
builder_public_label: "Tutorial für alle Nutzer:innen veröffentlichen",
|
||||
builder_review_head: "Tutorial veröffentlichen",
|
||||
builder_review_text:
|
||||
"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.",
|
||||
|
||||
/**
|
||||
* Login
|
||||
|
@ -222,6 +222,12 @@ export const UI = {
|
||||
builder_requirements_head: "Requirements.",
|
||||
builder_requirements_order:
|
||||
"Note that the order of ticking is authoritative.",
|
||||
builder_difficulty: "Difficulty level",
|
||||
builder_public_head: "Publish tutorial",
|
||||
builder_public_label: "Publish tutorial for all users",
|
||||
builder_review_head: "Publish tutorial",
|
||||
builder_review_text:
|
||||
"You can share your tutorial with other people directly from the link. If you want to publish your tutorial for all users in the overview you can activate it here. An administrator will view your tutorial and then activate it.",
|
||||
|
||||
/**
|
||||
* Login
|
||||
|
@ -190,7 +190,7 @@ class Compile extends Component {
|
||||
className={`compileBlocks ${this.props.classes.iconButton}`}
|
||||
onClick={() => this.compile()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faClipboardCheck} size="m" />
|
||||
<FontAwesomeIcon icon={faClipboardCheck} size="xs" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
) : (
|
||||
|
@ -1,20 +1,21 @@
|
||||
import React from "react";
|
||||
import Blockly from "blockly";
|
||||
import { useSelector } from "react-redux";
|
||||
import Accordion from "@material-ui/core/Accordion";
|
||||
import AccordionSummary from "@material-ui/core/AccordionSummary";
|
||||
import AccordionDetails from "@material-ui/core/AccordionDetails";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { LibraryVersions } from "../../data/versions.js";
|
||||
//import { useMonaco } from "@monaco-editor/react";
|
||||
//import { Button } from "@material-ui/core";
|
||||
import { useMonaco } from "@monaco-editor/react";
|
||||
import { Button } from "@material-ui/core";
|
||||
import Dialog from "../Dialog";
|
||||
import SerialMonitor from "./SerialMonitor.js";
|
||||
//import axios from "axios";
|
||||
import axios from "axios";
|
||||
|
||||
const Sidebar = () => {
|
||||
const [alert, setAlert] = React.useState(false);
|
||||
//const [examples, setExamples] = React.useState([]);
|
||||
|
||||
const user = useSelector((state) => state.auth.user);
|
||||
// useEffect(() => {
|
||||
// axios
|
||||
// .get("https://coelho.opensensemap.org/items/blocklysamples")
|
||||
@ -22,16 +23,23 @@ const Sidebar = () => {
|
||||
// setExamples(res.data.data);
|
||||
// });
|
||||
// }, []);
|
||||
|
||||
//const monaco = useMonaco();
|
||||
// const loadCode = (code) => {
|
||||
// monaco.editor.getModels()[0].setValue(code);
|
||||
// };
|
||||
const monaco = useMonaco();
|
||||
const loadCode = (code) => {
|
||||
monaco.editor.getModels()[0].setValue(code);
|
||||
};
|
||||
|
||||
const toggleDialog = () => {
|
||||
setAlert(false);
|
||||
};
|
||||
|
||||
const getOsemScript = (id) => {
|
||||
axios
|
||||
.get(`https://api.opensensemap.org/boxes/${id}/script/`)
|
||||
.then((res) => {
|
||||
loadCode(res.data);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{"serial" in navigator ? (
|
||||
@ -50,7 +58,6 @@ const Sidebar = () => {
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
) : null}
|
||||
|
||||
{/* <Accordion>
|
||||
<AccordionSummary
|
||||
expandIcon={""}
|
||||
@ -67,6 +74,7 @@ const Sidebar = () => {
|
||||
style={{ padding: "1rem", margin: "1rem" }}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
key={i}
|
||||
onClick={() => loadCode(object.code)}
|
||||
>
|
||||
{object.name}
|
||||
@ -76,6 +84,34 @@ const Sidebar = () => {
|
||||
</Typography>
|
||||
</AccordionDetails>
|
||||
</Accordion> */}
|
||||
{user ? (
|
||||
<Accordion>
|
||||
<AccordionSummary
|
||||
expandIcon={""}
|
||||
aria-controls="panel1a-content"
|
||||
id="panel1a-header"
|
||||
>
|
||||
<Typography>Deine openSenseMap Codes</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography>
|
||||
{user.boxes.map((box, i) => {
|
||||
return (
|
||||
<Button
|
||||
style={{ padding: "1rem", margin: "1rem" }}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
key={i}
|
||||
onClick={() => getOsemScript(box._id)}
|
||||
>
|
||||
{box.name}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Typography>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
) : null}
|
||||
<Accordion>
|
||||
<AccordionSummary
|
||||
expandIcon={""}
|
||||
|
@ -52,7 +52,7 @@ class SoundsSelector extends Component {
|
||||
SoundsSelector.propTypes = {
|
||||
setSounds: PropTypes.func.isRequired,
|
||||
language: PropTypes.string.isRequired,
|
||||
sounds: PropTypes.string.isRequired,
|
||||
sounds: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
|
@ -1,47 +1,55 @@
|
||||
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 withWidth from '@material-ui/core/withWidth';
|
||||
import withWidth from "@material-ui/core/withWidth";
|
||||
|
||||
import { Card } from '@material-ui/core';
|
||||
import * as Blockly from 'blockly'
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import { Card } from "@material-ui/core";
|
||||
import * as Blockly from "blockly";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
|
||||
class TooltipViewer extends Component {
|
||||
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<Card className="tooltipViewer" style={{ height: '100%', margin: '1vH 0 0 0', maxHeight: '19vH', overflow: 'auto' }} ref={this.myDiv}>
|
||||
<Card
|
||||
className="tooltipViewer"
|
||||
style={{
|
||||
height: "100%",
|
||||
margin: "1vH 0 0 0",
|
||||
maxHeight: "19vH",
|
||||
overflow: "auto",
|
||||
}}
|
||||
ref={this.myDiv}
|
||||
>
|
||||
<CardContent>
|
||||
<Typography variant="h5" component="h2">
|
||||
{Blockly.Msg.tooltip_viewer}
|
||||
</Typography>
|
||||
<Typography variant="body2" component="p">
|
||||
<ReactMarkdown linkTarget="_blank">{this.props.tooltip}</ReactMarkdown>
|
||||
|
||||
{this.props.helpurl !== '' ? <ReactMarkdown>{`${Blockly.Msg.tooltip_moreInformation} [${Blockly.Msg.labels_here}](${this.props.helpurl})`}</ReactMarkdown> : null}
|
||||
<ReactMarkdown linkTarget="_blank">
|
||||
{this.props.tooltip}
|
||||
</ReactMarkdown>
|
||||
|
||||
</Typography>
|
||||
{this.props.helpurl !== "" ? (
|
||||
<ReactMarkdown>{`${Blockly.Msg.tooltip_moreInformation} [${Blockly.Msg.labels_here}](${this.props.helpurl})`}</ReactMarkdown>
|
||||
) : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
TooltipViewer.propTypes = {
|
||||
tooltip: PropTypes.string.isRequired,
|
||||
helpurl: PropTypes.string.isRequired
|
||||
helpurl: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state) => ({
|
||||
tooltip: state.workspace.code.tooltip,
|
||||
helpurl: state.workspace.code.helpurl
|
||||
helpurl: state.workspace.code.helpurl,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(withWidth()(TooltipViewer));
|
||||
|
@ -20,6 +20,8 @@ import ReactMarkdown from "react-markdown";
|
||||
import { faCode } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { withStyles } from "@material-ui/core/styles";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkGemoji from "remark-gemoji";
|
||||
|
||||
const styles = (theme) => ({
|
||||
codeOn: {
|
||||
@ -101,7 +103,9 @@ class Assessment extends Component {
|
||||
}}
|
||||
>
|
||||
<Typography>
|
||||
<ReactMarkdown>{currentTask.text}</ReactMarkdown>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm, remarkGemoji]}>
|
||||
{currentTask.text}
|
||||
</ReactMarkdown>
|
||||
</Typography>
|
||||
</Card>
|
||||
<Card
|
||||
|
@ -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 (
|
||||
<div>
|
||||
<Breadcrumbs
|
||||
@ -451,7 +478,24 @@ class Builder extends Component {
|
||||
label="Tutorial"
|
||||
>
|
||||
{filteredTutorials.map((tutorial) => (
|
||||
<MenuItem value={tutorial._id}>{tutorial.title}</MenuItem>
|
||||
<MenuItem value={tutorial._id}>
|
||||
{tutorial.title}{" "}
|
||||
{tutorial.review && tutorial.public === false ? (
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faUserCheck} />
|
||||
<FontAwesomeIcon icon={faEyeSlash} />
|
||||
</div>
|
||||
) : tutorial.public === false ? (
|
||||
<FontAwesomeIcon icon={faEyeSlash} />
|
||||
) : null}
|
||||
</MenuItem>
|
||||
/* ) : tutorial.public === false ? (
|
||||
<MenuItem value={tutorial._id}>
|
||||
{tutorial.title} <FontAwesomeIcon icon={faEyeSlash} />
|
||||
</MenuItem>
|
||||
) : (
|
||||
<MenuItem value={tutorial._id}>{tutorial.title}</MenuItem>
|
||||
)} */
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
@ -476,6 +520,45 @@ class Builder extends Component {
|
||||
label={"Titel"}
|
||||
error={this.props.error.title}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
borderRadius: "25px",
|
||||
border: "1px solid lightgrey",
|
||||
padding: "10px 14px 10px 10px",
|
||||
marginBottom: "20px",
|
||||
}}
|
||||
>
|
||||
<Difficulty
|
||||
value={this.props.difficulty}
|
||||
property={"difficulty"}
|
||||
label={"difficulty"}
|
||||
error={this.props.error.difficulty}
|
||||
/>
|
||||
<Divider
|
||||
variant="fullWidth"
|
||||
style={{ margin: "30px 0 10px 0" }}
|
||||
/>
|
||||
|
||||
<Review
|
||||
value={this.props.review}
|
||||
property={"review"}
|
||||
label={"review"}
|
||||
error={this.props.error.review}
|
||||
/>
|
||||
<Divider
|
||||
variant="fullWidth"
|
||||
style={{ margin: "30px 0 10px 0" }}
|
||||
/>
|
||||
|
||||
{this.props.user.blocklyRole === "admin" ? (
|
||||
<Public
|
||||
value={this.props.public}
|
||||
property={"public"}
|
||||
label={"public"}
|
||||
error={this.props.error.public}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{this.props.steps.map((step, i) => (
|
||||
<Step step={step} index={i} key={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,
|
||||
|
101
src/components/Tutorial/Builder/Difficulty.js
Normal file
101
src/components/Tutorial/Builder/Difficulty.js
Normal file
@ -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 (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">
|
||||
{Blockly.Msg.builder_difficulty}
|
||||
</FormLabel>
|
||||
<FormGroup aria-label="position" row>
|
||||
<FormControlLabel
|
||||
value="Tutorial veröffentlichen"
|
||||
control={
|
||||
<ReactStars
|
||||
count={5}
|
||||
onChange={this.handleChange}
|
||||
value={this.props.value}
|
||||
size={30}
|
||||
isHalf={true}
|
||||
emptyIcon={<i className="fa fa-star"></i>}
|
||||
halfIcon={<i className="fa fa-star-half-alt"></i>}
|
||||
fullIcon={<i className="fa fa-star"></i>}
|
||||
activeColor="#ffd700"
|
||||
/>
|
||||
}
|
||||
label="Schwierigkeitsgrad"
|
||||
labelPlacement="start"
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
93
src/components/Tutorial/Builder/Public.js
Normal file
93
src/components/Tutorial/Builder/Public.js
Normal file
@ -0,0 +1,93 @@
|
||||
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 (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">
|
||||
{Blockly.Msg.builder_public_head}
|
||||
</FormLabel>
|
||||
<FormGroup aria-label="position" row>
|
||||
<FormControlLabel
|
||||
value="Tutorial veröffentlichen"
|
||||
control={
|
||||
<Checkbox
|
||||
checked={this.props.value}
|
||||
onChange={this.handleChange}
|
||||
color="primary"
|
||||
name="checkedA"
|
||||
inputProps={{ "aria-label": "secondary checkbox" }}
|
||||
/>
|
||||
}
|
||||
label={Blockly.Msg.builder_public_label}
|
||||
labelPlacement="start"
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
93
src/components/Tutorial/Builder/Review.js
Normal file
93
src/components/Tutorial/Builder/Review.js
Normal file
@ -0,0 +1,93 @@
|
||||
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 (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">
|
||||
{Blockly.Msg.builder_review_head}
|
||||
</FormLabel>
|
||||
{Blockly.Msg.builder_review_text}
|
||||
<FormGroup aria-label="position" row>
|
||||
<FormControlLabel
|
||||
value="Tutorial veröffentlichen"
|
||||
control={
|
||||
<Checkbox
|
||||
checked={this.props.value}
|
||||
onChange={this.handleChange}
|
||||
color="primary"
|
||||
name="checkedA"
|
||||
inputProps={{ "aria-label": "secondary checkbox" }}
|
||||
/>
|
||||
}
|
||||
label="Ich möchte mein Tutorial öffentlich machen"
|
||||
labelPlacement="start"
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
@ -1,124 +1,233 @@
|
||||
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 { 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 clsx from "clsx";
|
||||
import { withRouter, Link } from "react-router-dom";
|
||||
import { alpha } from "@material-ui/core/styles";
|
||||
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: alpha(theme.palette.secondary.main, 0.6),
|
||||
},
|
||||
outerDivError: {
|
||||
stroke: fade(theme.palette.error.dark, 0.6),
|
||||
color: fade(theme.palette.error.dark, 0.6)
|
||||
stroke: alpha(theme.palette.error.dark, 0.6),
|
||||
color: alpha(theme.palette.error.dark, 0.6),
|
||||
},
|
||||
outerDivSuccess: {
|
||||
stroke: fade(theme.palette.primary.main, 0.6),
|
||||
color: fade(theme.palette.primary.main, 0.6)
|
||||
stroke: alpha(theme.palette.primary.main, 0.6),
|
||||
color: alpha(theme.palette.primary.main, 0.6),
|
||||
},
|
||||
outerDivOther: {
|
||||
stroke: fade(theme.palette.secondary.main, 0.6)
|
||||
stroke: alpha(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': {
|
||||
background: fade(theme.palette.secondary.main, 0.5),
|
||||
borderRadius: '0 25px 25px 0 '
|
||||
}
|
||||
}
|
||||
"&:hover": {
|
||||
background: alpha(theme.palette.secondary.main, 0.5),
|
||||
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 (
|
||||
<div style={{ marginTop: '20px', marginBottom: '5px' }}>
|
||||
<div style={{ marginTop: "20px", marginBottom: "5px" }}>
|
||||
<Typography>{Blockly.Msg.tutorials_requirements}</Typography>
|
||||
<List component="div">
|
||||
{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 (
|
||||
<Link to={`/tutorial/${tutorialId}`} className={this.props.classes.link} key={i}>
|
||||
<Tooltip style={{ height: '50px', width: '50px', position: 'absolute', background: 'white', zIndex: 1, borderRadius: '25px' }} title={error ? `Mind. eine Aufgabe im Tutorial wurde nicht richtig gelöst.` : success === 1 ? `Das Tutorial wurde bereits erfolgreich abgeschlossen.` : `Das Tutorial ist zu ${success * 100}% abgeschlossen.`} arrow>
|
||||
<Link
|
||||
target={"_blank"}
|
||||
to={`/tutorial/${tutorialId}`}
|
||||
className={this.props.classes.link}
|
||||
key={i}
|
||||
>
|
||||
<Tooltip
|
||||
style={{
|
||||
height: "50px",
|
||||
width: "50px",
|
||||
position: "absolute",
|
||||
background: "white",
|
||||
zIndex: 1,
|
||||
borderRadius: "25px",
|
||||
}}
|
||||
title={
|
||||
error
|
||||
? `Mind. eine Aufgabe im Tutorial wurde nicht richtig gelöst.`
|
||||
: success === 1
|
||||
? `Das Tutorial wurde bereits erfolgreich abgeschlossen.`
|
||||
: `Das Tutorial ist zu ${success * 100}% abgeschlossen.`
|
||||
}
|
||||
arrow
|
||||
>
|
||||
<div>
|
||||
<div className={clsx(this.props.classes.outerDiv)} style={{ width: '50px', height: '50px', border: 0 }}>
|
||||
<svg style={{ width: '100%', height: '100%' }}>
|
||||
{error || success === 1 ?
|
||||
<circle className={error ? this.props.classes.outerDivError : this.props.classes.outerDivSuccess} r="22.5" cx="50%" cy="50%" fill="none" strokeWidth="5"></circle>
|
||||
: <circle className={this.props.classes.outerDivOther} r="22.5" cx="50%" cy="50%" fill="none" strokeWidth="5"></circle>}
|
||||
{success < 1 && !error ?
|
||||
<circle className={this.props.classes.outerDivSuccess} style={{ transform: 'rotate(-90deg)', transformOrigin: "50% 50%" }} r="22.5" cx="50%" cy="50%" fill="none" strokeWidth="5" strokeDashoffset={`${(22.5 * 2 * Math.PI) * (1 - success)}`} strokeDasharray={`${(22.5 * 2 * Math.PI)}`}>
|
||||
</circle>
|
||||
: null}
|
||||
<div
|
||||
className={clsx(this.props.classes.outerDiv)}
|
||||
style={{ width: "50px", height: "50px", border: 0 }}
|
||||
>
|
||||
<svg style={{ width: "100%", height: "100%" }}>
|
||||
{error || success === 1 ? (
|
||||
<circle
|
||||
className={
|
||||
error
|
||||
? this.props.classes.outerDivError
|
||||
: this.props.classes.outerDivSuccess
|
||||
}
|
||||
r="22.5"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
fill="none"
|
||||
strokeWidth="5"
|
||||
></circle>
|
||||
) : (
|
||||
<circle
|
||||
className={this.props.classes.outerDivOther}
|
||||
r="22.5"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
fill="none"
|
||||
strokeWidth="5"
|
||||
></circle>
|
||||
)}
|
||||
{success < 1 && !error ? (
|
||||
<circle
|
||||
className={this.props.classes.outerDivSuccess}
|
||||
style={{
|
||||
transform: "rotate(-90deg)",
|
||||
transformOrigin: "50% 50%",
|
||||
}}
|
||||
r="22.5"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
fill="none"
|
||||
strokeWidth="5"
|
||||
strokeDashoffset={`${
|
||||
22.5 * 2 * Math.PI * (1 - success)
|
||||
}`}
|
||||
strokeDasharray={`${22.5 * 2 * Math.PI}`}
|
||||
></circle>
|
||||
) : null}
|
||||
</svg>
|
||||
</div>
|
||||
<div className={clsx(this.props.classes.outerDiv, tutorialStatus === 'Error' ? this.props.classes.outerDivError : tutorialStatus === 'Success' ? this.props.classes.outerDivSuccess : null)}>
|
||||
<div
|
||||
className={clsx(
|
||||
this.props.classes.outerDiv,
|
||||
tutorialStatus === "Error"
|
||||
? this.props.classes.outerDivError
|
||||
: tutorialStatus === "Success"
|
||||
? this.props.classes.outerDivSuccess
|
||||
: null
|
||||
)}
|
||||
>
|
||||
<div className={this.props.classes.innerDiv}>
|
||||
{error || success === 1 ?
|
||||
<FontAwesomeIcon icon={tutorialStatus === 'Success' ? faCheck : faTimes} />
|
||||
: <Typography variant='h7' className={success > 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success * 100)}%</Typography>
|
||||
}
|
||||
{error || success === 1 ? (
|
||||
<FontAwesomeIcon
|
||||
icon={
|
||||
tutorialStatus === "Success" ? faCheck : faTimes
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Typography
|
||||
variant="h7"
|
||||
className={
|
||||
success > 0
|
||||
? this.props.classes.outerDivSuccess
|
||||
: {}
|
||||
}
|
||||
>
|
||||
{Math.round(success * 100)}%
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div style={{ height: '50px', width: 'calc(100% - 25px)', transform: 'translate(25px)' }} className={this.props.classes.hoverLink}>
|
||||
<Typography style={{ margin: 0, position: 'absolute', top: '50%', transform: 'translate(45px, -50%)', maxHeight: '50px', overflow: 'hidden', maxWidth: 'calc(100% - 45px)', textOverflow: 'ellipsis', whiteSpace: 'pre-line', overflowWrap: 'anywhere' }}>{title}</Typography>
|
||||
<div
|
||||
style={{
|
||||
height: "50px",
|
||||
width: "calc(100% - 25px)",
|
||||
transform: "translate(25px)",
|
||||
}}
|
||||
className={this.props.classes.hoverLink}
|
||||
>
|
||||
<Typography
|
||||
style={{
|
||||
margin: 0,
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
transform: "translate(45px, -50%)",
|
||||
maxHeight: "50px",
|
||||
overflow: "hidden",
|
||||
maxWidth: "calc(100% - 45px)",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "pre-line",
|
||||
overflowWrap: "anywhere",
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
)}
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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)));
|
||||
|
@ -63,7 +63,7 @@ class SolutionCheck extends Component {
|
||||
style={{ width: "40px", height: "40px", marginRight: "5px" }}
|
||||
onClick={() => this.check()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faClipboardCheck} size="m" />
|
||||
<FontAwesomeIcon icon={faClipboardCheck} size="xs" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
|
@ -7,8 +7,8 @@ import { withRouter } from "react-router-dom";
|
||||
import clsx from "clsx";
|
||||
|
||||
// import tutorials from '../../data/tutorials';
|
||||
import { alpha } from "@material-ui/core/styles";
|
||||
|
||||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||
import { withStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
@ -28,13 +28,13 @@ const styles = (theme) => ({
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
stepperSuccess: {
|
||||
backgroundColor: fade(theme.palette.primary.main, 0.6),
|
||||
backgroundColor: alpha(theme.palette.primary.main, 0.6),
|
||||
},
|
||||
stepperError: {
|
||||
backgroundColor: fade(theme.palette.error.dark, 0.6),
|
||||
backgroundColor: alpha(theme.palette.error.dark, 0.6),
|
||||
},
|
||||
stepperOther: {
|
||||
backgroundColor: fade(theme.palette.secondary.main, 0.6),
|
||||
backgroundColor: alpha(theme.palette.secondary.main, 0.6),
|
||||
},
|
||||
color: {
|
||||
backgroundColor: "transparent ",
|
||||
|
@ -1,36 +1,35 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { tutorialStep } from '../../actions/tutorialActions';
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { connect } from "react-redux";
|
||||
import { tutorialStep } from "../../actions/tutorialActions";
|
||||
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { withRouter } from "react-router-dom";
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { fade } from '@material-ui/core/styles/colorManipulator';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Stepper from '@material-ui/core/Stepper';
|
||||
import Step from '@material-ui/core/Step';
|
||||
import StepLabel from '@material-ui/core/StepLabel';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import clsx from "clsx";
|
||||
import { alpha } from "@material-ui/core/styles";
|
||||
import { withStyles } from "@material-ui/core/styles";
|
||||
import Stepper from "@material-ui/core/Stepper";
|
||||
import Step from "@material-ui/core/Step";
|
||||
import StepLabel from "@material-ui/core/StepLabel";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
|
||||
const styles = (theme) => ({
|
||||
verticalStepper: {
|
||||
padding: 0,
|
||||
width: '30px',
|
||||
width: "30px",
|
||||
},
|
||||
stepIcon: {
|
||||
borderStyle: `solid`,
|
||||
// borderWidth: '2px',
|
||||
borderRadius: '50%',
|
||||
borderRadius: "50%",
|
||||
borderColor: theme.palette.secondary.main,
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
margin: '0 auto',
|
||||
width: "12px",
|
||||
height: "12px",
|
||||
margin: "0 auto",
|
||||
},
|
||||
stepIconLarge: {
|
||||
width: '24px',
|
||||
height: '24px'
|
||||
width: "24px",
|
||||
height: "24px",
|
||||
},
|
||||
stepIconLargeSuccess: {
|
||||
borderColor: theme.palette.primary.main,
|
||||
@ -39,28 +38,27 @@ const styles = (theme) => ({
|
||||
borderColor: theme.palette.error.dark,
|
||||
},
|
||||
stepIconActiveOther: {
|
||||
backgroundColor: theme.palette.secondary.main
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
},
|
||||
stepIconActiveSuccess: {
|
||||
backgroundColor: fade(theme.palette.primary.main, 0.6)
|
||||
backgroundColor: alpha(theme.palette.primary.main, 0.6),
|
||||
},
|
||||
stepIconActiveError: {
|
||||
backgroundColor: fade(theme.palette.error.dark, 0.6)
|
||||
backgroundColor: alpha(theme.palette.error.dark, 0.6),
|
||||
},
|
||||
connector: {
|
||||
height: '10px',
|
||||
height: "10px",
|
||||
borderLeft: `2px solid black`,
|
||||
margin: 'auto'
|
||||
}
|
||||
margin: "auto",
|
||||
},
|
||||
});
|
||||
|
||||
class StepperVertical extends Component {
|
||||
|
||||
componentDidMount(){
|
||||
componentDidMount() {
|
||||
this.props.tutorialStep(0);
|
||||
}
|
||||
|
||||
componentDidUpdate(props){
|
||||
componentDidUpdate(props) {
|
||||
if (props.tutorial._id !== this.props.match.params.tutorialId) {
|
||||
this.props.tutorialStep(0);
|
||||
}
|
||||
@ -69,60 +67,103 @@ class StepperVertical extends Component {
|
||||
render() {
|
||||
var steps = this.props.steps;
|
||||
var activeStep = this.props.activeStep;
|
||||
var tutorialStatus = this.props.status.filter(status => status._id === this.props.tutorial._id)[0];
|
||||
var tutorialStatus = this.props.status.filter(
|
||||
(status) => status._id === this.props.tutorial._id
|
||||
)[0];
|
||||
return (
|
||||
<div style={{marginRight: '10px'}}>
|
||||
<div style={{ marginRight: "10px" }}>
|
||||
<Stepper
|
||||
activeStep={activeStep}
|
||||
orientation="vertical"
|
||||
connector={<div className={this.props.classes.connector}></div>}
|
||||
classes={{root: this.props.classes.verticalStepper}}
|
||||
classes={{ root: this.props.classes.verticalStepper }}
|
||||
>
|
||||
{steps.map((step, i) => {
|
||||
var tasksIndex = tutorialStatus.tasks.findIndex(task => task._id === step._id);
|
||||
var taskType = tasksIndex > -1 ? tutorialStatus.tasks[tasksIndex].type : null;
|
||||
var taskStatus = taskType === 'success' ? 'Success' : taskType === 'error' ? 'Error' : 'Other';
|
||||
var tasksIndex = tutorialStatus.tasks.findIndex(
|
||||
(task) => task._id === step._id
|
||||
);
|
||||
var taskType =
|
||||
tasksIndex > -1 ? tutorialStatus.tasks[tasksIndex].type : null;
|
||||
var taskStatus =
|
||||
taskType === "success"
|
||||
? "Success"
|
||||
: taskType === "error"
|
||||
? "Error"
|
||||
: "Other";
|
||||
return (
|
||||
<Step key={i}>
|
||||
<Tooltip title={step.headline} placement='right' arrow >
|
||||
<div style={i === activeStep ? {padding: '5px 0'} : {padding: '5px 0', cursor: 'pointer'}} onClick={i === activeStep ? null : () => { this.props.tutorialStep(i)}}>
|
||||
<Tooltip title={step.headline} placement="right" arrow>
|
||||
<div
|
||||
style={
|
||||
i === activeStep
|
||||
? { padding: "5px 0" }
|
||||
: { padding: "5px 0", cursor: "pointer" }
|
||||
}
|
||||
onClick={
|
||||
i === activeStep
|
||||
? null
|
||||
: () => {
|
||||
this.props.tutorialStep(i);
|
||||
}
|
||||
}
|
||||
>
|
||||
<StepLabel
|
||||
StepIconComponent={'div'}
|
||||
StepIconComponent={"div"}
|
||||
classes={{
|
||||
root: step.type === 'task' ?
|
||||
i === activeStep ?
|
||||
clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge, this.props.classes['stepIconLarge'+taskStatus], this.props.classes['stepIconActive'+taskStatus])
|
||||
: clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge, this.props.classes['stepIconLarge'+taskStatus])
|
||||
: i === activeStep ?
|
||||
clsx(this.props.classes.stepIcon, this.props.classes.stepIconActiveOther)
|
||||
: clsx(this.props.classes.stepIcon)
|
||||
root:
|
||||
step.type === "task"
|
||||
? i === activeStep
|
||||
? clsx(
|
||||
this.props.classes.stepIcon,
|
||||
this.props.classes.stepIconLarge,
|
||||
this.props.classes[
|
||||
"stepIconLarge" + taskStatus
|
||||
],
|
||||
this.props.classes[
|
||||
"stepIconActive" + taskStatus
|
||||
]
|
||||
)
|
||||
: clsx(
|
||||
this.props.classes.stepIcon,
|
||||
this.props.classes.stepIconLarge,
|
||||
this.props.classes[
|
||||
"stepIconLarge" + taskStatus
|
||||
]
|
||||
)
|
||||
: i === activeStep
|
||||
? clsx(
|
||||
this.props.classes.stepIcon,
|
||||
this.props.classes.stepIconActiveOther
|
||||
)
|
||||
: clsx(this.props.classes.stepIcon),
|
||||
}}
|
||||
>
|
||||
</StepLabel>
|
||||
></StepLabel>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Step>
|
||||
)})}
|
||||
);
|
||||
})}
|
||||
</Stepper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StepperVertical.propTypes = {
|
||||
status: PropTypes.array.isRequired,
|
||||
change: PropTypes.number.isRequired,
|
||||
activeStep: PropTypes.number.isRequired,
|
||||
tutorialStep: PropTypes.func.isRequired,
|
||||
tutorial: PropTypes.object.isRequired
|
||||
tutorial: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state) => ({
|
||||
change: state.tutorial.change,
|
||||
status: state.tutorial.status,
|
||||
activeStep: state.tutorial.activeStep,
|
||||
tutorial: state.tutorial.tutorials[0]
|
||||
tutorial: state.tutorial.tutorials[0],
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { tutorialStep })(withRouter(withStyles(styles, {withTheme: true})(StepperVertical)));
|
||||
export default connect(mapStateToProps, { tutorialStep })(
|
||||
withRouter(withStyles(styles, { withTheme: true })(StepperVertical))
|
||||
);
|
||||
|
@ -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";
|
||||
@ -13,16 +17,26 @@ import clsx from "clsx";
|
||||
import Breadcrumbs from "../Breadcrumbs";
|
||||
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||
import { alpha } from "@material-ui/core/styles";
|
||||
import { withStyles } from "@material-ui/core/styles";
|
||||
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: {
|
||||
@ -31,18 +45,18 @@ const styles = (theme) => ({
|
||||
bottom: "-30px",
|
||||
width: "160px",
|
||||
height: "160px",
|
||||
color: fade(theme.palette.secondary.main, 0.6),
|
||||
color: alpha(theme.palette.secondary.main, 0.6),
|
||||
},
|
||||
outerDivError: {
|
||||
stroke: fade(theme.palette.error.dark, 0.6),
|
||||
color: fade(theme.palette.error.dark, 0.6),
|
||||
stroke: alpha(theme.palette.error.dark, 0.6),
|
||||
color: alpha(theme.palette.error.dark, 0.6),
|
||||
},
|
||||
outerDivSuccess: {
|
||||
stroke: fade(theme.palette.primary.main, 0.6),
|
||||
color: fade(theme.palette.primary.main, 0.6),
|
||||
stroke: alpha(theme.palette.primary.main, 0.6),
|
||||
color: alpha(theme.palette.primary.main, 0.6),
|
||||
},
|
||||
outerDivOther: {
|
||||
stroke: fade(theme.palette.secondary.main, 0.6),
|
||||
stroke: alpha(theme.palette.secondary.main, 0.6),
|
||||
},
|
||||
innerDiv: {
|
||||
width: "inherit",
|
||||
@ -51,26 +65,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 +158,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 : (
|
||||
<div>
|
||||
<Breadcrumbs content={[{ link: "/tutorial", title: "Tutorial" }]} />
|
||||
|
||||
<h1>{Blockly.Msg.tutorials_home_head}</h1>
|
||||
<h2>Alle Tutorials</h2>
|
||||
<Grid container spacing={2}>
|
||||
{this.props.tutorials.map((tutorial, i) => {
|
||||
{publicTutorials.map((tutorial, i) => {
|
||||
var status = this.props.status.filter(
|
||||
(status) => status._id === tutorial._id
|
||||
)[0];
|
||||
@ -99,6 +188,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 (
|
||||
<Grid item xs={12} sm={6} md={4} xl={3} key={i} style={{}}>
|
||||
<Link
|
||||
@ -114,6 +209,7 @@ class TutorialHome extends Component {
|
||||
}}
|
||||
>
|
||||
{tutorial.title}
|
||||
<ReactStars {...firstExample} />
|
||||
<div
|
||||
className={clsx(this.props.classes.outerDiv)}
|
||||
style={{ width: "160px", height: "160px", border: 0 }}
|
||||
@ -216,6 +312,119 @@ class TutorialHome extends Component {
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
{this.props.user ? (
|
||||
<div>
|
||||
<h2>User Tutorials</h2>
|
||||
<Grid container spacing={2}>
|
||||
{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 (
|
||||
<Grid item xs={12} sm={6} md={4} xl={3} key={i} style={{}}>
|
||||
<Paper
|
||||
style={{
|
||||
height: "150",
|
||||
padding: "10px",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
backgroundColor: tutorial.review
|
||||
? "lightyellow"
|
||||
: "white",
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
to={`/tutorial/${tutorial._id}`}
|
||||
style={{ textDecoration: "none", color: "inherit" }}
|
||||
>
|
||||
{tutorial.title}
|
||||
<ReactStars {...firstExample} />
|
||||
</Link>
|
||||
<Divider
|
||||
style={{
|
||||
marginTop: "10px",
|
||||
marginBottom: "10px",
|
||||
}}
|
||||
/>
|
||||
<p>
|
||||
Creator:{tutorial.creator} <br />
|
||||
<div style={this.props.style}>
|
||||
<Tooltip
|
||||
title={Blockly.Msg.tooltip_share_tutorial}
|
||||
arrow
|
||||
>
|
||||
<IconButton
|
||||
className={`shareTutorial ${this.props.classes.button}`}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
`${window.location.origin}/tutorial/${tutorial._id}`
|
||||
);
|
||||
this.setState({
|
||||
snackbar: true,
|
||||
key: Date.now(),
|
||||
message:
|
||||
Blockly.Msg.messages_copylink_success,
|
||||
type: "success",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faShareAlt} size="xs" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={Blockly.Msg.tooltip_share_tutorial}
|
||||
arrow
|
||||
>
|
||||
<IconButton
|
||||
className={`publicTutorial ${this.props.classes.button}`}
|
||||
disabled={!tutorial.public}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEye} size="xs" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{tutorial.review ? (
|
||||
<Tooltip
|
||||
title={Blockly.Msg.tooltip_share_tutorial}
|
||||
arrow
|
||||
>
|
||||
<IconButton
|
||||
className={`publicTutorial ${this.props.classes.button}`}
|
||||
disabled={!tutorial.review}
|
||||
>
|
||||
<FontAwesomeIcon icon={faUserCheck} size="xs" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<Snackbar
|
||||
open={this.state.snackbar}
|
||||
message={Blockly.Msg.messages_copylink_success}
|
||||
type={this.state.type}
|
||||
key={this.state.key}
|
||||
/>
|
||||
</div>
|
||||
</p>
|
||||
</Paper>
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -223,6 +432,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 +443,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,
|
||||
|
@ -190,7 +190,7 @@ class Compile extends Component {
|
||||
className={`compileBlocks ${this.props.classes.iconButton}`}
|
||||
onClick={() => this.compile()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faClipboardCheck} size="m" />
|
||||
<FontAwesomeIcon icon={faClipboardCheck} size="xs" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
) : (
|
||||
|
@ -69,7 +69,7 @@ class CopyCode extends Component {
|
||||
className={`copyCode ${this.props.classes.iconButton}`}
|
||||
onClick={() => this.copyCode()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCopy} size="m" />
|
||||
<FontAwesomeIcon icon={faCopy} size="xs" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
) : (
|
||||
|
@ -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}
|
||||
>
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
<Typography>Über den folgenden Link kannst du dein Programm teilen:</Typography>
|
||||
<Link to={`/share/${this.state.id}`} onClick={() => this.toggleDialog()} className={this.props.classes.link}>{`${window.location.origin}/share/${this.state.id}`}</Link>
|
||||
<Tooltip title={Blockly.Msg.tooltip_copy_link} arrow style={{ marginRight: '5px' }}>
|
||||
<div style={{ marginTop: "10px" }}>
|
||||
<Typography>
|
||||
Über den folgenden Link kannst du dein Programm teilen:
|
||||
</Typography>
|
||||
<Link
|
||||
to={`/share/${this.state.id}`}
|
||||
onClick={() => this.toggleDialog()}
|
||||
className={this.props.classes.link}
|
||||
>{`${window.location.origin}/share/${this.state.id}`}</Link>
|
||||
<Tooltip
|
||||
title={Blockly.Msg.tooltip_copy_link}
|
||||
arrow
|
||||
style={{ marginRight: "5px" }}
|
||||
>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
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",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCopy} size="xs" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{this.props.project && this.props.project.shared && this.props.message.id !== 'SHARE_SUCCESS' ?
|
||||
<Typography variant='body2' style={{ marginTop: '20px' }}>{`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.`}</Typography>
|
||||
: <Typography variant='body2' style={{ marginTop: '20px' }}>{`Der Link ist nun ${process.env.REACT_APP_SHARE_LINK_EXPIRES} Tage gültig.`}</Typography>}
|
||||
{this.props.project &&
|
||||
this.props.project.shared &&
|
||||
this.props.message.id !== "SHARE_SUCCESS" ? (
|
||||
<Typography
|
||||
variant="body2"
|
||||
style={{ marginTop: "20px" }}
|
||||
>{`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.`}</Typography>
|
||||
) : (
|
||||
<Typography
|
||||
variant="body2"
|
||||
style={{ marginTop: "20px" }}
|
||||
>{`Der Link ist nun ${process.env.REACT_APP_SHARE_LINK_EXPIRES} Tage gültig.`}</Typography>
|
||||
)}
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
|
@ -3,7 +3,8 @@
|
||||
"id": "senseboxmcu",
|
||||
"name": "senseBox MCU",
|
||||
"src": "senseboxmcu.png",
|
||||
"url": "https://docs.sensebox.de/hardware/allgemein-sensebox-mcu/"
|
||||
"url": "https://docs.sensebox.de/hardware/allgemein-sensebox-mcu/",
|
||||
"description": "test"
|
||||
},
|
||||
{
|
||||
"id": "breadboard",
|
||||
|
@ -25,7 +25,7 @@ const initialSounds = () => {
|
||||
if (window.localStorage.getItem("sounds")) {
|
||||
return window.localStorage.getItem("sounds");
|
||||
} else {
|
||||
return "off";
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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__()
|
||||
)
|
||||
);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user