current tutorial id stored in redux store

This commit is contained in:
Delucse 2020-09-08 13:42:55 +02:00
parent c1d3962fb4
commit 7e0fbf1f75
9 changed files with 214 additions and 127 deletions

49
.idea/workspace.xml generated Normal file
View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="a119841b-a70a-4b0e-91b6-4da6cd81f169" name="Default Changelist" comment="">
<change beforePath="$PROJECT_DIR$/src/components/Tutorial/tutorials.json" beforeDir="false" />
</list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="a119841b-a70a-4b0e-91b6-4da6cd81f169" name="Default Changelist" comment="" />
<created>1599559503505</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1599559503505</updated>
</task>
<servers />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
</project>

View File

@ -1,4 +1,4 @@
import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE } from './types';
import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID } from './types';
export const tutorialChange = () => (dispatch) => {
dispatch({
@ -15,3 +15,19 @@ export const tutorialCheck = (id, status) => (dispatch, getState) => {
});
dispatch(tutorialChange());
};
export const storeTutorialXml = (id) => (dispatch, getState) => {
var tutorialsStatus = getState().tutorial.status;
tutorialsStatus[id] = {...tutorialsStatus[id]};
dispatch({
type: TUTORIAL_XML,
payload: tutorialsStatus
});
};
export const tutorialId = (id) => (dispatch) => {
dispatch({
type: TUTORIAL_ID,
payload: id
});
};

View File

@ -10,3 +10,5 @@ export const CLEAR_STATS = 'CLEAR_STATS';
export const TUTORIAL_SUCCESS = 'TUTORIAL_SUCCESS';
export const TUTORIAL_ERROR = 'TUTORIAL_ERROR';
export const TUTORIAL_CHANGE = 'TUTORIAL_CHANGE';
export const TUTORIAL_XML = 'TUTORIAL_XML';
export const TUTORIAL_ID = 'TUTORIAL_ID';

View File

@ -9,8 +9,6 @@ import Compile from '../Compile';
import { tutorials } from './tutorials';
import { withRouter } 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';
@ -51,56 +49,61 @@ class SolutionCheck extends Component {
check = () => {
const workspace = Blockly.getMainWorkspace();
var msg = tutorials[this.props.tutorial].test(workspace);
this.props.tutorialCheck(this.props.tutorial, msg.type);
var msg = tutorials[this.props.currentTutorialId].test(workspace);
this.props.tutorialCheck(this.props.currentTutorialId, msg.type);
this.setState({ msg, open: true });
}
render() {
return (
tutorials[this.props.tutorial].test ?
<div>
<Tooltip title='Lösung kontrollieren'>
<IconButton
className={this.props.classes.compile}
style={{width: '40px', height: '40px', position: 'absolute', top: 8, right: 8, zIndex: 21 }}
onClick={() => this.check()}
>
<FontAwesomeIcon icon={faPlay} size="xs"/>
</IconButton>
</Tooltip>
<Dialog fullWidth maxWidth={'sm'} onClose={this.toggleDialog} open={this.state.open} style={{zIndex: 9999999}}>
<DialogTitle>{this.state.msg.type === 'error' ? 'Fehler' : 'Erfolg'}</DialogTitle>
<DialogContent dividers>
{this.state.msg.text}
{this.state.msg.type === 'success' ?
<div style={{marginTop: '20px', display: 'flex'}}>
<Compile />
<Button
style={{marginLeft: '10px'}}
variant="contained"
color="primary"
onClick={() => {this.toggleDialog(); this.props.history.push(`/tutorial/${this.props.tutorial+2}`)}}
>
nächstes Tutorial
tutorials[this.props.currentTutorialId].test ?
<div>
<Tooltip title='Lösung kontrollieren'>
<IconButton
className={this.props.classes.compile}
style={{width: '40px', height: '40px', position: 'absolute', top: 8, right: 8, zIndex: 21 }}
onClick={() => this.check()}
>
<FontAwesomeIcon icon={faPlay} size="xs"/>
</IconButton>
</Tooltip>
<Dialog fullWidth maxWidth={'sm'} onClose={this.toggleDialog} open={this.state.open} style={{zIndex: 9999999}}>
<DialogTitle>{this.state.msg.type === 'error' ? 'Fehler' : 'Erfolg'}</DialogTitle>
<DialogContent dividers>
{this.state.msg.text}
{this.state.msg.type === 'success' ?
<div style={{marginTop: '20px', display: 'flex'}}>
<Compile />
<Button
style={{marginLeft: '10px'}}
variant="contained"
color="primary"
onClick={() => {this.toggleDialog(); this.props.history.push(`/tutorial/${this.props.currentTutorialId+2}`)}}
>
nächstes Tutorial
</Button>
</div>
: null}
</DialogContent>
<DialogActions>
<Button onClick={this.toggleDialog} color="primary">
Schließen
</Button>
</div>
: null}
</DialogContent>
<DialogActions>
<Button onClick={this.toggleDialog} color="primary">
Schließen
</Button>
</DialogActions>
</Dialog>
</div>
</DialogActions>
</Dialog>
</div>
: null
);
};
}
SolutionCheck.propTypes = {
tutorialCheck: PropTypes.func.isRequired
tutorialCheck: PropTypes.func.isRequired,
currentTutorialId: PropTypes.number
};
export default connect(null, { tutorialCheck })(withRouter(withStyles(styles, {withTheme: true})(SolutionCheck)));
const mapStateToProps = state => ({
currentTutorialId: state.tutorial.currentId
});
export default connect(mapStateToProps, { tutorialCheck })(withStyles(styles, {withTheme: true})(SolutionCheck));

View File

@ -49,39 +49,29 @@ const styles = (theme) => ({
class StepperHorizontal extends Component {
state={
tutorialId: Number(this.props.match.params.tutorialId),
}
componentDidUpdate(props, state){
if(state.tutorialId !== Number(this.props.match.params.tutorialId)){
this.setState({tutorialId: Number(this.props.match.params.tutorialId)})
}
}
render() {
var tutorialId = this.state.tutorialId;
var tutorialStatus = this.props.status[tutorialId-1].status === 'success' ? 'Success' :
this.props.status[tutorialId-1].status === 'error' ? 'Error' : 'Other';
var tutorialId = this.props.currentTutorialId;
var tutorialStatus = this.props.status[tutorialId].status === 'success' ? 'Success' :
this.props.status[tutorialId].status === 'error' ? 'Error' : 'Other';
return (
<div className={clsx(this.props.classes.stepper, this.props.classes['stepper'+tutorialStatus])}>
<Button
disabled={tutorialId-1 === 0}
onClick={() => {this.props.history.push(`/tutorial/${tutorialId-1}`)}}
disabled={tutorialId === 0}
onClick={() => {this.props.history.push(`/tutorial/${tutorialId}`)}}
>
{'<'}
</Button>
<Stepper activeStep={tutorialId} orientation="horizontal"
<Stepper activeStep={tutorialId+1} orientation="horizontal"
style={{padding: 0}} classes={{root: this.props.classes.color}}>
<Step expanded completed={false}>
<StepLabel icon={tutorialStatus !== 'Other' ? <div className={clsx(tutorialStatus === 'Error' ? this.props.classes.iconDivError: this.props.classes.iconDivSuccess)}><FontAwesomeIcon className={this.props.classes.icon} icon={tutorialStatus === 'Success' ? faCheck : faTimes}/></div> : ''}>
<h1 style={{margin: 0}}>{tutorials[tutorialId-1].title}</h1>
<h1 style={{margin: 0}}>{tutorials[tutorialId].title}</h1>
</StepLabel>
</Step>
</Stepper>
<Button
disabled={tutorialId+1 > tutorials.length}
onClick={() => {this.props.history.push(`/tutorial/${tutorialId+1}`)}}
disabled={tutorialId+2 > tutorials.length}
onClick={() => {this.props.history.push(`/tutorial/${tutorialId+2}`)}}
>
{'>'}
</Button>
@ -93,11 +83,13 @@ class StepperHorizontal extends Component {
StepperHorizontal.propTypes = {
status: PropTypes.array.isRequired,
change: PropTypes.number.isRequired,
currentTutorialId: PropTypes.number.isRequired
};
const mapStateToProps = state => ({
change: state.tutorial.change,
status: state.tutorial.status
status: state.tutorial.status,
currentTutorialId: state.tutorial.currentId
});
export default connect(mapStateToProps, null)(withRouter(withStyles(styles, {withTheme: true})(StepperHorizontal)));

View File

@ -24,7 +24,7 @@ const styles = (theme) => ({
},
stepIcon: {
borderStyle: `solid`,
borderWith: '2px',
// borderWidth: '2px',
borderRadius: '50%',
width: '12px',
height: '12px',
@ -72,67 +72,69 @@ const styles = (theme) => ({
},
progressBackground: {
backgroundColor: fade(theme.palette.primary.main, 0.2),
height: '100%'
height: '100%',
borderRadius: '2px'
}
});
class StepperVertical extends Component {
state={
tutorialArray: Number(this.props.match.params.tutorialId) === 1 ?
tutorials.slice(Number(this.props.match.params.tutorialId)-1, Number(this.props.match.params.tutorialId)+4)
: Number(this.props.match.params.tutorialId) === 2 ?
tutorials.slice(Number(this.props.match.params.tutorialId)-1-1, Number(this.props.match.params.tutorialId)+3)
: Number(this.props.match.params.tutorialId) === tutorials.length ?
tutorials.slice(Number(this.props.match.params.tutorialId)-4-1, Number(this.props.match.params.tutorialId)+4)
: Number(this.props.match.params.tutorialId) === tutorials.length-1 ?
tutorials.slice(Number(this.props.match.params.tutorialId)-3-1, Number(this.props.match.params.tutorialId)+3)
: tutorials.slice(Number(this.props.match.params.tutorialId)-2-1,Number(this.props.match.params.tutorialId)+2),
tutorialId: Number(this.props.match.params.tutorialId),
selectedVerticalTutorialId: Number(this.props.match.params.tutorialId)
constructor(props){
super(props);
this.state = {
tutorialArray: props.currentTutorialId === 0 ?
tutorials.slice(props.currentTutorialId, props.currentTutorialId+5)
: props.currentTutorialId === 1 ?
tutorials.slice(props.currentTutorialId-1, props.currentTutorialId+4)
: props.currentTutorialId === tutorials.length-1 ?
tutorials.slice(props.currentTutorialId-4, props.currentTutorialId+5)
: props.currentTutorialId === tutorials.length-2 ?
tutorials.slice(props.currentTutorialId-3, props.currentTutorialId+4)
: tutorials.slice(props.currentTutorialId-2, props.currentTutorialId+3),
selectedVerticalTutorialId: props.currentTutorialId
};
}
componentDidUpdate(props, state){
if(state.tutorialId !== Number(this.props.match.params.tutorialId)){
componentDidUpdate(props){
if(props.currentTutorialId !== this.props.currentTutorialId){
this.setState({
tutorialArray: Number(this.props.match.params.tutorialId) === 1 ?
tutorials.slice(Number(this.props.match.params.tutorialId)-1, Number(this.props.match.params.tutorialId)+4)
: Number(this.props.match.params.tutorialId) === 2 ?
tutorials.slice(Number(this.props.match.params.tutorialId)-1-1, Number(this.props.match.params.tutorialId)+3)
: Number(this.props.match.params.tutorialId) === tutorials.length ?
tutorials.slice(Number(this.props.match.params.tutorialId)-4-1, Number(this.props.match.params.tutorialId)+4)
: Number(this.props.match.params.tutorialId) === tutorials.length-1 ?
tutorials.slice(Number(this.props.match.params.tutorialId)-3-1, Number(this.props.match.params.tutorialId)+3)
: tutorials.slice(Number(this.props.match.params.tutorialId)-2-1,Number(this.props.match.params.tutorialId)+2),
tutorialId: Number(this.props.match.params.tutorialId),
selectedVerticalTutorialId: Number(this.props.match.params.tutorialId)
})
tutorialArray: props.currentTutorialId === 0 ?
tutorials.slice(props.currentTutorialId, props.currentTutorialId+5)
: props.currentTutorialId === 1 ?
tutorials.slice(props.currentTutorialId-1, props.currentTutorialId+4)
: props.currentTutorialId === tutorials.length-1 ?
tutorials.slice(props.currentTutorialId-4, props.currentTutorialId+5)
: props.currentTutorialId === tutorials.length-2 ?
tutorials.slice(props.currentTutorialId-3, props.currentTutorialId+4)
: tutorials.slice(props.currentTutorialId-2, props.currentTutorialId+3),
selectedVerticalTutorialId: props.currentTutorialId
});
}
}
verticalStepper = (step) => {
var newTutorialId = this.state.selectedVerticalTutorialId + step;
var tutorialArray = Number(newTutorialId) === 1 ?
tutorials.slice(newTutorialId-1, newTutorialId+4)
: newTutorialId === 2 ?
tutorials.slice(newTutorialId-1-1, newTutorialId+3)
: newTutorialId === tutorials.length ?
tutorials.slice(newTutorialId-4-1, newTutorialId+4)
: newTutorialId === tutorials.length-1 ?
tutorials.slice(newTutorialId-3-1, newTutorialId+3)
: tutorials.slice(newTutorialId-2-1, newTutorialId+2);
var tutorialArray = newTutorialId === 0 ?
tutorials.slice(newTutorialId, newTutorialId+5)
: newTutorialId === 1 ?
tutorials.slice(newTutorialId-1, newTutorialId+4)
: newTutorialId === tutorials.length-1 ?
tutorials.slice(newTutorialId-4, newTutorialId+5)
: newTutorialId === tutorials.length-2 ?
tutorials.slice(newTutorialId-3, newTutorialId+4)
: tutorials.slice(newTutorialId-2, newTutorialId+3);
this.setState({ tutorialArray: tutorialArray, selectedVerticalTutorialId: newTutorialId });
}
render() {
var tutorialId = this.state.tutorialId;
var tutorialId = this.props.currentTutorialId;
var selectedVerticalTutorialId = this.state.selectedVerticalTutorialId;
return (
isWidthUp('sm', this.props.width) ?
<div style={{marginRight: '10px'}}>
<Button
style={{minWidth: '30px', margin: 'auto', minHeight: '25px', padding: '0', writingMode: 'vertical-rl'}}
disabled={this.state.selectedVerticalTutorialId === 1}
disabled={selectedVerticalTutorialId === 0}
onClick={() => {this.verticalStepper(-1)}}
>
{'<'}
@ -141,22 +143,20 @@ class StepperVertical extends Component {
<div style={{position: 'relative'}}>
<div
className={clsx(this.props.classes.progress, this.props.classes.progressForeground)}
style={{ zIndex: 1, borderRadius: `${selectedVerticalTutorialId/tutorials.length === 1 ? '2px' : '2px 2px 0 0'}`, height: `${(selectedVerticalTutorialId/tutorials.length)*100}%`}}>
style={{ zIndex: 1, borderRadius: `${selectedVerticalTutorialId/(tutorials.length-1) === 1 ? '2px' : '2px 2px 0 0'}`, height: `${((selectedVerticalTutorialId+1)/tutorials.length)*100}%`}}>
</div>
<div
className={clsx(this.props.classes.progress, this.props.classes.progressBackground)}
style={{borderRadius: `${selectedVerticalTutorialId/tutorials.length === 1 ? '2px' : '2px 2px 0 0'}`}}>
<div className={clsx(this.props.classes.progress, this.props.classes.progressBackground)}>
</div>
</div>
<Stepper
activeStep={tutorialId}
activeStep={tutorialId+1}
orientation="vertical"
connector={<div style={{height: '10px'}}></div>}
classes={{root: this.props.classes.verticalStepper}}
>
{this.state.tutorialArray.map((tutorial, i) => {
var index = this.state.tutorialArray.indexOf(tutorials[selectedVerticalTutorialId-1]);
var verticalTutorialId = i === index ? selectedVerticalTutorialId : selectedVerticalTutorialId - index + i;
var index = this.state.tutorialArray.indexOf(tutorials[selectedVerticalTutorialId]);
var verticalTutorialId = i === index ? selectedVerticalTutorialId+1 : selectedVerticalTutorialId+1 - index + i;
var tutorialStatus = this.props.status[verticalTutorialId-1].status === 'success' ? 'Success' :
this.props.status[verticalTutorialId-1].status === 'error' ? 'Error' : 'Other';
return (
@ -166,15 +166,16 @@ class StepperVertical extends Component {
<StepLabel
StepIconComponent={'div'}
classes={{
root: tutorial === tutorials[selectedVerticalTutorialId-1] ?
tutorial === tutorials[tutorialId-1] ?
root: tutorial === tutorials[selectedVerticalTutorialId] ?
tutorial === tutorials[tutorialId] ?
clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge, this.props.classes['stepIcon'+tutorialStatus], this.props.classes['stepIconActive'+tutorialStatus])
: clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge, this.props.classes['stepIcon'+tutorialStatus])
: tutorial === tutorials[verticalTutorialId-2] || tutorial === tutorials[selectedVerticalTutorialId] ?
tutorial === tutorials[tutorialId-1] ?
: tutorial === tutorials[selectedVerticalTutorialId-1] || tutorial === tutorials[selectedVerticalTutorialId+1] ||
tutorial === tutorials[verticalTutorialId-2] ?
tutorial === tutorials[tutorialId] ?
clsx(this.props.classes.stepIcon, this.props.classes.stepIconMedium, this.props.classes['stepIcon'+tutorialStatus], this.props.classes['stepIconActive'+tutorialStatus])
: clsx(this.props.classes.stepIcon, this.props.classes.stepIconMedium, this.props.classes['stepIcon'+tutorialStatus])
: tutorial === tutorials[tutorialId-1] ?
: tutorial === tutorials[tutorialId] ?
clsx(this.props.classes.stepIcon, this.props.classes['stepIcon'+tutorialStatus], this.props.classes['stepIconActive'+tutorialStatus])
: clsx(this.props.classes.stepIcon, this.props.classes['stepIcon'+tutorialStatus])
}}
@ -188,7 +189,7 @@ class StepperVertical extends Component {
</div>
<Button
style={{minWidth: '30px', minHeight: '25px', padding: '0', writingMode: 'vertical-rl'}}
disabled={this.state.selectedVerticalTutorialId === tutorials.length}
disabled={selectedVerticalTutorialId === tutorials.length-1}
onClick={() => {this.verticalStepper(1)}}
>
{'>'}
@ -203,11 +204,13 @@ class StepperVertical extends Component {
StepperVertical.propTypes = {
status: PropTypes.array.isRequired,
change: PropTypes.number.isRequired,
currentTutorialId: PropTypes.number.isRequired
};
const mapStateToProps = state => ({
change: state.tutorial.change,
status: state.tutorial.status
status: state.tutorial.status,
currentTutorialId: state.tutorial.currentId
});
export default connect(mapStateToProps, null)(withRouter(withStyles(styles, {withTheme: true})(withWidth()(StepperVertical))));

View File

@ -1,4 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { tutorialId } from '../../actions/tutorialActions';
import Breadcrumbs from '../Breadcrumbs';
import StepperHorizontal from './StepperHorizontal';
@ -19,29 +22,32 @@ import Card from '@material-ui/core/Card';
class Tutorial extends Component {
state={
value: 'introduction',
tutorialId: Number(this.props.match.params.tutorialId)
value: 'introduction'
}
componentDidMount(){
this.props.tutorialId(Number(this.props.match.params.tutorialId)-1);
}
componentDidUpdate(props, state){
if(state.tutorialId !== Number(this.props.match.params.tutorialId)){
this.setState({ value: 'introduction', tutorialId: Number(this.props.match.params.tutorialId) });
if(props.currentTutorialId+1 !== Number(this.props.match.params.tutorialId)){
this.props.tutorialId(Number(this.props.match.params.tutorialId)-1);
this.setState({ value: 'introduction' });
}
}
onChange = (e, value) => {
console.log(value);
this.setState({ value: value });
}
render() {
var tutorialId = this.state.tutorialId;
var currentTutorialId = this.props.currentTutorialId;
return (
!Number.isInteger(tutorialId) || tutorialId < 1 || tutorialId > tutorials.length ?
!Number.isInteger(currentTutorialId) || currentTutorialId+1 < 1 || currentTutorialId+1 > tutorials.length ?
<NotFound button={{title: 'Zurück zur Tutorials-Übersicht', link: '/tutorial'}}/>
:
<div>
<Breadcrumbs content={[{link: '/', title: 'Home'},{link: '/tutorial', title: 'Tutorial'}, {link: `/tutorial/${tutorialId}`, title: tutorials[tutorialId-1].title}]}/>
<Breadcrumbs content={[{link: '/', title: 'Home'},{link: '/tutorial', title: 'Tutorial'}, {link: `/tutorial/${currentTutorialId+1}`, title: tutorials[currentTutorialId].title}]}/>
<StepperHorizontal />
@ -67,7 +73,7 @@ class Tutorial extends Component {
{this.state.value === 'assessment' ?
<Grid container spacing={2}>
<Grid item xs={12} md={6} lg={8} style={{ position: 'relative' }}>
<SolutionCheck tutorial={tutorialId-1}/>
<SolutionCheck />
<BlocklyWindow />
</Grid>
<Grid item xs={12} md={6} lg={4}>
@ -88,4 +94,13 @@ class Tutorial extends Component {
};
}
export default withWidth()(Tutorial);
Tutorial.propTypes = {
tutorialId: PropTypes.func.isRequired,
currentTutorialId: PropTypes.number.isRequired
};
const mapStateToProps = state => ({
currentTutorialId: state.tutorial.currentId
});
export default connect(mapStateToProps, { tutorialId })(withWidth()(Tutorial));

View File

@ -1,4 +1,4 @@
import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE } from '../actions/types';
import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID } from '../actions/types';
import { tutorials } from '../components/Tutorial/tutorials';
@ -6,6 +6,7 @@ const initialState = {
status: window.localStorage.getItem('tutorial') ?
JSON.parse(window.localStorage.getItem('tutorial'))
: new Array(tutorials.length).fill({}),
currentId: null,
change: 0
};
@ -13,6 +14,7 @@ export default function(state = initialState, action){
switch(action.type){
case TUTORIAL_SUCCESS:
case TUTORIAL_ERROR:
case TUTORIAL_XML:
// update locale storage - sync with redux store
window.localStorage.setItem('tutorial', JSON.stringify(action.payload));
return {
@ -24,6 +26,11 @@ export default function(state = initialState, action){
...state,
change: state.change += 1
}
case TUTORIAL_ID:
return {
...state,
currentId: action.payload
}
default:
return state;
}

View File

@ -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__()
)
);