diff --git a/package-lock.json b/package-lock.json index b525edb49670fdcd7b3d06a4129d93315ee17edf..d776380d08693d18a0f0a0c65507f68ad75474bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8233,9 +8233,9 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash._reinterpolate": { "version": "3.0.0", @@ -8559,9 +8559,9 @@ } }, "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" @@ -10643,6 +10643,11 @@ "resolved": "https://registry.npmjs.org/react-numeric-input/-/react-numeric-input-2.2.3.tgz", "integrity": "sha1-S/WRjD6v7YUagN8euZLZQQArtVI=" }, + "react-pure-modal": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/react-pure-modal/-/react-pure-modal-1.5.1.tgz", + "integrity": "sha512-ac5H4D9D6W1Mi7IMyFoNGxht+uK/YhFkB0S10rG6ptHs9B7kTvYic718u9mRPDep0AWhonl/pWT3E97dTWlrcg==" + }, "react-scripts": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.0.1.tgz", @@ -11438,9 +11443,9 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -12556,35 +12561,14 @@ } }, "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } + "set-value": "^2.0.1" } }, "uniq": { diff --git a/package.json b/package.json index 078fd8712c694c8243012fc6dc4451f558d43107..9a9a640694c54dccc094051436bb648ea95f898a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "d3": "^5.8.0", "d3-scale": "^2.2.2", "flexlayout-react": "^0.3.3", - "lodash": "^4.17.11", + "lodash": "^4.17.15", "mobx": "^5.9.0", "mobx-react": "^5.4.3", "prop-types": "^15.7.2", @@ -19,6 +19,7 @@ "react-dom": "^16.7.0", "react-jsonschema-form": "^1.5.0", "react-numeric-input": "^2.2.3", + "react-pure-modal": "^1.5.1", "react-scripts": "^3.0.1", "react-sizeme": "^2.5.2", "roslib": "^1.0.1", diff --git a/public/images/icons/add_circle_outline.svg b/public/images/icons/add_circle_outline.svg new file mode 100644 index 0000000000000000000000000000000000000000..55d52d7b12e9cd1525726f729c30752deb0686ad --- /dev/null +++ b/public/images/icons/add_circle_outline.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" fill="#A1A1A1"/></svg> diff --git a/src/Board.js b/src/Board/Board.js similarity index 74% rename from src/Board.js rename to src/Board/Board.js index 3de574c3ba1236a8cd0eb9011231899c0f030be0..77486e4de279954ceed3e8eb566281fbb424f67c 100755 --- a/src/Board.js +++ b/src/Board/Board.js @@ -2,23 +2,45 @@ import React from 'react'; // Tab-based dynamic layout manager import FlexLayout from 'flexlayout-react'; // MobX, a state manager -import {toJS} from 'mobx' +import {reaction, toJS} from 'mobx' import {observer} from 'mobx-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 {Graph, DemoGraph} from './GraphVisJs' -import DemoString from './String' +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' +// A component used to display the readme or config form for the widgets +import {GadgetModal} from '../utils/GadgetModal'; // get the default configuration for the layout -import {flexlayout_json as json} from './demo-1-config' -// import {flexlayout_json as json} from './demo-tabbed-config' -// import {flexlayout_json as json} from './devel-config' +import {flexlayout_json as json} from '../demo-1-config' +// import {flexlayout_json as json} from '../demo-tabbed-config' +// import {flexlayout_json as json} from '../devel-config' + + +export const tab_types = { + "video": VideoStream, + "demo-video": DemoVideoStream, + "line-chart": RosLineChart, + "demo-line-chart": DemoLineChart, + "h-gauge": RosHGauge, + "memory": RosMemoryRanked, + "demo-memory": DemoMemoryRanked, + "int-trace": RosInteractionTrace, + "demo-int-trace": DemoInteractionTrace, + "string": DemoString, + "demo-latest-interaction": DemoLatestInteraction, + "latest-interaction": RosLatestInteraction, + "mood": RosMood, + "demo-mood": DemoMood, + "graph": Graph, + "demo-graph": DemoGraph, +} const Board = observer( class Board extends React.Component { @@ -26,6 +48,8 @@ class Board extends React.Component { super(props) this.state = {model: FlexLayout.Model.fromJson(json)} + reaction(() => this.props.store.sharedData.get('newTab'), this.addTab.bind(this)); + this.refLayout = React.createRef(); } @@ -34,25 +58,6 @@ class Board extends React.Component { * @param node node to be added, as per the vocabulary of FlexLayout */ factory(node) { - const tab_types = { - "video": VideoStream, - "demo-video": DemoVideoStream, - "line-chart": RosLineChart, - "demo-line-chart": DemoLineChart, - "h-gauge": RosHGauge, - "memory": RosMemoryRanked, - "demo-memory": DemoMemoryRanked, - "int-trace": RosInteractionTrace, - "demo-int-trace": DemoInteractionTrace, - "string": DemoString, - "demo-latest-interaction": DemoLatestInteraction, - "latest-interaction": RosLatestInteraction, - "mood": RosMood, - "demo-mood": DemoMood, - "graph": Graph, - "demo-graph": DemoGraph, - } - /** This function gives the React Component correspinding to a tab type. * * A set of React components are mapped to "tab types" (which are strings). Hence, the tab type "video" maps @@ -77,12 +82,15 @@ class Board extends React.Component { }, null); } - onAdd(event) { - this.refLayout.current.addTabWithDragAndDropIndirect("Add panel<br>(Drag to location)", { - component: "h-gauge", - name: "added", - config: {value: 0.5} - }, null); + addTab() { + if (this.props.store.sharedData.has('newTab')) { + const {component, name} = this.props.store.sharedData.get('newTab'); + this.refLayout.current.addTabWithDragAndDrop(`Add ${name}<br>(Drag to location)`, { + component: component, + name: name + }, null); + this.props.store.sharedData.delete('newTab') + } } /** @@ -123,7 +131,9 @@ class Board extends React.Component { // If no modal is active, display the relevant one; // otherwise, get out of it. if (!this.props.store.modal.enabled) { - this.props.store.displayModal(modeName, selectedTab.getId()); + this.props.store.displayModal( + <GadgetModal store={this.props.store} component={selectedTab.getId()} mode={modeName}/> + ); } else { this.props.store.exitModal(); } diff --git a/src/Board/NewWidget.js b/src/Board/NewWidget.js new file mode 100644 index 0000000000000000000000000000000000000000..b7f2d6f537b000340c19a9334eceb525a9556695 --- /dev/null +++ b/src/Board/NewWidget.js @@ -0,0 +1,100 @@ +import React from 'react'; +import {decorate, observable} from 'mobx' +import {observer} from 'mobx-react' +import {map} from 'lodash' + +// Mappings from 'string' types (as per tab_types in Board.js) and display names of the available widgets. +const tab_names = { + "video": "Video stream", + "line-chart": "Simple line chart", + "h-gauge": "Gauge", + "memory": "Agent's memory", + "int-trace": "Interaction trace", + "latest-interaction": "Latest interaction", + "mood": "Current mood", + "graph": "Graph" +} +const demo_tab_names = { + "demo-video": "Video stream demo", + "demo-line-chart": "Line chart demo", + "demo-memory": "Agent's memory demo", + "demo-int-trace": "Interaction trace demo", + "demo-latest-interaction": "Latest interaction demo", + "demo-mood": "Current mood demo", + "demo-graph": "Graph demo", + "string": "ROS debug", +} + +/** + * This React component appears in a modal, once we click on the '+' button in the menu. It offers the user to choose + * which component he/she wants to add to the dashboard. + */ +export const NewWidget = observer( +class NewWidget extends React.Component { + selected = Object.keys(tab_names)[0]; + enableDemoWidgets = false; + + /** + * Set the data for a new tab/widget to be added in the dashboard. This will trigger a method in Board.js, thanks to + * Mobx's magic. It also closes this modal. + */ + addTab() { + this.props.store.sharedData.set('newTab', { + component: this.selected, + name: tab_names[this.selected] + }); + this.props.store.exitModal(); + } + + /** + * This method is used to generate radio inputs for the widgets we offer the user to add to the dashboard. It's + * called in a map (Lodash style). + * @param {string} name Display name of the widget + * @param {string} type string for the type of the widget, as used in Board.js' tab_types + */ + inputFromTabName(name, type) { + return ( + <div key={type}> + <label> + <input + type="radio" + name="component-type" + value={type} + checked={type === this.selected} + onChange={(changeEvent) => {this.selected = changeEvent.target.value}} + /> + {name} + </label> + </div> + ); + } + + render() { + let inputs = map(tab_names, this.inputFromTabName.bind(this)); + // if the user chose to see the demo widgets, add these to the list + if (this.enableDemoWidgets) { + inputs.push(map(demo_tab_names, this.inputFromTabName.bind(this))); + } + + return ( + <div> + {inputs} + + <label> + <input + type="checkbox" + checked={this.enableDemoWidgets} + onChange={(changeEvent) => {this.enableDemoWidgets = changeEvent.target.checked;}} + /> + Show demo widgets + </label><br/> + + <button onClick={this.addTab.bind(this)}>Add to the dashboard</button> + </div> + ) + } +}) +decorate(NewWidget, { + selected: observable, + enableDemoWidgets: observable, +}) diff --git a/src/Menu/Menu.css b/src/Menu/Menu.css index 03775738a72944038975928736e5ee47aa4b2adf..99e5148ed31117d09daff0bb1d771f0e7041f98b 100644 --- a/src/Menu/Menu.css +++ b/src/Menu/Menu.css @@ -75,11 +75,23 @@ } .lock-button { - margin-left:70px; + margin-left: 70px; } .about-button { - margin-left:30px; + margin-left: 30px; +} + +.add-button { + margin-left: 50px; + position: fixed; + bottom: 5vh; + display: inline-block; +} + +.add-button:hover { + filter: invert(67%) sepia(71%) saturate(551%) hue-rotate(1deg) brightness(111%) contrast(86%); + cursor: pointer; } .sidenav---sidenav---_2tBP.sidenav---expanded---1KdUL .title { diff --git a/src/Menu/index.js b/src/Menu/index.js index 7cb5bf297de22215ea4f76f13c003807cd16acac..cf3aa45e8c951163fd898214c643bf7a8f3a987f 100644 --- a/src/Menu/index.js +++ b/src/Menu/index.js @@ -1,10 +1,33 @@ import React from "react"; - +import {observer} from "mobx-react"; import SideNav, { NavItem, NavIcon, NavText } from '@trendmicro/react-sidenav'; import './Menu.css' +import {NewWidget} from '../Board/NewWidget' + + +export const Menu = observer( +function Menu(props) { + const lock = props.store.sharedData.get('lock') || 'closed'; + function toggle_lock() { + const new_value = (lock === 'closed') ? 'open' : 'closed'; + props.store.sharedData.set('lock', new_value); + } + + function add_widget() { + props.store.displayModal(<NewWidget store={props.store}/>); + } + // Display an "add" icon if the lock is open. This icon is used to add new widgets to the dashboard. + let add_button = null; + if (lock === 'open') { + add_button = ( + <img className="footer add-button" + onClick={add_widget} + src={window.location.origin + "/images/icons/add_circle_outline.svg"} + alt="add a new widget" + />); + } -export function Menu(props) { return( <SideNav @@ -29,12 +52,10 @@ export function Menu(props) { <NavItem eventKey="demo"> <NavIcon> - {/* <a href="#"> */} - <img className="menu-button" - src={window.location.origin + "/images/icons/dashboard-grey.svg"} - alt="Demo mode" - /> - {/* </a> */} + <img className="menu-button" + src={window.location.origin + "/images/icons/dashboard-grey.svg"} + alt="Demo mode" + /> </NavIcon> <NavText> Demo @@ -51,12 +72,10 @@ export function Menu(props) { <NavItem eventKey="memory"> <NavIcon> - {/* <a> */} - <img className="menu-button" - src={window.location.origin + "/images/icons/memory-grey.svg"} - alt="memory dashboard" - /> - {/* </a> */} + <img className="menu-button" + src={window.location.origin + "/images/icons/memory-grey.svg"} + alt="memory dashboard" + /> </NavIcon> <NavText> Memory @@ -65,12 +84,10 @@ export function Menu(props) { <NavItem eventKey="traces"> <NavIcon> - {/* <a> */} - <img className="menu-button" - src={window.location.origin + "/images/icons/trace-grey.svg"} - alt="trace dashboard" - /> - {/* </a> */} + <img className="menu-button" + src={window.location.origin + "/images/icons/trace-grey.svg"} + alt="trace dashboard" + /> </NavIcon> <NavText> Traces @@ -79,12 +96,10 @@ export function Menu(props) { <NavItem eventKey="mental-states"> <NavIcon> - {/* <a> */} - <img className="menu-button" - src={window.location.origin + "/images/icons/mood-grey.svg"} - alt="mood dashboard" - /> - {/* </a> */} + <img className="menu-button" + src={window.location.origin + "/images/icons/mood-grey.svg"} + alt="mood dashboard" + /> </NavIcon> <NavText> Mental states @@ -93,12 +108,10 @@ export function Menu(props) { <NavItem eventKey="decision-making"> <NavIcon> - {/* <a> */} - <img className="menu-button" - src={window.location.origin + "/images/icons/choice-grey.svg"} - alt="choice dashboard" - /> - {/* </a> */} + <img className="menu-button" + src={window.location.origin + "/images/icons/choice-grey.svg"} + alt="choice dashboard" + /> </NavIcon> <NavText> Decision making @@ -111,12 +124,10 @@ export function Menu(props) { <NavItem eventKey="video"> <NavIcon> - {/* <a> */} - <img className="menu-button" - src={window.location.origin + "/images/icons/video-grey.svg"} - alt="video dashboard" - /> - {/* </a> */} + <img className="menu-button" + src={window.location.origin + "/images/icons/video-grey.svg"} + alt="video dashboard" + /> </NavIcon> <NavText> Video @@ -125,12 +136,10 @@ export function Menu(props) { <NavItem eventKey="audio"> <NavIcon> - {/* <a> */} <img className="menu-button" src={window.location.origin + "/images/icons/microphone-grey.svg"} alt="microphone dashboard" /> - {/* </a> */} </NavIcon> <NavText> Audio @@ -139,18 +148,18 @@ export function Menu(props) { <NavItem eventKey="sensory"> <NavIcon> - {/* <a> */} <img className="menu-button" src={window.location.origin + "/images/icons/fingerprint-grey.svg"} alt="sensory dashboard" /> - {/* </a> */} </NavIcon> <NavText> Sensory </NavText> </NavItem> + {add_button} + <a href="more.html"> <img className="footer menu-button about-button" src={window.location.origin + "/images/icons/about.svg"} @@ -158,13 +167,12 @@ export function Menu(props) { /> </a> - {/* <a> */} - <img className="footer menu-button lock-button" - src={window.location.origin + "/images/icons/lock_closed-grey.svg"} - alt="lock/unlock the dashboard" - /> - {/* </a> */} + <img className="footer menu-button lock-button" + src={window.location.origin + "/images/icons/lock_" + lock + "-grey.svg"} + alt="lock/unlock the dashboard" + onClick={toggle_lock} + /> </SideNav.Nav> </SideNav> ) -} +}) diff --git a/src/index.js b/src/index.js index 7d2c6311c71fd49f4041c843b693b143c9f91121..4a283e657ca8044feb2bf1db0ce6e84489fda65b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,15 +1,17 @@ import React from "react"; import ReactDOM from "react-dom"; +// A little library to display modals on top of the page +import PureModal from 'react-pure-modal'; +import 'react-pure-modal/dist/react-pure-modal.min.css'; // MobX, a state manager import {decorate, observable} from "mobx" import {observer} from 'mobx-react' // ROS library import RosLib from 'roslib'; // React Components to be displayed -import {Board} from './Board'; +import {Board} from './Board/Board'; import {Header} from './Header'; import {Menu} from "./Menu"; -import {Modal} from './utils/modeModal'; import 'bootstrap/dist/css/bootstrap.min.css' // CSS common to the whole app @@ -20,12 +22,10 @@ class ObservableStore { sharedData = observable.map() modal = { enabled: false, - mode: null, component: null, } - displayModal(mode, component) { + displayModal(component) { this.modal.enabled = true; - this.modal.mode = mode; this.modal.component = component; } exitModal() { @@ -33,6 +33,7 @@ class ObservableStore { this.modal.mode = null; this.modal.component = null; } + dragging = false } decorate(ObservableStore, { components: observable, @@ -67,25 +68,36 @@ class Main extends React.Component { } } + // When the esc key is pressed, if the modal is enabled, disable it + handleKeyDown = (event) => { + if(event.keyCode === 27 && this.store.modal.enabled === true) { + this.store.modal.enabled = false; + } + } + + componentDidMount(){ + document.addEventListener("keydown", this.handleKeyDown, false); + } + componentWillUnmount() { - if (this.ros) { - this.ros.close() - } + if (this.ros) { + this.ros.close() + } + + document.removeEventListener("keydown", this.handleKeyDown, false); } render() { - let modal = null; - if (this.store.modal.enabled) { - modal = ( - <div id="overlay" tabIndex="-1" role="dialog"> - <Modal store={this.store}/> - </div>); + function addTab(component, name) { + return () => { + this.store.sharedData.set('newTab', {component: component, name: name}); + } } return ( <div id="level-1"> <div id="menu"> - <Menu ros={this.ros}/> + <Menu ros={this.ros} store={this.store} drag={addTab.bind(this)}/> </div> <div id="level-2"> <div id="header"> @@ -95,7 +107,14 @@ class Main extends React.Component { <Board ros={this.ros} store={this.store}/> </div> </div> - {modal} + <PureModal + width = "700px" + onClose={() => {this.store.modal.enabled = false}} + isOpen = {this.store.modal.enabled} + ref="modal" + > + {this.store.modal.component} + </PureModal> </div> ) } diff --git a/src/utils/modeModal.js b/src/utils/GadgetModal.js similarity index 93% rename from src/utils/modeModal.js rename to src/utils/GadgetModal.js index 544a581d919d1710a1b2b4f320bba56298f10f42..db8ec3afa34e58ff672ea5fc4c7da0fbcbdb6e8c 100644 --- a/src/utils/modeModal.js +++ b/src/utils/GadgetModal.js @@ -5,19 +5,19 @@ import { merge } from 'lodash/object'; /** * This component displays either the readme information about a component or a form to change the settings of a * component. - * + * * All changes to the node's settings are saved through a mobX store (passed as props). - * - * Expected React properties : + * + * Expected React properties: * - node: as provided by the `Board` component to all its children * - store: object containing information about all relevant React components, as well as information on the current * mode. */ -export class Modal extends Component { +export class GadgetModal extends Component { constructor(props) { super(props); - - this.component = this.props.store.components[this.props.store.modal.component]; + + this.component = this.props.store.components[this.props.component]; this.hasReadme = 'readme' in this.component; this.configurable = 'configSchema' in this.component; } @@ -27,7 +27,7 @@ export class Modal extends Component { * attribute of the modal's configuration. */ render() { - const mode = this.props.store.modal.mode; + const mode = this.props.mode; if (this.configurable && mode === "settings") { return [ <Form @@ -64,5 +64,5 @@ export class Modal extends Component { this.props.store.exitModal(); return config; } - + }