提交 26949548 编写于 作者: J Jason Park 提交者: Jason

Refactoring workspace for further use

上级 52963f85
$line-height: 32px;
$resizeable-min: 20px;
$font-size-normal: 12px;
$font-size-large: 14px;
\ No newline at end of file
const classes = (...arr) => arr.filter(v => v).join(' ');
const calculatePercentageWidth = (element, x) => {
const { offsetWidth, offsetLeft } = element;
return ((x - offsetLeft) / offsetWidth * 100).toFixed(1) + '%';
};
const calculatePercentageHeight = (element, y) => {
const { offsetHeight, offsetTop } = element;
return ((y - offsetTop) / offsetHeight * 100).toFixed(1) + '%';
};
const serialize = object => JSON.parse(JSON.stringify(object));
const distance = (a, b) => {
......@@ -20,8 +10,6 @@ const distance = (a, b) => {
export {
classes,
calculatePercentageWidth,
calculatePercentageHeight,
serialize,
distance,
};
\ No newline at end of file
import React from 'react';
import { connect } from 'react-redux';
import { loadProgressBar } from 'axios-progress-bar'
import { Divider, EditorSection, Header, Navigator, ToastContainer, ViewerSection } from '/components';
import {
CodeEditor,
DescriptionViewer,
Header,
Navigator,
RendererContainer,
ToastContainer,
WikiViewer
} from '/components';
import { actions as toastActions } from '/reducers/toast';
import { actions as envActions } from '/reducers/env';
import { calculatePercentageWidth } from '/common/util';
import { DirectoryApi } from '/apis';
import { tracerManager } from '/core';
import { tracerManager, Workspace } from '/core';
import styles from './stylesheet.scss';
import 'axios-progress-bar/dist/nprogress.css'
......@@ -25,11 +32,14 @@ class App extends React.Component {
constructor(props) {
super(props);
this.state = {
navigatorOpened: true,
navigatorWidth: '16%',
viewerSectionWidth: '50%',
}
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);
}
componentDidMount() {
......@@ -45,6 +55,12 @@ class App extends React.Component {
});
tracerManager.setOnError(error => this.props.showErrorToast(error.message));
this.workspace.setOnChange(() => this.forceUpdate());
}
componentWillUnmount() {
tracerManager.setOnError(null);
this.workspace.setOnChange(null);
}
componentWillReceiveProps(nextProps) {
......@@ -53,47 +69,21 @@ class App extends React.Component {
}
}
componentWillUnmount() {
tracerManager.setOnError(null);
}
updateDirectory({ categoryKey = null, algorithmKey = null }) {
this.props.setDirectory(categoryKey, algorithmKey);
}
toggleNavigator(navigatorOpened = !this.state.navigatorOpened) {
this.setState({ navigatorOpened });
}
handleResizeNavigator(x, y) {
const navigatorWidth = calculatePercentageWidth(this.elMain, x);
this.setState({ navigatorWidth });
}
handleResizeViewerSection(x, y) {
const viewerSectionWidth = calculatePercentageWidth(this.elWorkspace, x);
this.setState({ viewerSectionWidth });
}
render() {
const { navigatorOpened, navigatorWidth, viewerSectionWidth } = this.state;
const { categories, categoryKey, algorithmKey } = this.props.env;
const navigatorOpened = this.navigator.isVisible();
return categories && categoryKey && algorithmKey && (
<div className={styles.app}>
<Header onClickTitleBar={() => this.toggleNavigator()} navigatorOpened={navigatorOpened} />
<main className={styles.main} ref={ref => this.elMain = ref}>
{
navigatorOpened &&
<Navigator className={styles.navigator} style={{ width: navigatorWidth }} />
}
<Divider vertical onResize={(x, y) => this.handleResizeNavigator(x, y)} />
<div className={styles.workspace} ref={ref => this.elWorkspace = ref}>
<ViewerSection className={styles.viewer_section} style={{ width: viewerSectionWidth }} />
<Divider vertical onResize={(x, y) => this.handleResizeViewerSection(x, y)} />
<EditorSection className={styles.editor_section} />
</div>
</main>
<Header onClickTitleBar={() => this.navigator.setVisible(!navigatorOpened)} navigatorOpened={navigatorOpened} />
{
this.workspace.render({ className: styles.workspace })
}
<ToastContainer className={styles.toast_container} />
</div>
);
......
......@@ -37,41 +37,10 @@ input {
align-items: stretch;
height: 100%;
.loading_slider {
position: absolute;
top: 0;
left: 0;
right: 0;
}
.main {
.workspace {
flex: 1;
display: flex;
align-items: stretch;
.navigator {
min-width: $resizeable-min;
}
.workspace {
flex: 1;
display: flex;
align-items: stretch;
background-color: $theme-dark;
min-width: $resizeable-min * 2;
.viewer_section {
border: 1px solid $theme-light;
min-width: $resizeable-min;
}
.editor_section {
flex: 1;
margin-left: -1px;
border: 1px solid $theme-light;
min-width: $resizeable-min;
}
}
background-color: $theme-dark;
border-top: 1px solid $theme-light;
}
.toast_container {
......@@ -80,67 +49,4 @@ input {
right: 0;
z-index: 99;
}
}
//----!!!
.fa-spin-faster {
animation: fa-spin 1s infinite ease-in-out;
}
.mchrt-chart {
width: 100%;
height: 100%;
}
//------???
.buttonContainer {
width: 75px;
height: 25px;
display: block;
position: relative;
z-index: 100;
background-color: white;
}
.inputField {
width: 25px;
border: 0;
}
.sb-button {
border: 1px solid $theme-light;
height: 25px;
margin: 0 auto;
}
.auto-gen {
top: 30px;
height: 100%;
width: 100%;
text-align: center;
background-color: $theme-dark;
align-items: center;
}
.inputs {
display: inline-block;
border: 0;
background-color: $theme-light;
height: 25px;
width: 75px;
}
.grid {
width: 50%;
height: 50%;
float: left;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.fields {
margin-top: 5px;
margin-bottom: 5px;
}
}
\ No newline at end of file
import React from 'react';
import { classes } from '/common/util';
import { Section } from '/components';
import styles from './stylesheet.scss';
class BasicSection extends React.Component {
render() {
const { className, Component, style, relativeWeight } = this.props;
return (
<Section className={classes(styles.basic_section, className)} style={style}>
<Component className={styles.component} relativeWeight={relativeWeight} />
</Section>
);
}
}
export default BasicSection;
@import "~/common/stylesheet/index";
.basic_section {
.component {
flex: 1;
}
}
\ No newline at end of file
......@@ -3,13 +3,10 @@ import { connect } from 'react-redux';
import AceEditor from 'react-ace';
import 'brace/mode/javascript';
import 'brace/theme/tomorrow_night_eighties';
import FontAwesomeIcon from '@fortawesome/react-fontawesome'
import faInfoCircle from '@fortawesome/fontawesome-free-solid/faInfoCircle';
import { classes } from '/common/util';
import { Ellipsis, TabBar } from '/components';
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(
......@@ -19,7 +16,7 @@ import styles from './stylesheet.scss';
...envActions,
}
)
class EditorSection extends React.Component {
class CodeEditor extends React.Component {
constructor(props) {
super(props);
......@@ -79,42 +76,22 @@ class EditorSection extends React.Component {
render() {
const { lineMarker, code } = this.state;
const { className } = this.props;
const { categories, categoryKey, algorithmKey } = this.props.env;
const category = categories.find(category => category.key === categoryKey);
const algorithm = category.algorithms.find(algorithm => algorithm.key === algorithmKey);
const tabs = ['code.js'].map(fileName => ({
title: fileName,
props: {
to: `/${category.key}/${algorithm.key}`
},
}));
const tabIndex = 0; // TODO
const fileInfo = ''; // TODO
const { className, relativeWeight } = this.props;
return (
<section className={classes(styles.editor_section, className)}>
<TabBar tabs={tabs} tabIndex={tabIndex} />
<div className={styles.info_container}>
<FontAwesomeIcon fixedWidth icon={faInfoCircle} className={styles.info_icon} />
<Ellipsis className={styles.info_text}>{fileInfo}</Ellipsis>
</div>
<div className={styles.content}>
<AceEditor
className={styles.editor}
mode="javascript"
theme="tomorrow_night_eighties"
name="code_editor"
editorProps={{ $blockScrolling: true }}
onChange={value => this.handleChangeCode(value)}
markers={lineMarker ? [lineMarker] : []}
value={code} />
</div>
</section>
<AceEditor
className={classes(styles.code_editor, className)}
mode="javascript"
theme="tomorrow_night_eighties"
name="code_editor"
editorProps={{ $blockScrolling: true }}
onChange={value => this.handleChangeCode(value)}
markers={lineMarker ? [lineMarker] : []}
value={code}
width={relativeWeight} /> // trick to update on resize
);
}
}
export default EditorSection;
export default CodeEditor;
@import "~/common/stylesheet/index";
.code_editor {
width: 100% !important;
height: 100% !important;
min-width: 0 !important;
min-height: 0 !important;
.current_line_marker {
background: rgba(#29d, 0.4);
border: 1px solid #29d;
position: absolute;
width: 100% !important;
animation: line_highlight .1s;
}
@keyframes line_highlight {
from {
background: rgba(#29d, 0.1);
}
to {
background: rgba(#29d, 0.4);
}
}
}
\ No newline at end of file
......@@ -6,6 +6,7 @@
align-items: stretch;
padding: 16px;
font-size: $font-size-large;
overflow-y: scroll;
li {
margin: 10px 0px;
......
......@@ -12,13 +12,14 @@ class Divider extends React.Component {
}
handleMouseDown(e) {
this.target = e.target;
document.addEventListener('mousemove', this.handleMouseMove);
document.addEventListener('mouseup', this.handleMouseUp);
}
handleMouseMove(e) {
const { onResize } = this.props;
onResize(e.clientX, e.clientY);
onResize(this.target, e.clientX, e.clientY);
}
handleMouseUp(e) {
......@@ -27,11 +28,11 @@ class Divider extends React.Component {
}
render() {
const { className, vertical, horizontal } = this.props;
const { className, horizontal } = this.props;
return (
<div
className={classes(styles.divider, vertical && styles.vertical, horizontal && styles.horizontal, className)}
className={classes(styles.divider, horizontal ? styles.horizontal : styles.vertical, className)}
onMouseDown={this.handleMouseDown} />
);
}
......
......@@ -4,23 +4,53 @@
position: relative;
z-index: 99;
&.vertical:after {
position: absolute;
content: '';
top: 0;
bottom: 0;
left: -3px;
width: 6px;
cursor: ew-resize;
&:before {
background-color: $theme-light;
}
&.horizontal:after {
&:before,
&:after {
position: absolute;
content: '';
left: 0;
right: 0;
top: -3px;
height: 6px;
cursor: ns-resize;
}
&.horizontal {
&:before,
&:after {
top: 0;
bottom: 0;
}
&:before {
width: 1px;
}
&:after {
left: -3px;
width: 6px;
cursor: ew-resize;
}
}
&.vertical {
&:before,
&:after {
left: 0;
right: 0;
}
&:before {
height: 1px;
}
&:after {
top: -3px;
height: 6px;
cursor: ns-resize;
}
}
&:last-child {
display: none;
}
}
\ No newline at end of file
@import "~/common/stylesheet/index";
.editor_section {
display: flex;
flex-direction: column;
.info_container {
display: flex;
align-items: center;
padding: 8px;
&:hover {
position: relative;
z-index: 99;
height: auto;
bottom: auto;
box-shadow: 0 8px 8px -8px $color-shadow;
.info_text {
white-space: normal;
}
}
.info_icon {
margin-right: 8px;
}
.info_text {
display: block;
}
}
.content {
flex: 1;
display: flex;
flex-direction: column;
.editor {
width: 100% !important;
height: 100% !important;
.current_line_marker {
background: rgba(#29d, 0.4);
border: 1px solid #29d;
position: absolute;
width: 100% !important;
animation: line_highlight .1s;
}
@keyframes line_highlight {
from {
background: rgba(#29d, 0.1);
}
to {
background: rgba(#29d, 0.4);
}
}
}
}
}
\ No newline at end of file
......@@ -3,25 +3,23 @@ import FontAwesomeIcon from '@fortawesome/react-fontawesome'
import faCaretDown from '@fortawesome/fontawesome-free-solid/faCaretDown';
import faCaretRight from '@fortawesome/fontawesome-free-solid/faCaretRight';
import styles from './stylesheet.scss';
import { Ellipsis, ListItem } from '/components';
import { ListItem } from '/components';
import { classes } from '/common/util';
class ExpandableListItem extends React.Component {
render() {
const { className, children, label, opened, ...props } = this.props;
const { className, children, opened, ...props } = this.props;
return opened ? (
<div className={classes(styles.expandable_list_item, className)}>
<ListItem className={styles.category} {...props}>
<Ellipsis className={styles.label}>{label}</Ellipsis>
<FontAwesomeIcon fixedWidth icon={faCaretDown} />
<FontAwesomeIcon className={styles.icon} fixedWidth icon={faCaretDown} />
</ListItem>
{children}
</div>
) : (
<ListItem className={classes(styles.category, className)} {...props}>
<Ellipsis className={styles.label}>{label}</Ellipsis>
<FontAwesomeIcon fixedWidth icon={faCaretRight} />
<FontAwesomeIcon className={styles.icon} fixedWidth icon={faCaretRight} />
</ListItem>
);
}
......
......@@ -3,9 +3,8 @@
.category {
justify-content: space-between;
.label {
flex: 1;
margin-right: 4px;
.icon {
margin-left: 4px;
}
}
......
import React from 'react';
import styles from './stylesheet.scss';
import { classes } from '/common/util';
import { Button } from '/components';
import { Button, Ellipsis } from '/components';
class ListItem extends React.Component {
render() {
const { className, children, indent, ...props } = this.props;
const { className, children, indent, label, ...props } = this.props;
return (
<Button className={classes(styles.list_item, indent && styles.indent, className)} {...props}>
<Ellipsis className={styles.label}>{label}</Ellipsis>
{children}
</Button>
);
......
......@@ -3,6 +3,10 @@
.list_item {
height: $line-height;
.label {
flex: 1;
}
&.indent {
padding-left: 24px;
}
......
......@@ -5,10 +5,9 @@ import faSearch from '@fortawesome/fontawesome-free-solid/faSearch';
import faCode from '@fortawesome/fontawesome-free-solid/faCode';
import faBook from '@fortawesome/fontawesome-free-solid/faBook';
import faGithub from '@fortawesome/fontawesome-free-brands/faGithub';
import { Ellipsis, ExpandableListItem, ListItem } from '/components';
import { ExpandableListItem, ListItem } from '/components';
import { classes } from '/common/util';
import { actions as envActions } from '/reducers/env';
import 'github-fork-ribbon-css/gh-fork-ribbon.css';
import styles from './stylesheet.scss';
@connect(
......@@ -93,9 +92,7 @@ class Navigator extends React.Component {
const selected = category.key === selectedCategoryKey && algorithm.key === selectedAlgorithmKey;
return (
<ListItem indent key={algorithm.key} selected={selected}
to={`/${category.key}/${algorithm.key}`}>
<Ellipsis>{algorithm.name}</Ellipsis>
</ListItem>
to={`/${category.key}/${algorithm.key}`} label={algorithm.name} />
)
})
}
......@@ -105,25 +102,10 @@ class Navigator extends React.Component {
}
</div>
<div className={styles.footer}>
<ListItem className={styles.scratch_paper} icon={faCode}>Scratch Paper</ListItem>
<ListItem className={styles.documentation} icon={faBook}>Tracer API</ListItem>
<ExpandableListItem icon={faGithub} onClick={() => this.togglePoweredBy()} label="Powered by ..."
opened={poweredByOpened}>
<ListItem indent href="https://github.com/jquery/jquery">jquery/jquery</ListItem>
<ListItem indent href="https://github.com/ajaxorg/ace">ajaxorg/ace</ListItem>
<ListItem indent href="https://github.com/jacomyal/sigma.js">jacomyal/sigma.js</ListItem>
<ListItem indent href="https://github.com/chartjs/Chart.js">chartjs/Chart.js</ListItem>
<ListItem indent href="https://github.com/Daniel15/babel-standalone">Daniel15/babel-standalone</ListItem>
<ListItem indent href="https://github.com/showdownjs/showdown">showdownjs/showdown</ListItem>
<ListItem indent href="https://github.com/FortAwesome/Font-Awesome">FortAwesome/Font-Awesome</ListItem>
<ListItem indent href="https://github.com/simonwhitaker/github-fork-ribbon-css">simonwhitaker/github-fork-ribbon-css</ListItem>
<ListItem indent href="https://github.com/mathjax/MathJax">mathjax/MathJax</ListItem>
</ExpandableListItem>
<ListItem icon={faCode} label="Scratch Paper" />
<ListItem icon={faBook} label="Tracer API" />
<ListItem icon={faGithub} label="Fork me on GitHub" href="https://github.com/parkjs814/AlgorithmVisualizer" />
</div>
<a className={classes('github-fork-ribbon', 'right-bottom')}
href="http://github.com/parkjs814/AlgorithmVisualizer" data-ribbon="Fork me on GitHub"
title="Fork me on GitHub">Fork me on GitHub
</a>
</nav>
);
}
......
@import "~/common/stylesheet/index";
.navigator {
position: relative;
display: flex;
flex-direction: column;
background-color: $theme-normal;
.search_bar_container {
height: $line-height;
padding: 0 8px;
display: flex;
align-items: stretch;
border-top: 1px solid $theme-light;
border-bottom: 1px solid $theme-light;
&:focus-within {
background-color: $color-overlay;
......@@ -27,24 +27,15 @@
background: none;
border: none;
outline: none;
overflow: hidden;
}
}
.algorithm_list {
flex: 1;
border-bottom: 1px solid $theme-light;
overflow-y: scroll;
border-top: 1px solid $theme-light;
}
:global(.github-fork-ribbon) {
&:before {
background-color: $theme-normal;
}
&:after {
color: $color-font;
}
.footer {
border-top: 1px solid $theme-light;
}
}
\ No newline at end of file
@import "~/common/stylesheet/index";
.renderer_container {
flex: 1;
display: flex;
align-items: stretch;
flex-direction: column;
......
import React from 'react';
import { classes } from '/common/util';
import styles from './stylesheet.scss';
class Section extends React.Component {
render() {
const { className, children, style } = this.props;
return (
<section className={classes(styles.section, className)} style={style}>
{children}
</section>
);
}
}
export default Section;
@import "~/common/stylesheet/index";
.section {
display: flex;
flex-direction: column;
align-items: stretch;
min-width: 0;
min-height: 0;
overflow: hidden;
}
\ No newline at end of file
import React from 'react';
import { classes } from '/common/util';
import { Section } from '/components';
import styles from './stylesheet.scss';
class SectionContainer extends React.Component {
render() {
const { className, children, horizontal, style } = this.props;
return (
<Section className={classes(styles.section_container, horizontal && styles.horizontal, className)} style={style}>
{children}
</Section>
);
}
}
export default SectionContainer;
@import "~/common/stylesheet/index";
.section_container {
&.horizontal {
flex-direction: row;
}
}
\ 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 { Button } from '/components';
import { classes } from '/common/util';
import styles from './stylesheet.scss';
class TabBar extends React.Component {
render() {
const { className, tabs, tabIndex } = this.props;
const prevIndex = (tabIndex - 1 + tabs.length) % tabs.length;
const nextIndex = (tabIndex + 1) % tabs.length;
return (
<div className={classes(styles.tab_bar, className)}>
<Button className={styles.tab} icon={faAngleLeft} {...tabs[prevIndex].props} />
<div className={styles.wrapper}>
{
tabs.map((tab, i) => {
const { title, props } = tab;
const selected = tabIndex === i;
return (
<Button className={classes(styles.tab, selected && styles.selected)} key={i} {...props}>{title}</Button>
)
})
}
</div>
<Button className={styles.tab} icon={faAngleRight} {...tabs[nextIndex].props} />
</div>
);
}
}
export default TabBar;
@import "~/common/stylesheet/index";
.tab_bar {
position: relative;
display: flex;
align-items: stretch;
height: $line-height;
background-color: $theme-normal;
&:before {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1px;
background-color: $theme-light;
}
.wrapper {
position: relative;
flex: 1;
display: flex;
overflow-x: scroll;
white-space: nowrap;
.tab {
&.selected {
border-left: 1px solid $theme-light;
border-right: 1px solid $theme-light;
background-color: $theme-dark;
}
}
&.shadow-left {
box-shadow: inset 16px 0 16px -16px $color-shadow;
}
&.shadow-right {
box-shadow: inset -16px 0 16px -16px $color-shadow;
}
&.shadow-left.shadow-right {
box-shadow: inset 16px 0 16px -16px $color-shadow, inset -16px 0 16px -16px $color-shadow;
}
}
}
\ 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;
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;
const selected = tabIndex === i;
return <Component key={id} className={classes(styles.tab, selected && styles.selected)}
relativeWeight={relativeWeight} />;
})
}
</div>
</Section>
);
}
}
export default TabSection;
@import "~/common/stylesheet/index";
.tab_section {
.tab_bar {
position: relative;
display: flex;
align-items: stretch;
height: $line-height;
background-color: $theme-normal;
&:before {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1px;
background-color: $theme-light;
}
.wrapper {
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;
}
}
}
}
.content {
flex: 1;
display: flex;
flex-direction: column;
.tab {
flex: 1;
display: none;
&.selected {
display: flex;
}
}
}
}
\ No newline at end of file
import React from 'react';
import { classes } from '/common/util';
import { DescriptionViewer, RendererContainer, TabBar, WikiViewer } from '/components';
import styles from './stylesheet.scss';
class ViewerSection extends React.Component {
constructor(props) {
super(props);
this.state = {
tabIndex: 0,
};
}
setTabIndex(tabIndex) {
this.setState({ tabIndex });
}
render() {
const { className, style } = this.props;
const { tabIndex } = this.state;
const tabs = ['Visualization', 'Description', 'Tracer API'].map((title, i) => ({
title,
props: {
onClick: () => this.setTabIndex(i)
},
}));
return (
<section className={classes(styles.viewer_section, className)} style={style}>
<TabBar tabs={tabs} tabIndex={tabIndex} />
<div className={styles.content} data-tab_index={tabIndex}>
<RendererContainer className={styles.tab} />
<DescriptionViewer className={styles.tab} />
<WikiViewer className={styles.tab} />
</div>
</section>
);
}
}
export default ViewerSection;
@import "~/common/stylesheet/index";
.viewer_section {
display: flex;
flex-direction: column;
align-items: stretch;
.content {
flex: 1;
display: flex;
flex-direction: column;
overflow-y: scroll;
.tab {
display: none;
}
&[data-tab_index='0'] .tab:nth-child(1),
&[data-tab_index='1'] .tab:nth-child(2),
&[data-tab_index='2'] .tab:nth-child(3) {
display: flex;
}
}
}
\ No newline at end of file
......@@ -35,9 +35,8 @@ class WikiViewer extends React.Component {
};
return (
<div className={classes(styles.wiki_viewer, className)}>
<ReactMarkdown className={styles.markdown} source={wiki} renderers={{ link: InnerLink }} />
</div>
<ReactMarkdown className={classes(styles.wiki_viewer, className)} source={wiki} renderers={{ link: InnerLink }}>
</ReactMarkdown>
);
}
}
......
......@@ -4,14 +4,12 @@
display: flex;
flex-direction: column;
align-items: stretch;
padding: 16px;
font-size: $font-size-large;
overflow-y: scroll;
.markdown {
padding: 16px;
font-size: $font-size-large;
a {
text-decoration: underline;
cursor: pointer;
}
a {
text-decoration: underline;
cursor: pointer;
}
}
\ No newline at end of file
export { default as App } from './App';
export { default as BasicSection } from './BasicSection';
export { default as Button } from './Button';
export { default as CodeEditor } from './CodeEditor';
export { default as DescriptionViewer } from './DescriptionViewer';
export { default as Divider } from './Divider';
export { default as EditorSection } from './EditorSection';
export { default as Ellipsis } from './Ellipsis';
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 TabBar } from './TabBar';
export { default as Section } from './Section';
export { default as SectionContainer } from './SectionContainer';
export { default as TabSection } from './TabSection';
export { default as ToastContainer } from './ToastContainer';
export { default as ViewerSection } from './ViewerSection';
export { default as WikiViewer } from './WikiViewer';
import uuid from 'uuid';
import React from 'react';
import { BasicSection, Divider, SectionContainer, TabSection } from '/components';
const minSize = 20;
class UISection {
constructor(parent = null, weight = 1, visible = true) {
this.id = uuid.v4();
this.parent = parent;
this.weight = weight;
this.visible = visible;
}
setVisible(visible) {
this.visible = visible;
this.change();
}
isVisible() {
return this.visible;
}
handleResize(target, clientX, clientY) {
const { offsetLeft, offsetTop, offsetWidth, offsetHeight } = target.parentElement;
let position, size;
if (this.parent.horizontal) {
position = clientX - offsetLeft;
size = offsetWidth;
} else {
position = clientY - offsetTop;
size = offsetHeight;
}
const visibleSections = this.parent.sections.filter(section => section.visible);
const index = visibleSections.findIndex(section => section === this);
const weights = visibleSections.map(section => section.weight);
const totalWeight = weights.reduce((sumWeight, weight) => sumWeight + weight, 0);
const startWeight = weights.slice(0, index).reduce((sumWeight, weight) => sumWeight + weight, 0);
const endWeight = position / size * totalWeight;
const oldWeight = weights[index];
const newWeight = endWeight - startWeight;
const oldScale = totalWeight - startWeight - oldWeight;
const newScale = totalWeight - startWeight - newWeight;
const ratio = newScale / oldScale;
weights[index] = newWeight;
for (let i = index + 1; i < weights.length; i++) {
weights[i] *= ratio;
}
for (let i = index; i < weights.length; i++) {
if (weights[i] / totalWeight * size < minSize) return;
}
for (let i = index; i < visibleSections.length; i++) {
visibleSections[i].weight = weights[i];
}
this.change();
}
render(props) {
return null;
}
renderDivider(props) {
return (
<Divider {...props} onResize={(target, x, y) => this.handleResize(target, x, y)} />
);
}
change() {
if (this.parent) this.parent.change();
}
}
class UIBasicSection extends UISection {
constructor(parent, Component, weight, visible) {
super(parent, weight, visible);
this.Component = Component;
}
render(props) {
return (
<BasicSection {...props} Component={this.Component} />
);
}
}
class UITabSection extends UISection {
constructor(parent, weight, visible) {
super(parent, weight, visible);
this.tabs = [];
this.tabIndex = 0;
}
addTab(title, Component) {
const tab = { id: uuid.v4(), title, Component };
this.tabs.push(tab);
this.change();
return tab;
}
setTabIndex(tabIndex) {
this.tabIndex = tabIndex;
this.change();
}
render(props) {
return (
<TabSection {...props} tabs={this.tabs} tabIndex={this.tabIndex}
onChangeTabIndex={tabIndex => this.setTabIndex(tabIndex)} />
);
}
}
class UIContainer extends UISection {
constructor(parent, horizontal = true, weight, visible) {
super(parent, weight, visible);
this.horizontal = horizontal;
this.sections = [];
}
setHorizontal(horizontal) {
this.horizontal = horizontal;
this.change();
}
addBasicSection(Component, weight) {
const section = new UIBasicSection(this, Component, weight);
this.sections.push(section);
this.change();
return section;
}
addTabSection(weight) {
const section = new UITabSection(this, weight);
this.sections.push(section);
this.change();
return section;
}
addContainer(horizontal, weight) {
const container = new UIContainer(this, horizontal, weight);
this.sections.push(container);
this.change();
return container;
}
render(props) {
const visibleSections = this.sections.filter(section => section.visible);
const weights = visibleSections.map(section => section.weight);
const totalWeight = weights.reduce((sumWeight, weight) => sumWeight + weight, 0);
return (
<SectionContainer {...props} horizontal={this.horizontal}>
{
visibleSections.map(section => {
const relativeWeight = section.weight / totalWeight;
return [
section.render({
key: section.id,
style: { flex: relativeWeight },
relativeWeight,
}),
section.renderDivider({ key: `divider-${section.id}`, horizontal: this.horizontal }),
];
}).reduce((flat, toFlatten) => flat.concat(toFlatten), [])
}
</SectionContainer>
);
}
}
class Workspace {
constructor(horizontal) {
this.rootContainer = new UIContainer(this, horizontal);
this.onChange = null;
}
getRootContainer() {
return this.rootContainer;
}
setHorizontal(horizontal) {
this.rootContainer.setHorizontal(horizontal);
}
addBasicSection(Component, weight) {
return this.rootContainer.addBasicSection(Component, weight);
}
addTabSection(weight) {
return this.rootContainer.addTabSection(weight);
}
addContainer(horizontal, weight) {
return this.rootContainer.addContainer(horizontal, weight);
}
render(props) {
return this.rootContainer.render(props);
}
setOnChange(onChange) {
this.onChange = onChange;
this.change();
}
change() {
if (this.onChange) this.onChange(this.rootContainer);
}
}
export default Workspace;
\ No newline at end of file
......@@ -2,4 +2,5 @@ export { default as datas } from './datas';
export { default as tracers } from './tracers';
export { default as renderers } from './renderers';
export { default as Seed } from './Seed';
export { default as tracerManager } from './tracerManager';
\ No newline at end of file
export { default as tracerManager } from './tracerManager';
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';
class Renderer extends React.Component {
constructor(props) {
......@@ -100,7 +101,7 @@ class Renderer extends React.Component {
return (
<div className={styles.renderer} onMouseDown={this.handleMouseDown} onWheel={this.handleWheel}>
<span className={styles.title}>{title}</span>
<Ellipsis className={styles.title}>{title}</Ellipsis>
{
this.renderData()
}
......
......@@ -8,11 +8,11 @@
align-items: center;
justify-content: center;
overflow: hidden;
border-bottom: 1px solid $theme-light;
border-top: 1px solid $theme-light;
font-family: 'Roboto Mono', monospace;
&:last-child {
border-bottom: none;
&:first-child {
border-top: none;
}
.title {
......
......@@ -17,6 +17,7 @@ const render = (Component) => {
<BrowserRouter>
<Switch>
<Route exact path="/:categoryKey/:algorithmKey" component={Component} />
<Route exact path="/:categoryKey" component={Component} />
<Route path="/" component={Component} />
</Switch>
</BrowserRouter>
......
......@@ -13,9 +13,7 @@ export const actions = {
setDirectory,
};
const immutables = {};
const mutables = {
const defaultState = {
categories: null,
categoryKey: null,
algorithmKey: null,
......@@ -29,7 +27,4 @@ export default handleActions({
...state,
...payload,
}),
}, {
...immutables,
...mutables,
});
}, defaultState);
......@@ -13,9 +13,7 @@ export const actions = {
hideToast,
};
const immutables = {};
const mutables = {
const defaultState = {
toasts: [],
};
......@@ -38,9 +36,7 @@ export default handleActions({
toasts,
};
},
[combineActions(
hideToast,
)]: (state, { payload }) => {
[hideToast]: (state, { payload }) => {
const { id } = payload;
const toasts = state.toasts.filter(toast => toast.id !== id);
return {
......@@ -48,7 +44,4 @@ export default handleActions({
toasts,
};
},
}, {
...immutables,
...mutables,
});
}, defaultState);
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册