diff --git a/app/components/ProjectBrowserTree.jsx b/app/components/ProjectBrowserTree.jsx
new file mode 100644
index 00000000..73127332
--- /dev/null
+++ b/app/components/ProjectBrowserTree.jsx
@@ -0,0 +1,81 @@
+import R from 'ramda';
+import React from 'react';
+import classNames from 'classnames';
+import Tree from 'react-ui-tree';
+
+
+class ProjectBrowserTree extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ active: null,
+ tree: props.tree,
+ };
+
+ this.onClickNode = this.onClickNode.bind(this);
+ this.onChange = this.onChange.bind(this);
+ this.renderNode = this.renderNode.bind(this);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.tree !== this.state.tree) {
+ this.updateTree(nextProps.tree);
+ }
+ }
+
+ onClickNode(node) {
+ const nodeRef = node;
+ if (!node.hasOwnProperty('leaf')) {
+ nodeRef.collapsed = !nodeRef.collapsed;
+ }
+
+ this.setState(R.assoc('active', nodeRef, this.state));
+ }
+ onChange(tree) {
+ this.props.onChange(tree);
+ }
+
+ updateTree(tree) {
+ this.setState(R.assoc('tree', tree, this.state));
+ }
+
+ bindOnClickNode(node) {
+ return this.onClickNode.bind(this, node);
+ }
+
+ renderNode(node) {
+ const nodeClassName = classNames('node', {
+ 'is-active': node === this.state.active,
+ 'is-current': (node.hasOwnProperty('leaf') && node.id === this.props.currentPatchId),
+ });
+
+ return (
+
+ {node.module}
+
+ );
+ }
+
+ render() {
+ return (
+
-
-
+
+
+
+
diff --git a/app/containers/ProjectBrowser.jsx b/app/containers/ProjectBrowser.jsx
index 9c5bcd98..7f2d5142 100644
--- a/app/containers/ProjectBrowser.jsx
+++ b/app/containers/ProjectBrowser.jsx
@@ -1,27 +1,45 @@
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
-import * as Actions from '../actions';
+// import * as Actions from '../actions';
import Selectors from '../selectors';
+import ProjectBrowserTree from '../components/ProjectBrowserTree';
class ProjectBrowser extends React.Component {
constructor(props) {
super(props);
- this.displayName = 'ProjectBrowser';
+
+ this.onTreeChange = this.onTreeChange.bind(this);
}
+
+ onTreeChange(newTree) {
+ console.log('tree changed!', newTree);
+ }
+
render() {
- return
ProjectBrowser
;
+ return (
+
+ );
}
}
ProjectBrowser.propTypes = {
+ tree: React.PropTypes.object.isRequired,
actions: React.PropTypes.object,
patches: React.PropTypes.object,
currentPatchId: React.PropTypes.number,
};
const mapStateToProps = (state) => ({
+ tree: Selectors.Project.getTreeView(state),
patches: Selectors.Project.getPatches(state),
currentPatchId: Selectors.Editor.getCurrentPatchId(state),
});
diff --git a/app/selectors/project.js b/app/selectors/project.js
index b12e0ef6..d80f7d32 100644
--- a/app/selectors/project.js
+++ b/app/selectors/project.js
@@ -596,3 +596,59 @@ export const getLinkGhost = (state) => {
to: { x: 0, y: 0 },
};
};
+
+/*
+ Folders
+*/
+export const getFolders = R.pipe(
+ getProject,
+ R.prop('folders')
+);
+
+/*
+ Tree view (get / parse)
+*/
+export const getTreeView = (state) => {
+ const makeTree = (folders, patches, parentId) => {
+ const foldersAtLevel = R.pipe(
+ R.values,
+ R.filter(R.propEq('parentId', parentId))
+ )(folders);
+ const patchesAtLevel = R.pipe(
+ R.values,
+ R.map(R.prop('present')),
+ R.filter(R.propEq('folderId', parentId))
+ )(patches);
+
+ return R.concat(
+ R.map(
+ folder => ({
+ id: folder.id,
+ module: folder.name,
+ collapsed: true,
+ children: makeTree(folders, patches, folder.id),
+ }),
+ foldersAtLevel
+ ),
+ R.map(
+ patch => ({
+ id: patch.id,
+ module: patch.name,
+ leaf: true,
+ }),
+ patchesAtLevel
+ )
+ );
+ };
+
+ const folders = getFolders(state);
+ const patches = getPatches(state);
+
+ return {
+ module: 'Project',
+ collapsed: false,
+ children: makeTree(folders, patches, null),
+ };
+};
+
+// export const parseTreeView = (state) => {};
diff --git a/app/state.js b/app/state.js
index 9239e4e6..2c7e97d6 100644
--- a/app/state.js
+++ b/app/state.js
@@ -78,7 +78,8 @@ const initialState = {
},
},
nodeTypes,
- counter: {
+ folders: {},
+ counter: {
patches: 1,
nodes: 1,
pins: 1,
diff --git a/app/styles/abstracts/mixins.scss b/app/styles/abstracts/mixins.scss
index d806743a..483f5f91 100644
--- a/app/styles/abstracts/mixins.scss
+++ b/app/styles/abstracts/mixins.scss
@@ -22,7 +22,7 @@
@mixin inspector-widget() {
padding: 4px;
margin-bottom: 2px;
- border-top: 1px solid #ccc;
+ // border-top: 1px solid #ccc;
overflow: hidden;
font-size: $font-size-m;
diff --git a/app/styles/abstracts/variables.scss b/app/styles/abstracts/variables.scss
index 0081507c..d4e7b74a 100644
--- a/app/styles/abstracts/variables.scss
+++ b/app/styles/abstracts/variables.scss
@@ -23,3 +23,13 @@ $color-ctrl-background: #2d4c29;
$color-ctrl-background-text: #fff;
$snackbar-width: 200px;
+$sidebar-width: 200px;
+
+$sidebar-background: #eee;
+$sidebar-color: #000;
+$sidebar-title-color: #999;
+$sidebar-title-divider: #ccc;
+$sidebar-selected-background: rgb(128, 160, 190);
+$sidebar-selected-color: #000;
+$sidebar-block-shadow: inset 0 5px 15px 1px rgba(0,0,0,.1);
+$sidebar-block-divider: 1px solid #fff;
\ No newline at end of file
diff --git a/app/styles/components/Inspector.scss b/app/styles/components/Inspector.scss
index 463470bf..ae2c9b74 100644
--- a/app/styles/components/Inspector.scss
+++ b/app/styles/components/Inspector.scss
@@ -1,16 +1,10 @@
.Inspector {
- position: absolute;
- width: 200px;
+ display: block;
+ width: 100%;
height: 100%;
- background: #eee;
- color: #000;
-
- .title {
- display: block;
- padding: 26px 20px 4px 8px;
- color: #999;
- }
+ background: $sidebar-background;
+ color: $sidebar-color;
ul {
padding: 0;
diff --git a/app/styles/components/ProjectBrowserTree.scss b/app/styles/components/ProjectBrowserTree.scss
new file mode 100644
index 00000000..3a2b7025
--- /dev/null
+++ b/app/styles/components/ProjectBrowserTree.scss
@@ -0,0 +1,87 @@
+.ProjectBrowserTree {
+ width: $sidebar-width;
+ background: $sidebar-background;
+ color: $sidebar-color;
+ padding: 8px 0;
+
+ box-sizing: border-box;
+
+ * { box-sizing: border-box; }
+
+ .f-no-select {
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ }
+ .m-tree {
+ position: relative;
+ overflow: hidden;
+ font-family: $font-family-normal;
+ font-size: $font-size-m;
+
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ }
+ .m-draggable {
+ position: absolute;
+ opacity: 0.8;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ }
+ .m-node {
+ box-sizing: border-box;
+
+ &.placeholder {
+ border: 1px dashed $sidebar-selected-background;
+
+ & > * {
+ visibility: hidden;
+ }
+ }
+
+ .inner {
+ position: relative;
+ cursor: pointer;
+ padding-left: 12px;
+ }
+ .node {
+ display: block;
+ padding: 4px 6px;
+
+ &.is-active {
+ border-left: 100px solid $sidebar-selected-background;
+ border-right: 100px solid $sidebar-selected-background;
+ margin-left: -100px;
+ background: $sidebar-selected-background;
+ color: #006;
+ }
+ }
+ .collapse {
+ position: absolute;
+ left: 0;
+ padding: 2px 6px;
+ cursor: pointer;
+ }
+ .children {
+ display: block;
+ }
+
+ .caret-down:before {
+ font-size: $font-size-m;
+ content: '\25BE';
+ }
+ .caret-right:before {
+ font-size: $font-size-m;
+ content: '\25B8';
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/styles/components/Sidebar.scss b/app/styles/components/Sidebar.scss
new file mode 100644
index 00000000..259acf7d
--- /dev/null
+++ b/app/styles/components/Sidebar.scss
@@ -0,0 +1,20 @@
+.Sidebar {
+ position: absolute;
+ width: $sidebar-width;
+ height: 100%;
+
+ background: $sidebar-background;
+
+ & > * {
+ box-shadow: $sidebar-block-shadow;
+ border-bottom: $sidebar-block-divider;
+ }
+
+ .title {
+ display: block;
+ margin: 0 8px;
+ padding: 10px 20px 4px 0;
+ color: $sidebar-title-color;
+ border-bottom: 1px solid $sidebar-title-divider;
+ }
+}
\ No newline at end of file
diff --git a/app/styles/main.scss b/app/styles/main.scss
index 35e9fe9e..2fa6d8d4 100644
--- a/app/styles/main.scss
+++ b/app/styles/main.scss
@@ -14,6 +14,7 @@
'base/base';
@import
+ 'components/Sidebar',
'components/PatchWrapper',
'components/PatchSVG',
'components/BackgroundLayer',
@@ -25,4 +26,5 @@
'components/Inspector',
'components/SnackBarList',
'components/SnackBarError',
+ 'components/ProjectBrowserTree',
'components/CreateNodeWidget';
diff --git a/package.json b/package.json
index b95ef815..8b673cb6 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
"react-event-listener": "^0.2.1",
"react-redux": "^4.0.6",
"react-skylight": "^0.4.0",
+ "react-ui-tree": "^2.5.0",
"redux": "^3.0.5",
"redux-thunk": "^2.1.0",
"redux-undo": "^1.0.0-beta8",