diff --git a/package.json b/package.json index cf7d0cef194a36bb702fcfcd738e45d5052942ef..df559ba68e2cc2018683194e70cd40995db3b2ad 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,8 @@ "react": "^16.3.1", "react-ace": "^5.10.0", "react-chartjs-2": "^2.7.0", + "react-dnd": "^2.6.0", + "react-dnd-html5-backend": "^2.6.0", "react-dom": "^16.3.1", "react-input-range": "^1.3.0", "react-markdown": "^3.3.0", diff --git a/src/frontend/components/App/index.jsx b/src/frontend/components/App/index.jsx index 23d43b04834f323f3de79f046195de89f79748ce..888ea2045b245e2ed579cb98dd605cc1f92649e6 100644 --- a/src/frontend/components/App/index.jsx +++ b/src/frontend/components/App/index.jsx @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import { loadProgressBar } from 'axios-progress-bar' import { CodeEditor, DescriptionViewer, Header, Navigator, ToastContainer, WikiViewer, } from '/components'; import { Workspace, WSSectionContainer, WSTabContainer } from '/workspace/components'; -import { Tab, TabContainer } from '/workspace/core'; +import { Section } from '/workspace/core'; import { actions as toastActions } from '/reducers/toast'; import { actions as envActions } from '/reducers/env'; import { DirectoryApi } from '/apis'; @@ -65,25 +65,23 @@ class App extends React.Component { } handleChangeRenderers(renderers) { - const oldTabs = this.rendererTabs || {}; - const newTabs = {}; + const oldSections = this.rendererSections || {}; + const newSections = {}; for (const renderer of renderers) { const { tracerKey, element } = renderer; - let tab = null; - if (tracerKey in oldTabs) { - tab = oldTabs[tracerKey]; - tab.setElement(element); - delete oldTabs[tracerKey]; + let section = null; + if (tracerKey in oldSections) { + section = oldSections[tracerKey]; + section.setElement(element); + delete oldSections[tracerKey]; } else { - const tabContainer = new TabContainer(); - tab = new Tab(element); - tabContainer.addChild(tab); - this.spawnReference.core.addChild(tabContainer); + section = new Section(element); + this.spawnReference.core.addChild(section); } - newTabs[tracerKey] = tab; + newSections[tracerKey] = section; } - Object.values(oldTabs).forEach(tab => tab.remove()); - this.rendererTabs = newTabs; + Object.values(oldSections).forEach(tab => tab.remove()); + this.rendererSections = newSections; } render() { @@ -99,22 +97,27 @@ class App extends React.Component { size: 32, minSize: 32, maxSize: 64, + fixed: true, }} onClickTitleBar={() => this.navigatorReference.core.setVisible(!this.navigatorReference.core.visible)} navigatorOpened={navigatorOpened} /> - + - - + + + + diff --git a/src/frontend/components/App/stylesheet.scss b/src/frontend/components/App/stylesheet.scss index fc2b2e9ca8a7e715c99ab2bed49ee107e55d6423..75d72851f76c02291b4629f34c25253fb3180e6d 100644 --- a/src/frontend/components/App/stylesheet.scss +++ b/src/frontend/components/App/stylesheet.scss @@ -13,7 +13,6 @@ body { font-family: 'Roboto', sans-serif; -webkit-font-smoothing: subpixel-antialiased; user-select: none; - background-color: $theme-normal; color: $color-font; font-size: $font-size-normal; } diff --git a/src/frontend/components/Navigator/stylesheet.scss b/src/frontend/components/Navigator/stylesheet.scss index 12c402e47710f32216105778b39d54dbfb6c118a..7ed06faa59526adea72eaf9e310d71017aa7604c 100644 --- a/src/frontend/components/Navigator/stylesheet.scss +++ b/src/frontend/components/Navigator/stylesheet.scss @@ -4,7 +4,6 @@ flex: 1; display: flex; flex-direction: column; - background-color: $theme-normal; .search_bar_container { height: $line-height; diff --git a/src/frontend/core/renderers/Renderer/index.jsx b/src/frontend/core/renderers/Renderer/index.jsx index df1f0a51c6f6405521661ba610a3326aef42308c..1d64555bf0e04f3c7fd2f1582faac1c42e83bead 100644 --- a/src/frontend/core/renderers/Renderer/index.jsx +++ b/src/frontend/core/renderers/Renderer/index.jsx @@ -98,11 +98,12 @@ class Renderer extends React.Component { } render() { - const { className } = this.props; + const { className, title } = this.props; return (
+ {title} { this.renderData() } diff --git a/src/frontend/core/renderers/Renderer/stylesheet.scss b/src/frontend/core/renderers/Renderer/stylesheet.scss index 79f8382a2e86ecf17ab9c6bf1607ef7e4720f34b..ed2fb701f9979c6351473b62b734ffaa592bcf72 100644 --- a/src/frontend/core/renderers/Renderer/stylesheet.scss +++ b/src/frontend/core/renderers/Renderer/stylesheet.scss @@ -8,7 +8,6 @@ align-items: center; justify-content: center; overflow: hidden; - border-top: 1px solid $theme-light; font-family: 'Roboto Mono', monospace; &:first-child { diff --git a/src/frontend/core/tracerManager.jsx b/src/frontend/core/tracerManager.jsx index 2f7dd45eeecdf2413b75bc4672d0abd6e6d7964f..1208a6cbb2864beac561b9289e3ddac4a49fe39d 100644 --- a/src/frontend/core/tracerManager.jsx +++ b/src/frontend/core/tracerManager.jsx @@ -105,7 +105,7 @@ class TracerManager { this.datas[tracerKey] = data; const renderer = { tracerKey, - element: + element: }; this.renderers.push(renderer); } diff --git a/src/frontend/index.jsx b/src/frontend/index.jsx index 85aea1a8e92575e652ae540e026f4602b3873ef1..241ed336a2f881c89051f321625da0778b2a2f8a 100644 --- a/src/frontend/index.jsx +++ b/src/frontend/index.jsx @@ -1,5 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import HTML5Backend from 'react-dnd-html5-backend'; +import { DragDropContext } from 'react-dnd'; import { combineReducers, createStore } from 'redux'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { Provider } from 'react-redux'; @@ -8,12 +10,12 @@ import App from '/components/App'; import * as reducers from '/reducers'; const MOUNT_NODE = document.getElementById('root'); - const store = createStore(combineReducers({ ...reducers, routing: routerReducer })); +const Root = DragDropContext(HTML5Backend)(Provider); const render = (Component) => { ReactDOM.render( - + @@ -21,7 +23,7 @@ const render = (Component) => { - , + , MOUNT_NODE ); }; diff --git a/src/frontend/workspace/components/Divider/index.jsx b/src/frontend/workspace/components/Divider/index.jsx index 87830dbf72a446a35d9e457d6e94c553e2d174c4..154a47a8b4d23e4cbc5a34cd445521a603c47ac3 100644 --- a/src/frontend/workspace/components/Divider/index.jsx +++ b/src/frontend/workspace/components/Divider/index.jsx @@ -1,8 +1,25 @@ import React from 'react'; +import { DropTarget } from 'react-dnd'; import { classes } from '/common/util'; -import { Droppable } from '/workspace/components'; import styles from './stylesheet.scss'; +const dividerTarget = { + canDrop(props, monitor) { + return !props.disableDrop; + }, + drop(props, monitor, component) { + const item = monitor.getItem(); + const { section, tab } = item; + if (section) props.onDropSection(section); + else if (tab) props.onDropTab(tab); + } +}; + +@DropTarget(['section', 'tab'], dividerTarget, (connect, monitor) => ({ + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver(), + canDrop: monitor.canDrop(), +})) class Divider extends React.Component { constructor(props) { super(props); @@ -29,14 +46,12 @@ class Divider extends React.Component { } render() { - const { className, horizontal, onResize, ...props } = this.props; - - return ( - + const { className, horizontal, connectDropTarget, isOver, canDrop } = this.props; + + return connectDropTarget( +
); } } diff --git a/src/frontend/workspace/components/Divider/stylesheet.scss b/src/frontend/workspace/components/Divider/stylesheet.scss index 324736371576b7ad2cf987d1f9a213dade4e8433..334277aa9ab1cea85bb766768f427ab6e0f3755a 100644 --- a/src/frontend/workspace/components/Divider/stylesheet.scss +++ b/src/frontend/workspace/components/Divider/stylesheet.scss @@ -8,11 +8,24 @@ background-color: rgba($color-highlight, .4); } + &:after { + position: absolute; + background-color: $theme-light; + content: ''; + } + &.horizontal { width: 7px; margin: 0 -3px; cursor: ew-resize; + &:after { + top: 0; + bottom: 0; + left: 3px; + width: 1px; + } + &:first-child { width: 5px; margin: 0 -5px 0 0; @@ -29,6 +42,13 @@ margin: -3px 0; cursor: ns-resize; + &:after { + left: 0; + right: 0; + top: 3px; + height: 1px; + } + &:first-child { height: 5px; margin: 0 0 -5px 0; @@ -44,5 +64,9 @@ &:last-child { z-index: 97; cursor: auto; + + &:after { + content: none; + } } } \ No newline at end of file diff --git a/src/frontend/workspace/components/Droppable/index.jsx b/src/frontend/workspace/components/Droppable/index.jsx deleted file mode 100644 index cbc3d4e7a2fbcde51ae9410b8f4920edada56c38..0000000000000000000000000000000000000000 --- a/src/frontend/workspace/components/Droppable/index.jsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; -import { draggingData } from '/workspace/core'; -import { classes } from '/common/util'; -import styles from './stylesheet.scss'; - -class Droppable extends React.Component { - constructor(props) { - super(props); - - this.dropping = 0; - } - - handleDragOver(e) { - e.preventDefault(); - } - - handleDragEnter(e) { - if (++this.dropping === 1) this.forceUpdate(); - } - - handleDragLeave(e) { - if (--this.dropping === 0) this.forceUpdate(); - } - - handleDragEnd(e) { - if (this.dropping > 0) { - this.dropping = 0; - this.forceUpdate(); - } - } - - handleDrop(e) { - e.preventDefault(); - this.handleDragEnd(e); - const data = draggingData.get(e); - if (data) { - const { onDropTab, onDropSection } = this.props; - switch (data.type) { - case 'tab': - onDropTab && onDropTab(data.tab); - break; - case 'section': - onDropSection && onDropSection(data.section); - break; - } - } - } - - render() { - const { className, children, onDropTab, onDropSection, droppingClassName, ...props } = this.props; - - return ( -
0 && droppingClassName, className)} - onDragOver={e => this.handleDragOver(e)} - onDragEnter={e => this.handleDragEnter(e)} - onDragLeave={e => this.handleDragLeave(e)} - onDragEnd={e => this.handleDragEnd(e)} - onDrop={e => this.handleDrop(e)} {...props}> - {children} -
- ); - } -} - -export default Droppable; - diff --git a/src/frontend/workspace/components/Droppable/stylesheet.scss b/src/frontend/workspace/components/Droppable/stylesheet.scss deleted file mode 100644 index 92249938803b1ea1191050e6a1fc9500f8252704..0000000000000000000000000000000000000000 --- a/src/frontend/workspace/components/Droppable/stylesheet.scss +++ /dev/null @@ -1,4 +0,0 @@ -@import "~/common/stylesheet/index"; - -.droppable { -} \ No newline at end of file diff --git a/src/frontend/workspace/components/WSSectionContainer/index.jsx b/src/frontend/workspace/components/WSSectionContainer/index.jsx index f9a72baf51a638a56abf5431be9a2a3e27474428..4a8c8c212a3339710369c45136dac19a5482a760 100644 --- a/src/frontend/workspace/components/WSSectionContainer/index.jsx +++ b/src/frontend/workspace/components/WSSectionContainer/index.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { classes } from '/common/util'; -import { Divider, Droppable } from '/workspace/components'; +import { Divider } from '/workspace/components'; import { SectionContainer, TabContainer } from '/workspace/core'; import styles from './stylesheet.scss'; @@ -14,9 +14,10 @@ class WSSectionContainer extends React.Component { } handleResize(index, targetElement, clientX, clientY) { - const { offsetLeft, offsetTop, offsetWidth, offsetHeight } = targetElement.parentElement; + const { left, top } = targetElement.getBoundingClientRect(); + const { offsetWidth, offsetHeight } = targetElement.parentElement; const { horizontal } = this.core; - const position = horizontal ? clientX - offsetLeft : clientY - offsetTop; + const position = horizontal ? clientX - left : clientY - top; const containerSize = horizontal ? offsetWidth : offsetHeight; this.core.resizeChild(index, position, containerSize); } @@ -52,13 +53,15 @@ class WSSectionContainer extends React.Component { const visibleChildren = children.filter(child => child.visible); const totalWeight = visibleChildren.reduce((sumWeight, child) => sumWeight + (child.relative ? child.weight : 0), 0); const elements = []; + let prevFixed = true; visibleChildren.forEach((child, visibleIndex) => { const index = children.indexOf(child); elements.push( 0 && ((target, dx, dy) => this.handleResize(visibleIndex, target, dx, dy))} onDropTab={tab => this.handleDropTabToContainer(tab, index)} - onDropSection={section => this.handleDropSectionToContainer(section, index)} /> + onDropSection={section => this.handleDropSectionToContainer(section, index)} + disableDrop={prevFixed && child.fixed} /> ); const style = child.relative ? { flexGrow: child.weight / totalWeight, @@ -75,13 +78,19 @@ class WSSectionContainer extends React.Component { } else { elements.push(
- this.handleDropTabToSection(tab, index, true)} - onDropSection={section => this.handleDropSectionToSection(section, index, true)} /> + { + !child.fixed && + this.handleDropTabToSection(tab, index, true)} + onDropSection={section => this.handleDropSectionToSection(section, index, true)} /> + } {child.element} - this.handleDropTabToSection(tab, index)} - onDropSection={section => this.handleDropSectionToSection(section, index)} /> + { + !child.fixed && + this.handleDropTabToSection(tab, index)} + onDropSection={section => this.handleDropSectionToSection(section, index)} /> + }
); } @@ -89,20 +98,17 @@ class WSSectionContainer extends React.Component { elements.push( this.handleDropTabToContainer(tab, index + 1)} - onDropSection={section => this.handleDropSectionToContainer(section, index + 1)} /> + onDropSection={section => this.handleDropSectionToContainer(section, index + 1)} + disableDrop={child.fixed} /> ); } + prevFixed = child.fixed; }); return (
{ - elements.length ? - elements : ( - this.handleDropTabToContainer(tab)} - onDropSection={section => this.handleDropSectionToContainer(section)} /> - ) + elements }
); diff --git a/src/frontend/workspace/components/WSSectionContainer/stylesheet.scss b/src/frontend/workspace/components/WSSectionContainer/stylesheet.scss index b10a25efd604ab3af1482d48377e04a69da1b6ab..0b2d55ea8f5b6255a7299b678a94f7f48ac09c0f 100644 --- a/src/frontend/workspace/components/WSSectionContainer/stylesheet.scss +++ b/src/frontend/workspace/components/WSSectionContainer/stylesheet.scss @@ -7,7 +7,6 @@ align-items: stretch; min-width: 0; min-height: 0; - background-color: $theme-light; &.horizontal { flex-direction: row; @@ -19,7 +18,6 @@ flex-direction: column; align-items: stretch; overflow: hidden; - background-color: $theme-normal; &.horizontal { flex-direction: row; diff --git a/src/frontend/workspace/components/WSTabContainer/TabBar.jsx b/src/frontend/workspace/components/WSTabContainer/TabBar.jsx new file mode 100644 index 0000000000000000000000000000000000000000..e5b01d9eadb7982f48277cb4136a6f0331c3facc --- /dev/null +++ b/src/frontend/workspace/components/WSTabContainer/TabBar.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { DragSource } from 'react-dnd'; + +const sectionSource = { + beginDrag(props, monitor, component) { + return { + section: props.section + }; + }, +}; + +@DragSource('section', sectionSource, (connect, monitor) => ({ + connectDragSource: connect.dragSource(), +})) +class TabBar extends React.Component { + render() { + const { section, connectDragSource, ...props } = this.props; + + return connectDragSource(
); + } +} + +export default TabBar; + diff --git a/src/frontend/workspace/components/WSTabContainer/TabTitle.jsx b/src/frontend/workspace/components/WSTabContainer/TabTitle.jsx new file mode 100644 index 0000000000000000000000000000000000000000..3da14780f2a24e9db42598728b9ae55b314ce733 --- /dev/null +++ b/src/frontend/workspace/components/WSTabContainer/TabTitle.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { DragSource } from 'react-dnd'; + +const tabSource = { + beginDrag(props, monitor, component) { + return { + tab: props.tab + }; + }, +}; + +@DragSource('tab', tabSource, (connect, monitor) => ({ + connectDragSource: connect.dragSource(), +})) +class TabTitle extends React.Component { + render() { + const { tab, connectDragSource, ...props } = this.props; + + return connectDragSource(
); + } +} + +export default TabTitle; + diff --git a/src/frontend/workspace/components/WSTabContainer/index.jsx b/src/frontend/workspace/components/WSTabContainer/index.jsx index a0119d00cb3c7cf81665e489e13f4f0a2648a114..be4632d0307badf6928bff3f345d4152b83d9a1a 100644 --- a/src/frontend/workspace/components/WSTabContainer/index.jsx +++ b/src/frontend/workspace/components/WSTabContainer/index.jsx @@ -1,10 +1,22 @@ import React from 'react'; +import { DropTarget } from 'react-dnd'; import { classes } from '/common/util'; -import { Button } from '/components'; -import { draggingData } from '/workspace/core'; -import { Droppable } from '/workspace/components'; import styles from './stylesheet.scss'; +import TabBar from './TabBar'; +import TabTitle from './TabTitle'; +const tabContainerTarget = { + drop(props, monitor, component) { + const item = monitor.getItem(); + const { tab } = item; + component.core.addChild(tab); + } +}; + +@DropTarget('tab', tabContainerTarget, (connect, monitor) => ({ + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver(), +})) class WSTabContainer extends React.Component { constructor(props) { super(props); @@ -14,65 +26,38 @@ class WSTabContainer extends React.Component { this.core = core; } - setDraggingTab(e, tab) { - e.stopPropagation(); - draggingData.set(e, { - type: 'tab', - tab, - }); - } - - setDraggingSection(e) { - e.stopPropagation(); - draggingData.set(e, { - type: 'section', - section: this.core, - }) - } - - handleOnClickTab(tabIndex) { this.core.setTabIndex(tabIndex); } - handleDropTab(tab) { - this.core.addChild(tab); - } - render() { - const { className } = this.props; + const { className, connectDropTarget, isOver } = this.props; const { children, tabIndex } = this.core; - return ( - this.handleDropTab(tab)}> -
this.setDraggingSection(e)}> + return connectDropTarget( +
+
{ children.map((tab, i) => { const { title } = tab; const selected = i === tabIndex; return ( - + ); }) }
-
+
{ ~tabIndex ? children[tabIndex].element : null }
- +
); } } diff --git a/src/frontend/workspace/components/WSTabContainer/stylesheet.scss b/src/frontend/workspace/components/WSTabContainer/stylesheet.scss index 0012fdbcae886b917afdb1626103cf7b7dab62f5..2e1d91a8bad665209382bcb346af73023481b502 100644 --- a/src/frontend/workspace/components/WSTabContainer/stylesheet.scss +++ b/src/frontend/workspace/components/WSTabContainer/stylesheet.scss @@ -31,6 +31,11 @@ cursor: move; .title { + display: flex; + align-items: center; + cursor: pointer; + padding: 0 12px; + margin: 0; border-bottom: 1px solid $theme-light; &.selected { diff --git a/src/frontend/workspace/components/Workspace/stylesheet.scss b/src/frontend/workspace/components/Workspace/stylesheet.scss index 8a5dff1b1234e0cc15cac3beff718b42dd22ad53..51b4d17be1e5e47d390812af84333176aa0779d8 100644 --- a/src/frontend/workspace/components/Workspace/stylesheet.scss +++ b/src/frontend/workspace/components/Workspace/stylesheet.scss @@ -1,4 +1,5 @@ @import "~/common/stylesheet/index"; .workspace { + background-color: $theme-normal; } \ No newline at end of file diff --git a/src/frontend/workspace/components/index.js b/src/frontend/workspace/components/index.js index 0ed9d7488328b3b1f9ef2d6649dc6ed039a6ab17..d6dbb95ecf2bfab964c39dddf5749e5f6beccc89 100644 --- a/src/frontend/workspace/components/index.js +++ b/src/frontend/workspace/components/index.js @@ -1,5 +1,4 @@ export { default as Divider } from './Divider'; -export { default as Droppable } from './Droppable'; export { default as Workspace } from './Workspace'; export { default as WSSectionContainer } from './WSSectionContainer'; export { default as WSTabContainer } from './WSTabContainer'; diff --git a/src/frontend/workspace/core/Child.js b/src/frontend/workspace/core/Child.js index 661417470e9ec38bd07f03676adf4c84067d204a..7fe6138a871bff65b47233bd84cbbc8973acf830 100644 --- a/src/frontend/workspace/core/Child.js +++ b/src/frontend/workspace/core/Child.js @@ -7,7 +7,6 @@ class Child { return { reference: Workspace.createReference(), removable: true, - movable: true, // TODO: }; } diff --git a/src/frontend/workspace/core/Section.js b/src/frontend/workspace/core/Section.js index 207c08a5d1f1aaa19c7f3c6c4c1c833cc750255d..6b9dbff4547b5f053607e84de7d2fb27e87a712a 100644 --- a/src/frontend/workspace/core/Section.js +++ b/src/frontend/workspace/core/Section.js @@ -12,6 +12,7 @@ class Section extends Child { size: -1, minSize: 0, maxSize: Number.MAX_VALUE, + fixed: false, }; } diff --git a/src/frontend/workspace/core/Tab.js b/src/frontend/workspace/core/Tab.js index da7741c1f020e022315365dca5e6cd21e4fc4d3e..e20f27d9f39ee99f0c667b361800b78235715caf 100644 --- a/src/frontend/workspace/core/Tab.js +++ b/src/frontend/workspace/core/Tab.js @@ -1,6 +1,6 @@ -import { Child } from '/workspace/core'; +import { SectionContainer } from './index'; -class Tab extends Child { +class Tab extends SectionContainer { getDefaultProps() { return { ...super.getDefaultProps(), diff --git a/src/frontend/workspace/core/draggingData.js b/src/frontend/workspace/core/draggingData.js index b348560700f7a377416d1e921bc811af759f05d7..921764acff412cb0fa8e0a214e8638774bc6fc2e 100644 --- a/src/frontend/workspace/core/draggingData.js +++ b/src/frontend/workspace/core/draggingData.js @@ -8,9 +8,9 @@ class DraggingData { this.data = null; } - set(e, data) { + set(e, type, child) { this.id = uuid.v4(); - this.data = data; + this.data = { type, child }; e.dataTransfer.dropEffect = 'move'; e.dataTransfer.setData(key, this.id); }