diff --git a/webapp/components/general/FileBrowser.js b/webapp/components/general/FileBrowser.js index 6eed874a..c9df9fa9 100644 --- a/webapp/components/general/FileBrowser.js +++ b/webapp/components/general/FileBrowser.js @@ -1,5 +1,4 @@ import React from 'react'; -import Tree from '@metacell/geppetto-meta-ui/tree-viewer/Tree'; import Button from '@material-ui/core/Button'; import { changeNodeAtPath, walk } from 'react-sortable-tree'; import Dialog from '@material-ui/core/Dialog'; @@ -10,6 +9,7 @@ import DialogContent from '@material-ui/core/DialogContent'; import { Tooltip } from 'netpyne/components'; import IconButton from '@material-ui/core/IconButton'; import Icon from '@material-ui/core/Icon'; +import Tree from './tree/Tree'; import Utils from '../../Utils'; import { bgLight, fontColor } from '../../theme'; diff --git a/webapp/components/general/tree/Tree.js b/webapp/components/general/tree/Tree.js new file mode 100644 index 00000000..930ea60f --- /dev/null +++ b/webapp/components/general/tree/Tree.js @@ -0,0 +1,196 @@ +import React from 'react'; + +var SortableTree = require('react-sortable-tree').default; +var toggleExpandedForAll = require('react-sortable-tree').toggleExpandedForAll; +var changeNodeAtPath = require('react-sortable-tree').changeNodeAtPath; +var walk = require('react-sortable-tree').walk; +require('./Tree.less'); + +export default class Tree extends React.Component { + constructor (props) { + super(props); + + this.updateTreeData = this.updateTreeData.bind(this); + this.expandAll = this.expandAll.bind(this); + this.collapseAll = this.collapseAll.bind(this); + this.state = { treeData: this.props.treeData }; + } + + updateTreeData(treeData) { + this.setState({ treeData }); + } + + expand(expanded) { + this.setState({ + treeData: toggleExpandedForAll({ + treeData: this.state.treeData, + expanded, + }), + }); + } + + expandAll() { + this.expand(true); + } + + collapseAll() { + this.expand(false); + } + + handleClick(event, rowInfo) { + var toggleMode = this.props.toggleMode; + var currentTreeData = this.state.treeData; + // If node has children, we expand/collapse the node + if ( + rowInfo.node.children != undefined && + rowInfo.node.children.length > 0 + ) { + // If parents can be activate, iterate over the whole tree + if (this.props.activateParentsNodeOnClick) { + walk({ + treeData: currentTreeData, + getNodeKey: ({ treeIndex }) => treeIndex, + ignoreCollapsed: true, + callback: (rowInfoIter) => { + var isActive = rowInfoIter.treeIndex == rowInfo.treeIndex; + /* + * If toggleMode just toggle to activate/inactivate selected node and expand/collapse + * If non toggle mode inactive all nodes but selected and expand/collapse + */ + if (isActive && toggleMode) { + rowInfoIter.node.active = !rowInfoIter.node.active; + rowInfoIter.node.expanded = !rowInfoIter.node.expanded; + currentTreeData = changeNodeAtPath({ + treeData: currentTreeData, + path: rowInfoIter.path, + newNode: rowInfoIter.node, + getNodeKey: ({ treeIndex }) => treeIndex, + ignoreCollapsed: true, + }); + } else if (isActive && !toggleMode) { + rowInfoIter.node.active = isActive; + rowInfoIter.node.expanded = !rowInfoIter.node.expanded; + currentTreeData = changeNodeAtPath({ + treeData: currentTreeData, + path: rowInfoIter.path, + newNode: rowInfoIter.node, + getNodeKey: ({ treeIndex }) => treeIndex, + ignoreCollapsed: true, + }); + } else if (isActive != rowInfoIter.node.active && !toggleMode) { + rowInfoIter.node.active = isActive; + currentTreeData = changeNodeAtPath({ + treeData: currentTreeData, + path: rowInfoIter.path, + newNode: rowInfoIter.node, + getNodeKey: ({ treeIndex }) => treeIndex, + ignoreCollapsed: true, + }); + } + }, + }); + } else { + rowInfo.node.expanded = !rowInfo.node.expanded; + currentTreeData = changeNodeAtPath({ + treeData: currentTreeData, + path: rowInfo.path, + newNode: rowInfo.node, + getNodeKey: ({ treeIndex }) => treeIndex, + ignoreCollapsed: true, + }); + } + } else if (rowInfo.node.children == undefined) { + // If node has no children, we select the node + walk({ + treeData: currentTreeData, + getNodeKey: ({ treeIndex }) => treeIndex, + ignoreCollapsed: true, + callback: (rowInfoIter) => { + var isActive = rowInfoIter.treeIndex == rowInfo.treeIndex; + /* + * If toggleMode just toggle to activate/inactivate selected node + * If non toggle mode inactive all nodes but selected + */ + if (isActive && toggleMode) { + rowInfoIter.node.active = !rowInfoIter.node.active; + currentTreeData = changeNodeAtPath({ + treeData: currentTreeData, + path: rowInfoIter.path, + newNode: rowInfoIter.node, + getNodeKey: ({ treeIndex }) => treeIndex, + ignoreCollapsed: true, + }); + } else if (isActive != rowInfoIter.node.active && !toggleMode) { + rowInfoIter.node.active = isActive; + currentTreeData = changeNodeAtPath({ + treeData: currentTreeData, + path: rowInfoIter.path, + newNode: rowInfoIter.node, + getNodeKey: ({ treeIndex }) => treeIndex, + ignoreCollapsed: true, + }); + } + }, + }); + } + + // Update tree with latest changes + this.updateTreeData(currentTreeData); + + // If there is a callback, we use it + if (this.props.handleClick != undefined) { + this.props.handleClick(event, rowInfo); + } + } + + getNodeProps(rowInfo) { + var nodeProps = {}; + nodeProps['onClick'] = (event) => this.handleClick(event, rowInfo); + + if (this.props.getButtons !== undefined) { + nodeProps['buttons'] = this.props.getButtons(rowInfo); + } + if (rowInfo.node.instance !== undefined) { + nodeProps['style'] = { cursor: 'pointer' }; + } + if (rowInfo.node.active) { + nodeProps['className'] = 'activeNode'; + } + if (this.props.getNodesProps !== undefined) { + nodeProps['title'] = this.props.getNodesProps(rowInfo); + } + return nodeProps; + } + + render() { + var onlyExpandSearchedNodes = + this.props.searchQuery !== undefined && this.props.searchQuery !== null; + return ( +
+ {this.props.controls ? this.props.controls : null} + this.getNodeProps(rowInfo)} + onChange={(treeData) => this.updateTreeData(treeData)} + searchQuery={ + this.props.searchQuery !== undefined ? this.props.searchQuery : null + } + onlyExpandSearchedNodes={ + this.props.onlyExpandSearchedNodes !== undefined + ? this.props.onlyExpandSearchedNodes + : false + } + /> +
+ ); + } +}; diff --git a/webapp/components/general/tree/Tree.less b/webapp/components/general/tree/Tree.less new file mode 100644 index 00000000..05705211 --- /dev/null +++ b/webapp/components/general/tree/Tree.less @@ -0,0 +1,47 @@ +.treeViewer { + height: 100%; +} +.rst__tree { + height:100%; +} +.rst__rowTitleWithSubtitle{ + height: initial; +} +.rst__rowContents{ + background: #2e2a2a; + border: 0px; + color: #e9e9e9; + min-width: 160px; +} + +.rst__lineChildren:after { + background-color: white; +} + +.rst__lineBlock:before, +.rst__lineBlock:after { + background-color: white; +} + +.rst__rowWrapper { + padding: 3px 10px 2px 0; +} + +.rst__collapseButton, +.rst__expandButton { + box-shadow: 0 0 0 1px #FFF; + width: 12px; + height: 12px; +} + +.rst__collapseButton:hover:not(:active), +.rst__expandButton:hover:not(:active){ + width: 12px; + height: 12px; + background-size: initial; +} + +.rst__collapseButton:focus, +.rst__expandButton:focus{ + box-shadow: 0 0 0 1px #FFF, 0 0 1px 3px #83bef9; +} \ No newline at end of file diff --git a/webapp/components/instantiation/NetPyNEInstantiated.js b/webapp/components/instantiation/NetPyNEInstantiated.js index ebcc47e2..254eee6d 100644 --- a/webapp/components/instantiation/NetPyNEInstantiated.js +++ b/webapp/components/instantiation/NetPyNEInstantiated.js @@ -7,7 +7,7 @@ import CameraControls from '@metacell/geppetto-meta-ui/camera-controls/CameraCon // import ControlPanel from 'geppetto-client/js/components/interface/controlPanel/controlpanel'; import { NetWorkControlButtons } from 'netpyne/components'; -import { primaryColor, canvasBgDark, canvasBgLight } from '../../theme'; +import { primaryColor, canvasBgDark, canvasBgLight, bgRegular } from '../../theme'; import { THEMES } from '../../constants'; const CANVAS_LIGHT = 'canvas-toolbar-btns-light'; @@ -222,7 +222,7 @@ class NetPyNEInstantiated extends React.Component { cameraOptions={camOptions} key="CanvasContainer" data={canvasData} - backgroundColor="#000000" + backgroundColor={bgRegular} />
{/* TODO: refactor the control panel with the list viewer diff --git a/webapp/css/flexlayout.less b/webapp/css/flexlayout.less index 7d1c6f35..554d85a7 100644 --- a/webapp/css/flexlayout.less +++ b/webapp/css/flexlayout.less @@ -1,12 +1,42 @@ @import "variables"; -.flexlayout__layout { +div.flexlayout__layout { border-left: none !important; border-right: none !important; background-color: transparent; top: 0px; border-top: none; + .flexlayout__tab_button { + &:hover { + .flexlayout__tab_button_trailing { + background: none; + } + } + + &.flexlayout__tab_button--selected { + .flexlayout__tab_button_trailing { + background: none; + } + } + } + + .flexlayout__tab_button_top { + box-shadow: none; + } + + .flexlayout__tabset_tabbar_outer { + background-color: transparent; + } + + .flexlayout__tabset_tabbar_outer_top { + border-bottom: none; + } + + .flexlayout__tabset_tabbar_inner_tab_container_top { + border-top: none; + } + a { color: @colorLink; } diff --git a/webapp/css/netpyne.less b/webapp/css/netpyne.less index f0e81aeb..6a0ac8db 100644 --- a/webapp/css/netpyne.less +++ b/webapp/css/netpyne.less @@ -8,6 +8,16 @@ body { font-size: 16px; } +html { + * { + box-sizing: border-box; + } + body { + background: @bgDarker; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + } +} + :root { --m2: 8px; --m4: 16px; @@ -142,6 +152,7 @@ button.actionButton { color: white; left: 22px; pointer-events: none; + word-wrap: break-word; z-index: 10; } .breadcrumbButtony button { @@ -574,13 +585,21 @@ div.flexlayout__layout { } } @-webkit-keyframes spin { - 0% { -webkit-transform: rotate(0deg); } - 100% { -webkit-transform: rotate(360deg); } + 0% { + -webkit-transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + } } @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } body { @@ -588,3 +607,13 @@ body { border-collapse: separate; } } + +.instantiatedContainer { + .position-toolbar { + button { + background-color: rgba(255, 255, 255, 0.2); + font-size: 0.875rem; + border-radius: 0; + } + } +}