Commit 5f1541a7 authored by Dorian Goepp's avatar Dorian Goepp

Merge branch 'master' into dgoepp/config-modules

parents 5dcae08f 0215139b
This diff is collapsed.
......@@ -21,7 +21,10 @@
"react-numeric-input": "^2.2.3",
"react-scripts": "^3.0.1",
"react-sizeme": "^2.5.2",
"roslib": "^1.0.1"
"roslib": "^1.0.1",
"ts-pnp": "^1.1.2",
"typescript": "^3.5.1",
"visjs-network": "^4.24.7"
},
"scripts": {
"start": "react-scripts start",
......
import React from "react";
import React from 'react';
// Tab-based dynamic layout manager
import FlexLayout from 'flexlayout-react';
// MobX, a state manager
import {toJS} from 'mobx'
import {observer} from 'mobx-react'
// Tab-based dynamic layout manager
import FlexLayout from "flexlayout-react";
// The React Components that are going into tabs
import {VideoStream, DemoVideoStream} from "./VideoStream"
import { RosLineChart, DemoLineChart } from "./LineChart"
import { RosHGauge } from "./HorizontalGauge"
import { RosMemoryRanked, DemoMemoryRanked } from './MemoryRanked'
import { RosInteractionTrace, DemoInteractionTrace } from './InteractionTrace'
import { RosLatestInteraction, DemoLatestInteraction } from './LatestInteraction'
import { RosMood, DemoMood } from './Mood'
import {VideoStream, DemoVideoStream} from './VideoStream'
import {RosLineChart, DemoLineChart} from './LineChart'
import {RosHGauge} from './HorizontalGauge'
import {RosMemoryRanked, DemoMemoryRanked} from './MemoryRanked'
import {RosInteractionTrace, DemoInteractionTrace} from './InteractionTrace'
import {RosLatestInteraction, DemoLatestInteraction} from './LatestInteraction'
import {RosMood, DemoMood} from './Mood'
import {Graph, DemoGraph} from './GraphVisJs'
import DemoString from './String'
// get the default configuration for the layout
......@@ -47,6 +49,8 @@ class Board extends React.Component {
"latest-interaction": RosLatestInteraction,
"mood": RosMood,
"demo-mood": DemoMood,
"graph": Graph,
"demo-graph": DemoGraph,
}
/** This function gives the React Component correspinding to a tab type.
......
import React from 'react';
import 'visjs-network/dist/vis-network.min.css'
import vis from 'visjs-network'
export class Graph extends React.Component {
constructor(props){
super(props);
this.ref = React.createRef();
}
/**
* Draw a graph which representation in the Dot format (from Graphviz) is provided as the 'graph' property.
*/
draw() {
const parseData = vis.network.convertDot(this.props.graph);
var data = {
nodes: fixLabels(parseData.nodes),
edges: fixLabels(parseData.edges)
};
var options = parseData.options;
options.nodes = { font: { color : "white"}};
var network = new vis.Network(this.ref.current, data, options);
}
componentDidMount() {
if ('graph' in this.props && this.props.graph) {
this.draw();
}
}
componentDidUpdate() {
if ('graph' in this.props && this.props.graph) {
this.draw();
}
}
render() {
return <div ref={this.ref} style={{minHeight:"200px", height:"100%"}}/>;
}
}
export function DemoGraph(props) {
const machine = `dinetwork {1 -> 1 -> 2; 2 -> 3; 2 -- 4; 2 -> 1 }`;
return <Graph graph={machine}/>;
}
/**
* Fix the labels of nodes or edges, extracted from a Dot (graphviz format) file.
* The Dot parser provided by vis.js stores numeric labels as numbers and the network object of the same library
* expects labels to be strings. This function does the conversion from any type other than string to a string.
* @param {Array(Object)} entities entities (nodes or edges) of a graph
* @return entities which label keys (when applicable) are convertet to string.
*/
function fixLabels(entities) {
entities = entities.map((entity) => {
if ('label' in entity && typeof entity.label != "string") {
entity.label = String(entity.label);
}
return entity;
});
return entities;
}
import React, {Component} from 'react'
import RosLib from 'roslib'
import {Graph} from './Graph'
/**
* React Widget that displays a graph described at ROS topic. This topic is expected to publish a string in the Dot
* format (from Graphviz).
*
* To test this widget, in a terminal with ROS in scope, you can type
* ```
* rostopic pub /graph_dot std_msgs/String -l -r 0.5 'digraph "machine_0" {
* 0 [color=blue, pos="94.8148148148,0.0!", label="1, S0"];
* 1 [color=black, pos="-47.4074074074,46.1880215352!", label="0, S1"];
* 2 [color=black, pos="-47.4074074074,-46.1880215352!", label="0, S2"];
* 0 -> 2 [key=0, label="a0, 1"];
* 1 -> 0 [key=0, label=a3];
* 2 -> 0 [key=0, label=a9];
* 2 -> 1 [key=0, label=a8];
* 2 -> 2 [key=0, label="a2, 3, 7"];
* }'
* ```
*/
export class RosGraph extends Component {
constructor(props) {
super(props);
this.state = {graph: null};
}
static get modes() {
return {
readme: <p className="about">This widget displays graphs from a ROS topic that sends Dot-formatted graph
descriptions.</p>,
settingsSchema: {
title: "Graph",
type: "object",
required: ["topic"],
properties: {
topic: {
type: "string",
title: "ROS topic (absolute name)",
default: "/graph_dot"}
}
},
};
}
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: config.topic,
});
this.listener.subscribe(function(message) {
this.setState({graph: message.data});
}.bind(this));
}else {
console.warn('RosGraph expects to be passed a valid Ros object as property, got ', this.props.ros);
}
}
componentWillUnmount() {
if ('listener' in this) {
this.listener.unsubscribe();
}
}
render() {
return <Graph
graph={this.state.graph}
{...this.props}/>;
}
}
export {DemoGraph} from './Graph'
export {RosGraph as Graph} from './RosGraph'
......@@ -3,20 +3,6 @@
height: 110px;
}
/* Name of this piece of software */
#header .title {
width: 10vw;
font-size: 35px;
font-weight: bold;
letter-spacing: 0.3px;
text-align: left;
color: #edc61b;
line-height: 100px;
float: left;
margin-left: 1vw;
}
/* Name of the algorithm being run */
#header .algo-name {
......@@ -27,7 +13,7 @@
text-align: center;
letter-spacing: 0.3px;
padding-top: 25px;
padding-right: 8vw;
padding-right: 10vw;
float:left;
}
......@@ -55,9 +41,9 @@
/* Buttons to control the algorithm's lifecycle : play/pause, restart, next step, etc. */
#header .controls {
padding-right:7vw;
padding-left:4vw;
padding-top: 20px;
padding-right:9vw;
padding-left:12vw;
padding-top: 24px;
float: left;
filter: invert(26%) sepia(0%) saturate(0%) hue-rotate(98deg) brightness(99%) contrast(91%);
}
......
......@@ -17,9 +17,6 @@ export function Header(props) {
})
return (
<div id="header">
<div className="title">
Lavizu
</div>
<div className="controls">
{buttons}
</div>
......
/*! react-sidenav v0.4.5 | (c) 2018 Trend Micro Inc. | MIT | https://github.com/trendmicro-frontend/react-sidenav */
/* Name of this piece of software */
.title {
display:none;
}
/* Menu level 1 title */
.menu-title-level1 {
width: 100%;
......@@ -13,9 +19,9 @@
letter-spacing: 0.2px;
color: #484848;
background-color:#d8d8d8;
padding-left: 20px;
padding-left: 25px;
margin: 1.2vh 0px 0.5vh 0px;
border-radius: 4px;
border-radius: 2px;
}
/* Menu level 2 title */
......@@ -30,14 +36,14 @@
line-height: normal;
letter-spacing: 0.2px;
color: #484848;
padding-left: 20px;
margin: 1.5vh 0px 0.5vh 0px;
padding-left: 25px;
margin: 1.5vh 10px 0.5vh 0px;
}
/*Menu icons*/
.menu-button {
height: 25px;
margin: 0.9vh 0px 0px 7px;
margin: 1.2vh 0px 0px 5px;
filter: invert(14%) sepia(1%) saturate(2442%) hue-rotate(316deg) brightness(98%) contrast(77%);
}
......@@ -51,8 +57,9 @@
/* Behaviors logo */
.logo {
margin: 5px 5px 1vh 5px;
height: 100px;
margin: 20px 0px 1vh 20px;
height: 80px;
display: inline-block;
}
/* Footer with About us and lock button */
......@@ -64,6 +71,7 @@
.footer:hover {
filter: invert(67%) sepia(71%) saturate(551%) hue-rotate(1deg) brightness(111%) contrast(86%);
cursor: pointer;
}
.lock-button {
......@@ -74,6 +82,22 @@
margin-left:30px;
}
.sidenav---sidenav---_2tBP.sidenav---expanded---1KdUL .title {
display: inline-block;
float:right;
width: 100px;
height:80px;
font-size: 35px;
font-weight: bold;
letter-spacing: 0.3px;
text-align: left;
color: #edc61b;
line-height: 80px;
margin-right: 22px;
margin-top:18px;
}
/* Side navigation menu */
.sidenav---sidenav---_2tBP {
z-index: 1006;
......@@ -95,7 +119,7 @@
display: block;
}
.sidenav---sidenav---_2tBP.sidenav---expanded---1KdUL {
min-width: 220px;
min-width: 240px;
}
.sidenav---sidenav---_2tBP.sidenav---expanded---1KdUL .sidenav---sidenav-nav---3tvij > .sidenav---sidenav-navitem---uwIJ- .sidenav---navicon---3gCRo + .sidenav---navtext---1AE_f {
display: inline-block;
......
......@@ -13,9 +13,12 @@ export function Menu(props) {
}}
>
<img className="logo"
src={window.location.origin + "/images/logoBehaviors.png"}
src={window.location.origin + "/images/logoBehaviors2.png"}
alt="logo of the Behaviors.ai project"
/>
<div className="title">
Lavizu
</div>
<br></br>
<SideNav.Toggle />
......
......@@ -30,7 +30,8 @@ decorate(MoodStore, {
config: observable,
})
const RosMood = sizeMe({monitorHeight: true})(observer(
const RosMood = sizeMe({monitorHeight: true})(
observer(
class RosMood extends Component {
constructor(props) {
super(props)
......
import React, {Component} from 'react'
import {decorate, observable, computed} from 'mobx'
import {reaction, decorate, observable, computed} from 'mobx'
import {observer} from 'mobx-react'
import {defaultConfig} from '../utils/configurationHelpers'
......@@ -41,6 +41,11 @@ class VideoStream extends Component {
super(props);
this.store = new VideoStore();
// Automatically detect if the configured topic name is valid
this.hasTopic = observable.box(false);
this.searchForTopic()
// Fill the undefined fields of the configuration object with their default values.
defaultConfig(this.store);
this.props.store.components['video'] = this.store;
......@@ -57,7 +62,7 @@ class VideoStream extends Component {
get streamUrl() {
if ('url' in this.props && this.props.url) {
return this.props.url;
} else if ('topic' in this.store.config && 'host' in this.store.config) {
} else if (this.hasTopic.get()) {
// all requierd configuration fields are defined
return 'http://' + this.store.config.host + '/stream?topic='
+ this.store.config.topic;
......@@ -66,6 +71,42 @@ class VideoStream extends Component {
}
}
/**
* Check that all the requirements are met for subscribing to the required
* topic
*
* Thanks to MobX magic, the result is computed again each time the topic
* configuration is changed.
*/
searchForTopic() {
if (this.props.ros) {
this.topicWatcher = reaction(() => (
this.store.config.topic + this.store.config.host
),
(data, reaction) => {
if ('topic' in this.store.config && 'host' in this.store.config) {
this.props.ros.getTopics(infos => {
this.hasTopic.set(undefined !== infos.topics.find(
elem => elem === this.store.config.topic));
if (!this.hasTopic.get()) {
console.warn("[VideoStream] The required topic "
+ this.store.config.topic + " is not published");
}
console.debug(this.hasTopic.get());
})
} else {
console.warn("[VideoStream] either no topic is configured "
+ "or no host is configured. This should never "
+ "happen at runtime. Contact the developpers.");
}
})
} else {
console.warn("[VideoStream] ROS property is provided. This should "
+ "never happen at runtime. Contact the developpers.");
}
}
render() {
return (
<div className="video-stream container">
......
......@@ -5,8 +5,8 @@ export const flexlayout_json = {
tabSetHeaderHeight: 25,
enableEdgeDock: false,
tabEnableRename: false,
tabSetEnableHeader: false,
tabSetEnableTabStrip: false,
// tabSetEnableHeader: false,
// tabSetEnableTabStrip: false,
},
layout: {
"type": "row",
......@@ -14,14 +14,23 @@ export const flexlayout_json = {
"children": [
{
"type": "tabset",
"name": "Interaction trace",
"weight": 50,
"selected": 0,
"children": [
{
"name": "Interaction trace",
"type": "tab",
"component": "int-trace",
},
{
"name": "The magic graph",
"type": "tab",
"component": "graph",
"config": {
"hasReadme": true,
"topic": "/graph_dot",
}
},
]
},
{
......@@ -30,11 +39,11 @@ export const flexlayout_json = {
"children": [
{
"type": "tabset",
"name": "Robot's memory",
"weight": 60,
"selected": 0,
"children": [
{
"name": "Robot's memory",
"type": "tab",
"component": "memory",
},
......@@ -50,11 +59,11 @@ export const flexlayout_json = {
"children": [
{
"type": "tabset",
"name": "Latest interaction",
"weight": 78,
"selected": 0,
"children": [
{
"name": "Latest interaction",
"type": "tab",
"component": "latest-interaction",
},
......@@ -62,11 +71,11 @@ export const flexlayout_json = {
},
{
"type": "tabset",
"name": "Involvement",
"weight": 22,
"height": 70,
"children": [
{
"name": "Involvement",
"type": "tab",
"component": "h-gauge",
},
......@@ -76,10 +85,10 @@ export const flexlayout_json = {
},
{
"type": "tabset",
"name": "Robot video stream",
"weight": 75,
"children": [
{
"name": "Robot video stream",
"type": "tab",
"component": "video",
}
......
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