Skip to content

Commit

Permalink
Set optional drawer menu for share ( see #1632) (#1698)
Browse files Browse the repository at this point in the history
* Set optional drawer menu for share ( see #1632)

 - Documented drawer menu plugin
 - Added a forceDrawer and a hide option
 - Improved share to manage forceDrawer option

* experssion handling for hide param. more doc

* removed hideButton prop
  • Loading branch information
offtherailz authored and mbarto committed Apr 7, 2017
1 parent f448fbf commit 66f434c
Show file tree
Hide file tree
Showing 12 changed files with 94 additions and 13 deletions.
2 changes: 2 additions & 0 deletions docma-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
"web/client/components/buttons/GoFullButton.jsx",
"web/client/components/mapcontrols/search/SearchBar.jsx",
"web/client/components/buttons/ToggleButton.jsx",
"web/client/components/plugins/PluginsContainer.jsx",

"web/client/actions/index.jsdoc",
"web/client/actions/controls.js",
Expand All @@ -132,6 +133,7 @@
"plugins": [
"web/client/plugins/index.jsdoc",
"web/client/plugins/BackgroundSwitcher.jsx",
"web/client/plugins/DrawerMenu.jsx",
"web/client/plugins/GoFull.jsx",
"web/client/plugins/Map.jsx",
"web/client/plugins/FullScreen.jsx",
Expand Down
17 changes: 15 additions & 2 deletions web/client/components/plugins/PluginsContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,20 @@ const PluginsUtils = require('../../utils/PluginsUtils');
const assign = require('object-assign');

const {get} = require('lodash');

/**
* Container for plugins. Get's the plugin definitions (`plugins`) and configuration (`pluginsConfig`)
* to render the configured plugins.
* The plugins to render will come from the `mode` entry of the `pluginsConfig`
* @class
* @memberof components.plugins
* @prop {string} mode key of the pluginsConfig object to get the plugins to render
* @prop {string} defaultMode mode to use if mode is not defined
* @prop {object} params params of the current page, usually from react router. Used as state to get plugins descriptor if monitored state is not present.
* @prop {object} plugins the Plugins definitions
* @prop {object} pluginsConfig the configuration for the plugins. a map of [mode]: [{pluginCfg1}...]
* @prop {object} pluginsState a piece of state to use. usually controls.
* @prop {object} monitoredState the piece of state to monitor Used as state to get plugins descriptor.
*/
const PluginsContainer = React.createClass({
propTypes: {
mode: React.PropTypes.string,
Expand Down Expand Up @@ -60,7 +73,7 @@ const PluginsContainer = React.createClass({
},
renderPlugins(plugins) {
return plugins
.filter((Plugin) => !Plugin.hide)
.filter((Plugin) => !PluginsUtils.handleExpression(this.props.pluginsState, this.props.plugins && this.props.plugins.requires, Plugin.hide))
.map(this.getPluginDescriptor)
.filter((Plugin) => Plugin && !Plugin.impl.loadPlugin)
.filter(this.filterPlugins)
Expand Down
21 changes: 17 additions & 4 deletions web/client/components/share/ShareEmbed.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
const React = require('react');
const CopyToClipboard = require('react-copy-to-clipboard');
const Message = require('../../components/I18N/Message');
const {Glyphicon, Col, Grid, Row, Tooltip, Button} = require('react-bootstrap');
const {Glyphicon, Col, Grid, Row, Tooltip, Button, Checkbox} = require('react-bootstrap');
const OverlayTrigger = require('../misc/OverlayTrigger');


const url = require('url');
// css required
require('./share.css');

Expand All @@ -27,11 +27,11 @@ const ShareEmbed = React.createClass({
shareUrl: React.PropTypes.string
},
getInitialState() {
return {copied: false};
return {copied: false, forceDrawer: false};
},
render() {

const codeEmbedded = "<iframe style=\"border: none;\" height=\"400\" width=\"600\" src=\"" + this.props.shareUrl + "\"></iframe>";
const codeEmbedded = "<iframe style=\"border: none;\" height=\"400\" width=\"600\" src=\"" + this.generateUrl(this.props.shareUrl) + "\"></iframe>";
const tooltip = (<Tooltip placement="bottom" className="in" id="tooltip-bottom" style={{zIndex: 2001}}>
{this.state.copied ? <Message msgId="share.msgCopiedUrl"/> : <Message msgId="share.msgToCopyUrl"/>}
</Tooltip>);
Expand All @@ -44,11 +44,16 @@ const ShareEmbed = React.createClass({
</OverlayTrigger>);
return (
<div className="input-link">


<Grid className="embed-box" fluid={true}>
<Row key="title">
<h4>
<Message msgId="share.embeddedLinkTitle"/>
</h4>
<Checkbox checked={this.state.forceDrawer} onChange={() => this.setState({forceDrawer: !this.state.forceDrawer})}>
<Message msgId="share.forceDrawer"/>
</Checkbox>
</Row>
<Row key="data" className="row-button">
<Col key="textarea" xs={10} sm={10} md={10}><textarea name="description" rows="6" value={codeEmbedded} enabled="false" readOnly /></Col>
Expand All @@ -59,6 +64,14 @@ const ShareEmbed = React.createClass({
</Grid>
</div>
);
},
generateUrl() {
const parsed = url.parse(this.props.shareUrl, true);
if (this.state.forceDrawer) {
parsed.query.forceDrawer = true;
}
return url.format(parsed);

}
});

Expand Down
5 changes: 4 additions & 1 deletion web/client/localConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,10 @@
"cfg": {
"tools": ["locate"]
}
}, "DrawerMenu", {
}, {
"name": "DrawerMenu",
"hide": "{!(request.query && request.query.forceDrawer)}"
}, {
"name": "Identify",
"cfg": {
"style": {
Expand Down
16 changes: 16 additions & 0 deletions web/client/plugins/DrawerMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const Section = require('./drawer/Section');

const {partialRight} = require('lodash');


const Menu = connect((state) => ({
show: state.controls.drawer && state.controls.drawer.enabled,
activeKey: state.controls.drawer && state.controls.drawer.menu || "1",
Expand All @@ -33,6 +34,21 @@ const Menu = connect((state) => ({

require('./drawer/drawer.css');

/**
* DrawerMenu plugin. Shows a left menu with some pluins rendered inside it (typically the TOC).
* @prop {string} cfg.glyph glyph icon to use for the button
* @prop {object} cfg.menuButtonStyle Css inline style for the button. Display property will be overridden by the hideButton/forceDrawer options.
* @prop {string} cfg.buttonClassName class for the toggle button
* @prop {object} cfg.menuOptions options for the drawer menu. They can be `docked`, `width.
* @memberof plugins
* @class
* @example
* {
* "name": "DrawerMenu",
* "cfg": {
* "hideButton": true
* }
*/
const DrawerMenu = React.createClass({
propTypes: {
items: React.PropTypes.array,
Expand Down
17 changes: 16 additions & 1 deletion web/client/plugins/containers/ToolsContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,22 @@ const {setControlProperty, toggleControl} = require('../../actions/controls');
const {partial} = require('lodash');

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

/**
* A container for tools.
* @memberof plugins.containers.ToolsContainer
* @class ToolsContainer
* @static
* @prop {object[]} tools An array of tools. Each tool have this shape. the first in order wins:
* ```
* {
* tool: {boolean|node} if boolean and true, renders the plugins itself, if object, renders this object as a react component,
* exclusive: if true, gets a selector to make it active or not, setting active property of the tool. tool.toggleControl | tool.name is used from controls state to retrieve the status of the tool
* toggle: same as above, but sets also bsStyle
* action: if present, this action will be binded to the context and associated to the tool as eventSelector (default onClick)
* }
* ```
*
*/
const ToolsContainer = React.createClass({
propTypes: {
id: React.PropTypes.string.isRequired,
Expand Down
1 change: 1 addition & 0 deletions web/client/translations/data.de-DE
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@
"socialIntro": "In deinem bevorzugten sozialen Netzwerk",
"directLinkTitle": "Über einen direkten Link",
"embeddedLinkTitle": "Über einen eingebetteten Code",
"forceDrawer": "Inhaltsverzeichnis anzeigen",
"apiLinkTitle": "API verwenden",
"social": "Social",
"direct": "Link",
Expand Down
1 change: 1 addition & 0 deletions web/client/translations/data.en-US
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@
"socialIntro": "In your favourite social network",
"directLinkTitle": "Via a direct link",
"embeddedLinkTitle": "Via the embedded code",
"forceDrawer": "Show TOC",
"apiLinkTitle": "Using APIs",
"social": "Social",
"direct": "Link",
Expand Down
1 change: 1 addition & 0 deletions web/client/translations/data.fr-FR
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@
"socialIntro": "via votre réseau social favori",
"directLinkTitle": "Via un lien direct",
"embeddedLinkTitle": "Via le code intégré",
"forceDrawer": "Afficher la table des matières",
"apiLinkTitle": "Via le APIs",
"social": "Social",
"direct": "Link",
Expand Down
1 change: 1 addition & 0 deletions web/client/translations/data.it-IT
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@
"direct": "Link",
"code": "Embed",
"embeddedLinkTitle": "Con il codice embedded",
"forceDrawer": "Mostra l'indice dei livelli",
"apiLinkTitle": "Usando le API",
"QRCodeLinkTitle": "Usando il QR Code",
"msgCopiedUrl":"Copiato",
Expand Down
22 changes: 17 additions & 5 deletions web/client/utils/PluginsUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const assign = require('object-assign');
const {omit, isObject, head, isArray, isString} = require('lodash');
const {combineReducers} = require('redux');
const {connect} = require('react-redux');
const url = require('url');

const {combineEpics} = require('redux-observable');

Expand All @@ -34,20 +35,30 @@ const isPluginConfigured = (pluginsConfig, plugin) => {
};

/*eslint-disable */
const parseExpression = (state, requires, value) => {
const parseExpression = (state = {}, context = {}, value) => {
const searchExpression = /^\{(.*?)\}$/;
const context = requires || {};
const expression = searchExpression.exec(value);
const request = url.parse(location.href, true);
if (expression !== null) {
return eval(expression[1]);
}
return value;
};
/*eslint-enable */

const handleExpression = (state, requires, expression) => {
/**
* Parses a expression string "{some javascript}" and evaluate it.
* The expression will be evalueted getting as parameters the state and the context and the request.
* @memberof utils.PluginsUtils
* @param {object} state the state context
* @param {object} context the context element
* @param {string} expression the expression to parse, it's a string
* @return {object} the result of the expression
* @example "{1===0 && request.query.queryParam1=paramValue1}"
* @example "{1===0 && context.el1 === 'checked'}"
*/
const handleExpression = (state, context, expression) => {
if (isString(expression) && expression.indexOf('{') === 0) {
return parseExpression(state, requires, expression);
return parseExpression(state, context, expression);
}
return expression;
};
Expand Down Expand Up @@ -213,6 +224,7 @@ const PluginsUtils = {
connect: (mapStateToProps, mapDispatchToProps, mergeProps, options) => {
return connect(mapStateToProps, mapDispatchToProps, mergeProps || pluginsMergeProps, options);
},
handleExpression,
getMorePrioritizedContainer
};
module.exports = PluginsUtils;
3 changes: 3 additions & 0 deletions web/client/utils/__tests__/PluginUtils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,7 @@ describe('PluginsUtils', () => {
const domElement = ReactDOM.findDOMNode(app);
expect(domElement.innerText).toBe("plugintest");
});
it('handleExpression', () => {
expect(PluginsUtils.handleExpression({state1: "test1"}, {context1: "test2"}, "{state.state1 + ' ' + context.context1}")).toBe("test1 test2");
});
});

0 comments on commit 66f434c

Please sign in to comment.