Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #1421, live coding of a plugin in the plugins example #1422

Merged
merged 1 commit into from
Feb 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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