From 3462dffdbd51a8570aa6e1b7275f65b1ec89544c Mon Sep 17 00:00:00 2001 From: hackape Date: Tue, 18 Oct 2016 15:09:09 +0800 Subject: [PATCH 1/7] bunch of miscellaneous minor changes --- .babelrc | 3 ++- app/components/Modal/index.jsx | 6 +----- app/components/Pane/PanesContainer.jsx | 2 +- app/components/Tab/index.jsx | 12 ++++-------- app/components/Tab/reducer.js | 2 +- app/styles/components/PaneView.styl | 2 +- app/styles/zindex.styl | 3 +++ package.json | 1 + 8 files changed, 14 insertions(+), 17 deletions(-) diff --git a/.babelrc b/.babelrc index 65b44c5d..b3ae6f35 100644 --- a/.babelrc +++ b/.babelrc @@ -5,7 +5,8 @@ "stage-0" ], "plugins": [ - "transform-runtime" + "transform-runtime", + "transform-decorators-legacy" ], "env": { "development": { diff --git a/app/components/Modal/index.jsx b/app/components/Modal/index.jsx index 669121fd..1afe5c6d 100644 --- a/app/components/Modal/index.jsx +++ b/app/components/Modal/index.jsx @@ -21,11 +21,7 @@ var ModalContainer = (props) => { ) : null } - -ModalContainer = connect( - state => state.ModalState -, null -)(ModalContainer); +ModalContainer = connect(state => state.ModalState, null)(ModalContainer) class Modal extends Component { diff --git a/app/components/Pane/PanesContainer.jsx b/app/components/Pane/PanesContainer.jsx index 741d8742..357e74f5 100644 --- a/app/components/Pane/PanesContainer.jsx +++ b/app/components/Pane/PanesContainer.jsx @@ -9,7 +9,7 @@ var PrimaryPaneAxis = connect(state => { })(PaneAxis) var PanesContainer = (props) => { - return + return } export default PanesContainer diff --git a/app/components/Tab/index.jsx b/app/components/Tab/index.jsx index 74c71e86..d9af1d98 100644 --- a/app/components/Tab/index.jsx +++ b/app/components/Tab/index.jsx @@ -7,8 +7,8 @@ import cx from 'classnames' import * as TabActions from './actions' import AceEditor from '../AceEditor' -const TabView = ({TabState, groupId, ...otherProps}) => { - let tabGroup = TabState.getGroupById(groupId) +const TabView = ({getGroupById, groupId, ...otherProps}) => { + let tabGroup = getGroupById(groupId) return (
@@ -90,7 +90,7 @@ class TabViewContainer extends Component { } componentWillMount () { - let tabGroup = this.props.TabState.getGroupById(this.props.tabGroupId) + let tabGroup = this.props.getGroupById(this.props.tabGroupId) if (!tabGroup) this.props.dispatch(TabActions.createGroup(this.state.groupId, this.props.defaultContentType)) } @@ -110,11 +110,7 @@ class TabViewContainer extends Component { } TabViewContainer = connect( - state => { - return { - TabState: state.TabState - } - }, + state => state.TabState, dispatch => { return { addTab: (groupId) => dispatch(TabActions.createTabInGroup(groupId)), diff --git a/app/components/Tab/reducer.js b/app/components/Tab/reducer.js index 9182ed10..6f6dbd50 100644 --- a/app/components/Tab/reducer.js +++ b/app/components/Tab/reducer.js @@ -98,7 +98,7 @@ const normalizeState = (prevState) => { tabGroups.forEach(tabGroup => { tabGroup.tabs.forEach(tab => tab.group = tabGroup) }) - return {tabGroups, getGroupById, getActiveGroup, activateGroup, activatePrevGroup, normalizeState} + return {_timestamp: Date.now(), tabGroups, getGroupById, getActiveGroup, activateGroup, activatePrevGroup, normalizeState} } function getGroupById (id) { diff --git a/app/styles/components/PaneView.styl b/app/styles/components/PaneView.styl index 13d52661..b0ab51f9 100644 --- a/app/styles/components/PaneView.styl +++ b/app/styles/components/PaneView.styl @@ -41,7 +41,7 @@ } .resize-bar { - z-index: 1000; + z-index: z(pane-resize-bar); &.col-resize { cursor: col-resize; absolute(right 0 top 0 bottom 0); diff --git a/app/styles/zindex.styl b/app/styles/zindex.styl index 659eb269..0e36ef26 100644 --- a/app/styles/zindex.styl +++ b/app/styles/zindex.styl @@ -17,10 +17,13 @@ z($e) { } $z-index-list = \ + (placeholder), (main-pane-view modal-container), + (pane-drop-area pane-resize-bar), (filetree-node-bg filetree-node-label), (modal-backdrop modal), (context-menu menu menu-bar-item-container), (utilities-container), (workspace-list); + diff --git a/package.json b/package.json index 1da76761..be4a6172 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "devDependencies": { "babel-core": "^6.13.2", "babel-loader": "^6.2.4", + "babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-runtime": "^6.12.0", "babel-polyfill": "^6.13.0", "babel-preset-es2015": "^6.13.2", From ebff912f3cf9d33c4ff4b0bd97f70c8fb6896175 Mon Sep 17 00:00:00 2001 From: hackape Date: Tue, 18 Oct 2016 17:49:15 +0800 Subject: [PATCH 2/7] add DragAndDrop component this is a singleton that acts as the global handler of all DnD event --- app/components/DragAndDrop/actions.js | 19 ++++ app/components/DragAndDrop/index.jsx | 133 +++++++++++++++++++++++++ app/components/DragAndDrop/reducer.js | 44 ++++++++ app/components/Tab/index.jsx | 5 +- app/containers/Utilities.jsx | 3 + app/store.js | 4 +- app/styles/components/DragAndDrop.styl | 6 ++ app/styles/components/index.styl | 1 + app/styles/zindex.styl | 2 +- 9 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 app/components/DragAndDrop/actions.js create mode 100644 app/components/DragAndDrop/index.jsx create mode 100644 app/components/DragAndDrop/reducer.js create mode 100644 app/styles/components/DragAndDrop.styl diff --git a/app/components/DragAndDrop/actions.js b/app/components/DragAndDrop/actions.js new file mode 100644 index 00000000..48a177ef --- /dev/null +++ b/app/components/DragAndDrop/actions.js @@ -0,0 +1,19 @@ +/* @flow weak */ +export const DND_DRAG_START = 'DND_DRAG_START' +export function dragStart ({sourceType, sourceId}) { + return { + type: DND_DRAG_START, + payload: {sourceType, sourceId} + } +} + +export const DND_DRAG_OVER = 'DND_DRAG_OVER' +export function dragOver (payload) { + return { + type: DND_DRAG_OVER, + payload: payload + } +} + + +export const DND_DRAG_END = 'DND_DRAG_END' diff --git a/app/components/DragAndDrop/index.jsx b/app/components/DragAndDrop/index.jsx new file mode 100644 index 00000000..d470d92b --- /dev/null +++ b/app/components/DragAndDrop/index.jsx @@ -0,0 +1,133 @@ +/* @flow weak */ +import React, { Component } from 'react' +import { connect } from 'react-redux' +import _ from 'lodash' +import { dragOver } from './actions' + +@connect(state => state.DragAndDrop) +class DragAndDrop extends Component { + + constructor (props) { + super(props) + } + + render () { + const {paneLayoutOverlay, isDragging} = this.props + if (!isDragging) return null + + if (paneLayoutOverlay) { + const {top, left, width, height} = paneLayoutOverlay + var overlayStyle = { + position: 'fixed', + top: top, + left: left, + width: width, + height: height, + opacity: 0.2 + } + } + + return paneLayoutOverlay + ?
+ : null + } + + componentDidMount () { + window.ondragover = this.onDragOver + } + + onDragOver = (e) => { + const {sourceType, sourceId, droppables, dispatch} = this.props + + const [oX, oY] = [e.pageX, e.pageY] + const targetDroppable = _.find(droppables, droppable => { + const {top, left, right, bottom} = droppable.rect + return (left <= oX && oX <= right && top <= oY && oY <= bottom) + }) + if (!targetDroppable) return + + switch (sourceType) { + case 'TAB': + const {top, left, right, bottom, height, width} = targetDroppable.rect + const leftRule = left + width/3 + const rightRule = right - width/3 + const topRule = top + height/3 + const bottomRule = bottom - height/3 + + let overlayPos + if (oX < leftRule) { + overlayPos = 'left' + } else if (oX > rightRule) { + overlayPos = 'right' + } else if (oY < topRule) { + overlayPos = 'top' + } else if (oY > bottomRule) { + overlayPos = 'bottom' + } else { + overlayPos = 'center' + } + + // nothing changed, stop here + // if (this.overlayPos === overlayPos) return + // this.overlayPos = overlayPos + + const heightTabBar = targetDroppable.element.querySelector('.tab-bar').offsetHeight + let overlay + switch (overlayPos) { + case 'left': + overlay = { + top: top + heightTabBar, + left: left, + width: width/2, + height: height - heightTabBar + } + break + case 'right': + overlay = { + top: top + heightTabBar, + left: left + width/2, + width: width/2, + height: height - heightTabBar + } + break + case 'top': + overlay = { + top: top + heightTabBar, + left: left, + width: width, + height: (height - heightTabBar)/2 + } + break + case 'bottom': + overlay = { + top: top + (height + heightTabBar)/2, + left: left, + width: width, + height: (height - heightTabBar)/2 + } + break + default: + overlay = { + top: top + heightTabBar, + left: left, + width: width, + height: height - heightTabBar + } + } + + dispatch(dragOver({ + paneLayoutOverlay: overlay + })) + break + + default: + break + } + // dispatch(PaneActions.dragOverPane({ + // ...paneLayoutOverlay, + // direction: overlayPos + // })) + } +} + +export default DragAndDrop diff --git a/app/components/DragAndDrop/reducer.js b/app/components/DragAndDrop/reducer.js new file mode 100644 index 00000000..6fc38c1e --- /dev/null +++ b/app/components/DragAndDrop/reducer.js @@ -0,0 +1,44 @@ +/* @flow weak */ +import _ from 'lodash' +import { + DND_DRAG_START, + DND_DRAG_OVER, + DND_DRAG_END +} from './actions' + +function getDroppables () { + var droppables = {} + _.forEach(document.querySelectorAll('[data-droppable]'), (elem) => { + droppables[elem.id] = { + id: elem.id, + element: elem, + rect: elem.getBoundingClientRect() + } + }) + return droppables +} + +export default function DragAndDropReducer (state={}, action) { + switch (action.type) { + case DND_DRAG_START: + var {sourceType, sourceId} = action.payload + return { + sourceType, + sourceId, + isDragging: true, + droppables: getDroppables() + } + + case DND_DRAG_OVER: + return { + ...state, + paneLayoutOverlay: action.payload.paneLayoutOverlay + } + + case DND_DRAG_END: + return {...state, ...action.payload, isDragging: false} + + default: + return state + } +} diff --git a/app/components/Tab/index.jsx b/app/components/Tab/index.jsx index d9af1d98..687bfb8a 100644 --- a/app/components/Tab/index.jsx +++ b/app/components/Tab/index.jsx @@ -5,6 +5,7 @@ import { createStore } from 'redux' import { connect } from 'react-redux' import cx from 'classnames' import * as TabActions from './actions' +import { dragStart } from '../DragAndDrop/actions' import AceEditor from '../AceEditor' const TabView = ({getGroupById, groupId, ...otherProps}) => { @@ -33,7 +34,7 @@ const TabBar = ({tabs, groupId, addTab, ...otherProps}) => { ) } -const Tab = ({tab, removeTab, activateTab}) => { +const Tab = ({tab, removeTab, dispatch, activateTab}) => { const possibleStatus = { 'modified': '*', 'warning': '!', @@ -48,6 +49,8 @@ const Tab = ({tab, removeTab, activateTab}) => { modified: tab.flags.modified })} onClick={e => activateTab(tab.id)} + draggable='true' + onDragStart={e => dispatch(dragStart({sourceType: 'TAB', sourceId: tab.id}))} >
{tab.title}
diff --git a/app/containers/Utilities.jsx b/app/containers/Utilities.jsx index 00c9ee2b..e78deca5 100644 --- a/app/containers/Utilities.jsx +++ b/app/containers/Utilities.jsx @@ -1,13 +1,16 @@ /* @flow weak */ import React, { Component } from 'react' +import { connect } from 'react-redux' import ModalContainer from '../components/Modal' import Notification from '../components/Notification' +import DragAndDrop from '../components/DragAndDrop' const Utilities = () => { return (
+
) } diff --git a/app/store.js b/app/store.js index a77b005d..e7f12928 100644 --- a/app/store.js +++ b/app/store.js @@ -13,6 +13,7 @@ import NotificationReducer from './components/Notification/reducer' import TerminalReducer from './components/Terminal/reducer' import GitReducer from './components/Git/reducer' import WorkspaceReducer from './components/Workspace/reducer' +import DragAndDropReducer from './components/DragAndDrop/reducer' const combinedReducers = combineReducers({ WindowPaneState: PanelReducer, @@ -24,7 +25,8 @@ const combinedReducers = combineReducers({ TerminalState: TerminalReducer, GitState: GitReducer, NotificationState: NotificationReducer, - WorkspaceState: WorkspaceReducer + WorkspaceState: WorkspaceReducer, + DragAndDrop: DragAndDropReducer }) const crossReducers = composeReducers(PaneCrossReducer) diff --git a/app/styles/components/DragAndDrop.styl b/app/styles/components/DragAndDrop.styl new file mode 100644 index 00000000..566615ef --- /dev/null +++ b/app/styles/components/DragAndDrop.styl @@ -0,0 +1,6 @@ +.pane-layout-overlay { + z-index: z(pane-layout-overlay); + transition: all ease 0.2s; + opacity: 0; + background-color: white; +} diff --git a/app/styles/components/index.styl b/app/styles/components/index.styl index 2fa945bd..97323c58 100644 --- a/app/styles/components/index.styl +++ b/app/styles/components/index.styl @@ -10,3 +10,4 @@ @import "./StatusBar"; @import "./Workspace"; @import "./CommandPalette"; +@import "./DragAndDrop"; diff --git a/app/styles/zindex.styl b/app/styles/zindex.styl index 0e36ef26..9adaab24 100644 --- a/app/styles/zindex.styl +++ b/app/styles/zindex.styl @@ -19,7 +19,7 @@ z($e) { $z-index-list = \ (placeholder), (main-pane-view modal-container), - (pane-drop-area pane-resize-bar), + (pane-layout-overlay pane-resize-bar), (filetree-node-bg filetree-node-label), (modal-backdrop modal), (context-menu menu menu-bar-item-container), From 6dbb201d7d90f80b39caea56026e7e114fc5d573 Mon Sep 17 00:00:00 2001 From: hackape Date: Tue, 18 Oct 2016 17:50:30 +0800 Subject: [PATCH 3/7] rewrite Pane component in react class style, for simplicity --- app/components/Pane/PaneAxis.jsx | 119 +++++++++++++++++-------------- 1 file changed, 66 insertions(+), 53 deletions(-) diff --git a/app/components/Pane/PaneAxis.jsx b/app/components/Pane/PaneAxis.jsx index 3537d6db..66dc4e9b 100644 --- a/app/components/Pane/PaneAxis.jsx +++ b/app/components/Pane/PaneAxis.jsx @@ -7,34 +7,74 @@ import * as PaneActions from './actions' import TabViewContainer from '../Tab' import AceEditor from '../AceEditor' -const Pane = (props) => { - const {id, views, size, flexDirection, parentFlexDirection, resizingListeners} = props - var content - if (views.length > 1) { - content = - } else if (typeof views[0] === 'string') { - var tabGroupId = views[0] - console.log(tabGroupId) - content = ( -
- + +@connect(state => state.Panes) +class Pane extends Component { + constructor (props) { + super(props) + this.state = {} + } + + render () { + const {id, views, size, flexDirection, parentFlexDirection, resizingListeners, dropArea} = this.props + var content + if (views.length > 1) { + content = + } else if (typeof views[0] === 'string') { + var tabGroupId = views[0] + content = ( +
+ +
+ ) + } else { + content = null + } + + var style = {flexGrow: size, display: this.props.display} + return ( +
this.paneDOM = r} + > { content } +
) - } else { - content = null } - var style = {flexGrow: size, display: props.display} - return ( -
- { content } - -
- ) + startResize = (sectionId, e) => { + if (e.button !== 0) return // do nothing unless left button pressed + e.preventDefault() + + // dispatch(PaneActions.setCover(true)) + var [oX, oY] = [e.pageX, e.pageY] + + const handleResize = (e) => { + var [dX, dY] = [oX - e.pageX, oY - e.pageY] + ;[oX, oY] = [e.pageX, e.pageY] + this.props.dispatch(PaneActions.resize(sectionId, dX, dY)) + this.props.resizingListeners.forEach(listener => listener()) + } + + const stopResize = () => { + window.document.removeEventListener('mousemove', handleResize) + window.document.removeEventListener('mouseup', stopResize) + this.props.dispatch(PaneActions.confirmResize()) + } + + window.document.addEventListener('mousemove', handleResize) + window.document.addEventListener('mouseup', stopResize) + } + } -let ResizeBar = ({parentFlexDirection, sectionId, startResize}) => { + +const ResizeBar = ({parentFlexDirection, sectionId, startResize}) => { var barClass = (parentFlexDirection == 'row') ? 'col-resize' : 'row-resize' return (
{ ) } -ResizeBar = connect(null, (dispatch, ownProps) => { - return { - startResize: (sectionId, e) => { - if (e.button !== 0) return // do nothing unless left button pressed - e.preventDefault() - - // dispatch(PaneActions.setCover(true)) - var [oX, oY] = [e.pageX, e.pageY] - - const handleResize = (e) => { - var [dX, dY] = [oX - e.pageX, oY - e.pageY] - ;[oX, oY] = [e.pageX, e.pageY] - dispatch(PaneActions.resize(sectionId, dX, dY)) - ownProps.resizingListeners.forEach(listener => listener()) - } - - const stopResize = () => { - window.document.removeEventListener('mousemove', handleResize) - window.document.removeEventListener('mouseup', stopResize) - dispatch(PaneActions.confirmResize()) - } - - window.document.addEventListener('mousemove', handleResize) - window.document.addEventListener('mouseup', stopResize) - } - } -})(ResizeBar) - class PaneAxis extends Component { static get propTypes () { @@ -103,7 +115,7 @@ class PaneAxis extends Component { render () { let props = this.props.hasOwnProperty('root') ? this.props.root : this.props - var { views, flexDirection, className, style } = props + let { views, flexDirection } = props if (views.length === 1 && !Array.isArray(views[0].views) ) views = [props] var Subviews = views.map( _props => { return { Subviews }
+
{ Subviews }
) } } From db89cdd323707d4cf9a55abc30bacc1af93a7b0e Mon Sep 17 00:00:00 2001 From: hackape Date: Wed, 19 Oct 2016 13:16:20 +0800 Subject: [PATCH 4/7] refactor DragAndDrop's state object to a better-looking shape --- app/components/DragAndDrop/actions.js | 15 +++++++- app/components/DragAndDrop/index.jsx | 49 ++++++++++++++++----------- app/components/DragAndDrop/reducer.js | 31 +++++++++++------ app/components/Pane/PaneAxis.jsx | 2 +- 4 files changed, 65 insertions(+), 32 deletions(-) diff --git a/app/components/DragAndDrop/actions.js b/app/components/DragAndDrop/actions.js index 48a177ef..e9829a09 100644 --- a/app/components/DragAndDrop/actions.js +++ b/app/components/DragAndDrop/actions.js @@ -8,12 +8,25 @@ export function dragStart ({sourceType, sourceId}) { } export const DND_DRAG_OVER = 'DND_DRAG_OVER' -export function dragOver (payload) { +export function dragOverTarget (payload) { return { type: DND_DRAG_OVER, payload: payload } } +export const DND_UPDATE_DRAG_OVER_META = 'DND_UPDATE_DRAG_OVER_META' +export function updateDragOverMeta (payload) { + return { + type: DND_UPDATE_DRAG_OVER_META, + payload: payload + } +} export const DND_DRAG_END = 'DND_DRAG_END' +export function dragEnd (payload) { + return { + type: DND_DRAG_END, + payload: payload + } +} diff --git a/app/components/DragAndDrop/index.jsx b/app/components/DragAndDrop/index.jsx index d470d92b..e9b30824 100644 --- a/app/components/DragAndDrop/index.jsx +++ b/app/components/DragAndDrop/index.jsx @@ -2,7 +2,7 @@ import React, { Component } from 'react' import { connect } from 'react-redux' import _ from 'lodash' -import { dragOver } from './actions' +import { dragOverTarget, updateDragOverMeta, dragEnd } from './actions' @connect(state => state.DragAndDrop) class DragAndDrop extends Component { @@ -12,11 +12,11 @@ class DragAndDrop extends Component { } render () { - const {paneLayoutOverlay, isDragging} = this.props + const {isDragging, meta, target} = this.props if (!isDragging) return null - if (paneLayoutOverlay) { - const {top, left, width, height} = paneLayoutOverlay + if (meta && meta.paneLayoutOverlay) { + const {top, left, width, height} = meta.paneLayoutOverlay var overlayStyle = { position: 'fixed', top: top, @@ -27,28 +27,38 @@ class DragAndDrop extends Component { } } - return paneLayoutOverlay + return meta && meta.paneLayoutOverlay ?
: null } componentDidMount () { window.ondragover = this.onDragOver + window.ondrop = this.onDrop + window.ondragend = this.onDragEnd } onDragOver = (e) => { - const {sourceType, sourceId, droppables, dispatch} = this.props + e.preventDefault() + const {source, droppables, dispatch, meta} = this.props + const prevTarget = this.props.target const [oX, oY] = [e.pageX, e.pageY] - const targetDroppable = _.find(droppables, droppable => { + const target = _.find(droppables, droppable => { const {top, left, right, bottom} = droppable.rect return (left <= oX && oX <= right && top <= oY && oY <= bottom) }) - if (!targetDroppable) return - switch (sourceType) { + if (!target) return + if (!prevTarget || target.id !== prevTarget.id) { + dispatch(dragOverTarget({id: target.id, type: target.type})) + } + + switch (source.type) { case 'TAB': - const {top, left, right, bottom, height, width} = targetDroppable.rect + if (target.type !== 'PANE') break + + const {top, left, right, bottom, height, width} = target.rect const leftRule = left + width/3 const rightRule = right - width/3 const topRule = top + height/3 @@ -68,10 +78,9 @@ class DragAndDrop extends Component { } // nothing changed, stop here - // if (this.overlayPos === overlayPos) return - // this.overlayPos = overlayPos + if (meta && meta.paneSplitDirection === overlayPos) return - const heightTabBar = targetDroppable.element.querySelector('.tab-bar').offsetHeight + const heightTabBar = target.DOMNode.querySelector('.tab-bar').offsetHeight let overlay switch (overlayPos) { case 'left': @@ -115,18 +124,20 @@ class DragAndDrop extends Component { } } - dispatch(dragOver({ + dispatch(updateDragOverMeta({ + paneSplitDirection: overlayPos, paneLayoutOverlay: overlay })) break default: - break } - // dispatch(PaneActions.dragOverPane({ - // ...paneLayoutOverlay, - // direction: overlayPos - // })) + } + + onDrop = (e) => { + } + + onDragEnd = (e) => { } } diff --git a/app/components/DragAndDrop/reducer.js b/app/components/DragAndDrop/reducer.js index 6fc38c1e..e05aec7e 100644 --- a/app/components/DragAndDrop/reducer.js +++ b/app/components/DragAndDrop/reducer.js @@ -3,40 +3,49 @@ import _ from 'lodash' import { DND_DRAG_START, DND_DRAG_OVER, + DND_UPDATE_DRAG_OVER_META, DND_DRAG_END } from './actions' function getDroppables () { - var droppables = {} - _.forEach(document.querySelectorAll('[data-droppable]'), (elem) => { - droppables[elem.id] = { - id: elem.id, - element: elem, - rect: elem.getBoundingClientRect() + var droppables = _.map(document.querySelectorAll('[data-droppable]'), (DOMNode) => { + return { + id: DOMNode.id, + DOMNode: DOMNode, + type: DOMNode.getAttribute('data-droppable'), + rect: DOMNode.getBoundingClientRect() } }) return droppables } -export default function DragAndDropReducer (state={}, action) { +export default function DragAndDropReducer (state={isDragging: false}, action) { switch (action.type) { case DND_DRAG_START: var {sourceType, sourceId} = action.payload return { - sourceType, - sourceId, isDragging: true, + source: { + type: sourceType, + id: sourceId + }, droppables: getDroppables() } case DND_DRAG_OVER: return { ...state, - paneLayoutOverlay: action.payload.paneLayoutOverlay + target: action.payload + } + + case DND_UPDATE_DRAG_OVER_META: + return { + ...state, + meta: action.payload } case DND_DRAG_END: - return {...state, ...action.payload, isDragging: false} + return {isDragging: false} default: return state diff --git a/app/components/Pane/PaneAxis.jsx b/app/components/Pane/PaneAxis.jsx index 66dc4e9b..5d197e8d 100644 --- a/app/components/Pane/PaneAxis.jsx +++ b/app/components/Pane/PaneAxis.jsx @@ -36,7 +36,7 @@ class Pane extends Component {
this.paneDOM = r} > { content } Date: Wed, 19 Oct 2016 13:30:21 +0800 Subject: [PATCH 5/7] wrap switch-case procedure into function call --- app/components/DragAndDrop/index.jsx | 151 ++++++++++++++------------- 1 file changed, 78 insertions(+), 73 deletions(-) diff --git a/app/components/DragAndDrop/index.jsx b/app/components/DragAndDrop/index.jsx index e9b30824..efbd4d64 100644 --- a/app/components/DragAndDrop/index.jsx +++ b/app/components/DragAndDrop/index.jsx @@ -56,79 +56,7 @@ class DragAndDrop extends Component { switch (source.type) { case 'TAB': - if (target.type !== 'PANE') break - - const {top, left, right, bottom, height, width} = target.rect - const leftRule = left + width/3 - const rightRule = right - width/3 - const topRule = top + height/3 - const bottomRule = bottom - height/3 - - let overlayPos - if (oX < leftRule) { - overlayPos = 'left' - } else if (oX > rightRule) { - overlayPos = 'right' - } else if (oY < topRule) { - overlayPos = 'top' - } else if (oY > bottomRule) { - overlayPos = 'bottom' - } else { - overlayPos = 'center' - } - - // nothing changed, stop here - if (meta && meta.paneSplitDirection === overlayPos) return - - const heightTabBar = target.DOMNode.querySelector('.tab-bar').offsetHeight - let overlay - switch (overlayPos) { - case 'left': - overlay = { - top: top + heightTabBar, - left: left, - width: width/2, - height: height - heightTabBar - } - break - case 'right': - overlay = { - top: top + heightTabBar, - left: left + width/2, - width: width/2, - height: height - heightTabBar - } - break - case 'top': - overlay = { - top: top + heightTabBar, - left: left, - width: width, - height: (height - heightTabBar)/2 - } - break - case 'bottom': - overlay = { - top: top + (height + heightTabBar)/2, - left: left, - width: width, - height: (height - heightTabBar)/2 - } - break - default: - overlay = { - top: top + heightTabBar, - left: left, - width: width, - height: height - heightTabBar - } - } - - dispatch(updateDragOverMeta({ - paneSplitDirection: overlayPos, - paneLayoutOverlay: overlay - })) - break + return this.dragTabOverPane(e, target) default: } @@ -139,6 +67,83 @@ class DragAndDrop extends Component { onDragEnd = (e) => { } + + dragTabOverPane (e, target) { + if (target.type !== 'PANE') return + const {meta, dispatch} = this.props + const [oX, oY] = [e.pageX, e.pageY] + + const {top, left, right, bottom, height, width} = target.rect + const leftRule = left + width/3 + const rightRule = right - width/3 + const topRule = top + height/3 + const bottomRule = bottom - height/3 + + let overlayPos + if (oX < leftRule) { + overlayPos = 'left' + } else if (oX > rightRule) { + overlayPos = 'right' + } else if (oY < topRule) { + overlayPos = 'top' + } else if (oY > bottomRule) { + overlayPos = 'bottom' + } else { + overlayPos = 'center' + } + + // nothing changed, stop here + if (meta && meta.paneSplitDirection === overlayPos) return + + const heightTabBar = target.DOMNode.querySelector('.tab-bar').offsetHeight + let overlay + switch (overlayPos) { + case 'left': + overlay = { + top: top + heightTabBar, + left: left, + width: width/2, + height: height - heightTabBar + } + break + case 'right': + overlay = { + top: top + heightTabBar, + left: left + width/2, + width: width/2, + height: height - heightTabBar + } + break + case 'top': + overlay = { + top: top + heightTabBar, + left: left, + width: width, + height: (height - heightTabBar)/2 + } + break + case 'bottom': + overlay = { + top: top + (height + heightTabBar)/2, + left: left, + width: width, + height: (height - heightTabBar)/2 + } + break + default: + overlay = { + top: top + heightTabBar, + left: left, + width: width, + height: height - heightTabBar + } + } + + dispatch(updateDragOverMeta({ + paneSplitDirection: overlayPos, + paneLayoutOverlay: overlay + })) + } } export default DragAndDrop From 00dd6324fab96f11d094698a46e4c435ed80a001 Mon Sep 17 00:00:00 2001 From: hackape Date: Thu, 20 Oct 2016 16:12:19 +0800 Subject: [PATCH 6/7] improve the algorithm to find dragged-over target --- app/components/DragAndDrop/index.jsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/components/DragAndDrop/index.jsx b/app/components/DragAndDrop/index.jsx index efbd4d64..7674de64 100644 --- a/app/components/DragAndDrop/index.jsx +++ b/app/components/DragAndDrop/index.jsx @@ -44,10 +44,15 @@ class DragAndDrop extends Component { const prevTarget = this.props.target const [oX, oY] = [e.pageX, e.pageY] - const target = _.find(droppables, droppable => { + const target = droppables.reduce((result, droppable) => { const {top, left, right, bottom} = droppable.rect - return (left <= oX && oX <= right && top <= oY && oY <= bottom) - }) + if (left <= oX && oX <= right && top <= oY && oY <= bottom) { + if (!result) return droppable + return result.DOMNode.contains(droppable.DOMNode) ? droppable : result + } else { + return result + } + }, null) if (!target) return if (!prevTarget || target.id !== prevTarget.id) { @@ -66,6 +71,9 @@ class DragAndDrop extends Component { } onDragEnd = (e) => { + e.preventDefault() + const {dispatch} = this.props + dispatch(dragEnd()) } dragTabOverPane (e, target) { From fca9f09ace24e14e1d318a14725768d07c8c4d8d Mon Sep 17 00:00:00 2001 From: hackape Date: Thu, 20 Oct 2016 17:14:24 +0800 Subject: [PATCH 7/7] basic dnd-to-split-pane feature implemented remained is to move corresponding tab to the right tabGroup --- app/components/DragAndDrop/index.jsx | 13 ++++ app/components/Pane/actions.js | 13 +++- app/components/Pane/reducer.js | 97 ++++++++++++++++++++++++---- 3 files changed, 106 insertions(+), 17 deletions(-) diff --git a/app/components/DragAndDrop/index.jsx b/app/components/DragAndDrop/index.jsx index 7674de64..7afdd135 100644 --- a/app/components/DragAndDrop/index.jsx +++ b/app/components/DragAndDrop/index.jsx @@ -3,6 +3,7 @@ import React, { Component } from 'react' import { connect } from 'react-redux' import _ from 'lodash' import { dragOverTarget, updateDragOverMeta, dragEnd } from './actions' +import * as PaneActions from '../Pane/actions' @connect(state => state.DragAndDrop) class DragAndDrop extends Component { @@ -68,6 +69,18 @@ class DragAndDrop extends Component { } onDrop = (e) => { + e.preventDefault() + const {source, target, meta, dispatch} = this.props + if (!source || !target) return + switch (`${source.type}_to_${target.type}`) { + case 'TAB_to_PANE': + if (meta.paneSplitDirection === 'center') { + + } else { + dispatch(PaneActions.splitTo(target.id, meta.paneSplitDirection)) + } + } + dispatch(dragEnd()) } onDragEnd = (e) => { diff --git a/app/components/Pane/actions.js b/app/components/Pane/actions.js index 43a9257b..469b3033 100644 --- a/app/components/Pane/actions.js +++ b/app/components/Pane/actions.js @@ -24,11 +24,18 @@ export function confirmResize () { return {type: PANE_CONFIRM_RESIZE} } -export const PANE_SPLIT = 'PANE_SPLIT' +export const PANE_SPLIT_WITH_KEY = 'PANE_SPLIT_WITH_KEY' export function split (splitCount, flexDirection='row') { + return { + type: PANE_SPLIT_WITH_KEY, + payload: {flexDirection, splitCount} + } +} + +export const PANE_SPLIT = 'PANE_SPLIT' +export function splitTo (paneId, splitDirection) { return { type: PANE_SPLIT, - flexDirection: flexDirection, - splitCount: splitCount + payload: {paneId, splitDirection} } } diff --git a/app/components/Pane/reducer.js b/app/components/Pane/reducer.js index 925bfe60..f0dc8b80 100644 --- a/app/components/Pane/reducer.js +++ b/app/components/Pane/reducer.js @@ -6,7 +6,8 @@ import { PANE_UNSET_COVER, PANE_RESIZE, PANE_CONFIRM_RESIZE, - PANE_SPLIT + PANE_SPLIT, + PANE_SPLIT_WITH_KEY } from './actions' // removeSingleChildedInternalNode 是为了避免无限级嵌套的 views.length === 1 出现 @@ -71,6 +72,61 @@ class Pane { Pane.indexes[this.id] = this } + addSibling (siblingPane, toTheLeft) { + let parent = getPaneById(this.parentId) + let atIndex = parent.views.indexOf(this) + 1 + if (toTheLeft === -1) atIndex -= 1 + parent.views.splice(atIndex, 0, siblingPane) + } + + addChild (childPane, atIndex) { + if (typeof atIndex !== 'number') atIndex = this.views.length + this.views.splice(atIndex, 0, childPane) + } + /** + * Mutate a pane instance's "views" property to reflect a splitted pane view + * @param {string} splitDirection + * @param {string} splitCount + */ + splitToDirection (splitDirection) { + let flexDirection + switch (splitDirection) { + case 'right': + case 'left': + flexDirection = 'row' + break + case 'top': + case 'bottom': + flexDirection = 'column' + break + default: + throw 'Pane.splitToDirection method requires param "splitDirection"' + } + this.flexDirection = flexDirection + + let parent = getPaneById(this.parentId) + // If flexDirection is same as parent's, + // then we can simply push the newly splitted view into parent's "views" array + if (parent && parent.flexDirection === flexDirection) { + let newPane = new Pane({views: ['']}, parent) + if (splitDirection === 'right' || splitDirection === 'bottom') { + this.addSibling(newPane) + } else { + this.addSibling(newPane, -1) + } + } else { + let curTabGroupId = this.views.pop() + this.views.push(new Pane({views:[curTabGroupId]}, this)) + + let childPane = new Pane({views: ['']}, this) + if (splitDirection === 'right' || splitDirection === 'bottom') { + this.addChild(childPane, 1) + } else { + this.addChild(childPane, 0) + } + } + } + splitPane (splitCount=this.views.length+1, flexDirection=this.flexDirection) { this.flexDirection = flexDirection if (splitCount <= 0) splitCount = 1 @@ -130,7 +186,7 @@ class Pane { Pane.indexes = {} -const getViewById = (id) => Pane.indexes[id] +const getPaneById = (id) => Pane.indexes[id] const debounced = _.debounce(function (func) { func() }, 50) const _state = { @@ -144,8 +200,8 @@ const _state = { export default function PaneReducer (state = _state, action) { switch (action.type) { case PANE_RESIZE: - let section_A = getViewById(action.sectionId) - let parent = getViewById(section_A.parentId) + let section_A = getPaneById(action.sectionId) + let parent = getPaneById(section_A.parentId) let section_B = parent.views[parent.views.indexOf(section_A) + 1] let section_A_Dom = document.getElementById(section_A.id) let section_B_Dom = document.getElementById(section_B.id) @@ -180,8 +236,6 @@ export default function PaneReducer (state = _state, action) { case PANE_CONFIRM_RESIZE: return state - - default: return state } @@ -189,19 +243,21 @@ export default function PaneReducer (state = _state, action) { export function PaneCrossReducer (allStates, action) { switch (action.type) { - case PANE_SPLIT: - const {Panes, TabState} = allStates - var pane = Panes.root - if (action.splitCount === pane.views.length && - action.flexDirection === pane.flexDirection) { + case PANE_SPLIT_WITH_KEY: + var {Panes, TabState} = allStates + var {pane, splitCount, flexDirection} = action.payload + if (!pane) pane = Panes.root + + if (splitCount === pane.views.length && + flexDirection === pane.flexDirection) { return allStates } pane = new Pane(pane) - var tabGroupIds = pane.splitPane(action.splitCount, action.flexDirection) + var tabGroupIds = pane.splitPane(splitCount, flexDirection) if (tabGroupIds.length > 1) { - const tabGroupIdToMergeInto = tabGroupIds[0] - const tabGroupIdsToBeMerged = tabGroupIds.slice(1) + var tabGroupIdToMergeInto = tabGroupIds[0] + var tabGroupIdsToBeMerged = tabGroupIds.slice(1) var mergedTabs = tabGroupIdsToBeMerged.reduceRight((acc, tabGroupId) => { var tabGroup = TabState.getGroupById(tabGroupId) tabGroup.deactivateAllTabsInGroup() @@ -214,6 +270,19 @@ export function PaneCrossReducer (allStates, action) { Panes: {root: new Pane(pane)}, TabState: TabState.normalizeState(TabState) } + + case PANE_SPLIT: + var {Panes, TabState} = allStates + var {paneId, splitDirection} = action.payload + var pane = getPaneById(paneId) + console.log(paneId+': '+splitDirection); + pane.splitToDirection(splitDirection) + + return { ...allStates, + Panes: {root: Pane.root, timestamp: Date.now()} + } + + default: return allStates }