提交 70fd417b 编写于 作者: J Jason Park 提交者: Jason

Add each renderer to workspace

上级 26949548
import React from 'react';
import { connect } from 'react-redux';
import { loadProgressBar } from 'axios-progress-bar'
import {
CodeEditor,
DescriptionViewer,
Header,
Navigator,
RendererContainer,
ToastContainer,
WikiViewer
} from '/components';
import { CodeEditor, DescriptionViewer, Header, Navigator, ToastContainer, WikiViewer } from '/components';
import { actions as toastActions } from '/reducers/toast';
import { actions as envActions } from '/reducers/env';
import { DirectoryApi } from '/apis';
import { tracerManager, Workspace } from '/core';
import { tracerManager, workspace } from '/core';
import styles from './stylesheet.scss';
import 'axios-progress-bar/dist/nprogress.css'
......@@ -32,14 +24,12 @@ class App extends React.Component {
constructor(props) {
super(props);
this.workspace = new Workspace();
this.navigator = this.workspace.addBasicSection(Navigator, 2);
const leftTabSection = this.workspace.addTabSection(5);
leftTabSection.addTab('Visualization', RendererContainer);
leftTabSection.addTab('Description', DescriptionViewer);
leftTabSection.addTab('Tracer API', WikiViewer);
const rightTabSection = this.workspace.addTabSection(5);
rightTabSection.addTab('code.js', CodeEditor);
this.navigator = workspace.addBasicSection(Navigator, { weight: 2, removable: false });
this.leftContainer = workspace.addContainer({ horizontal: false, weight: 5, removable: false });
this.rightTabSection = workspace.addTabSection({ weight: 5, removable: false });
this.rightTabSection.addTab('Description', DescriptionViewer);
this.rightTabSection.addTab('Tracer API', WikiViewer);
this.rightTabSection.addTab('code.js', CodeEditor);
}
componentDidMount() {
......@@ -54,13 +44,15 @@ class App extends React.Component {
this.props.history.push(`/${category.key}/${algorithm.key}`);
});
workspace.setOnChange(() => this.forceUpdate());
tracerManager.setOnRender(renderers => this.handleChangeRenderers(renderers));
tracerManager.setOnError(error => this.props.showErrorToast(error.message));
this.workspace.setOnChange(() => this.forceUpdate());
}
componentWillUnmount() {
workspace.setOnChange(null);
tracerManager.setOnRender(null);
tracerManager.setOnError(null);
this.workspace.setOnChange(null);
}
componentWillReceiveProps(nextProps) {
......@@ -70,7 +62,33 @@ class App extends React.Component {
}
updateDirectory({ categoryKey = null, algorithmKey = null }) {
this.props.setDirectory(categoryKey, algorithmKey);
if (categoryKey && algorithmKey) {
this.props.setDirectory(categoryKey, algorithmKey);
DirectoryApi.getFile(categoryKey, algorithmKey, 'code.js').then(code => tracerManager.setCode(code));
}
}
handleChangeRenderers(renderers) {
workspace.disableChange();
const oldTabs = this.rendererTabs || {};
const newTabs = {};
for (const renderer of renderers) {
const { title, tracerKey, Component } = renderer;
let tab = null;
if (tracerKey in oldTabs) {
tab = oldTabs[tracerKey];
tab.setTitle(title);
tab.setComponent(Component);
delete oldTabs[tracerKey];
} else {
tab = this.leftContainer.addTabSection().addTab(title, Component);
}
newTabs[tracerKey] = tab;
}
Object.values(oldTabs).forEach(tab => tab.remove());
this.rendererTabs = newTabs;
workspace.enableChange();
workspace.change();
}
render() {
......@@ -82,7 +100,7 @@ class App extends React.Component {
<div className={styles.app}>
<Header onClickTitleBar={() => this.navigator.setVisible(!navigatorOpened)} navigatorOpened={navigatorOpened} />
{
this.workspace.render({ className: styles.workspace })
workspace.render({ className: styles.workspace })
}
<ToastContainer className={styles.toast_container} />
</div>
......
import React from 'react';
import { connect } from 'react-redux';
import AceEditor from 'react-ace';
import 'brace/mode/javascript';
import 'brace/theme/tomorrow_night_eighties';
import { actions as envActions } from '/reducers/env';
import { tracerManager } from '/core';
import { DirectoryApi } from '/apis';
import { classes } from '/common/util';
import styles from './stylesheet.scss';
@connect(
({ env }) => ({
env,
}), {
...envActions,
}
)
class CodeEditor extends React.Component {
constructor(props) {
super(props);
const { lineIndicator } = tracerManager;
const { lineIndicator, code } = tracerManager;
this.state = {
lineMarker: this.createLineMarker(lineIndicator),
code: '',
code,
};
}
componentDidMount() {
tracerManager.setCodeGetter(() => this.state.code);
tracerManager.setOnUpdateCode(code => this.setState({ code }));
tracerManager.setOnUpdateLineIndicator(lineIndicator => this.setState({ lineMarker: this.createLineMarker(lineIndicator) }));
const { categoryKey, algorithmKey } = this.props.env;
this.loadCode(categoryKey, algorithmKey);
}
componentWillUnmount() {
tracerManager.setCodeGetter(null);
tracerManager.setOnUpdateCode(null);
tracerManager.setOnUpdateLineIndicator(null);
}
componentWillReceiveProps(nextProps) {
const { categoryKey, algorithmKey } = nextProps.env;
if (categoryKey !== this.props.env.categoryKey ||
algorithmKey !== this.props.env.algorithmKey) {
this.loadCode(categoryKey, algorithmKey);
}
}
createLineMarker(lineIndicator) {
if (lineIndicator === null) return null;
const { lineNumber, cursor } = lineIndicator;
......@@ -63,17 +42,6 @@ class CodeEditor extends React.Component {
};
}
loadCode(categoryKey, algorithmKey) {
DirectoryApi.getFile(categoryKey, algorithmKey, 'code.js')
.then(code => {
this.setState({ code }, () => tracerManager.runInitial());
});
}
handleChangeCode(code) {
this.setState({ code }, () => tracerManager.runInitial());
}
render() {
const { lineMarker, code } = this.state;
const { className, relativeWeight } = this.props;
......@@ -85,10 +53,10 @@ class CodeEditor extends React.Component {
theme="tomorrow_night_eighties"
name="code_editor"
editorProps={{ $blockScrolling: true }}
onChange={value => this.handleChangeCode(value)}
onChange={code => tracerManager.setCode(code)}
markers={lineMarker ? [lineMarker] : []}
value={code}
width={relativeWeight} /> // trick to update on resize
width={`${relativeWeight}`} /> // trick to update on resize
);
}
}
......
import React from 'react';
import { classes } from '/common/util';
import { tracerManager } from '/core';
import styles from './stylesheet.scss';
class RendererContainer extends React.Component {
constructor(props) {
super(props);
const { renderers } = tracerManager;
this.state = {
renderers,
};
}
componentDidMount() {
tracerManager.setOnRender(renderers => this.setState({ renderers }));
}
componentWillMount() {
tracerManager.setOnRender(null);
}
render() {
const { className } = this.props;
const { renderers } = this.state;
return (
<div className={classes(styles.renderer_container, className)}>
{renderers}
</div>
);
}
}
export default RendererContainer;
@import "~/common/stylesheet/index";
.renderer_container {
display: flex;
align-items: stretch;
flex-direction: column;
}
\ No newline at end of file
import React from 'react';
import faAngleLeft from '@fortawesome/fontawesome-free-solid/faAngleLeft';
import faAngleRight from '@fortawesome/fontawesome-free-solid/faAngleRight';
import { classes } from '/common/util';
import { Button, Section } from '/components';
import styles from './stylesheet.scss';
class TabSection extends React.Component {
render() {
const { className, tabs, tabIndex, onChangeTabIndex, style, relativeWeight } = this.props;
const prevIndex = (tabIndex - 1 + tabs.length) % tabs.length;
const nextIndex = (tabIndex + 1) % tabs.length;
const { className, children, titles, tabIndex, onChangeTabIndex, style } = this.props;
return (
<Section className={classes(styles.tab_section, className)} style={style}>
<div className={styles.tab_bar}>
<Button className={styles.tab} icon={faAngleLeft} onClick={() => onChangeTabIndex(prevIndex)} />
<div className={styles.wrapper}>
{
tabs.map((tab, i) => {
const { id, title } = tab;
const selected = tabIndex === i;
return (
<Button className={classes(styles.title, selected && styles.selected)} key={id}
onClick={() => onChangeTabIndex(i)}>
{title}
</Button>
)
})
}
</div>
<Button className={styles.tab} icon={faAngleRight} onClick={() => onChangeTabIndex(nextIndex)} />
</div>
<div className={styles.content} data-tab_index={tabIndex}>
{
tabs.map((tab, i) => {
const { id, Component } = tab;
titles.map((title, i) => {
const selected = tabIndex === i;
return <Component key={id} className={classes(styles.tab, selected && styles.selected)}
relativeWeight={relativeWeight} />;
return (
<Button className={classes(styles.title, selected && styles.selected)} key={i}
onClick={() => onChangeTabIndex(i)}>
{title}
</Button>
)
})
}
</div>
<div className={styles.content} data-tab_index={tabIndex}>
{children}
</div>
</Section>
);
}
......
......@@ -7,6 +7,9 @@
align-items: stretch;
height: $line-height;
background-color: $theme-normal;
overflow-x: scroll;
white-space: nowrap;
flex-shrink: 0;
&:before {
content: '';
......@@ -18,19 +21,13 @@
background-color: $theme-light;
}
.wrapper {
.title {
position: relative;
flex: 1;
display: flex;
overflow-x: scroll;
white-space: nowrap;
.title {
&.selected {
border-left: 1px solid $theme-light;
border-right: 1px solid $theme-light;
background-color: $theme-dark;
}
&.selected {
border-left: 1px solid $theme-light;
border-right: 1px solid $theme-light;
background-color: $theme-dark;
}
}
}
......
......@@ -9,7 +9,6 @@ export { default as ExpandableListItem } from './ExpandableListItem';
export { default as Header } from './Header';
export { default as ListItem } from './ListItem';
export { default as Navigator } from './Navigator';
export { default as RendererContainer } from './RendererContainer';
export { default as Section } from './Section';
export { default as SectionContainer } from './SectionContainer';
export { default as TabSection } from './TabSection';
......
......@@ -3,4 +3,4 @@ export { default as tracers } from './tracers';
export { default as renderers } from './renderers';
export { default as Seed } from './Seed';
export { default as tracerManager } from './tracerManager';
export { default as Workspace } from './Workspace';
\ No newline at end of file
export { default as workspace } from './workspace';
\ No newline at end of file
import React from 'react';
import styles from './stylesheet.scss';
import { Ellipsis } from '/components';
import { classes } from '/common/util';
class Renderer extends React.Component {
constructor(props) {
......@@ -97,11 +98,11 @@ class Renderer extends React.Component {
}
render() {
const { title } = this.props;
const { className } = this.props;
return (
<div className={styles.renderer} onMouseDown={this.handleMouseDown} onWheel={this.handleWheel}>
<Ellipsis className={styles.title}>{title}</Ellipsis>
<div className={classes(styles.renderer, className)} onMouseDown={this.handleMouseDown}
onWheel={this.handleWheel}>
{
this.renderData()
}
......
......@@ -15,27 +15,35 @@ class TracerManager {
this.paused = false;
this.started = false;
this.lineIndicator = null;
this.code = '';
this.reset();
}
setOnRender(onRender) {
this.onRender = onRender;
this.render();
}
setOnUpdateStatus(onUpdateStatus) {
this.onUpdateStatus = onUpdateStatus;
if (this.onUpdateStatus) {
const { interval, paused, started } = this;
this.onUpdateStatus({ interval, paused, started });
}
}
setOnUpdateLineIndicator(onUpdateLineIndicator) {
this.onUpdateLineIndicator = onUpdateLineIndicator;
if (this.onUpdateLineIndicator) this.onUpdateLineIndicator(this.lineIndicator);
}
setOnError(onError) {
this.onError = onError;
}
setCodeGetter(codeGetter) {
this.codeGetter = codeGetter;
setOnUpdateCode(onUpdateCode) {
this.onUpdateCode = onUpdateCode;
if (this.onUpdateCode) this.onUpdateCode(this.code);
}
render() {
......@@ -62,9 +70,10 @@ class TracerManager {
if (this.onUpdateLineIndicator) this.onUpdateLineIndicator(lineIndicator);
}
getCode() {
if (this.codeGetter) return this.codeGetter();
return null;
setCode(code) {
this.code = code;
this.runInitial();
if (this.onUpdateCode) this.onUpdateCode(code);
}
reset(seed = new Seed()) {
......@@ -94,7 +103,11 @@ class TracerManager {
}[className];
const data = new DataClass(options);
this.datas[tracerKey] = data;
const renderer = <RendererClass key={tracerKey} title={title} data={data} />;
const renderer = {
title,
tracerKey,
Component: props => <RendererClass data={data} {...props} />
};
this.renderers.push(renderer);
}
......@@ -151,8 +164,7 @@ class TracerManager {
execute(callback) {
try {
const code = this.getCode();
const lines = code.split('\n').map((line, i) => line.replace(/(.+\. *wait *)(\( *\))/g, `$1(${i})`));
const lines = this.code.split('\n').map((line, i) => line.replace(/(.+\. *wait *)(\( *\))/g, `$1(${i})`));
const seed = new Seed();
Tracer.seed = seed;
eval(Babel.transform(lines.join('\n'), { presets: ['es2015'] }).code);
......
......@@ -5,11 +5,12 @@ import { BasicSection, Divider, SectionContainer, TabSection } from '/components
const minSize = 20;
class UISection {
constructor(parent = null, weight = 1, visible = true) {
constructor(parent = null, { weight = 1, visible = true, removable = true } = {}) {
this.id = uuid.v4();
this.parent = parent;
this.weight = weight;
this.visible = visible;
this.removable = removable;
}
setVisible(visible) {
......@@ -21,6 +22,13 @@ class UISection {
return this.visible;
}
remove() {
if (!this.removable) return;
this.parent.sections = this.parent.sections.filter(section => section !== this);
if (this.parent.sections.length) this.change();
else this.parent.remove();
}
handleResize(target, clientX, clientY) {
const { offsetLeft, offsetTop, offsetWidth, offsetHeight } = target.parentElement;
let position, size;
......@@ -80,8 +88,8 @@ class UISection {
}
class UIBasicSection extends UISection {
constructor(parent, Component, weight, visible) {
super(parent, weight, visible);
constructor(parent, Component, options) {
super(parent, options);
this.Component = Component;
}
......@@ -92,16 +100,41 @@ class UIBasicSection extends UISection {
}
}
class UITab extends UISection {
constructor(parent, title, Component, options) {
super(parent, options);
this.title = title;
this.Component = Component;
}
setTitle(title) {
this.title = title;
this.change();
}
setComponent(Component) {
this.Component = Component;
this.change();
}
render(props) {
const { Component } = this;
return (
<Component {...props} />
);
}
}
class UITabSection extends UISection {
constructor(parent, weight, visible) {
super(parent, weight, visible);
this.tabs = [];
constructor(parent, options) {
super(parent, options);
this.sections = [];
this.tabIndex = 0;
}
addTab(title, Component) {
const tab = { id: uuid.v4(), title, Component };
this.tabs.push(tab);
addTab(title, Component, options) {
const tab = new UITab(this, title, Component, options);
this.sections.push(tab);
this.change();
return tab;
}
......@@ -112,16 +145,21 @@ class UITabSection extends UISection {
}
render(props) {
const titles = this.sections.map(tab => tab.title);
return (
<TabSection {...props} tabs={this.tabs} tabIndex={this.tabIndex}
onChangeTabIndex={tabIndex => this.setTabIndex(tabIndex)} />
<TabSection {...props} titles={titles} tabIndex={this.tabIndex}
onChangeTabIndex={tabIndex => this.setTabIndex(tabIndex)}>
{
this.sections[this.tabIndex].render({})
}
</TabSection>
);
}
}
class UIContainer extends UISection {
constructor(parent, horizontal = true, weight, visible) {
super(parent, weight, visible);
constructor(parent, { horizontal = true, ...options } = {}) {
super(parent, options);
this.horizontal = horizontal;
this.sections = [];
}
......@@ -131,22 +169,22 @@ class UIContainer extends UISection {
this.change();
}
addBasicSection(Component, weight) {
const section = new UIBasicSection(this, Component, weight);
addBasicSection(Component, options) {
const section = new UIBasicSection(this, Component, options);
this.sections.push(section);
this.change();
return section;
}
addTabSection(weight) {
const section = new UITabSection(this, weight);
addTabSection(options) {
const section = new UITabSection(this, options);
this.sections.push(section);
this.change();
return section;
}
addContainer(horizontal, weight) {
const container = new UIContainer(this, horizontal, weight);
addContainer(options) {
const container = new UIContainer(this, options);
this.sections.push(container);
this.change();
return container;
......@@ -178,8 +216,9 @@ class UIContainer extends UISection {
class Workspace {
constructor(horizontal) {
this.rootContainer = new UIContainer(this, horizontal);
this.rootContainer = new UIContainer(this, { horizontal });
this.onChange = null;
this.shouldChange = true;
}
getRootContainer() {
......@@ -190,16 +229,16 @@ class Workspace {
this.rootContainer.setHorizontal(horizontal);
}
addBasicSection(Component, weight) {
return this.rootContainer.addBasicSection(Component, weight);
addBasicSection(Component, options) {
return this.rootContainer.addBasicSection(Component, options);
}
addTabSection(weight) {
return this.rootContainer.addTabSection(weight);
addTabSection(options) {
return this.rootContainer.addTabSection(options);
}
addContainer(horizontal, weight) {
return this.rootContainer.addContainer(horizontal, weight);
addContainer(options) {
return this.rootContainer.addContainer(options);
}
render(props) {
......@@ -212,8 +251,17 @@ class Workspace {
}
change() {
if (this.onChange) this.onChange(this.rootContainer);
if (this.shouldChange && this.onChange) this.onChange(this.rootContainer);
}
disableChange() {
this.shouldChange = false;
}
enableChange() {
this.shouldChange = true;
}
}
export default Workspace;
\ No newline at end of file
const workspace = new Workspace();
export default workspace;
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册