From 627c0ef6b8cc990a7fe8b9eac6773c26ec9be5e6 Mon Sep 17 00:00:00 2001 From: Dorian Goepp <dorian.goepp@gmail.com> Date: Tue, 7 May 2019 11:33:33 +0200 Subject: [PATCH] We are getting closer to a wokring proposal --- src/Board.js | 10 ++- src/HorizontalGauge/HozirontalGauge.js | 22 ++--- src/HorizontalGauge/RosHGauge.js | 38 ++++++-- src/HorizontalGauge/index.js | 7 +- src/devel-config.js | 8 +- src/utils/modeHandler.js | 118 +++++++++++++++++++++++-- 6 files changed, 163 insertions(+), 40 deletions(-) diff --git a/src/Board.js b/src/Board.js index d8dee81..aedbded 100755 --- a/src/Board.js +++ b/src/Board.js @@ -14,8 +14,8 @@ import { RosMood, DemoMood } from './Mood' import DemoString from './String' // get the default configuration for the layout -import {flexlayout_json as json} from './demo-1-config' -// import {flexlayout_json as json} from './devel-config' +// import {flexlayout_json as json} from './demo-1-config' +import {flexlayout_json as json} from './devel-config' // CSS common to the whole app import './Common.css' @@ -132,10 +132,16 @@ class Board extends React.Component { const tab = this.getSelectedNode(); // retrieve the selected tab const config = tab.getConfig(); // get the configuration of this tab if (!("displayMode" in config) || (config.displayMode !== modeName)) { + if (!this.isMaximized()) { // Maximize the tabset + this._model.doAction(Actions.maximizeToggle(this.getId())); + } config.displayMode = modeName; this._model.doAction( FlexLayout.Actions.updateNodeAttributes(tab.getId(), {config: config})); } else { + if (this.isMaximized()) { // Return the tabset to its original size + this._model.doAction(Actions.maximizeToggle(this.getId())); + } delete config.displayMode; this._model.doAction( FlexLayout.Actions.updateNodeAttributes(tab.getId(), {config: config})); diff --git a/src/HorizontalGauge/HozirontalGauge.js b/src/HorizontalGauge/HozirontalGauge.js index 65fca8a..9b1d3f9 100644 --- a/src/HorizontalGauge/HozirontalGauge.js +++ b/src/HorizontalGauge/HozirontalGauge.js @@ -8,14 +8,16 @@ class HorizontalGauge extends Component { const value = ('value' in this.props && this.props.value) ? this.props.value : 0; // Define the dimensions of the gaug - const margin = { left:10, top: 10, right: 10, bottom: 10} - , width = this.props.size.width - margin.left - margin.right - , height = 20; + const margin = { left:10, top: 10, right: 10, bottom: 10}, + width = this.props.size.width - margin.left - margin.right, + height = 20; const viewbox = "0 0 " + (width + margin.left + margin.right) +" "+ (height + margin.top + margin.bottom); + const min = this.props.min || 0, + max = this.props.max || 1; // Scale to map from values in [0, 1] to values in [0, width] (callable) const xScale = scaleLinear() - .domain([0, 1]) + .domain([min, max]) .range([0, width]) .clamp(true); @@ -33,18 +35,6 @@ class HorizontalGauge extends Component { </g> </svg>; - const config = this.props.node.getConfig() - - if (config && 'displayMode' in config) { - if (config.displayMode === "readme") { - return <p className="about">This gauge represent a <b>measure</b> ranging from 0 to 1.</p>; - } else if (config.displayMode === "settings") { - return <div> - <p>config panel</p> - </div>; - } - } - return <div>{svg}</div>; } } diff --git a/src/HorizontalGauge/RosHGauge.js b/src/HorizontalGauge/RosHGauge.js index 874fd0d..07eb69c 100644 --- a/src/HorizontalGauge/RosHGauge.js +++ b/src/HorizontalGauge/RosHGauge.js @@ -1,5 +1,6 @@ import React, {Component} from 'react' -import RosLib from 'roslib'; +import RosLib from 'roslib' +import {get} from 'lodash/object' import HorizontalGauge from './index' @@ -8,20 +9,37 @@ class RosHGauge extends Component { super(props) this.state = {datum: null} - this.topic = "/predictions/interaction_involvement" - this.type = "april_messages/head_orientation_prediction" + } + + static get modes() { + return { + readme: <p className="about">This gauge represent a <b>measure</b> ranging from 0 to 1.</p>, + settingsSchema: { + title: "Horizontal Gauge", + type: "object", + required: ["topic", "field", "min", "max"], + properties: { + topic: {type: "string", title: "ROS topic (absolute name)", + default: "/predictions/interaction_involvement"}, + field: {type: "string", title: "the field to be read from the message", + default: "prediction_of_head_orientation.list[0].values[1]"}, + min: {type: "number", title: "Lower end of the data range", default: 0}, + max: {type: "number", title: "Upper end of the data range", default: 1}, + } + }, + }; } componentDidMount() { if ('ros' in this.props && this.props.ros) { + const config = this.props.node.getConfig(); this.listener = new RosLib.Topic({ ros : this.props.ros, - name: this.topic, - messageType: this.type + name: config.topic, }); this.listener.subscribe(function(message) { - this.setState({datum: message.prediction_of_head_orientation.list[0].values[1]}); + this.setState({datum: get(message, config.field)}); }.bind(this)); }else { console.warn('RosHGauge expects to be passed a valid Ros object as property, got ', this.props.ros); @@ -35,7 +53,13 @@ class RosHGauge extends Component { } render() { - return <HorizontalGauge value={this.state.datum} {...this.props}/> + const config = this.props.node.getConfig(); + + return <HorizontalGauge + value={this.state.datum} + min={config.min} + max={config.max} + {...this.props}/>; } } diff --git a/src/HorizontalGauge/index.js b/src/HorizontalGauge/index.js index 7959d5c..aca98b1 100644 --- a/src/HorizontalGauge/index.js +++ b/src/HorizontalGauge/index.js @@ -1,6 +1,11 @@ +import React from 'react' import HorizontalGauge from './HozirontalGauge' import RosHGauge from './RosHGauge' +import {Modal} from '../utils/modeHandler' export default HorizontalGauge; -export {RosHGauge}; \ No newline at end of file +function modal(props){ + return <Modal component={RosHGauge} {...props}/>; +} +export {modal as RosHGauge}; \ No newline at end of file diff --git a/src/devel-config.js b/src/devel-config.js index ee0d339..d8ce5f4 100644 --- a/src/devel-config.js +++ b/src/devel-config.js @@ -96,9 +96,6 @@ export const flexlayout_json = { { "type": "tab", "component": "mood", - "config": { - "hasReadme": true - }, }, ] }, @@ -124,10 +121,7 @@ export const flexlayout_json = { "children": [ { "type": "tab", - "component": "demo-video", - "config": { - "hasReadme": true - } + "component": "video", } ] } diff --git a/src/utils/modeHandler.js b/src/utils/modeHandler.js index 8b1afed..898f635 100644 --- a/src/utils/modeHandler.js +++ b/src/utils/modeHandler.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {Component} from 'react'; import Form from 'react-jsonschema-form'; import { merge } from 'lodash/object'; @@ -72,19 +72,14 @@ export class modeHandler { } } - noop(value) { - return value; - } - /** * Return either the value of the normal parameter, in normal mode, or the content for the `readme` and `settings` * modes. * - * The display mode is defined by the 'displayMode' attribute of the component's configuraiton. + * The display mode is defined by the 'displayMode' attribute of the component's configuration. * @param {Jsx} normal The content to return in the normal mode (neither settings nor readme) */ modalDisplay(normal) { - // return normal; const config = this.configGetter(); if (config && 'displayMode' in config) { if (config.configurable && config.displayMode === "settings") { @@ -115,4 +110,113 @@ export class modeHandler { return config; } +} + +export class Modal extends Component { + constructor(props) { + super(props); + + this.modes = this.props.component.modes; + this.configGetter = this.props.node.getConfig.bind(this.props.node); + this.configSetter = this.props.updateConfig; + + // get the current config; if it does not exist yet, create it + let config = this.configGetter() || {}; + + merge(config, this.announceCapabilities(config)); + if (config.configurable) { + merge(config, this.defaultConfigFromSchema(config)); + } + this.configSetter(config); + } + + /** + * Search for the 'settingsSchema' and 'readme' properties of the managed component. A corresponding field in the + * configuration is set accordingly. This is used to display or not the UI buttons to show the Readme or the + * configuration for of a component. + * @param {Object} config current configuration of the object + * @return the new configuration object (possibly unmodified) + */ + announceCapabilities(config) { + if (this.modes.settingsSchema) { + config.configurable = true; + } + else { + config.configurable = false; + } + if (this.modes.readme) { + config.hasReadme = true; + } + else { + config.hasReadme = false; + } + return config; + } + + /** + * Search for properties in a JSON Schema. If they are here, use the default value of each JsonSchema property for + * the configuration. This means that if a property is already defined in the configuration, it will not be + * altered. + * @param {Object} config current configuration of the object + * @return the new configuration object (possibly unmodified) + */ + defaultConfigFromSchema(config) { + const schema = this.modes.settingsSchema; + if (!schema) { + return config; + } + else if (!('properties' in schema)) { + console.warn("no properties in the schema"); + return config; + } + else { + for (let [key, value] of Object.entries(schema.properties)) { + if (!(key in config)) { + let message = "The key '" + key + "' is missing in the configuration."; + if ('default' in value) { + config[key] = value.default; + message += " Using default value '" + value.default + "'."; + } + console.debug(message); + } + } + return config; + } + } + + /** + * Render either the value of the normal parameter, in normal mode, or the content for the `readme` and `settings` + * modes. The display mode is defined by the 'displayMode' attribute of the component's configuration. + */ + render() { + const config = this.configGetter(); + if (config && 'displayMode' in config) { + if (config.configurable && config.displayMode === "settings") { + return ( + <Form + schema={this.modes.settingsSchema} + onSubmit={this.handleSettingsChange.bind(this)} + formData={config} />); + } + if (config.hasReadme && config.displayMode === "readme") { + return this.modes.readme; + } + } + return React.createElement(this.props.component, this.props, null); + } + + /** + * Given a new configuration object (from the form), update the configuration of the component (in FlexLayout). + * + * The update is done through Lodash's merge function. + * @param {Object} value update for the configuration of the component (we take the field formData) + * @return Also return the updated configuration. + */ + handleSettingsChange({ formData }) { + let config = this.configGetter(); + merge(config, formData); + this.configSetter(config); + return config; + } + } \ No newline at end of file -- GitLab