Skip to content

Commit

Permalink
Fixes #1421, live coding of a plugin in the plugins example (#1422)
Browse files Browse the repository at this point in the history
  • Loading branch information
mbarto authored Feb 7, 2017
1 parent 231244d commit 78adf24
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 54 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"mocha": "2.4.5",
"ncp": "2.0.0",
"parallelshell": "1.2.0",
"raw-loader": "0.5.1",
"react-addons-css-transition-group": "0.14.8",
"react-addons-test-utils": "0.14.8",
"react-hot-loader": "1.3.0",
Expand Down
12 changes: 9 additions & 3 deletions web/client/components/data/template/jsx/Template.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ const Template = React.createClass({
propTypes: {
template: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.func]),
model: React.PropTypes.object,
renderContent: React.PropTypes.func
renderContent: React.PropTypes.func,
onError: React.PropTypes.func
},
getDefaultProps() {
return {
template: "",
model: {}
model: {},
onError: () => {}
};
},
componentWillMount() {
Expand All @@ -45,7 +47,11 @@ const Template = React.createClass({
},
parseTemplate(temp) {
let template = (typeof temp === 'function') ? temp() : temp;
this.comp = Babel.transform(template, { presets: ['es2015', 'react', 'stage-0'] }).code;
try {
this.comp = Babel.transform(template, { presets: ['es2015', 'react', 'stage-0'] }).code;
} catch(e) {
this.props.onError(e.message);
}
}
});

Expand Down
17 changes: 16 additions & 1 deletion web/client/examples/plugins/actions/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
const SAVE_PLUGIN_CONFIG = 'SAVE_PLUGIN_CONFIG';
const COMPILE_ERROR = 'COMPILE_ERROR';

function savePluginConfig(plugin, cfg) {
return {
Expand All @@ -15,4 +16,18 @@ function savePluginConfig(plugin, cfg) {
};
}

module.exports = {SAVE_PLUGIN_CONFIG, savePluginConfig};
function compileError(message) {
return {
type: COMPILE_ERROR,
message
};
}

function resetError() {
return {
type: COMPILE_ERROR,
message: null
};
}

module.exports = {SAVE_PLUGIN_CONFIG, COMPILE_ERROR, savePluginConfig, compileError, resetError};
70 changes: 64 additions & 6 deletions web/client/examples/plugins/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ const startApp = () => {

const {plugins} = require('./plugins');

let userPlugin;
const Template = require('../../components/data/template/jsx/Template');

let pluginsCfg = {
standard: ['Map', 'Toolbar']
};
Expand All @@ -38,12 +41,33 @@ const startApp = () => {
const SaveAndLoad = require('./components/SaveAndLoad');

const Debug = require('../../components/development/Debug');
const store = require('./store')(plugins);

const {savePluginConfig} = require('./actions/config');
const assign = require('object-assign');
const codeSample = require("raw!./sample.js.raw");

let customReducers;

const context = require('./context');

const customReducer = (state={}, action) => {
if (customReducers) {
const newState = assign({}, state);
Object.keys(customReducers).forEach((stateKey) => {
assign(newState, {[stateKey]: customReducers[stateKey](state[stateKey], action)});
});
return newState;
}
return state;
};

const store = require('./store')(plugins, customReducer);

const {savePluginConfig, compileError, resetError} = require('./actions/config');

require('./assets/css/plugins.css');

const Babel = require('babel-standalone');

let mapType = 'leaflet';

const Localized = connect((state) => ({
Expand Down Expand Up @@ -71,8 +95,39 @@ const startApp = () => {
callback();
};

const customPlugin = (callback, code) => {
/*eslint-disable */
const require = context;
try {
customReducers = eval(Babel.transform(code, { presets: ['es2015', 'react', 'stage-0'] }).code).reducers || null;

/*eslint-enable */
userPlugin = connect(() => ({
template: code,
renderContent: (comp) => {
/*eslint-disable */
return eval(comp).Plugin;
/*eslint-enable */
},
getReducers() {
return this.comp;
}
}), {
onError: compileError
})(Template);
store.dispatch(resetError());
callback();
} catch(e) {
store.dispatch(compileError(e.message));
}
};

const PluginConfigurator = require('./components/PluginConfigurator');

const PluginCreator = connect((state) => ({
error: state.pluginsConfig && state.pluginsConfig.error
}))(require('./components/PluginCreator'));

const renderPlugins = (callback) => {
return Object.keys(plugins).map((plugin) => {
const pluginName = plugin.substring(0, plugin.length - 6);
Expand All @@ -93,12 +148,10 @@ const startApp = () => {
name: plugin,
hide: isHidden(plugin),
cfg: userCfg[plugin + 'Plugin'] || {}
}))
})).concat(userPlugin ? ['My'] : [])
};
};

const assign = require('object-assign');

const changeMapType = (callback, e) => {
mapType = e.target.options[e.target.selectedIndex].value;
callback();
Expand Down Expand Up @@ -131,6 +184,10 @@ const startApp = () => {
}
};

const getPlugins = () => {
return assign({}, plugins, userPlugin ? {MyPlugin: {MyPlugin: userPlugin}} : {});
};

const renderPage = () => {
ReactDOM.render(
(
Expand All @@ -146,11 +203,12 @@ const startApp = () => {
</Input>
<SaveAndLoad onSave={save.bind(null, renderPage)} onLoad={load.bind(null, renderPage)}/>
<ul>
<PluginCreator pluginCode={codeSample} onApplyCode={customPlugin.bind(null, renderPage)}/>
{renderPlugins(renderPage)}
</ul>
</div>
<div style={{position: "absolute", right: 0, left: "300px", height: "100%"}}>
<PluginsContainer params={{mapType}} plugins={PluginsUtils.getPlugins(plugins)} pluginsConfig={getPluginsConfiguration()} mode="standard"/>
<PluginsContainer params={{mapType}} plugins={PluginsUtils.getPlugins(getPlugins())} pluginsConfig={getPluginsConfiguration()} mode="standard"/>
</div>
<Debug/>
</div>
Expand Down
89 changes: 89 additions & 0 deletions web/client/examples/plugins/components/PluginCreator.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* Copyright 2016, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
const React = require('react');

const {Button, Glyphicon, Modal, Input} = require('react-bootstrap');

const Codemirror = require('react-codemirror');


require('codemirror/lib/codemirror.css');

require('codemirror/mode/javascript/javascript');

const PluginCreator = React.createClass({
propTypes: {
pluginCode: React.PropTypes.string,
error: React.PropTypes.string,
onApplyCode: React.PropTypes.func
},
getDefaultProps() {
return {
pluginCode: '',
onApplyCode: () => {}
};
},
getInitialState() {
return {
code: "",
configVisible: false
};
},
componentWillMount() {
this.setState({
code: this.props.pluginCode
});
},
componentWillReceiveProps(newProps) {
if (newProps.pluginCode !== this.props.pluginCode) {
this.setState({
code: newProps.pluginConfig
});
}
},
render() {
return (<li style={{border: "solid 1px lightgrey", borderRadius: "3px", paddingLeft: "10px", paddingRight: "10px", marginBottom: "3px", marginRight: "10px"}} key="plugin-creator">
<Button bsSize="small" onClick={this.toggleCfg}><Glyphicon glyph={this.state.configVisible ? "minus" : "plus"}/></Button>
<Input className="pluginEnable" type="checkbox" name="toolscontainer"
disabled={true}
checked={true}
label="Live edit your own plugin"/>

<Modal show={this.state.configVisible} bsSize="large" backdrop={false} onHide={() => {
this.setState({
configVisible: false
});
}}>
<Modal.Header className="dialog-error-header-side" closeButton>
<Modal.Title>Live edit your own plugin</Modal.Title>
</Modal.Header>
<Modal.Body>
<Codemirror style={{width: '500px'}} key="code-mirror" value={this.state.code} onChange={this.updateCode} options={{
mode: {name: "javascript"},
lineNumbers: true
}}/>
<Button key="apply-cfg" onClick={this.applyCode}>Apply</Button>
<div className="error">{this.props.error}</div>
</Modal.Body>
</Modal>
</li>);
},
updateCode(newCode) {
this.setState({
code: newCode
});
},
applyCode() {
this.props.onApplyCode(this.state.code);
},
toggleCfg() {
this.setState({configVisible: !this.state.configVisible});
}
});

module.exports = PluginCreator;
3 changes: 3 additions & 0 deletions web/client/examples/plugins/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
var context = require.context('../..', true, /^\.*((\/components)|(\/actions)|(\/reducers))((?!__tests__).)*jsx?$/);
context.keys().forEach(context);
module.exports = context;
41 changes: 0 additions & 41 deletions web/client/examples/plugins/plugins/My.jsx

This file was deleted.

5 changes: 4 additions & 1 deletion web/client/examples/plugins/reducers/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@
* LICENSE file in the root directory of this source tree.
*/

const {SAVE_PLUGIN_CONFIG} = require('../actions/config');
const {SAVE_PLUGIN_CONFIG, COMPILE_ERROR} = require('../actions/config');
const assign = require('object-assign');

function my(state = {}, action) {
switch (action.type) {
case SAVE_PLUGIN_CONFIG: {
return assign({}, state, {[action.plugin]: action.cfg});
}
case COMPILE_ERROR: {
return assign({}, state, {error: action.message});
}
default:
return state;
}
Expand Down
36 changes: 36 additions & 0 deletions web/client/examples/plugins/sample.js.raw
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const clickHandler = () => ({type: 'CLICKED'});

const ToggleButton = require('./components/buttons/ToggleButton.jsx');

const Comp = connect((state) => ({
msg: state.custom.comp && state.custom.comp.msg || "Click me!"
}), {
onClick: clickHandler
})(
React.createClass({
render() {
return (
<div style={{position:"absolute",bottom:"100px",left:"200px"}}>
{this.props.msg} <ToggleButton glyphicon="plus" onClick={this.props.onClick}/>
</div>);
}
})
);

module.exports = {
Plugin: <Comp/>,
reducers: {
comp: (state = {msg: '', counter: 0}, action) => {
switch(action.type) {
case 'CLICKED': {
return {
counter: state.counter + 1,
msg: 'CLICKED ' + (state.counter + 1) + ' times'
};
}
default:
return state;
}
}
}
};
Loading

0 comments on commit 78adf24

Please sign in to comment.