diff --git a/package.json b/package.json index 72a6539..44ff8d2 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", + "axios": "^0.21.0", "blockly": "^3.20200924.0", "file-saver": "^2.0.2", "mnemonic-id": "^3.2.7", diff --git a/src/actions/messageActions.js b/src/actions/messageActions.js new file mode 100644 index 0000000..517c973 --- /dev/null +++ b/src/actions/messageActions.js @@ -0,0 +1,32 @@ +import { GET_ERRORS, CLEAR_MESSAGES, GET_SUCCESS } from './types'; + +// RETURN Errors +export const returnErrors = (msg, status, id = null) => { + return { + type: GET_ERRORS, + payload: { + msg: msg, + status: status, + id: id + } + }; +}; + +// RETURN Success +export const returnSuccess = (msg, status, id = null) => { + return { + type: GET_SUCCESS, + payload: { + msg: msg, + status: status, + id: id + } + }; +}; + +// CLEAR_MESSAGES +export const clearMessages = () => { + return { + type: CLEAR_MESSAGES + }; +}; diff --git a/src/actions/tutorialActions.js b/src/actions/tutorialActions.js index 4ab1980..10013e0 100644 --- a/src/actions/tutorialActions.js +++ b/src/actions/tutorialActions.js @@ -1,6 +1,34 @@ -import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID, TUTORIAL_STEP } from './types'; +import { TUTORIAL_PROGRESS, GET_TUTORIAL, TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID, TUTORIAL_STEP } from './types'; -import tutorials from '../data/tutorials'; +import axios from 'axios'; +import { returnErrors, returnSuccess } from './messageActions'; + +// import tutorials from '../data/tutorials'; + +export const getTutorial = (id) => (dispatch) => { + dispatch({type: TUTORIAL_PROGRESS}); + axios.get(`https://api.blockly.sensebox.de/tutorial/${id}`) + .then(res => { + dispatch({type: TUTORIAL_PROGRESS}); + dispatch({ + type: GET_TUTORIAL, + payload: res.data + }); + }) + .catch(err => { + dispatch({type: TUTORIAL_PROGRESS}); + if(err.response){ + dispatch(returnErrors(err.response.data.message, err.response.status, 'GET_TUTORIAL_FAIL')); + } + }); +}; + +export const resetTutorial = () => (dispatch) => { + dispatch({ + type: GET_TUTORIAL, + payload: {} + }); +}; export const tutorialChange = () => (dispatch) => { dispatch({ @@ -10,7 +38,7 @@ export const tutorialChange = () => (dispatch) => { export const tutorialCheck = (status, step) => (dispatch, getState) => { var tutorialsStatus = getState().tutorial.status; - var id = getState().tutorial.currentId; + var id = getState().tutorial.tutorial.id; var tutorialsStatusIndex = tutorialsStatus.findIndex(tutorialStatus => tutorialStatus.id === id); var tasksIndex = tutorialsStatus[tutorialsStatusIndex].tasks.findIndex(task => task.id === step.id); tutorialsStatus[tutorialsStatusIndex].tasks[tasksIndex] = { @@ -25,11 +53,12 @@ export const tutorialCheck = (status, step) => (dispatch, getState) => { }; export const storeTutorialXml = (code) => (dispatch, getState) => { - var id = getState().tutorial.currentId; + var tutorial = getState().tutorial.tutorial; + var id = tutorial.id; if (id !== null) { var activeStep = getState().tutorial.activeStep; - var steps = tutorials.filter(tutorial => tutorial.id === id)[0].steps; - if (steps[activeStep].type === 'task') { + var steps = tutorial.steps;//tutorials.filter(tutorial => tutorial.id === id)[0].steps; + if (steps && steps[activeStep].type === 'task') { var tutorialsStatus = getState().tutorial.status; var tutorialsStatusIndex = tutorialsStatus.findIndex(tutorialStatus => tutorialStatus.id === id); var tasksIndex = tutorialsStatus[tutorialsStatusIndex].tasks.findIndex(task => task.id === steps[activeStep].id); @@ -46,12 +75,12 @@ export const storeTutorialXml = (code) => (dispatch, getState) => { }; -export const tutorialId = (id) => (dispatch) => { - dispatch({ - type: TUTORIAL_ID, - payload: id - }); -}; +// export const tutorialId = (id) => (dispatch) => { +// dispatch({ +// type: TUTORIAL_ID, +// payload: id +// }); +// }; export const tutorialStep = (step) => (dispatch) => { dispatch({ diff --git a/src/actions/types.js b/src/actions/types.js index c65c9ec..15ca8c1 100644 --- a/src/actions/types.js +++ b/src/actions/types.js @@ -7,7 +7,8 @@ export const DELETE_BLOCK = 'DELETE_BLOCK'; export const CLEAR_STATS = 'CLEAR_STATS'; export const NAME = 'NAME'; - +export const TUTORIAL_PROGRESS = 'TUTORIAL_PROGRESS'; +export const GET_TUTORIAL = 'GET_TUTORIAL'; export const TUTORIAL_SUCCESS = 'TUTORIAL_SUCCESS'; export const TUTORIAL_ERROR = 'TUTORIAL_ERROR'; export const TUTORIAL_CHANGE = 'TUTORIAL_CHANGE'; @@ -30,3 +31,8 @@ export const PROGRESS = 'PROGRESS'; export const VISIT = 'VISIT'; + +// messages +export const GET_ERRORS = 'GET_ERRORS'; +export const GET_SUCCESS = 'GET_SUCCESS'; +export const CLEAR_MESSAGES = 'CLEAR_MESSAGES'; diff --git a/src/components/Tutorial/Assessment.js b/src/components/Tutorial/Assessment.js index 3cd546b..2729774 100644 --- a/src/components/Tutorial/Assessment.js +++ b/src/components/Tutorial/Assessment.js @@ -27,7 +27,7 @@ class Assessment extends Component { } render() { - var tutorialId = this.props.currentTutorialId; + var tutorialId = this.props.tutorial.id //currentTutorialId; var currentTask = this.props.step; var status = this.props.status.filter(status => status.id === tutorialId)[0]; var taskIndex = status.tasks.findIndex(task => task.id === currentTask.id); @@ -61,16 +61,18 @@ class Assessment extends Component { } Assessment.propTypes = { - currentTutorialId: PropTypes.number, + // currentTutorialId: PropTypes.number, status: PropTypes.array.isRequired, change: PropTypes.number.isRequired, - workspaceName: PropTypes.func.isRequired + workspaceName: PropTypes.func.isRequired, + tutorial: PropTypes.object.isRequired }; const mapStateToProps = state => ({ change: state.tutorial.change, status: state.tutorial.status, - currentTutorialId: state.tutorial.currentId + tutorial: state.tutorial.tutorial + // currentTutorialId: state.tutorial.currentId }); export default connect(mapStateToProps, { workspaceName })(withWidth()(Assessment)); diff --git a/src/components/Tutorial/Instruction.js b/src/components/Tutorial/Instruction.js index 4ffd491..fb82d8d 100644 --- a/src/components/Tutorial/Instruction.js +++ b/src/components/Tutorial/Instruction.js @@ -56,11 +56,11 @@ class Instruction extends Component { } Instruction.propTypes = { - currentTutorialId: PropTypes.number, + // currentTutorialId: PropTypes.number, }; const mapStateToProps = state => ({ - currentTutorialId: state.tutorial.currentId + // currentTutorialId: state.tutorial.currentId }); export default connect(mapStateToProps, null)(Instruction); diff --git a/src/components/Tutorial/SolutionCheck.js b/src/components/Tutorial/SolutionCheck.js index 92c093f..08bec49 100644 --- a/src/components/Tutorial/SolutionCheck.js +++ b/src/components/Tutorial/SolutionCheck.js @@ -8,7 +8,7 @@ import { withRouter } from 'react-router-dom'; import Compile from '../Compile'; import Dialog from '../Dialog'; -import tutorials from '../../data/tutorials'; +// import tutorials from '../../data/tutorials'; import { checkXml } from '../../helpers/compareXml'; import { withStyles } from '@material-ui/core/styles'; @@ -47,7 +47,7 @@ class SolutionCheck extends Component { } check = () => { - const tutorial = tutorials.filter(tutorial => tutorial.id === this.props.currentTutorialId)[0]; + const tutorial = this.props.tutorial //tutorials.filter(tutorial => tutorial.id === this.props.currentTutorialId)[0]; const step = tutorial.steps[this.props.activeStep]; var msg = checkXml(step.xml, this.props.xml); this.props.tutorialCheck(msg.type, step); @@ -55,7 +55,7 @@ class SolutionCheck extends Component { } render() { - const steps = tutorials.filter(tutorial => tutorial.id === this.props.currentTutorialId)[0].steps; + const steps = this.props.tutorial.steps //tutorials.filter(tutorial => tutorial.id === this.props.currentTutorialId)[0].steps; return (
@@ -114,15 +114,17 @@ class SolutionCheck extends Component { SolutionCheck.propTypes = { tutorialCheck: PropTypes.func.isRequired, tutorialStep: PropTypes.func.isRequired, - currentTutorialId: PropTypes.number, + // currentTutorialId: PropTypes.number, activeStep: PropTypes.number.isRequired, - xml: PropTypes.string.isRequired + xml: PropTypes.string.isRequired, + tutorial: PropTypes.object.isRequired }; const mapStateToProps = state => ({ - currentTutorialId: state.tutorial.currentId, + // currentTutorialId: state.tutorial.currentId, activeStep: state.tutorial.activeStep, - xml: state.workspace.code.xml + xml: state.workspace.code.xml, + tutorial: state.tutorial.tutorial }); export default connect(mapStateToProps, { tutorialCheck, tutorialStep })(withStyles(styles, { withTheme: true })(withRouter(SolutionCheck))); diff --git a/src/components/Tutorial/StepperHorizontal.js b/src/components/Tutorial/StepperHorizontal.js index 6853550..f100ffb 100644 --- a/src/components/Tutorial/StepperHorizontal.js +++ b/src/components/Tutorial/StepperHorizontal.js @@ -50,14 +50,14 @@ const styles = (theme) => ({ class StepperHorizontal extends Component { render() { - var tutorialId = this.props.currentTutorialId; + var tutorialId = this.props.tutorial.id //this.props.currentTutorialId; var tutorialIndex = this.props.currentTutorialIndex; var status = this.props.status.filter(status => status.id === tutorialId)[0]; var tasks = status.tasks; var error = tasks.filter(task => task.type === 'error').length > 0; var success = tasks.filter(task => task.type === 'success').length / tasks.length; var tutorialStatus = success === 1 ? 'Success' : error ? 'Error' : 'Other'; - var title = tutorials.filter(tutorial => tutorial.id === tutorialId)[0].title; + var title = this.props.tutorial.title; return (
{error || success > 0 ? @@ -96,15 +96,17 @@ class StepperHorizontal extends Component { StepperHorizontal.propTypes = { status: PropTypes.array.isRequired, change: PropTypes.number.isRequired, - currentTutorialId: PropTypes.number.isRequired, - currentTutorialIndex: PropTypes.number.isRequired + // currentTutorialId: PropTypes.number.isRequired, + currentTutorialIndex: PropTypes.number.isRequired, + tutorial: PropTypes.object.isRequired }; const mapStateToProps = state => ({ change: state.tutorial.change, status: state.tutorial.status, - currentTutorialId: state.tutorial.currentId, - currentTutorialIndex: state.tutorial.currentIndex + // currentTutorialId: state.tutorial.currentId, + currentTutorialIndex: state.tutorial.currentIndex, + tutorial: state.tutorial.tutorial }); export default connect(mapStateToProps, null)(withRouter(withStyles(styles, { withTheme: true })(StepperHorizontal))); diff --git a/src/components/Tutorial/StepperVertical.js b/src/components/Tutorial/StepperVertical.js index dcaf36e..81e81a6 100644 --- a/src/components/Tutorial/StepperVertical.js +++ b/src/components/Tutorial/StepperVertical.js @@ -61,7 +61,7 @@ class StepperVertical extends Component { } componentDidUpdate(props){ - if(props.currentTutorialId !== Number(this.props.match.params.tutorialId)){ + if (props.tutorial.id !== Number(this.props.match.params.tutorialId)) { this.props.tutorialStep(0); } } @@ -69,7 +69,7 @@ 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.currentTutorialId)[0]; + var tutorialStatus = this.props.status.filter(status => status.id === this.props.tutorial.id/*currentTutorialId*/)[0]; return (
({ change: state.tutorial.change, status: state.tutorial.status, - currentTutorialId: state.tutorial.currentId, - activeStep: state.tutorial.activeStep + // currentTutorialId: state.tutorial.currentId, + activeStep: state.tutorial.activeStep, + tutorial: state.tutorial.tutorial }); export default connect(mapStateToProps, { tutorialStep })(withRouter(withStyles(styles, {withTheme: true})(StepperVertical))); diff --git a/src/components/Tutorial/Tutorial.js b/src/components/Tutorial/Tutorial.js index 09c8e27..bb9f097 100644 --- a/src/components/Tutorial/Tutorial.js +++ b/src/components/Tutorial/Tutorial.js @@ -2,7 +2,8 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { workspaceName } from '../../actions/workspaceActions'; -import { tutorialId, tutorialStep } from '../../actions/tutorialActions'; +import { clearMessages } from '../../actions/messageActions'; +import { getTutorial, resetTutorial, tutorialStep } from '../../actions/tutorialActions'; import Breadcrumbs from '../Breadcrumbs'; import StepperHorizontal from './StepperHorizontal'; @@ -13,79 +14,99 @@ import NotFound from '../NotFound'; import { detectWhitespacesAndReturnReadableResult } from '../../helpers/whitespace'; -import tutorials from '../../data/tutorials'; +// import tutorials from '../../data/tutorials'; import Card from '@material-ui/core/Card'; import Button from '@material-ui/core/Button'; +import LinearProgress from '@material-ui/core/LinearProgress'; class Tutorial extends Component { componentDidMount() { - this.props.tutorialId(Number(this.props.match.params.tutorialId)); + this.props.getTutorial(this.props.match.params.tutorialId); + // this.props.tutorialId(Number(this.props.match.params.tutorialId)); } componentDidUpdate(props, state) { - if (props.currentTutorialId !== Number(this.props.match.params.tutorialId)) { - this.props.tutorialId(Number(this.props.match.params.tutorialId)); + if (props.tutorial.id && !props.isLoading && Number(props.tutorial.id) !== Number(this.props.match.params.tutorialId)) { + this.props.getTutorial(this.props.match.params.tutorialId); + // this.props.tutorialId(Number(this.props.match.params.tutorialId)); + } + if(this.props.message.id === 'GET_TUTORIAL_FAIL'){ + alert(this.props.message.msg); + this.props.clearMessages(); } } componentWillUnmount() { - this.props.tutorialId(null); + this.props.resetTutorial(); this.props.workspaceName(null); + if(this.props.message.msg){ + this.props.clearMessages(); + } } render() { - var currentTutorialId = this.props.currentTutorialId; - var tutorial = tutorials.filter(tutorial => tutorial.id === currentTutorialId)[0]; - var steps = tutorial ? tutorial.steps : null; - var step = steps ? steps[this.props.activeStep] : null; - var name = step ? `${detectWhitespacesAndReturnReadableResult(tutorial.title)}_${detectWhitespacesAndReturnReadableResult(step.headline)}` : null; return ( - !Number.isInteger(currentTutorialId) || currentTutorialId < 1 || !tutorial ? - - : -
- +
+ {this.props.isLoading ? : + Object.keys(this.props.tutorial).length === 0 ? + : (() => { + var tutorial = this.props.tutorial; + var steps = this.props.tutorial.steps; + var step = steps[this.props.activeStep]; + var name = `${detectWhitespacesAndReturnReadableResult(tutorial.title)}_${detectWhitespacesAndReturnReadableResult(step.headline)}`; + return( +
+ - + -
- - {/* calc(Card-padding: 10px + Button-height: 35px + Button-marginTop: 15px)*/} - - {step ? - step.type === 'instruction' ? - - : // if step.type === 'assessment' - : null} +
+ + {/* calc(Card-padding: 10px + Button-height: 35px + Button-marginTop: 15px)*/} + + {step ? + step.type === 'instruction' ? + + : // if step.type === 'assessment' + : null} -
- - -
-
-
-
+
+ + +
+ +
+
+ )})() + } +
); }; } Tutorial.propTypes = { - tutorialId: PropTypes.func.isRequired, + getTutorial: PropTypes.func.isRequired, + resetTutorial: PropTypes.func.isRequired, + clearMessages: PropTypes.func.isRequired, tutorialStep: PropTypes.func.isRequired, workspaceName: PropTypes.func.isRequired, - currentTutorialId: PropTypes.number, status: PropTypes.array.isRequired, change: PropTypes.number.isRequired, - activeStep: PropTypes.number.isRequired + activeStep: PropTypes.number.isRequired, + tutorial: PropTypes.object.isRequired, + isLoading: PropTypes.bool.isRequired, + message: PropTypes.object.isRequired }; const mapStateToProps = state => ({ change: state.tutorial.change, status: state.tutorial.status, - currentTutorialId: state.tutorial.currentId, - activeStep: state.tutorial.activeStep + activeStep: state.tutorial.activeStep, + tutorial: state.tutorial.tutorial, + isLoading: state.tutorial.progress, + message: state.message }); -export default connect(mapStateToProps, { tutorialId, tutorialStep, workspaceName })(Tutorial); +export default connect(mapStateToProps, { getTutorial, resetTutorial, tutorialStep, clearMessages, workspaceName })(Tutorial); diff --git a/src/reducers/index.js b/src/reducers/index.js index 077c60c..16eae53 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -3,10 +3,12 @@ import workspaceReducer from './workspaceReducer'; import tutorialReducer from './tutorialReducer'; import tutorialBuilderReducer from './tutorialBuilderReducer'; import generalReducer from './generalReducer'; +import messageReducer from './messageReducer'; export default combineReducers({ workspace: workspaceReducer, tutorial: tutorialReducer, builder: tutorialBuilderReducer, - general: generalReducer + general: generalReducer, + message: messageReducer }); diff --git a/src/reducers/messageReducer.js b/src/reducers/messageReducer.js new file mode 100644 index 0000000..c8579d7 --- /dev/null +++ b/src/reducers/messageReducer.js @@ -0,0 +1,27 @@ +import { GET_ERRORS, CLEAR_MESSAGES, GET_SUCCESS } from '../actions/types'; + +const initialState = { + msg: {}, + status: null, + id: null +}; + +export default function(state = initialState, action){ + switch(action.type){ + case GET_ERRORS: + case GET_SUCCESS: + return { + msg: action.payload.msg, + status: action.payload.status, + id: action.payload.id + }; + case CLEAR_MESSAGES: + return { + msg: {}, + status: null, + id: null + }; + default: + return state; + } +} diff --git a/src/reducers/tutorialReducer.js b/src/reducers/tutorialReducer.js index c231f78..23398cd 100644 --- a/src/reducers/tutorialReducer.js +++ b/src/reducers/tutorialReducer.js @@ -1,4 +1,4 @@ -import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID, TUTORIAL_STEP } from '../actions/types'; +import { TUTORIAL_PROGRESS, GET_TUTORIAL, TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID, TUTORIAL_STEP } from '../actions/types'; import tutorials from '../data/tutorials'; @@ -40,14 +40,26 @@ const initialStatus = () => { const initialState = { status: initialStatus(), - currentId: null, + // currentId: null, currentIndex: null, activeStep: 0, - change: 0 + change: 0, + tutorial: {}, + progress: false }; export default function (state = initialState, action) { switch (action.type) { + case TUTORIAL_PROGRESS: + return { + ...state, + progress: !state.progress + } + case GET_TUTORIAL: + return { + ...state, + tutorial: action.payload + }; case TUTORIAL_SUCCESS: case TUTORIAL_ERROR: case TUTORIAL_XML: @@ -65,7 +77,7 @@ export default function (state = initialState, action) { case TUTORIAL_ID: return { ...state, - currentId: action.payload, + // currentId: action.payload, currentIndex: tutorials.findIndex(tutorial => tutorial.id === action.payload) } case TUTORIAL_STEP: