diff --git a/src/backend/controllers/tracers.js b/src/backend/controllers/tracers.js
index d4304a45eda8ed8e4328b7c1a30acd8aad365c99..04806e497ae2de971446f6f50952096218b8cb66 100644
--- a/src/backend/controllers/tracers.js
+++ b/src/backend/controllers/tracers.js
@@ -38,7 +38,7 @@ const trace = lang => (req, res, next) => {
}).finally(() => clearTimeout(timer));
})
.then(() => new Promise((resolve, reject) => {
- const visualizationPath = path.resolve(tempPath, 'traces.json');
+ const visualizationPath = path.resolve(tempPath, 'visualization.json');
res.sendFile(visualizationPath, err => {
if (err) return reject(new Error('Visualization Not Found'));
resolve();
diff --git a/src/backend/tracers/js/worker.js b/src/backend/tracers/js/worker.js
index 681d748a46a21bd0ec290e294889b24f7e8afabf..7d5ac5b8e564efb51094d043e864afa60a0f7106 100644
--- a/src/backend/tracers/js/worker.js
+++ b/src/backend/tracers/js/worker.js
@@ -10,5 +10,5 @@ onmessage = e => {
const lines = e.data.split('\n').map((line, i) => line.replace(/(\.\s*delay\s*)\(\s*\)/g, `$1(${i})`));
const code = lines.join('\n');
sandbox(code);
- postMessage(AlgorithmVisualizer.Tracer.traces);
+ postMessage(AlgorithmVisualizer.Commander.commands);
};
diff --git a/src/frontend/apis/index.js b/src/frontend/apis/index.js
index f55e348cb927e0c0a00daefa1384f813c1de902c..a4b645c1349092762f160bd1c74eb79596bf6d79 100644
--- a/src/frontend/apis/index.js
+++ b/src/frontend/apis/index.js
@@ -69,11 +69,11 @@ const GitHubApi = {
const TracerApi = {
md: ({ code }) => Promise.resolve([{
- tracerKey: '0-MarkdownTracer-Markdown',
- method: 'construct',
- args: ['MarkdownTracer', 'Markdown'],
+ key: 'markdown',
+ method: 'MarkdownTracer',
+ args: ['Markdown'],
}, {
- tracerKey: '0-MarkdownTracer-Markdown',
+ key: 'markdown',
method: 'set',
args: [code],
}]),
diff --git a/src/frontend/components/App/index.jsx b/src/frontend/components/App/index.jsx
index 2e37cbd4adda1385ddac9f21d67b2f24e9249a12..69cbe69ff5d7de1468630eec3734f4f0fa46f4bf 100644
--- a/src/frontend/components/App/index.jsx
+++ b/src/frontend/components/App/index.jsx
@@ -27,13 +27,16 @@ class App extends BaseComponent {
super(props);
this.state = {
- navigatorOpened: true,
+ workspaceVisibles: [true, true, true],
workspaceWeights: [1, 2, 2],
};
this.codeEditorRef = React.createRef();
this.ignoreHistoryBlock = this.ignoreHistoryBlock.bind(this);
+ this.handleClickTitleBar = this.handleClickTitleBar.bind(this);
+ this.loadScratchPapers = this.loadScratchPapers.bind(this);
+ this.handleChangeWorkspaceWeights = this.handleChangeWorkspaceWeights.bind(this);
}
componentDidMount() {
@@ -161,7 +164,7 @@ class App extends BaseComponent {
login: undefined,
gistId,
title: 'Untitled',
- files: [CONTRIBUTING_MD, createUserFile('traces.json', JSON.stringify(content))],
+ files: [CONTRIBUTING_MD, createUserFile('visualization.json', JSON.stringify(content))],
});
});
} else if (gistId === 'new') {
@@ -182,7 +185,10 @@ class App extends BaseComponent {
return Promise.resolve();
};
fetch()
- .then(() => this.selectDefaultTab())
+ .then(() => {
+ this.selectDefaultTab();
+ return null; // to suppress unnecessary bluebird warning
+ })
.catch(error => {
this.handleError(error);
this.props.history.push('/');
@@ -204,15 +210,22 @@ class App extends BaseComponent {
this.codeEditorRef.current.getWrappedInstance().handleResize();
}
- toggleNavigatorOpened(navigatorOpened = !this.state.navigatorOpened) {
- this.setState({ navigatorOpened });
+ toggleNavigatorOpened(navigatorOpened = !this.state.workspaceVisibles[0]) {
+ const workspaceVisibles = [...this.state.workspaceVisibles];
+ workspaceVisibles[0] = navigatorOpened;
+ this.setState({ workspaceVisibles });
}
- render() {
- const { navigatorOpened, workspaceWeights } = this.state;
+ handleClickTitleBar() {
+ this.toggleNavigatorOpened();
+ }
+ render() {
+ const { workspaceVisibles, workspaceWeights } = this.state;
const { titles, description, saved } = this.props.current;
+
const title = `${saved ? '' : '(Unsaved) '}${titles.join(' - ')}`;
+ const [navigatorOpened] = workspaceVisibles;
return (
@@ -220,12 +233,11 @@ class App extends BaseComponent {
{title}
-
this.toggleNavigatorOpened()}
- navigatorOpened={navigatorOpened} loadScratchPapers={() => this.loadScratchPapers()}
+
this.handleChangeWorkspaceWeights(weights)}>
+ visibles={workspaceVisibles} onChangeWeights={this.handleChangeWorkspaceWeights}>
diff --git a/src/frontend/components/CodeEditor/index.jsx b/src/frontend/components/CodeEditor/index.jsx
index c50ad8bfc57907345dfcaae543a3e6e0341e463a..5c67cd916beb0ad0576ec7ed677c8dad5d906e5a 100644
--- a/src/frontend/components/CodeEditor/index.jsx
+++ b/src/frontend/components/CodeEditor/index.jsx
@@ -17,7 +17,7 @@ class CodeEditor extends React.Component {
}
handleResize() {
- this.aceEditorRef.current.editor.resize();
+ this.aceEditorRef.current.getWrappedInstance().resize();
}
render() {
diff --git a/src/frontend/components/FoldableAceEditor/index.jsx b/src/frontend/components/FoldableAceEditor/index.jsx
index 5f9f0a9eec02737b4c89636854330d220307fcfe..1a7713bdc39f5df01ced45f7fcf3224b6db24639 100644
--- a/src/frontend/components/FoldableAceEditor/index.jsx
+++ b/src/frontend/components/FoldableAceEditor/index.jsx
@@ -11,7 +11,7 @@ import 'brace/theme/tomorrow_night_eighties';
import 'brace/ext/searchbox';
import { actions } from '/reducers';
-@connect(({ current }) => ({ current }), actions)
+@connect(({ current }) => ({ current }), actions, null, { withRef: true })
class FoldableAceEditor extends AceEditor {
componentDidMount() {
super.componentDidMount();
@@ -40,6 +40,10 @@ class FoldableAceEditor extends AceEditor {
}
}
}
+
+ resize() {
+ this.editor.resize();
+ }
}
export default FoldableAceEditor;
diff --git a/src/frontend/components/Player/index.jsx b/src/frontend/components/Player/index.jsx
index a9884725bdb7e8b4fe2e7a456e4f070b22ca1aed..0333fcc9b173316df067cd022626b2eff3a35828 100644
--- a/src/frontend/components/Player/index.jsx
+++ b/src/frontend/components/Player/index.jsx
@@ -41,22 +41,23 @@ class Player extends BaseComponent {
}
}
- reset(traces = []) {
+ reset(commands = []) {
const chunks = [{
- traces: [],
+ commands: [],
lineNumber: undefined,
}];
- while (traces.length) {
- const trace = traces.shift();
- if (trace.method === 'delay') {
- const [lineNumber] = trace.args;
+ while (commands.length) {
+ const command = commands.shift();
+ const { key, method, args } = command;
+ if (key === null && method === 'delay') {
+ const [lineNumber] = args;
chunks[chunks.length - 1].lineNumber = lineNumber;
chunks.push({
- traces: [],
+ commands: [],
lineNumber: undefined,
});
} else {
- chunks[chunks.length - 1].traces.push(trace);
+ chunks[chunks.length - 1].commands.push(command);
}
}
this.props.setChunks(chunks);
@@ -76,10 +77,10 @@ class Player extends BaseComponent {
const ext = extension(file.name);
if (ext in TracerApi) {
TracerApi[ext]({ code: file.content }, undefined, this.tracerApiSource.token)
- .then(traces => {
+ .then(commands => {
this.tracerApiSource = null;
this.setState({ building: false });
- this.reset(traces);
+ this.reset(commands);
this.next();
})
.catch(error => {
diff --git a/src/frontend/components/ResizableContainer/index.jsx b/src/frontend/components/ResizableContainer/index.jsx
index 24a3778c014c2691cd6a00475f2bdc654169fa3e..aa93dafa784ec67ed6266145d8f06b03bf38394e 100644
--- a/src/frontend/components/ResizableContainer/index.jsx
+++ b/src/frontend/components/ResizableContainer/index.jsx
@@ -16,7 +16,7 @@ class ResizableContainer extends React.Component {
let totalWeight = 0;
let subtotalWeight = 0;
weights.forEach((weight, i) => {
- if (!visibles[i]) return;
+ if (visibles && !visibles[i]) return;
totalWeight += weight;
if (i < index) subtotalWeight += weight;
});
@@ -34,9 +34,10 @@ class ResizableContainer extends React.Component {
const elements = [];
let lastIndex = -1;
- const totalWeight = weights.filter((weight, i) => visibles[i]).reduce((sumWeight, weight) => sumWeight + weight, 0);
+ const totalWeight = weights.filter((weight, i) => !visibles || visibles[i])
+ .reduce((sumWeight, weight) => sumWeight + weight, 0);
children.forEach((child, i) => {
- if (visibles[i]) {
+ if (!visibles || visibles[i]) {
if (~lastIndex) {
const prevIndex = lastIndex;
elements.push(
diff --git a/src/frontend/components/VisualizationViewer/index.jsx b/src/frontend/components/VisualizationViewer/index.jsx
index fde46b2e1474f4d34213ca23c7a87bb65a9b3de9..b066dd7c9368cf4841925dcb0e8958aaedd758af 100644
--- a/src/frontend/components/VisualizationViewer/index.jsx
+++ b/src/frontend/components/VisualizationViewer/index.jsx
@@ -1,21 +1,23 @@
import React from 'react';
import { connect } from 'react-redux';
-import { classes } from '/common/util';
-import { BaseComponent, ResizableContainer } from '/components';
+import { BaseComponent } from '/components';
import { actions } from '/reducers';
import styles from './stylesheet.scss';
-import { Array1DData, Array2DData, ChartData, Data, GraphData, LogData, MarkdownData } from '/core/datas';
+import * as TracerClasses from '/core/tracers';
+import * as LayoutClasses from '/core/layouts';
+import { classes } from '/common/util';
@connect(({ player }) => ({ player }), actions)
class VisualizationViewer extends BaseComponent {
constructor(props) {
super(props);
- this.state = {
- dataWeights: {},
- };
+ this.reset();
+ }
- this.datas = [];
+ reset() {
+ this.root = null;
+ this.objects = {};
}
componentDidMount() {
@@ -36,19 +38,11 @@ class VisualizationViewer extends BaseComponent {
if (cursor > oldCursor) {
applyingChunks = chunks.slice(oldCursor, cursor);
} else {
- this.datas = [];
+ this.reset();
applyingChunks = chunks.slice(0, cursor);
}
applyingChunks.forEach(chunk => this.applyChunk(chunk));
- const dataWeights = chunks === oldChunks ? { ...this.state.dataWeights } : {};
- this.datas.forEach(data => {
- if (!(data.tracerKey in dataWeights)) {
- dataWeights[data.tracerKey] = 1;
- }
- });
- this.setState({ dataWeights });
-
const lastChunk = applyingChunks[applyingChunks.length - 1];
if (lastChunk && lastChunk.lineNumber !== undefined) {
this.props.setLineIndicator({ lineNumber: lastChunk.lineNumber, cursor });
@@ -57,29 +51,24 @@ class VisualizationViewer extends BaseComponent {
}
}
- addTracer(className, tracerKey, title) {
- const DataClass = {
- Tracer: Data,
- MarkdownTracer: MarkdownData,
- LogTracer: LogData,
- Array2DTracer: Array2DData,
- Array1DTracer: Array1DData,
- ChartTracer: ChartData,
- GraphTracer: GraphData,
- }[className];
- const data = new DataClass(tracerKey, title, this.datas);
- this.datas.push(data);
- }
-
- applyTrace(trace) {
- const { tracerKey, method, args } = trace;
+ applyCommand(command) {
+ const { key, method, args } = command;
try {
- if (method === 'construct') {
- const [className, title] = args;
- this.addTracer(className, tracerKey, title);
+ if (key === null && method === 'setRoot') {
+ const [root] = args;
+ this.root = this.objects[root];
+ } else if (method === 'destroy') {
+ delete this.objects[key];
+ } else if (method in LayoutClasses) {
+ const [children] = args;
+ const LayoutClass = LayoutClasses[method];
+ this.objects[key] = new LayoutClass(key, key => this.objects[key], children);
+ } else if (method in TracerClasses) {
+ const [title] = args;
+ const TracerClass = TracerClasses[method];
+ this.objects[key] = new TracerClass(key, key => this.objects[key], title);
} else {
- const data = this.datas.find(data => data.tracerKey === tracerKey);
- data[method](...args);
+ this.objects[key][method](...args);
}
} catch (error) {
this.handleError(error);
@@ -87,30 +76,18 @@ class VisualizationViewer extends BaseComponent {
}
applyChunk(chunk) {
- chunk.traces.forEach(trace => this.applyTrace(trace));
- }
-
- handleChangeWeights(weights) {
- const dataWeights = {};
- weights.forEach((weight, i) => {
- dataWeights[this.datas[i].tracerKey] = weight;
- });
- this.setState({ dataWeights });
+ chunk.commands.forEach(command => this.applyCommand(command));
}
render() {
const { className } = this.props;
- const { dataWeights } = this.state;
return (
- dataWeights[data.tracerKey])}
- visibles={this.datas.map(() => true)}
- onChangeWeights={weights => this.handleChangeWeights(weights)}>
+
{
- this.datas.map(data => data.render())
+ this.root && this.root.render()
}
-
+
);
}
}
diff --git a/src/frontend/components/VisualizationViewer/stylesheet.scss b/src/frontend/components/VisualizationViewer/stylesheet.scss
index cdc44e1f58de6d949d85e7d5423e823fed1018b5..8db0a8ed9642f2904d75315fab038e78d398a710 100644
--- a/src/frontend/components/VisualizationViewer/stylesheet.scss
+++ b/src/frontend/components/VisualizationViewer/stylesheet.scss
@@ -1,4 +1,8 @@
@import "~/common/stylesheet/index";
.visualization_viewer {
-}
\ No newline at end of file
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+}
diff --git a/src/frontend/core/datas/ChartData.js b/src/frontend/core/datas/ChartData.js
deleted file mode 100644
index ab8149b312c8bfb824a5f88d835016c72d22bef2..0000000000000000000000000000000000000000
--- a/src/frontend/core/datas/ChartData.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Array1DData } from '/core/datas';
-import { ChartRenderer } from '/core/renderers';
-
-class ChartData extends Array1DData {
- getRendererClass() {
- return ChartRenderer;
- }
-}
-
-export default ChartData;
diff --git a/src/frontend/core/datas/index.js b/src/frontend/core/datas/index.js
deleted file mode 100644
index e8dc77294bb06bbb104b45cb590edec032562da6..0000000000000000000000000000000000000000
--- a/src/frontend/core/datas/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export { default as Data } from './Data';
-export { default as MarkdownData } from './MarkdownData';
-export { default as LogData } from './LogData';
-export { default as Array2DData } from './Array2DData';
-export { default as Array1DData } from './Array1DData';
-export { default as ChartData } from './ChartData';
-export { default as GraphData } from './GraphData';
diff --git a/src/frontend/core/layouts/HorizontalLayout.js b/src/frontend/core/layouts/HorizontalLayout.js
new file mode 100644
index 0000000000000000000000000000000000000000..0de4f957b23d4d024fed5b2fab4281891550067b
--- /dev/null
+++ b/src/frontend/core/layouts/HorizontalLayout.js
@@ -0,0 +1,6 @@
+import { Layout } from '/core/layouts';
+
+class HorizontalLayout extends Layout {
+}
+
+export default HorizontalLayout;
diff --git a/src/frontend/core/layouts/Layout.jsx b/src/frontend/core/layouts/Layout.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..b37579fc6d72f7c6c49baf296b0cb96f363d39c6
--- /dev/null
+++ b/src/frontend/core/layouts/Layout.jsx
@@ -0,0 +1,55 @@
+import React from 'react';
+import { ResizableContainer } from '/components';
+import { HorizontalLayout } from '/core/layouts';
+
+class Layout {
+ constructor(key, getObject, children) {
+ this.key = key;
+ this.getObject = getObject;
+ this.children = children.map(key => this.getObject(key));
+ this.weights = children.map(() => 1);
+ this.ref = React.createRef();
+
+ this.handleChangeWeights = this.handleChangeWeights.bind(this);
+ }
+
+ add(key, index = this.children.length) {
+ const child = this.getObject(key);
+ this.children.splice(index, 0, child);
+ this.weights.splice(index, 0, 1);
+ }
+
+ remove(key) {
+ const child = this.getObject(key);
+ const index = this.children.indexOf(child);
+ if (~index) {
+ this.children.splice(index, 1);
+ this.weights.splice(index, 1);
+ }
+ }
+
+ removeAll() {
+ this.children = [];
+ this.weights = [];
+ }
+
+ handleChangeWeights(weights) {
+ this.weights.splice(0, this.weights.length, ...weights);
+ this.ref.current.forceUpdate();
+ }
+
+ render() {
+ const horizontal = this instanceof HorizontalLayout;
+
+ return (
+
+ {
+ this.children.map(tracer => tracer && tracer.render())
+ }
+
+ );
+ }
+}
+
+export default Layout;
diff --git a/src/frontend/core/layouts/VerticalLayout.js b/src/frontend/core/layouts/VerticalLayout.js
new file mode 100644
index 0000000000000000000000000000000000000000..a98d9b20f58847a869cc8f1ff8cd9aafc33fd70e
--- /dev/null
+++ b/src/frontend/core/layouts/VerticalLayout.js
@@ -0,0 +1,6 @@
+import { Layout } from '/core/layouts';
+
+class VerticalLayout extends Layout {
+}
+
+export default VerticalLayout;
diff --git a/src/frontend/core/layouts/index.js b/src/frontend/core/layouts/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..c833ea739dc5c811497dbd338fbf39f87d060f51
--- /dev/null
+++ b/src/frontend/core/layouts/index.js
@@ -0,0 +1,3 @@
+export { default as Layout } from './Layout';
+export { default as HorizontalLayout } from './HorizontalLayout';
+export { default as VerticalLayout } from './VerticalLayout';
diff --git a/src/frontend/core/renderers/GraphRenderer/index.jsx b/src/frontend/core/renderers/GraphRenderer/index.jsx
index 9bb65474c26c3b0f689930db0a0615d1a673c75d..92fec190abffa3e51cac8ab08ab1901f170c6af6 100644
--- a/src/frontend/core/renderers/GraphRenderer/index.jsx
+++ b/src/frontend/core/renderers/GraphRenderer/index.jsx
@@ -7,7 +7,7 @@ class GraphRenderer extends Renderer {
constructor(props) {
super(props);
- this.element = React.createRef();
+ this.elementRef = React.createRef();
this.selectedNode = null;
this.togglePan(true);
@@ -35,7 +35,7 @@ class GraphRenderer extends Renderer {
}
computeCoords(e) {
- const svg = this.element.current;
+ const svg = this.elementRef.current;
const s = svg.createSVGPoint();
s.x = e.clientX;
s.y = e.clientY;
@@ -53,7 +53,7 @@ class GraphRenderer extends Renderer {
baseHeight * this.zoom,
];
return (
-