Skip to content

Commit bada1b2

Browse files
committed
feat: add ActionEditor to visualize actions json, update tests
closes #226
1 parent b2dd03d commit bada1b2

File tree

7 files changed

+281
-1
lines changed

7 files changed

+281
-1
lines changed

src/App.js

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import LoadSpace from './components/LoadSpace';
1717
import SpaceScreen from './components/space/SpaceScreen';
1818
import DeveloperScreen from './components/developer/DeveloperScreen';
1919
import { OnlineTheme, OfflineTheme } from './themes';
20+
import ActionDashboard from './components/actionDashboard/ActionDashboard';
2021
import {
2122
SETTINGS_PATH,
2223
SPACE_PATH,
@@ -25,6 +26,7 @@ import {
2526
VISIT_PATH,
2627
LOAD_SPACE_PATH,
2728
DEVELOPER_PATH,
29+
ACTION_DASHBOARD_PATH,
2830
} from './config/paths';
2931
import {
3032
getGeolocation,
@@ -159,6 +161,11 @@ export class App extends Component {
159161
path={DEVELOPER_PATH}
160162
component={DeveloperScreen}
161163
/>
164+
<Route
165+
exact
166+
path={ACTION_DASHBOARD_PATH}
167+
component={ActionDashboard}
168+
/>
162169
</Switch>
163170
</div>
164171
</Router>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import React, { Component } from 'react';
2+
import classNames from 'classnames';
3+
import { withStyles } from '@material-ui/core';
4+
import { withTranslation } from 'react-i18next';
5+
import { withRouter } from 'react-router';
6+
import Typography from '@material-ui/core/Typography';
7+
import CssBaseline from '@material-ui/core/CssBaseline';
8+
import AppBar from '@material-ui/core/AppBar';
9+
import Toolbar from '@material-ui/core/Toolbar';
10+
import IconButton from '@material-ui/core/IconButton';
11+
import MenuIcon from '@material-ui/icons/Menu';
12+
import ChevronLeftIcon from '@material-ui/icons/ChevronLeft';
13+
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
14+
import Drawer from '@material-ui/core/Drawer';
15+
import Divider from '@material-ui/core/Divider';
16+
import PropTypes from 'prop-types';
17+
import MainMenu from '../common/MainMenu';
18+
import ActionEditor from './ActionEditor';
19+
import Styles from '../../Styles';
20+
import Banner from '../common/Banner';
21+
22+
export class ActionDashboard extends Component {
23+
state = {
24+
open: false,
25+
};
26+
27+
static propTypes = {
28+
t: PropTypes.func.isRequired,
29+
classes: PropTypes.shape({
30+
root: PropTypes.string.isRequired,
31+
appBar: PropTypes.string.isRequired,
32+
appBarShift: PropTypes.string.isRequired,
33+
menuButton: PropTypes.string.isRequired,
34+
hide: PropTypes.string.isRequired,
35+
drawer: PropTypes.string.isRequired,
36+
drawerPaper: PropTypes.string.isRequired,
37+
drawerHeader: PropTypes.string.isRequired,
38+
content: PropTypes.string.isRequired,
39+
contentShift: PropTypes.string.isRequired,
40+
developer: PropTypes.string.isRequired,
41+
screenTitle: PropTypes.string.isRequired,
42+
}).isRequired,
43+
theme: PropTypes.shape({
44+
direction: PropTypes.string.isRequired,
45+
}).isRequired,
46+
history: PropTypes.shape({
47+
replace: PropTypes.func.isRequired,
48+
}).isRequired,
49+
i18n: PropTypes.shape({
50+
changeLanguage: PropTypes.func.isRequired,
51+
}).isRequired,
52+
};
53+
54+
handleDrawerOpen = () => {
55+
this.setState({ open: true });
56+
};
57+
58+
handleDrawerClose = () => {
59+
this.setState({ open: false });
60+
};
61+
62+
render() {
63+
const { classes, theme, t } = this.props;
64+
const { open } = this.state;
65+
66+
return (
67+
<div className={classes.root}>
68+
<CssBaseline />
69+
<AppBar
70+
position="fixed"
71+
className={classNames(classes.appBar, {
72+
[classes.appBarShift]: open,
73+
})}
74+
>
75+
<Toolbar disableGutters={!open}>
76+
<IconButton
77+
color="inherit"
78+
aria-label="Open drawer"
79+
onClick={this.handleDrawerOpen}
80+
className={classNames(classes.menuButton, open && classes.hide)}
81+
>
82+
<MenuIcon />
83+
</IconButton>
84+
</Toolbar>
85+
</AppBar>
86+
<Drawer
87+
className={classes.drawer}
88+
variant="persistent"
89+
anchor="left"
90+
open={open}
91+
classes={{
92+
paper: classes.drawerPaper,
93+
}}
94+
>
95+
<div className={classes.drawerHeader}>
96+
<IconButton onClick={this.handleDrawerClose}>
97+
{theme.direction === 'ltr' ? (
98+
<ChevronLeftIcon />
99+
) : (
100+
<ChevronRightIcon />
101+
)}
102+
</IconButton>
103+
</div>
104+
<Divider />
105+
<MainMenu />
106+
</Drawer>
107+
<main
108+
className={classNames(classes.content, {
109+
[classes.contentShift]: open,
110+
})}
111+
>
112+
<div className={classes.drawerHeader} />
113+
<div className={classes.developer}>
114+
<Typography variant="h4" className={classes.screenTitle}>
115+
{t('Action Dashboard')}
116+
</Typography>
117+
<br />
118+
<Banner
119+
text={t(
120+
'Danger Zone! Proceed with caution as changes to this section might lead to data loss.'
121+
)}
122+
type="error"
123+
/>
124+
<ActionEditor />
125+
</div>
126+
</main>
127+
</div>
128+
);
129+
}
130+
}
131+
132+
const StyledComponent = withStyles(Styles, { withTheme: true })(
133+
ActionDashboard
134+
);
135+
136+
const TranslatedComponent = withTranslation()(StyledComponent);
137+
138+
export default withRouter(TranslatedComponent);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import React, { Component } from 'react';
2+
import _ from 'lodash';
3+
import { connect } from 'react-redux';
4+
import ReactJson from 'react-json-view';
5+
import PropTypes from 'prop-types';
6+
import Button from '@material-ui/core/Button';
7+
import Typography from '@material-ui/core/Typography';
8+
import { withTranslation } from 'react-i18next';
9+
import { withStyles } from '@material-ui/core';
10+
import { getDatabase, setDatabase } from '../../actions';
11+
import Loader from '../common/Loader';
12+
import Styles from '../../Styles';
13+
import SampleDatabase from '../../data/sample.json';
14+
15+
export class ActionEditor extends Component {
16+
static propTypes = {
17+
t: PropTypes.func.isRequired,
18+
classes: PropTypes.shape({
19+
button: PropTypes.string.isRequired,
20+
}).isRequired,
21+
dispatchGetDatabase: PropTypes.func.isRequired,
22+
dispatchSetDatabase: PropTypes.func.isRequired,
23+
database: PropTypes.shape({
24+
user: PropTypes.object,
25+
spaces: PropTypes.array,
26+
actions: PropTypes.array,
27+
}),
28+
};
29+
30+
static defaultProps = {
31+
database: {},
32+
};
33+
34+
componentDidMount() {
35+
const { dispatchGetDatabase } = this.props;
36+
dispatchGetDatabase();
37+
}
38+
39+
handleEdit = ({ updated_src: updatedSrc }) => {
40+
const { dispatchSetDatabase } = this.props;
41+
dispatchSetDatabase(updatedSrc);
42+
};
43+
44+
handleUseSampleDatabase = () => {
45+
const { dispatchSetDatabase } = this.props;
46+
dispatchSetDatabase(SampleDatabase);
47+
};
48+
49+
render() {
50+
const { database, t, classes } = this.props;
51+
52+
if (!database || _.isEmpty(database)) {
53+
return <Loader />;
54+
}
55+
56+
return (
57+
<div>
58+
<Typography variant="h6">{t('Manually Edit the Database')}</Typography>
59+
<ReactJson
60+
name="actions"
61+
collapsed
62+
src={database.actions}
63+
onEdit={this.handleEdit}
64+
onAdd={this.handleEdit}
65+
onDelete={this.handleEdit}
66+
/>
67+
<br />
68+
<Button
69+
variant="contained"
70+
className={classes.button}
71+
onClick={this.handleUseSampleDatabase}
72+
color="primary"
73+
>
74+
{t('Use Sample Database')}
75+
</Button>
76+
</div>
77+
);
78+
}
79+
}
80+
81+
const mapStateToProps = ({ Developer }) => ({
82+
database: Developer.get('database'),
83+
});
84+
85+
const mapDispatchToProps = {
86+
dispatchGetDatabase: getDatabase,
87+
dispatchSetDatabase: setDatabase,
88+
};
89+
90+
const StyledComponent = withStyles(Styles, { withTheme: true })(ActionEditor);
91+
const TranslatedComponent = withTranslation()(StyledComponent);
92+
const ConnectedComponent = connect(
93+
mapStateToProps,
94+
mapDispatchToProps
95+
)(TranslatedComponent);
96+
97+
export default ConnectedComponent;

src/components/common/MainMenu.js

+11
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
SPACES_NEARBY_PATH,
2323
VISIT_PATH,
2424
DEVELOPER_PATH,
25+
ACTION_DASHBOARD_PATH,
2526
} from '../../config/paths';
2627

2728
export class MainMenu extends Component {
@@ -147,6 +148,16 @@ export class MainMenu extends Component {
147148
</ListItemIcon>
148149
<ListItemText primary={t('Settings')} />
149150
</MenuItem>
151+
<MenuItem
152+
onClick={() => this.handleClick(ACTION_DASHBOARD_PATH)}
153+
button
154+
selected={path === ACTION_DASHBOARD_PATH}
155+
>
156+
<ListItemIcon>
157+
<SettingsIcon />
158+
</ListItemIcon>
159+
<ListItemText primary={t('Action Dashboard')} />
160+
</MenuItem>
150161
{this.renderDeveloperMode()}
151162
</List>
152163
);

src/components/common/MainMenu.test.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import {
1212
LOAD_SPACE_PATH,
1313
SETTINGS_PATH,
1414
DEVELOPER_PATH,
15+
ACTION_DASHBOARD_PATH,
1516
} from '../../config/paths';
1617

1718
// offline: 2, online: 5
1819
const MENUITEM_OFFLINE_NUMBER = 2;
19-
const MENUITEM_OFFLINE_ONLINE_COUNT = 7;
20+
const MENUITEM_OFFLINE_ONLINE_COUNT = 8;
2021

2122
const createMainMenuProps = (developerMode, path) => {
2223
return {
@@ -105,6 +106,7 @@ describe('<MainMenu />', () => {
105106
[true, LOAD_SPACE_PATH, 'Load'],
106107
[true, SETTINGS_PATH, 'Settings'],
107108
[true, DEVELOPER_PATH, 'Developer'],
109+
[true, ACTION_DASHBOARD_PATH, 'Action Dashboard'],
108110
])('<MainMenu /> selects one MenuItem', (developerMode, path, text) => {
109111
it(`select path=${path} (developerMode = ${developerMode})`, () => {
110112
const props = createMainMenuProps(developerMode, path);

src/components/common/__snapshots__/MainMenu.test.js.snap

+24
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,18 @@ exports[`<MainMenu /> <MainMenu /> with developerMode = false renders correctly
122122
primary="Settings"
123123
/>
124124
</WithStyles(ForwardRef(MenuItem))>
125+
<WithStyles(ForwardRef(MenuItem))
126+
button={true}
127+
onClick={[Function]}
128+
selected={false}
129+
>
130+
<WithStyles(ForwardRef(ListItemIcon))>
131+
<SettingsIcon />
132+
</WithStyles(ForwardRef(ListItemIcon))>
133+
<WithStyles(ForwardRef(ListItemText))
134+
primary="Action Dashboard"
135+
/>
136+
</WithStyles(ForwardRef(MenuItem))>
125137
</WithStyles(ForwardRef(List))>
126138
`;
127139

@@ -247,6 +259,18 @@ exports[`<MainMenu /> <MainMenu /> with developerMode = true renders correctly 1
247259
primary="Settings"
248260
/>
249261
</WithStyles(ForwardRef(MenuItem))>
262+
<WithStyles(ForwardRef(MenuItem))
263+
button={true}
264+
onClick={[Function]}
265+
selected={false}
266+
>
267+
<WithStyles(ForwardRef(ListItemIcon))>
268+
<SettingsIcon />
269+
</WithStyles(ForwardRef(ListItemIcon))>
270+
<WithStyles(ForwardRef(ListItemText))
271+
primary="Action Dashboard"
272+
/>
273+
</WithStyles(ForwardRef(MenuItem))>
250274
<WithStyles(ForwardRef(MenuItem))
251275
button={true}
252276
onClick={[Function]}

src/config/paths.js

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export const LOAD_SPACE_PATH = '/load-space';
55
export const SETTINGS_PATH = '/settings';
66
export const SPACE_PATH = '/space/:id';
77
export const DEVELOPER_PATH = '/developer';
8+
export const ACTION_DASHBOARD_PATH = '/action-dashboard';

0 commit comments

Comments
 (0)