diff --git a/package.json b/package.json
index 5d34cee..58fa266 100644
--- a/package.json
+++ b/package.json
@@ -72,6 +72,7 @@
"react": "^15.2.1",
"react-addons-test-utils": "^15.2.1",
"react-dom": "^15.2.1",
+ "react-router": "^2.6.1",
"react-transform-catch-errors": "^1.0.0",
"react-transform-hmr": "^1.0.1",
"redbox-react": "^1.2.0",
@@ -92,10 +93,10 @@
"material-ui": "0.15.3",
"react-icons": "^2.2.1",
"react-redux": "^4.0.6",
+ "react-router-redux": "^4.0.5",
"react-switcher": "^1.0.2",
"react-tap-event-plugin": "^1.0.0",
"redux": "^3.0.5",
- "redux-devtools": "^3.2.0",
"redux-devtools-chart-monitor": "^1.4.1",
"redux-devtools-inspector": "^0.7.1",
"redux-devtools-log-monitor": "^1.0.11",
diff --git a/src/app/actions/index.js b/src/app/actions/index.js
new file mode 100644
index 0000000..3880563
--- /dev/null
+++ b/src/app/actions/index.js
@@ -0,0 +1,55 @@
+import {
+ LIFTED_ACTION, MONITOR_ACTION, SELECT_INSTANCE, SELECT_MONITOR,
+ TOGGLE_SYNC, TOGGLE_SLIDER, TOGGLE_DISPATCHER, GET_REPORT_REQUEST,
+ SHOW_NOTIFICATION, CLEAR_NOTIFICATION
+} from '../constants/actionTypes';
+import { RECONNECT } from '../constants/socketActionTypes';
+
+export function liftedDispatch(action) {
+ if (action.type[0] === '@') return { type: MONITOR_ACTION, action };
+ return { type: LIFTED_ACTION, message: 'DISPATCH', action };
+}
+
+export function selectInstance(event, index, selected) {
+ return { type: SELECT_INSTANCE, selected };
+}
+
+export function selectMonitor(event, index, value) {
+ return { type: SELECT_MONITOR, monitor: value };
+}
+
+export function importState(state) {
+ return { type: LIFTED_ACTION, message: 'IMPORT', state };
+}
+
+export function dispatchRemotely(action) {
+ return { type: LIFTED_ACTION, message: 'ACTION', action };
+}
+
+export function toggleSync() {
+ return { type: TOGGLE_SYNC };
+}
+
+export function toggleSlider() {
+ return { type: TOGGLE_SLIDER };
+}
+
+export function toggleDispatcher() {
+ return { type: TOGGLE_DISPATCHER };
+}
+
+export function saveSocketSettings(isCustom, options) {
+ return { type: RECONNECT, isCustom, options };
+}
+
+export function showNotification(message) {
+ return { type: SHOW_NOTIFICATION, notification: { type: 'ERROR', message } };
+}
+
+export function clearNotification() {
+ return { type: CLEAR_NOTIFICATION };
+}
+
+export function getReport(report) {
+ return { type: GET_REPORT_REQUEST, report };
+}
diff --git a/src/app/components/ButtonBar.js b/src/app/components/ButtonBar.js
index 7b43e84..3537e82 100644
--- a/src/app/components/ButtonBar.js
+++ b/src/app/components/ButtonBar.js
@@ -11,23 +11,15 @@ import Settings from './Settings';
export default class ButtonBar extends Component {
static propTypes = {
- openModal: PropTypes.func.isRequired,
- toggleDispatcher: PropTypes.func.isRequired,
- toggleSlider: PropTypes.func.isRequired,
+ liftedState: PropTypes.object.isRequired,
dispatcherIsOpen: PropTypes.bool,
sliderIsOpen: PropTypes.bool,
- closeModal: PropTypes.func.isRequired,
- saveSettings: PropTypes.func.isRequired,
- importState: PropTypes.func.isRequired,
- exportState: PropTypes.func.isRequired,
- socketOptions: PropTypes.object,
noSettings: PropTypes.bool
};
constructor() {
super();
this.state = { settingsOpened: false };
-
this.openSettings = this.openSettings.bind(this);
this.closeSettings = this.closeSettings.bind(this);
}
@@ -53,23 +45,16 @@ export default class ButtonBar extends Component {
render() {
return (
-
-
-
-
+
+
+
+
{!this.props.noSettings &&
}
{!this.props.noSettings &&
-
+
}
);
diff --git a/src/app/components/Instances.js b/src/app/components/Instances.js
index 47e5ad9..5566d5b 100644
--- a/src/app/components/Instances.js
+++ b/src/app/components/Instances.js
@@ -1,10 +1,13 @@
import React, { Component, PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import shallowCompare from 'react/lib/shallowCompare';
+import { selectInstance } from '../actions';
import styles from '../styles';
-export default class Instances extends Component {
+class Instances extends Component {
static propTypes = {
selected: PropTypes.string,
instances: PropTypes.object.isRequired,
@@ -18,7 +21,7 @@ export default class Instances extends Component {
render() {
this.select = [['Autoselect instances', null]];
Object.keys(this.props.instances).forEach(key => {
- this.select.push([this.props.instances[key], key]);
+ this.select.push([this.props.instances[key].name, key]);
});
return (
@@ -38,3 +41,17 @@ export default class Instances extends Component {
);
}
}
+
+function mapStateToProps(state) {
+ return {
+ instances: state.instances.options
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ onSelect: bindActionCreators(selectInstance, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Instances);
diff --git a/src/app/components/MonitorSelector.js b/src/app/components/MonitorSelector.js
index 089f440..eb2887e 100644
--- a/src/app/components/MonitorSelector.js
+++ b/src/app/components/MonitorSelector.js
@@ -1,15 +1,22 @@
import React, { Component, PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
-import { sideMonitors } from '../containers/DevTools';
+import { monitors } from '../containers/getMonitor';
+import { selectMonitor } from '../actions';
import styles from '../styles';
-export default class MonitorSelector extends Component {
+class MonitorSelector extends Component {
static propTypes = {
selected: PropTypes.string,
- onSelect: PropTypes.func.isRequired
+ selectMonitor: PropTypes.func.isRequired
};
+ items = monitors.map((item, i) =>
+
+ );
+
shouldComponentUpdate(nextProps) {
return nextProps.selected !== this.props.selected;
}
@@ -20,13 +27,19 @@ export default class MonitorSelector extends Component {
style={styles.select}
labelStyle={styles.selectLabel}
iconStyle={styles.selectIcon}
- onChange={this.props.onSelect}
+ onChange={this.props.selectMonitor}
value={this.props.selected || 'InspectorMonitor'}
>
- {sideMonitors.map((item, i) =>
-
- )}
+ {this.items}
);
}
}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ selectMonitor: bindActionCreators(selectMonitor, dispatch)
+ };
+}
+
+export default connect(null, mapDispatchToProps)(MonitorSelector);
diff --git a/src/app/components/Notification.js b/src/app/components/Notification.js
new file mode 100644
index 0000000..214f289
--- /dev/null
+++ b/src/app/components/Notification.js
@@ -0,0 +1,59 @@
+import React, { Component, PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import * as themes from 'redux-devtools-themes';
+import { clearNotification } from '../actions';
+
+class Notification extends Component {
+ static propTypes = {
+ notification: PropTypes.shape({
+ message: PropTypes.string,
+ type: PropTypes.string
+ }),
+ clearNotification: PropTypes.func.isRequired
+ };
+
+ shouldComponentUpdate(nextProps) {
+ return nextProps.notification !== this.props.notification;
+ }
+
+ render() {
+ if (!this.props.notification) return null;
+ const theme = themes.nicinabox;
+ const buttonStyle = {
+ color: theme.base06, backgroundColor: theme.base00,
+ margin: '0', background: '#DC2424'
+ };
+ const containerStyle = {
+ color: theme.base06, background: '#FC2424',
+ padding: '5px 10px', minHeight: '20px', display: 'flex'
+ };
+ return (
+
+
+
{this.props.notification.message}
+
+
+
+
+
+ );
+ }
+}
+
+function mapStateToProps(state) {
+ return {
+ notification: state.notification
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ clearNotification: bindActionCreators(clearNotification, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Notification);
diff --git a/src/app/components/Settings.js b/src/app/components/Settings.js
index 2609766..408340b 100644
--- a/src/app/components/Settings.js
+++ b/src/app/components/Settings.js
@@ -1,39 +1,41 @@
import React, { Component, PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import Checkbox from 'material-ui/Checkbox';
import TextField from 'material-ui/TextField';
+import { saveSocketSettings } from '../actions';
import styles from '../styles';
-export default class Settings extends Component {
+class Settings extends Component {
static propTypes = {
isOpen: PropTypes.bool,
close: PropTypes.func.isRequired,
saveSettings: PropTypes.func.isRequired,
- socketOptions: PropTypes.object
+ socketOptions: PropTypes.object.isRequired,
+ isCustom: PropTypes.bool
};
constructor(props) {
super(props);
- let isLocal;
+ const isCustom = props.isCustom;
this.options = {};
- if (props.socketOptions) {
- isLocal = true;
+ if (isCustom) {
this.options.hostname = props.socketOptions.hostname;
this.options.port = props.socketOptions.port;
this.options.secure = props.socketOptions.secure;
} else {
- isLocal = false;
this.options.hostname = 'localhost';
this.options.port = '8000';
this.options.secure = false;
}
- this.state = { isLocal };
+ this.state = { isCustom };
}
handleLocalChecked = (e, checked) => {
- this.setState({ isLocal: checked });
+ this.setState({ isCustom: checked });
};
handleInputChange = (e, value) => {
@@ -45,7 +47,7 @@ export default class Settings extends Component {
};
save = () => {
- this.props.saveSettings(this.state.isLocal, this.options);
+ this.props.saveSettings(this.state.isCustom, this.options);
this.props.close();
};
@@ -77,10 +79,10 @@ export default class Settings extends Component {
>
- {this.state.isLocal &&
+ {this.state.isCustom &&
{
- selected = saveToStorage('test-templates-sel', value);
- this.setState({ selected });
+ this.props.dispatch({ type: TEST_SELECT, selected: value });
};
- getDefaultTemplates(isRedux = this.props.isRedux) {
- if (isRedux) return [mochaTemplate, tapeTemplate, avaTemplate];
+ getDefaultTemplates() {
+ if (this.props.options.lib === 'redux') {
+ return [mochaTemplate, tapeTemplate, avaTemplate];
+ }
return [mochaVTemplate, tapeVTemplate, avaVTemplate];
}
@@ -68,33 +43,25 @@ export default class TestGen extends Component {
this.setState({ dialogStatus: 2 });
};
- handleSave = (template) => {
- testTemplates = [...this.state.testTemplates];
- selected = this.state.selected;
+ dispatch = (action) => {
+ let templates;
+ if (!this.props.templates) templates = this.getDefaultTemplates();
+ this.props.dispatch({ ...action, templates });
+ };
+ handleSave = (template) => {
if (this.state.dialogStatus === 1) {
- testTemplates[this.state.selected] = template;
+ this.dispatch({ type: TEST_EDIT, template });
} else {
- testTemplates.push(template);
- selected = testTemplates.length - 1;
- saveToStorage('test-templates-sel', selected);
+ this.dispatch({ type: TEST_ADD, template });
}
-
- saveToStorage('test-templates', testTemplates);
- this.setState({ testTemplates, selected, dialogStatus: 0 });
- isDefaultTemplate = false;
+ this.handleCloseDialog();
};
handleRemove = () => {
// Todo: add snackbar with undo
- selected = 0;
- testTemplates = [...this.state.testTemplates];
- testTemplates.splice(this.state.selected, 1);
- if (testTemplates.length === 0) testTemplates = this.getDefaultTemplates();
- saveToStorage('test-templates-sel', selected);
- saveToStorage('test-templates', testTemplates);
- this.setState({ testTemplates, selected, dialogStatus: 0 });
- isDefaultTemplate = false;
+ this.dispatch({ type: TEST_REMOVE });
+ this.handleCloseDialog();
};
handleCloseDialog = () => {
@@ -102,15 +69,18 @@ export default class TestGen extends Component {
};
render() {
- const { dialogStatus, selected, testTemplates } = this.state; // eslint-disable-line
- const template = testTemplates[selected];
+ const { dialogStatus } = this.state;
+ const { selected } = this.props;
+ const templates = this.props.templates || this.getDefaultTemplates();
+ const template = templates[selected];
const { assertion, dispatcher, wrap } = template;
return (
- {testTemplates.map((item, i) =>
+ {templates.map((item, i) =>
)}
@@ -142,14 +112,19 @@ export default class TestGen extends Component {
}
TestGen.propTypes = {
- isRedux: PropTypes.bool,
- testTemplates: PropTypes.oneOfType([
- PropTypes.array,
- PropTypes.string
- ]),
- selectedTemplate: PropTypes.oneOfType([
- PropTypes.number,
- PropTypes.string
- ]),
- useCodemirror: PropTypes.bool
+ templates: PropTypes.array,
+ selected: PropTypes.number,
+ options: PropTypes.object.isRequired,
+ dispatch: PropTypes.func.isRequired
};
+
+function mapStateToProps(state) {
+ const instances = state.instances;
+ return {
+ templates: state.test.templates,
+ selected: state.test.selected,
+ options: instances.options[getActiveInstance(instances)]
+ };
+}
+
+export default connect(mapStateToProps)(TestGen);
diff --git a/src/app/components/buttons/DispatcherButton.js b/src/app/components/buttons/DispatcherButton.js
index 08ad928..252c9e9 100644
--- a/src/app/components/buttons/DispatcherButton.js
+++ b/src/app/components/buttons/DispatcherButton.js
@@ -1,12 +1,15 @@
import React, { Component, PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
import DispatchIcon from 'react-icons/lib/md/keyboard';
import DispatchHideIcon from 'react-icons/lib/md/keyboard-hide';
import Button from '../Button';
+import { toggleDispatcher } from '../../actions';
-export default class DispatcherButton extends Component {
+class DispatcherButton extends Component {
static propTypes = {
dispatcherIsOpen: PropTypes.bool,
- onClick: PropTypes.func.isRequired
+ toggleDispatcher: PropTypes.func.isRequired
};
shouldComponentUpdate(nextProps) {
@@ -17,8 +20,16 @@ export default class DispatcherButton extends Component {
return (
);
}
}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ toggleDispatcher: bindActionCreators(toggleDispatcher, dispatch)
+ };
+}
+
+export default connect(null, mapDispatchToProps)(DispatcherButton);
diff --git a/src/app/components/buttons/ExportButton.js b/src/app/components/buttons/ExportButton.js
index c6737c0..1d949d7 100644
--- a/src/app/components/buttons/ExportButton.js
+++ b/src/app/components/buttons/ExportButton.js
@@ -5,7 +5,7 @@ import Button from '../Button';
export default class ExportButton extends Component {
static propTypes = {
- exportState: PropTypes.func.isRequired
+ liftedState: PropTypes.object.isRequired
};
constructor() {
@@ -19,9 +19,7 @@ export default class ExportButton extends Component {
}
handleExport() {
- let state = this.props.exportState();
- if (!state) return;
- state = encodeURIComponent(stringify(state));
+ const state = encodeURIComponent(stringify(this.props.liftedState));
this.setState({ href: 'data:text/json;charset=utf-8,' + state });
}
diff --git a/src/app/components/buttons/ImportButton.js b/src/app/components/buttons/ImportButton.js
index ca1c902..8d1843f 100644
--- a/src/app/components/buttons/ImportButton.js
+++ b/src/app/components/buttons/ImportButton.js
@@ -1,11 +1,15 @@
import React, { Component, PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
import { parse } from 'jsan';
import UploadIcon from 'react-icons/lib/md/file-upload';
import Button from '../Button';
+import { importState, showNotification } from '../../actions';
-export default class ImportButton extends Component {
+class ImportButton extends Component {
static propTypes = {
- importState: PropTypes.func.isRequired
+ importState: PropTypes.func.isRequired,
+ showNotification: PropTypes.func.isRequired
};
constructor() {
@@ -36,10 +40,7 @@ export default class ImportButton extends Component {
parse(state); // Check if it is in JSON format
this.props.importState(state);
} catch (error) {
- // FIXME: add error notification
- /* eslint-disable no-alert */
- alert('Invalid file');
- /* eslint-enable */
+ this.props.showNotification('Invalid file');
}
};
reader.readAsText(file);
@@ -58,3 +59,12 @@ export default class ImportButton extends Component {
);
}
}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ importState: bindActionCreators(importState, dispatch),
+ showNotification: bindActionCreators(showNotification, dispatch)
+ };
+}
+
+export default connect(null, mapDispatchToProps)(ImportButton);
diff --git a/src/app/components/buttons/SliderButton.js b/src/app/components/buttons/SliderButton.js
index 3586d4b..60b7f77 100644
--- a/src/app/components/buttons/SliderButton.js
+++ b/src/app/components/buttons/SliderButton.js
@@ -1,12 +1,15 @@
import React, { Component, PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
import TimerIcon from 'react-icons/lib/md/timer';
import TimerOffIcon from 'react-icons/lib/md/timer-off';
import Button from '../Button';
+import { toggleSlider } from '../../actions';
-export default class DispatcherButton extends Component {
+class SliderButton extends Component {
static propTypes = {
isOpen: PropTypes.bool,
- onClick: PropTypes.func.isRequired
+ toggleSlider: PropTypes.func.isRequired
};
shouldComponentUpdate(nextProps) {
@@ -17,8 +20,16 @@ export default class DispatcherButton extends Component {
return (
);
}
}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ toggleSlider: bindActionCreators(toggleSlider, dispatch)
+ };
+}
+
+export default connect(null, mapDispatchToProps)(SliderButton);
diff --git a/src/app/components/reports/Table.js b/src/app/components/reports/Table.js
new file mode 100644
index 0000000..c1b7f10
--- /dev/null
+++ b/src/app/components/reports/Table.js
@@ -0,0 +1,54 @@
+import React, { Component, PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import {
+ Table, TableBody, TableRow, TableRowColumn
+} from 'material-ui/Table';
+import { getReport } from '../../actions';
+
+const style = {
+ error: { color: '#EF5350' },
+ date: { textAlign: 'right', maxWidth: '150px' }
+};
+
+class Reports extends Component {
+ onSelect = r => {
+ const idx = r[0];
+ if (typeof idx === 'undefined') return;
+ this.props.getReport(this.props.reports[idx]);
+ };
+
+ render() {
+ return (
+
+
+ {this.props.reports.map((data) => (
+
+ {data.title}
+ {data.added}
+
+ ))}
+
+
+ );
+ }
+}
+
+Reports.propTypes = {
+ reports: PropTypes.array.isRequired,
+ getReport: PropTypes.func.isRequired
+};
+
+function mapStateToProps(state) {
+ return {
+ reports: state.reports.data
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ getReport: bindActionCreators(getReport, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Reports);
diff --git a/src/app/constants/actionTypes.js b/src/app/constants/actionTypes.js
new file mode 100644
index 0000000..91d41ea
--- /dev/null
+++ b/src/app/constants/actionTypes.js
@@ -0,0 +1,23 @@
+export const UPDATE_STATE = 'devTools/UPDATE_STATE';
+export const SET_STATE = 'devTools/SET_STATE';
+export const SELECT_INSTANCE = 'devTools/SELECT_INSTANCE';
+export const REMOVE_INSTANCE = 'devTools/REMOVE_INSTANCE';
+export const LIFTED_ACTION = 'devTools/LIFTED_ACTION';
+export const MONITOR_ACTION = 'devTools/MONITOR_ACTION';
+export const TOGGLE_SYNC = 'devTools/TOGGLE_SYNC';
+export const SELECT_MONITOR = 'devTools/SELECT_MONITOR';
+export const TOGGLE_SLIDER = 'devTools/TOGGLE_SLIDER';
+export const TOGGLE_DISPATCHER = 'devTools/TOGGLE_DISPATCHER';
+export const SHOW_NOTIFICATION = 'devTools/SHOW_NOTIFICATION';
+export const CLEAR_NOTIFICATION = 'devTools/CLEAR_NOTIFICATION';
+
+export const UPDATE_REPORTS = 'reports/UPDATE';
+export const GET_REPORT_REQUEST = 'reports/GET_REPORT_REQUEST';
+export const GET_REPORT_ERROR = 'reports/GET_REPORT_ERROR';
+export const GET_REPORT_SUCCESS = 'reports/GET_REPORT_SUCCESS';
+export const ERROR = 'ERROR';
+
+export const TEST_ADD = 'test/ADD';
+export const TEST_EDIT = 'test/EDIT';
+export const TEST_REMOVE = 'test/REMOVE';
+export const TEST_SELECT = 'test/SELECT';
diff --git a/src/app/constants/socketActionTypes.js b/src/app/constants/socketActionTypes.js
new file mode 100644
index 0000000..1df2861
--- /dev/null
+++ b/src/app/constants/socketActionTypes.js
@@ -0,0 +1,19 @@
+import socketCluster from 'socketcluster-client';
+
+export const {
+ CLOSED, CONNECTING, OPEN, AUTHENTICATED, PENDING, UNAUTHENTICATED
+ } = socketCluster.SCSocket;
+export const CONNECT_REQUEST = 'socket/CONNECT_REQUEST';
+export const CONNECT_SUCCESS = 'socket/CONNECT_SUCCESS';
+export const CONNECT_ERROR = 'socket/CONNECT_ERROR';
+export const RECONNECT = 'socket/RECONNECT';
+export const AUTH_REQUEST = 'socket/AUTH_REQUEST';
+export const AUTH_SUCCESS = 'socket/AUTH_SUCCESS';
+export const AUTH_ERROR = 'socket/AUTH_ERROR';
+export const DISCONNECTED = 'socket/DISCONNECTED';
+export const DEAUTHENTICATE = 'socket/DEAUTHENTICATE';
+export const SUBSCRIBE_REQUEST = 'socket/SUBSCRIBE_REQUEST';
+export const SUBSCRIBE_SUCCESS = 'socket/SUBSCRIBE_SUCCESS';
+export const SUBSCRIBE_ERROR = 'socket/SUBSCRIBE_ERROR';
+export const UNSUBSCRIBE = 'socket/UNSUBSCRIBE';
+export const EMIT = 'socket/EMIT';
diff --git a/src/app/constants/socketOptions.js b/src/app/constants/socketOptions.js
index f0636f6..688e727 100644
--- a/src/app/constants/socketOptions.js
+++ b/src/app/constants/socketOptions.js
@@ -2,7 +2,10 @@ const socketOptions = {
hostname: 'remotedev.io',
port: 80,
autoReconnect: true,
- secure: false
+ secure: false,
+ autoReconnectOptions: {
+ randomness: 30000
+ }
};
export default socketOptions;
diff --git a/src/app/containers/App.js b/src/app/containers/App.js
index e2a0259..ae3ab33 100644
--- a/src/app/containers/App.js
+++ b/src/app/containers/App.js
@@ -1,194 +1,87 @@
import React, { Component, PropTypes } from 'react';
-import {
- saveObjToStorage, getSettings, getFromStorage, saveToStorage
-} from '../utils/localStorage';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import { liftedDispatch } from '../actions';
+import { getActiveInstance } from '../reducers/instances';
import styles from '../styles';
import DevTools from '../containers/DevTools';
import Dispatcher from './monitors/Dispatcher';
-import {
- createRemoteStore, updateStoreInstance, enableSync,
- startMonitoring, importState, exportState
-} from '../store/createRemoteStore';
-import { addInstance, deleteInstance } from '../services/messaging';
import ButtonBar from '../components/ButtonBar';
+import Notification from '../components/Notification';
import Instances from '../components/Instances';
import MonitorSelector from '../components/MonitorSelector';
import SyncToggle from '../components/SyncToggle';
import TestGenerator from '../components/TestGenerator';
-export default class App extends Component {
+class App extends Component {
static propTypes = {
- selectMonitor: PropTypes.string,
- testTemplates: PropTypes.array,
- useCodemirror: PropTypes.bool,
- selectedTemplate: PropTypes.number,
- socketOptions: PropTypes.shape({
- hostname: PropTypes.string,
- port: PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]),
- autoReconnect: PropTypes.bool,
- secure: PropTypes.bool
- }),
+ liftedDispatch: PropTypes.func.isRequired,
+ selected: PropTypes.string,
+ liftedState: PropTypes.object.isRequired,
+ options: PropTypes.object.isRequired,
+ monitor: PropTypes.string,
+ dispatcherIsOpen: PropTypes.bool,
+ sliderIsOpen: PropTypes.bool,
+ shouldSync: PropTypes.bool,
noSettings: PropTypes.bool
};
- constructor() {
- super();
- this.openModal = this.openModal.bind(this);
- this.closeModal = this.closeModal.bind(this);
- this.toggleDispatcher = this.toggleDispatcher.bind(this);
- this.toggleSlider = this.toggleSlider.bind(this);
- this.saveSettings = this.saveSettings.bind(this);
- this.clearError = this.clearError.bind(this);
- this.handleError = this.handleError.bind(this);
- }
-
- componentWillMount() {
- this.state = {
- monitor: getFromStorage('select-monitor') || this.props.selectMonitor || 'default',
- modalIsOpen: false,
- dispatcherIsOpen: false,
- sliderIsOpen: true,
- instances: {},
- instance: null,
- error: null,
- shouldSync: false
- };
- this.socketOptions = getSettings() || this.props.socketOptions;
- this.store = this.createStore();
- this.testComponent = (props) => (
-
- );
- }
-
- handleInstancesChanged = ({ id, instanceId }, name, toRemove) => {
- const instances = this.state.instances;
- if (toRemove) {
- deleteInstance(id, (instance) => {
- delete instances[instance];
- this.store.liftedStore.deleteInstance(instance);
- if (this.state.instance === instance) {
- updateStoreInstance(null);
- this.setState({ instance: null, shouldSync: false });
- }
- });
- } else {
- addInstance(id, instanceId);
- instances[instanceId] = name || instanceId;
- startMonitoring(instanceId);
- }
- this.setState({ instances });
- };
-
- handleSelectInstance = (event, index, instance) => {
- updateStoreInstance(instance);
- this.setState({ instance, shouldSync: false });
- };
-
- handleSelectMonitor = (event, index, value) => {
- this.setState({ monitor: saveToStorage('select-monitor', value) });
- };
-
- handleSyncToggle = () => {
- const shouldSync = !this.state.shouldSync;
- enableSync(shouldSync);
- this.setState({ shouldSync });
- };
-
- createStore() {
- return createRemoteStore(
- this.socketOptions,
- this.handleInstancesChanged,
- this.handleError,
- this.state.instance
- );
- }
-
- saveSettings(isLocal, options) {
- this.socketOptions = saveObjToStorage(
- !isLocal, ['hostname', 'port', 'secure'], options
- ) || undefined;
- this.store = this.createStore();
- this.closeModal();
- }
-
- toggleDispatcher() {
- this.setState({ dispatcherIsOpen: !this.state.dispatcherIsOpen });
- }
-
- handleError(error) {
- this.setState({ error });
- }
-
- clearError() {
- this.setState({ error: null });
- }
-
- toggleSlider() {
- this.setState({ sliderIsOpen: !this.state.sliderIsOpen });
- }
-
- openModal(content) {
- this.modalContent = content;
- this.setState({ modal: this.modal, modalIsOpen: true });
- }
- closeModal() {
- this.modalContent = null;
- this.setState({ modalIsOpen: false });
- }
-
render() {
- const { monitor } = this.state;
- const key = (this.socketOptions ? this.socketOptions.hostname : '') + this.state.instance;
+ const { monitor, dispatcherIsOpen, sliderIsOpen, options, liftedState } = this.props;
return (
-
-
+
+
- {this.state.sliderIsOpen &&
-
-
}
- {this.state.dispatcherIsOpen && this.store.liftedStore.getInstance() &&
-
+ {sliderIsOpen &&
+
+
}
+ {dispatcherIsOpen && options.connectionId &&
+
}
);
}
}
+
+function mapStateToProps(state) {
+ const instances = state.instances;
+ const id = getActiveInstance(instances);
+ return {
+ selected: instances.selected,
+ liftedState: instances.states[id],
+ options: instances.options[id],
+ monitor: state.monitor.selected,
+ dispatcherIsOpen: state.monitor.dispatcherIsOpen,
+ sliderIsOpen: state.monitor.sliderIsOpen,
+ shouldSync: state.instances.sync
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return { liftedDispatch: bindActionCreators(liftedDispatch, dispatch) };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(App);
diff --git a/src/app/containers/DevTools.js b/src/app/containers/DevTools.js
index 6347585..1adc20d 100644
--- a/src/app/containers/DevTools.js
+++ b/src/app/containers/DevTools.js
@@ -1,57 +1,43 @@
import React, { Component, PropTypes, createElement } from 'react';
-import { createDevTools } from 'redux-devtools';
-import LogMonitor from 'redux-devtools-log-monitor';
-import SliderMonitor from 'redux-slider-monitor';
-import InspectorMonitor from 'redux-devtools-inspector';
-import ChartMonitor from 'redux-devtools-chart-monitor';
+import getMonitor from './getMonitor';
-export const sideMonitors = [
- { key: 'LogMonitor', title: 'Log monitor' },
- { key: 'InspectorMonitor', title: 'Inspector' },
- { key: 'ChartMonitor', title: 'Chart' }
-];
-
-function getMonitor(type, props) {
- switch (type) {
- case 'LogMonitor':
- return createElement(LogMonitor, { preserveScrollTop: false });
- case 'SliderMonitor':
- return createElement(SliderMonitor);
- case 'DispatchMonitor':
- return createElement('div'); // deprecated
- case 'ChartMonitor':
- return createElement(ChartMonitor, {
- defaultIsVisible: true, invertTheme: true,
- tooltipOptions: {
- style: {
- 'background-color': '#ffffff',
- 'color': '#000000',
- 'opacity': '0.9',
- 'border-radius': '5px',
- 'padding': '5px'
- }
- }
- });
- default:
- return createElement(InspectorMonitor, {
- shouldPersistState: false, isLightTheme: false, theme: 'nicinabox',
- tabs: defaultTabs => [...defaultTabs, { name: 'Test', component: props.testComponent }]
- });
+export default class DevTools extends Component {
+ constructor(props) {
+ super(props);
+ this.monitorElement = getMonitor(props);
}
-}
-export default class extends Component {
- static propTypes = {
- monitor: PropTypes.string
- };
+ componentWillReceiveProps(nextProps) {
+ if (
+ nextProps.monitor !== this.props.monitor ||
+ nextProps.testComponent !== this.props.testComponent
+ ) {
+ this.monitorElement = getMonitor(nextProps);
+ }
+ }
shouldComponentUpdate(nextProps) {
- return nextProps.monitor !== this.props.monitor;
+ return (
+ nextProps.monitor !== this.props.monitor ||
+ nextProps.liftedState !== this.props.liftedState ||
+ nextProps.testComponent !== this.props.testComponent
+ );
}
render() {
- const { monitor, ...rest } = this.props;
- const DevTools = createDevTools(getMonitor(monitor, rest));
- return ;
+ const { liftedState, dispatch } = this.props;
+ const monitorProps = this.monitorElement.props;
+ const Monitor = this.monitorElement.type;
+ return ;
}
}
+
+DevTools.propTypes = {
+ liftedState: PropTypes.object,
+ dispatch: PropTypes.func.isRequired,
+ monitor: PropTypes.string,
+ testComponent: PropTypes.oneOfType([
+ PropTypes.bool,
+ PropTypes.func
+ ])
+};
diff --git a/src/app/containers/Layout.js b/src/app/containers/Layout.js
new file mode 100644
index 0000000..4d4d0d5
--- /dev/null
+++ b/src/app/containers/Layout.js
@@ -0,0 +1,39 @@
+import React, { Component, PropTypes, cloneElement } from 'react';
+import { withRouter } from 'react-router';
+import { Tabs, Tab } from 'material-ui/Tabs';
+import enhance from '../hoc';
+import styles from '../styles';
+
+export default class Layout extends Component {
+ constructor(props) {
+ super(props);
+ this.state = { tab: 0 };
+ }
+ handleChange = (pathname) => {
+ this.props.router.push(pathname);
+ };
+ render() {
+ const { children, ...rest } = this.props;
+ return (
+
+
+
+
+
+ {cloneElement(children, rest)}
+
+ );
+ }
+}
+
+Layout.propTypes = {
+ children: PropTypes.any,
+ router: PropTypes.object,
+ location: PropTypes.object
+};
+
+export default enhance(withRouter(Layout));
diff --git a/src/app/containers/getMonitor.js b/src/app/containers/getMonitor.js
new file mode 100644
index 0000000..7c2bff0
--- /dev/null
+++ b/src/app/containers/getMonitor.js
@@ -0,0 +1,41 @@
+import React, { Component, PropTypes, createElement } from 'react';
+import LogMonitor from 'redux-devtools-log-monitor';
+import SliderMonitor from 'redux-slider-monitor';
+import InspectorMonitor from 'redux-devtools-inspector';
+import ChartMonitor from 'redux-devtools-chart-monitor';
+
+export const monitors = [
+ { key: 'LogMonitor', title: 'Log monitor' },
+ { key: 'InspectorMonitor', title: 'Inspector' },
+ { key: 'ChartMonitor', title: 'Chart' }
+];
+
+export default function getMonitor({ monitor, testComponent }) {
+ switch (monitor) {
+ case 'LogMonitor':
+ return ;
+ case 'SliderMonitor':
+ return ;
+ case 'ChartMonitor':
+ return createElement(ChartMonitor, {
+ defaultIsVisible: true, invertTheme: true,
+ tooltipOptions: {
+ style: {
+ 'background-color': '#ffffff',
+ 'color': '#000000',
+ 'opacity': '0.9',
+ 'border-radius': '5px',
+ 'padding': '5px'
+ }
+ }
+ });
+ default:
+ let tabs;
+ if (testComponent) {
+ tabs = defaultTabs => [...defaultTabs, { name: 'Test', component: testComponent }];
+ }
+ return createElement(InspectorMonitor, {
+ shouldPersistState: false, isLightTheme: false, theme: 'nicinabox', tabs
+ });
+ }
+}
diff --git a/src/app/containers/monitors/Dispatcher.js b/src/app/containers/monitors/Dispatcher.js
index fb36551..075203a 100644
--- a/src/app/containers/monitors/Dispatcher.js
+++ b/src/app/containers/monitors/Dispatcher.js
@@ -2,6 +2,9 @@
import React, { Component, PropTypes } from 'react';
import * as themes from 'redux-devtools-themes';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import { dispatchRemotely } from '../../actions';
const styles = {
button: {
@@ -33,15 +36,14 @@ const styles = {
}
};
-export default class Dispatcher extends Component {
+class Dispatcher extends Component {
static propTypes = {
- store: PropTypes.object.isRequired,
+ options: PropTypes.object.isRequired,
+ dispatch: PropTypes.func.isRequired,
theme: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string,
- ]),
- error: PropTypes.string,
- clearError: PropTypes.func.isRequired
+ ])
};
static defaultProps = {
@@ -62,7 +64,7 @@ export default class Dispatcher extends Component {
if (selected !== 'default') {
// Shrink the number args to the number of the new ones
args = this.state.args.slice(
- 0, this.props.store.getActionCreators()[selected].args.length
+ 0, this.props.options.actionCreators[selected].args.length
);
}
this.setState({
@@ -87,16 +89,15 @@ export default class Dispatcher extends Component {
let rest = this.refs.restArgs.textContent.trim();
if (rest === '') rest = undefined;
const { selected, args } = this.state;
- this.props.store.dispatch({
- name: this.props.store.getActionCreators()[selected].name,
+ this.props.dispatch({
+ name: this.props.options.actionCreators[selected].name,
selected, args, rest
});
} else {
if (this.refs.action.textContent !== '') {
- this.props.store.dispatch(this.refs.action.textContent);
+ this.props.dispatch(this.refs.action.textContent);
}
}
- this.props.clearError();
}
componentDidMount() {
@@ -113,7 +114,7 @@ export default class Dispatcher extends Component {
}
componentWillReceiveProps(nextProps) {
- if (this.state.selected !== 'default' && !nextProps.store.getActionCreators()) {
+ if (this.state.selected !== 'default' && !nextProps.options.actionCreators) {
this.setState({
selected: 'default',
args: []
@@ -122,7 +123,9 @@ export default class Dispatcher extends Component {
}
resetCustomAction() {
- this.refs.action.innerHTML = this.props.store.isRedux() ? '{
type: \'\'
}' : 'this.';
+ this.refs.action.innerHTML = (
+ this.props.options.lib === 'redux' ? '{
type: \'\'
}' : 'this.'
+ );
}
getTheme() {
@@ -147,7 +150,7 @@ export default class Dispatcher extends Component {
const buttonStyle = {
...styles.button, color: theme.base06, backgroundColor: theme.base00
};
- const actionCreators = this.props.store.getActionCreators();
+ const actionCreators = this.props.options.actionCreators;
let fields = ;
if (this.state.selected !== 'default' && actionCreators) {
@@ -169,23 +172,6 @@ export default class Dispatcher extends Component {
);
}
- let error;
- if (this.props.error) {
- error = (
-
- );
- }
-
let dispatchButtonStyle = buttonStyle;
if (!actionCreators || actionCreators.length <= 0) {
dispatchButtonStyle = {
@@ -209,7 +195,6 @@ export default class Dispatcher extends Component {
position: 'relative'
}}
>
- {error}
{fields}
{actionCreators && actionCreators.length > 0 ?