diff --git a/src/frontend/common/stylesheet/dimensions.scss b/src/frontend/common/stylesheet/dimensions.scss
index 984219e71e9f867990860d7d9ed5bf0c40459e97..2c4d99c6c88e22634778d23a3104161a48a7de5c 100644
--- a/src/frontend/common/stylesheet/dimensions.scss
+++ b/src/frontend/common/stylesheet/dimensions.scss
@@ -1,4 +1,3 @@
$line-height: 32px;
-$resizeable-min: 20px;
$font-size-normal: 12px;
$font-size-large: 14px;
\ No newline at end of file
diff --git a/src/frontend/common/util.js b/src/frontend/common/util.js
index c0346880e246b75781d6a9a52d8380c51477f77d..f3545ab00d0e1add1dd91bf872afb28848862bf2 100644
--- a/src/frontend/common/util.js
+++ b/src/frontend/common/util.js
@@ -1,15 +1,5 @@
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
diff --git a/src/frontend/components/App/index.jsx b/src/frontend/components/App/index.jsx
index a21d0c63057412375ed56afe4e28605640d855fd..685b51e6ad09977fa9fd3db44548b66024af13c0 100644
--- a/src/frontend/components/App/index.jsx
+++ b/src/frontend/components/App/index.jsx
@@ -1,12 +1,19 @@
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 && (
-
this.toggleNavigator()} navigatorOpened={navigatorOpened} />
- this.elMain = ref}>
- {
- navigatorOpened &&
-
- }
- this.handleResizeNavigator(x, y)} />
- this.elWorkspace = ref}>
-
-
this.handleResizeViewerSection(x, y)} />
-
-
-
+ this.navigator.setVisible(!navigatorOpened)} navigatorOpened={navigatorOpened} />
+ {
+ this.workspace.render({ className: styles.workspace })
+ }
);
diff --git a/src/frontend/components/App/stylesheet.scss b/src/frontend/components/App/stylesheet.scss
index 86f6b735997926c5aff49d2eab79ccb13a142795..d11d2f20432910052de705273b0fd1bf050f8a79 100644
--- a/src/frontend/components/App/stylesheet.scss
+++ b/src/frontend/components/App/stylesheet.scss
@@ -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
diff --git a/src/frontend/components/BasicSection/index.jsx b/src/frontend/components/BasicSection/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..a0e526e3bc39d98d97192a6c79c7f051383cfb3e
--- /dev/null
+++ b/src/frontend/components/BasicSection/index.jsx
@@ -0,0 +1,18 @@
+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 (
+
+ );
+ }
+}
+
+export default BasicSection;
diff --git a/src/frontend/components/BasicSection/stylesheet.scss b/src/frontend/components/BasicSection/stylesheet.scss
new file mode 100644
index 0000000000000000000000000000000000000000..f6b98a5cbd9307f0fda18fb0cbd04b947b964517
--- /dev/null
+++ b/src/frontend/components/BasicSection/stylesheet.scss
@@ -0,0 +1,7 @@
+@import "~/common/stylesheet/index";
+
+.basic_section {
+ .component {
+ flex: 1;
+ }
+}
\ No newline at end of file
diff --git a/src/frontend/components/EditorSection/index.jsx b/src/frontend/components/CodeEditor/index.jsx
similarity index 57%
rename from src/frontend/components/EditorSection/index.jsx
rename to src/frontend/components/CodeEditor/index.jsx
index 9422271e72427c33f0ab847d256eabc4fb38a6a2..8118dbf9490aa359e0a38ce3fd25f6516e5b6a8b 100644
--- a/src/frontend/components/EditorSection/index.jsx
+++ b/src/frontend/components/CodeEditor/index.jsx
@@ -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 (
-
-
-
-
- {fileInfo}
-
-
-
this.handleChangeCode(value)}
- markers={lineMarker ? [lineMarker] : []}
- value={code} />
-
-
+ this.handleChangeCode(value)}
+ markers={lineMarker ? [lineMarker] : []}
+ value={code}
+ width={relativeWeight} /> // trick to update on resize
);
}
}
-export default EditorSection;
+export default CodeEditor;
diff --git a/src/frontend/components/CodeEditor/stylesheet.scss b/src/frontend/components/CodeEditor/stylesheet.scss
new file mode 100644
index 0000000000000000000000000000000000000000..88a92b2da61607d6ae9def7b991acc965a6789c7
--- /dev/null
+++ b/src/frontend/components/CodeEditor/stylesheet.scss
@@ -0,0 +1,26 @@
+@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
diff --git a/src/frontend/components/DescriptionViewer/stylesheet.scss b/src/frontend/components/DescriptionViewer/stylesheet.scss
index c2c85bd3a38bdd6ce4a7c186ff0bc53393013b6a..f94545da4999b986f7eedadbcf28d5d8e42a33c0 100644
--- a/src/frontend/components/DescriptionViewer/stylesheet.scss
+++ b/src/frontend/components/DescriptionViewer/stylesheet.scss
@@ -6,6 +6,7 @@
align-items: stretch;
padding: 16px;
font-size: $font-size-large;
+ overflow-y: scroll;
li {
margin: 10px 0px;
diff --git a/src/frontend/components/Divider/index.jsx b/src/frontend/components/Divider/index.jsx
index 385ecf5d39cdfb3a0c83fbcdbf8132adeba7caff..46bd50fcd6ab448240d3c9afe66aa62c83e934f7 100644
--- a/src/frontend/components/Divider/index.jsx
+++ b/src/frontend/components/Divider/index.jsx
@@ -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 (
);
}
diff --git a/src/frontend/components/Divider/stylesheet.scss b/src/frontend/components/Divider/stylesheet.scss
index 14fd267f13f5c653589612897bc0951d38851bc2..2118f6bda682a9759a2ae376e348ba4ec619c370 100644
--- a/src/frontend/components/Divider/stylesheet.scss
+++ b/src/frontend/components/Divider/stylesheet.scss
@@ -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
diff --git a/src/frontend/components/EditorSection/stylesheet.scss b/src/frontend/components/EditorSection/stylesheet.scss
deleted file mode 100644
index 5bda71eb240e2a7b98eb836c76167072b51633dd..0000000000000000000000000000000000000000
--- a/src/frontend/components/EditorSection/stylesheet.scss
+++ /dev/null
@@ -1,61 +0,0 @@
-@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
diff --git a/src/frontend/components/ExpandableListItem/index.jsx b/src/frontend/components/ExpandableListItem/index.jsx
index 7799922a7fe0ac3685aaf65e6229d3151b189b25..4acb746502a74e3a7b124c2e12ba8da9e2d86073 100644
--- a/src/frontend/components/ExpandableListItem/index.jsx
+++ b/src/frontend/components/ExpandableListItem/index.jsx
@@ -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 ? (
- {label}
-
+
{children}
) : (
- {label}
-
+
);
}
diff --git a/src/frontend/components/ExpandableListItem/stylesheet.scss b/src/frontend/components/ExpandableListItem/stylesheet.scss
index ba350d29162695c9d1cf3402f1949617aedac810..9dfd59246cd620a781bd71380a8667e30148dc49 100644
--- a/src/frontend/components/ExpandableListItem/stylesheet.scss
+++ b/src/frontend/components/ExpandableListItem/stylesheet.scss
@@ -3,9 +3,8 @@
.category {
justify-content: space-between;
- .label {
- flex: 1;
- margin-right: 4px;
+ .icon {
+ margin-left: 4px;
}
}
diff --git a/src/frontend/components/ListItem/index.jsx b/src/frontend/components/ListItem/index.jsx
index 9d3e14e082854d8f58b6848e889e76f79891c13a..ba41855b9754c9b535147ca41d7755fbfcd20a6f 100644
--- a/src/frontend/components/ListItem/index.jsx
+++ b/src/frontend/components/ListItem/index.jsx
@@ -1,14 +1,15 @@
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 (
);
diff --git a/src/frontend/components/ListItem/stylesheet.scss b/src/frontend/components/ListItem/stylesheet.scss
index 890ef9be01e07b980ed097d2a540436c2e254868..ffd4152348068104ef66f319528f5c6911bcb190 100644
--- a/src/frontend/components/ListItem/stylesheet.scss
+++ b/src/frontend/components/ListItem/stylesheet.scss
@@ -3,6 +3,10 @@
.list_item {
height: $line-height;
+ .label {
+ flex: 1;
+ }
+
&.indent {
padding-left: 24px;
}
diff --git a/src/frontend/components/Navigator/index.jsx b/src/frontend/components/Navigator/index.jsx
index 4fbc913581622185c7d130d784fb7c727074e141..4493571d7cdb8c61302a236c2260a25322834422 100644
--- a/src/frontend/components/Navigator/index.jsx
+++ b/src/frontend/components/Navigator/index.jsx
@@ -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 (
- {algorithm.name}
-
+ to={`/${category.key}/${algorithm.key}`} label={algorithm.name} />
)
})
}
@@ -105,25 +102,10 @@ class Navigator extends React.Component {
}
- Scratch Paper
- Tracer API
- this.togglePoweredBy()} label="Powered by ..."
- opened={poweredByOpened}>
- jquery/jquery
- ajaxorg/ace
- jacomyal/sigma.js
- chartjs/Chart.js
- Daniel15/babel-standalone
- showdownjs/showdown
- FortAwesome/Font-Awesome
- simonwhitaker/github-fork-ribbon-css
- mathjax/MathJax
-
+
+
+
- Fork me on GitHub
-
);
}
diff --git a/src/frontend/components/Navigator/stylesheet.scss b/src/frontend/components/Navigator/stylesheet.scss
index 51d4feee2ebcc8b91d5d594ecf48bbb8927fc1e5..609e1a0b12d1e0f768f8bded55e187f3064e336f 100644
--- a/src/frontend/components/Navigator/stylesheet.scss
+++ b/src/frontend/components/Navigator/stylesheet.scss
@@ -1,16 +1,16 @@
@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
diff --git a/src/frontend/components/RendererContainer/stylesheet.scss b/src/frontend/components/RendererContainer/stylesheet.scss
index d0835fc07e7f928c62fcf53480c209c52fbb539a..f919b75241ac80e237447fad831a8a925f6faaea 100644
--- a/src/frontend/components/RendererContainer/stylesheet.scss
+++ b/src/frontend/components/RendererContainer/stylesheet.scss
@@ -1,7 +1,6 @@
@import "~/common/stylesheet/index";
.renderer_container {
- flex: 1;
display: flex;
align-items: stretch;
flex-direction: column;
diff --git a/src/frontend/components/Section/index.jsx b/src/frontend/components/Section/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..e6ab8b469f44f1caac6f8685beba8ad6d5b4233d
--- /dev/null
+++ b/src/frontend/components/Section/index.jsx
@@ -0,0 +1,17 @@
+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 (
+
+ );
+ }
+}
+
+export default Section;
diff --git a/src/frontend/components/Section/stylesheet.scss b/src/frontend/components/Section/stylesheet.scss
new file mode 100644
index 0000000000000000000000000000000000000000..79832e3ae45bc209410a95fa67986901d962fa4d
--- /dev/null
+++ b/src/frontend/components/Section/stylesheet.scss
@@ -0,0 +1,10 @@
+@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
diff --git a/src/frontend/components/SectionContainer/index.jsx b/src/frontend/components/SectionContainer/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..0e00911fc08866e4e89845750e209ea06cdc51a5
--- /dev/null
+++ b/src/frontend/components/SectionContainer/index.jsx
@@ -0,0 +1,18 @@
+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 (
+
+ );
+ }
+}
+
+export default SectionContainer;
diff --git a/src/frontend/components/SectionContainer/stylesheet.scss b/src/frontend/components/SectionContainer/stylesheet.scss
new file mode 100644
index 0000000000000000000000000000000000000000..2dc213363ec10dd9fdd2592a0d869ee03bbbd8f6
--- /dev/null
+++ b/src/frontend/components/SectionContainer/stylesheet.scss
@@ -0,0 +1,7 @@
+@import "~/common/stylesheet/index";
+
+.section_container {
+ &.horizontal {
+ flex-direction: row;
+ }
+}
\ No newline at end of file
diff --git a/src/frontend/components/TabBar/index.jsx b/src/frontend/components/TabBar/index.jsx
deleted file mode 100644
index 9befdb99b18104ba98e515563e0647c9239cbad8..0000000000000000000000000000000000000000
--- a/src/frontend/components/TabBar/index.jsx
+++ /dev/null
@@ -1,36 +0,0 @@
-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 (
-
-
-
- {
- tabs.map((tab, i) => {
- const { title, props } = tab;
- const selected = tabIndex === i;
- return (
-
- )
- })
- }
-
-
-
- );
- }
-}
-
-export default TabBar;
-
diff --git a/src/frontend/components/TabBar/stylesheet.scss b/src/frontend/components/TabBar/stylesheet.scss
deleted file mode 100644
index 5b806ce608e4080fbe244ab8a923344d225ec8cc..0000000000000000000000000000000000000000
--- a/src/frontend/components/TabBar/stylesheet.scss
+++ /dev/null
@@ -1,47 +0,0 @@
-@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
diff --git a/src/frontend/components/TabSection/index.jsx b/src/frontend/components/TabSection/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..0d83f42b17dd5767f283569e6352e3b6e6f6ba2f
--- /dev/null
+++ b/src/frontend/components/TabSection/index.jsx
@@ -0,0 +1,51 @@
+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 (
+
+
+
+
+ {
+ tabs.map((tab, i) => {
+ const { id, Component } = tab;
+ const selected = tabIndex === i;
+ return ;
+ })
+ }
+
+
+ );
+ }
+}
+
+export default TabSection;
+
diff --git a/src/frontend/components/TabSection/stylesheet.scss b/src/frontend/components/TabSection/stylesheet.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e8bd458dbc3805137f5c61e14eaa9b6dc34750ab
--- /dev/null
+++ b/src/frontend/components/TabSection/stylesheet.scss
@@ -0,0 +1,52 @@
+@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
diff --git a/src/frontend/components/ViewerSection/index.jsx b/src/frontend/components/ViewerSection/index.jsx
deleted file mode 100644
index 866492275f2d68b249b8b15dab020a78fc670e2b..0000000000000000000000000000000000000000
--- a/src/frontend/components/ViewerSection/index.jsx
+++ /dev/null
@@ -1,44 +0,0 @@
-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 (
-
- );
- }
-}
-
-export default ViewerSection;
-
diff --git a/src/frontend/components/ViewerSection/stylesheet.scss b/src/frontend/components/ViewerSection/stylesheet.scss
deleted file mode 100644
index d13211c1ea0f8ac5aadf6536dc1f968d284b68c3..0000000000000000000000000000000000000000
--- a/src/frontend/components/ViewerSection/stylesheet.scss
+++ /dev/null
@@ -1,24 +0,0 @@
-@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
diff --git a/src/frontend/components/WikiViewer/index.jsx b/src/frontend/components/WikiViewer/index.jsx
index 25827c1a94955b81523e74eeb06037854f083b23..e4479a0896ebffa09471aedab3f0c2f85b963548 100644
--- a/src/frontend/components/WikiViewer/index.jsx
+++ b/src/frontend/components/WikiViewer/index.jsx
@@ -35,9 +35,8 @@ class WikiViewer extends React.Component {
};
return (
-
-
-
+
+
);
}
}
diff --git a/src/frontend/components/WikiViewer/stylesheet.scss b/src/frontend/components/WikiViewer/stylesheet.scss
index 23b5d01f363de36dda85c4b09f67c32ca4048d96..147d19f7503eb9548a90ab7703e90cf58d00314b 100644
--- a/src/frontend/components/WikiViewer/stylesheet.scss
+++ b/src/frontend/components/WikiViewer/stylesheet.scss
@@ -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
diff --git a/src/frontend/components/index.js b/src/frontend/components/index.js
index a20a0977c3e4f3cd22fa94442926b9f0b6f78055..2c01c3e0f54c064252fbcf88a43f3a06a2c8f8fe 100644
--- a/src/frontend/components/index.js
+++ b/src/frontend/components/index.js
@@ -1,15 +1,17 @@
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';
diff --git a/src/frontend/core/Workspace.js b/src/frontend/core/Workspace.js
new file mode 100644
index 0000000000000000000000000000000000000000..4650cfcbf4b5ece3bb25880d036f65706fd49d88
--- /dev/null
+++ b/src/frontend/core/Workspace.js
@@ -0,0 +1,219 @@
+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 (
+ 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 (
+
+ );
+ }
+}
+
+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 (
+ 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 (
+
+ {
+ 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), [])
+ }
+
+ );
+ }
+}
+
+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
diff --git a/src/frontend/core/index.js b/src/frontend/core/index.js
index 5b2afadc280c741333c26ee2824f7ed9b14c6d32..2f4d4404d5213a9cdc49f62f356f7bbdb77c464f 100644
--- a/src/frontend/core/index.js
+++ b/src/frontend/core/index.js
@@ -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
diff --git a/src/frontend/core/renderers/Renderer/index.jsx b/src/frontend/core/renderers/Renderer/index.jsx
index f78895fdea3a73e4f1b84d6b0a88037c89ca1a45..6fbc6574d2558d70d037f313292ff3cdaec7cb74 100644
--- a/src/frontend/core/renderers/Renderer/index.jsx
+++ b/src/frontend/core/renderers/Renderer/index.jsx
@@ -1,5 +1,6 @@
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 (
- {title}
+ {title}
{
this.renderData()
}
diff --git a/src/frontend/core/renderers/Renderer/stylesheet.scss b/src/frontend/core/renderers/Renderer/stylesheet.scss
index 9380026685a1fffad0d9ddb41b0b645067517fa5..79f8382a2e86ecf17ab9c6bf1607ef7e4720f34b 100644
--- a/src/frontend/core/renderers/Renderer/stylesheet.scss
+++ b/src/frontend/core/renderers/Renderer/stylesheet.scss
@@ -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 {
diff --git a/src/frontend/index.jsx b/src/frontend/index.jsx
index 74c0d5e42b26705affa1b3453cf3ce606163cb48..85aea1a8e92575e652ae540e026f4602b3873ef1 100644
--- a/src/frontend/index.jsx
+++ b/src/frontend/index.jsx
@@ -17,6 +17,7 @@ const render = (Component) => {
+
diff --git a/src/frontend/reducers/env.js b/src/frontend/reducers/env.js
index 88b36ad9506b97b6cfec56ec82b49077dd5eadd2..a0bc138f74e15caf11b2b9b0de6e9d91e0f528bb 100644
--- a/src/frontend/reducers/env.js
+++ b/src/frontend/reducers/env.js
@@ -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);
diff --git a/src/frontend/reducers/toast.js b/src/frontend/reducers/toast.js
index 4a994c940c3914f52e542272ad1ba46d4a8d82ca..830417390ec87b5aefa6ce65ac510af82b9ff89f 100644
--- a/src/frontend/reducers/toast.js
+++ b/src/frontend/reducers/toast.js
@@ -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);