Commit 20d56e88 authored by Dorian Goepp's avatar Dorian Goepp

Fully configurable VideoStream

parent ba6d9a3c
import React, {Component} from 'react'
import Form from 'react-jsonschema-form'
import {has} from 'lodash/object'
import './VideoStream.css'
const schema = {
title: "Video streamer",
type: "object",
required: ["host", "topic"],
properties: {
host: {type: "string", title: "Host (IP or name)", default:"localhost:8081"},
topic: {type: "string", title: "ROS topic (absolute name)", default: "/pepper/image_raw"}
}
};
const log = (type) => console.log.bind(console, type);
import { modeHandler } from '../utils/modeHandler';
class VideoStream extends Component {
constructor(props) {
super(props)
const hasUrl = ('url' in this.props && this.props.url);
this.topic = '/pepper/image_raw';
this.defaultImage = '';
this.state = {
url: hasUrl ? this.props.url : this.defaultImage
this.settingsSchema = {
title: "Video streamer",
type: "object",
required: ["host", "topic"],
properties: {
host: {type: "string", title: "Host (IP or name)", default:"localhost:8081"},
topic: {type: "string", title: "ROS topic (absolute name)", default: "/pepper/image_raw"}
}
};
this.readme = (
<div className="about">
<h1>See what the agent is seeing</h1>
<p><b>Video stream</b> coming from the robot or the device.</p>
</div>
);
this.props.ros.getTopics(function(infos){
if (undefined !== infos.topics.find(elem => elem === this.topic)) {
this.setState({url: 'http://localhost:8081/stream?topic=' + this.topic});
}
}.bind(this))
}
this.defaultImage = '';
handleSettingsChange(event) {
this.setState({url: event.target.value})
this.modeHandler = new modeHandler(this,
() => (this.props.node.getConfig()),
(config) => {this.props.updateConfig(config)});
}
render() {
const config = this.props.node.getConfig()
if (config && 'displayMode' in config) {
if (config.displayMode === "settings") {
return <div>
{/* <form className="pure-form pure-form-stacked" onSubmit={(event)=>event.preventDefault()}>
<fieldset>
<label>Nice modification option for the module</label>
<label htmlFor="url">URL of the stream</label>
<input className="pure-input-1" id="url" name="url" type="text" placeholder="http://localhost:8080/..."
value={this.state.url} onChange={this.handleSettingsChange.bind(this)}/>
</fieldset>
</form>
<p>Changes are automatically saved!</p> */}
<Form schema={schema}
onChange={log("changed")}
onSubmit={({formData}, e) => log(data.formData)}
onError={log("errors")} />
</div>;
}
if (config.displayMode === "readme") {
return <div className="about">
<h1>See what the agent is seeing</h1>
<p><b>Video stream</b> coming from the robot or the device.</p>
</div>;
}
/**
* Return the url of the video stream to display, based on the current configuration.
* If there is no stream configured, gives the URL passed as a property to this object, or the one defined in
* this.defaultImage.
*/
getStreamUrl() {
const config = this.props.node.getConfig();
const config_fields = ['topic', 'host'];
// Check that all requierd configuration fields are defined
if (config_fields.reduce((accumulator, field) => accumulator && has(config, field), true)) {
return 'http://' + config.host + '/stream?topic=' + config.topic;
} else {
return ('url' in this.props && this.props.url) ? this.props.url : this.defaultImage;
}
}
return (
render() {
return this.modeHandler.modalDisplay(
<div className="video-stream container">
<img src={this.state.url} alt="there should have been a video stream here"/>
<img src={this.getStreamUrl()} alt="there should have been a video stream here"/>
</div>);
}
}
......
......@@ -94,10 +94,6 @@ export const flexlayout_json = {
{
"type": "tab",
"component": "video",
"config": {
"hasReadme": true,
"configurable": true
}
}
]
}
......
import React from 'react';
import Form from 'react-jsonschema-form';
import { merge } from 'lodash/object';
export class modeHandler {
constructor(object, configGetter, configSetter) {
this.target = object
this.configGetter = configGetter;
this.configSetter = configSetter;
// 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.target.settingsSchema) {
config.configurable = true;
}
else {
config.configurable = false;
}
if (this.target.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.target.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;
}
}
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.
* @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") {
return (
<Form
schema={this.target.settingsSchema}
onSubmit={this.handleSettingsChange.bind(this)}
formData={config} />);
}
if (config.hasReadme && config.displayMode === "readme") {
return this.target.readme;
}
}
return normal;
}
/**
* 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
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment