From c8aa6032dddae358c8ffd962a1222fe979d3a754 Mon Sep 17 00:00:00 2001 From: Kirill Shumilov Date: Tue, 2 Aug 2016 18:52:44 +0300 Subject: [PATCH] feat([projectBrowser, state, treeView, Sidebar, styles]): add projectBrowser, that contains folders and patches, that just could be selected and dragged at this moment, but without any outcome for state --- app/components/ProjectBrowserTree.jsx | 81 +++++++++++++++++ app/components/Sidebar.jsx | 13 +++ app/containers/Editor.jsx | 17 ++-- app/containers/ProjectBrowser.jsx | 24 ++++- app/selectors/project.js | 56 ++++++++++++ app/state.js | 3 +- app/styles/abstracts/mixins.scss | 2 +- app/styles/abstracts/variables.scss | 10 +++ app/styles/components/Inspector.scss | 14 +-- app/styles/components/ProjectBrowserTree.scss | 87 +++++++++++++++++++ app/styles/components/Sidebar.scss | 20 +++++ app/styles/main.scss | 2 + package.json | 1 + 13 files changed, 308 insertions(+), 22 deletions(-) create mode 100644 app/components/ProjectBrowserTree.jsx create mode 100644 app/components/Sidebar.jsx create mode 100644 app/styles/components/ProjectBrowserTree.scss create mode 100644 app/styles/components/Sidebar.scss 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 ( +
+ +
+ ); + } +} + +ProjectBrowserTree.propTypes = { + tree: React.PropTypes.object.isRequired, + currentPatchId: React.PropTypes.number, + onChange: React.PropTypes.func, +}; + +export default ProjectBrowserTree; diff --git a/app/components/Sidebar.jsx b/app/components/Sidebar.jsx new file mode 100644 index 00000000..2a44fc26 --- /dev/null +++ b/app/components/Sidebar.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +const Sidebar = ({ children }) => ( +
+ {children} +
+); + +Sidebar.propTypes = { + children: React.PropTypes.arrayOf(React.PropTypes.element), +}; + +export default Sidebar; diff --git a/app/containers/Editor.jsx b/app/containers/Editor.jsx index f5b11c47..d99866b0 100644 --- a/app/containers/Editor.jsx +++ b/app/containers/Editor.jsx @@ -8,6 +8,7 @@ import * as KEYCODE from '../constants/keycodes'; import { isInput } from '../utils/browser'; import Patch from './Patch'; import EventListener from 'react-event-listener'; +import Sidebar from '../components/Sidebar'; import Inspector from '../components/Inspector'; import ProjectBrowser from './ProjectBrowser'; @@ -74,13 +75,15 @@ class Editor extends React.Component { 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 ( +
+ Project browser + +
+ ); } } 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",