提交 9871d1c7 编写于 作者: J Jason Park 提交者: Jason

Implement react-dnd

上级 22f93120
......@@ -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} />
<WSSectionContainer>
<WSSectionContainer wsProps={{ fixed: true }}>
<Navigator wsProps={{
removable: false,
size: 240,
minSize: 120,
reference: this.navigatorReference
reference: this.navigatorReference,
fixed: true,
}} />
<WSTabContainer>
<WSSectionContainer wsProps={{
title: 'Visualization',
removable: false,
horizontal: false,
reference: this.spawnReference,
reference: this.spawnReference
}} />
<WSTabContainer wsProps={{ weight: 1 }}>
</WSTabContainer>
<WSTabContainer>
<DescriptionViewer wsProps={{ title: 'Description' }} />
<WikiViewer wsProps={{ title: 'Tracer API' }} />
<CodeEditor wsProps={{ title: 'code.js' }} />
......
......@@ -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;
}
......
......@@ -4,7 +4,6 @@
flex: 1;
display: flex;
flex-direction: column;
background-color: $theme-normal;
.search_bar_container {
height: $line-height;
......
......@@ -98,11 +98,12 @@ class Renderer extends React.Component {
}
render() {
const { className } = this.props;
const { className, title } = this.props;
return (
<div className={classes(styles.renderer, className)} onMouseDown={this.handleMouseDown}
onWheel={this.handleWheel}>
<Ellipsis className={styles.title}>{title}</Ellipsis>
{
this.renderData()
}
......
......@@ -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 {
......
......@@ -105,7 +105,7 @@ class TracerManager {
this.datas[tracerKey] = data;
const renderer = {
tracerKey,
element: <RendererClass data={data} wsProps={{ title }} />
element: <RendererClass title={title} data={data} wsProps={{ fixed: true }} />
};
this.renderers.push(renderer);
}
......
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(
<Provider store={store}>
<Root store={store}>
<BrowserRouter>
<Switch>
<Route exact path="/:categoryKey/:algorithmKey" component={Component} />
......@@ -21,7 +23,7 @@ const render = (Component) => {
<Route path="/" component={Component} />
</Switch>
</BrowserRouter>
</Provider>,
</Root>,
MOUNT_NODE
);
};
......
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 (
<Droppable
className={classes(styles.divider, horizontal ? styles.horizontal : styles.vertical, className)}
droppingClassName={styles.dropping}
onMouseDown={this.handleMouseDown}
{...props} />
const { className, horizontal, connectDropTarget, isOver, canDrop } = this.props;
return connectDropTarget(
<div
className={classes(styles.divider, horizontal ? styles.horizontal : styles.vertical, isOver && canDrop && styles.dropping, className)}
onMouseDown={this.handleMouseDown} />
);
}
}
......
......@@ -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
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 (
<div
className={classes(styles.droppable, this.dropping > 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}
</div>
);
}
}
export default Droppable;
@import "~/common/stylesheet/index";
.droppable {
}
\ No newline at end of file
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(
<Divider key={`divider-before-${child.key}`} horizontal={horizontal}
onResize={visibleIndex > 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(
<div key={child.key} className={classes(styles.wrapper, !horizontal && styles.horizontal)} style={style}>
{
!child.fixed &&
<Divider horizontal={!horizontal}
onDropTab={tab => this.handleDropTabToSection(tab, index, true)}
onDropSection={section => this.handleDropSectionToSection(section, index, true)} />
}
{child.element}
{
!child.fixed &&
<Divider horizontal={!horizontal}
onDropTab={tab => this.handleDropTabToSection(tab, index)}
onDropSection={section => this.handleDropSectionToSection(section, index)} />
}
</div>
);
}
......@@ -89,20 +98,17 @@ class WSSectionContainer extends React.Component {
elements.push(
<Divider key={`divider-after-${child.key}`} horizontal={horizontal}
onDropTab={tab => 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 (
<div className={classes(styles.container, horizontal && styles.horizontal, className)}>
{
elements.length ?
elements : (
<Droppable key="empty" className={styles.wrapper} droppingClassName={styles.dropping}
onDropTab={tab => this.handleDropTabToContainer(tab)}
onDropSection={section => this.handleDropSectionToContainer(section)} />
)
elements
}
</div>
);
......
......@@ -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;
......
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(<div {...props} />);
}
}
export default TabBar;
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(<div {...props} />);
}
}
export default TabTitle;
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 (
<Droppable className={classes(styles.tab_container, className)}
droppingClassName={styles.dropping}
onDropTab={tab => this.handleDropTab(tab)}>
<div className={styles.tab_bar}
draggable={true}
onDragStart={e => this.setDraggingSection(e)}>
return connectDropTarget(
<div className={classes(styles.tab_container, isOver && styles.dropping, className)}>
<TabBar className={styles.tab_bar} section={this.core}>
<div className={classes(styles.title, styles.fake)} />
{
children.map((tab, i) => {
const { title } = tab;
const selected = i === tabIndex;
return (
<Button className={classes(styles.title, selected && styles.selected)}
onClick={() => this.handleOnClickTab(i)}
draggable={true} key={i}
onDragStart={e => this.setDraggingTab(e, tab)}>
<TabTitle className={classes(styles.title, selected && styles.selected)} key={i}
onClick={() => this.handleOnClickTab(i)} tab={tab}>
{title}
</Button>
</TabTitle>
);
})
}
<div className={classes(styles.title, styles.fake)} />
</div>
</TabBar>
<div className={styles.content}>
{
~tabIndex ? children[tabIndex].element : null
}
</div>
</Droppable>
</div>
);
}
}
......
......@@ -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 {
......
@import "~/common/stylesheet/index";
.workspace {
background-color: $theme-normal;
}
\ No newline at end of file
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';
......@@ -7,7 +7,6 @@ class Child {
return {
reference: Workspace.createReference(),
removable: true,
movable: true, // TODO:
};
}
......
......@@ -12,6 +12,7 @@ class Section extends Child {
size: -1,
minSize: 0,
maxSize: Number.MAX_VALUE,
fixed: false,
};
}
......
import { Child } from '/workspace/core';
import { SectionContainer } from './index';
class Tab extends Child {
class Tab extends SectionContainer {
getDefaultProps() {
return {
...super.getDefaultProps(),
......
......@@ -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);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册