Skip to content

Commit c7ec896

Browse files
committed
Add layout feature
1 parent 7271b34 commit c7ec896

File tree

26 files changed

+213
-148
lines changed

26 files changed

+213
-148
lines changed

src/backend/controllers/tracers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const trace = lang => (req, res, next) => {
3838
}).finally(() => clearTimeout(timer));
3939
})
4040
.then(() => new Promise((resolve, reject) => {
41-
const visualizationPath = path.resolve(tempPath, 'traces.json');
41+
const visualizationPath = path.resolve(tempPath, 'visualization.json');
4242
res.sendFile(visualizationPath, err => {
4343
if (err) return reject(new Error('Visualization Not Found'));
4444
resolve();

src/backend/tracers/js/worker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ onmessage = e => {
1010
const lines = e.data.split('\n').map((line, i) => line.replace(/(\.\s*delay\s*)\(\s*\)/g, `$1(${i})`));
1111
const code = lines.join('\n');
1212
sandbox(code);
13-
postMessage(AlgorithmVisualizer.Tracer.traces);
13+
postMessage(AlgorithmVisualizer.Commander.commands);
1414
};

src/frontend/apis/index.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,11 @@ const GitHubApi = {
6969

7070
const TracerApi = {
7171
md: ({ code }) => Promise.resolve([{
72-
tracerKey: '0-MarkdownTracer-Markdown',
73-
method: 'construct',
74-
args: ['MarkdownTracer', 'Markdown'],
72+
key: 'markdown',
73+
method: 'MarkdownTracer',
74+
args: ['Markdown'],
7575
}, {
76-
tracerKey: '0-MarkdownTracer-Markdown',
76+
key: 'markdown',
7777
method: 'set',
7878
args: [code],
7979
}]),

src/frontend/components/App/index.jsx

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,16 @@ class App extends BaseComponent {
2727
super(props);
2828

2929
this.state = {
30-
navigatorOpened: true,
30+
workspaceVisibles: [true, true, true],
3131
workspaceWeights: [1, 2, 2],
3232
};
3333

3434
this.codeEditorRef = React.createRef();
3535

3636
this.ignoreHistoryBlock = this.ignoreHistoryBlock.bind(this);
37+
this.handleClickTitleBar = this.handleClickTitleBar.bind(this);
38+
this.loadScratchPapers = this.loadScratchPapers.bind(this);
39+
this.handleChangeWorkspaceWeights = this.handleChangeWorkspaceWeights.bind(this);
3740
}
3841

3942
componentDidMount() {
@@ -161,7 +164,7 @@ class App extends BaseComponent {
161164
login: undefined,
162165
gistId,
163166
title: 'Untitled',
164-
files: [CONTRIBUTING_MD, createUserFile('traces.json', JSON.stringify(content))],
167+
files: [CONTRIBUTING_MD, createUserFile('visualization.json', JSON.stringify(content))],
165168
});
166169
});
167170
} else if (gistId === 'new') {
@@ -182,7 +185,10 @@ class App extends BaseComponent {
182185
return Promise.resolve();
183186
};
184187
fetch()
185-
.then(() => this.selectDefaultTab())
188+
.then(() => {
189+
this.selectDefaultTab();
190+
return null; // to suppress unnecessary bluebird warning
191+
})
186192
.catch(error => {
187193
this.handleError(error);
188194
this.props.history.push('/');
@@ -204,28 +210,34 @@ class App extends BaseComponent {
204210
this.codeEditorRef.current.getWrappedInstance().handleResize();
205211
}
206212

207-
toggleNavigatorOpened(navigatorOpened = !this.state.navigatorOpened) {
208-
this.setState({ navigatorOpened });
213+
toggleNavigatorOpened(navigatorOpened = !this.state.workspaceVisibles[0]) {
214+
const workspaceVisibles = [...this.state.workspaceVisibles];
215+
workspaceVisibles[0] = navigatorOpened;
216+
this.setState({ workspaceVisibles });
209217
}
210218

211-
render() {
212-
const { navigatorOpened, workspaceWeights } = this.state;
219+
handleClickTitleBar() {
220+
this.toggleNavigatorOpened();
221+
}
213222

223+
render() {
224+
const { workspaceVisibles, workspaceWeights } = this.state;
214225
const { titles, description, saved } = this.props.current;
226+
215227
const title = `${saved ? '' : '(Unsaved) '}${titles.join(' - ')}`;
228+
const [navigatorOpened] = workspaceVisibles;
216229

217230
return (
218231
<div className={styles.app}>
219232
<Helmet>
220233
<title>{title}</title>
221234
<meta name="description" content={description} />
222235
</Helmet>
223-
<Header className={styles.header} onClickTitleBar={() => this.toggleNavigatorOpened()}
224-
navigatorOpened={navigatorOpened} loadScratchPapers={() => this.loadScratchPapers()}
236+
<Header className={styles.header} onClickTitleBar={this.handleClickTitleBar}
237+
navigatorOpened={navigatorOpened} loadScratchPapers={this.loadScratchPapers}
225238
ignoreHistoryBlock={this.ignoreHistoryBlock} />
226239
<ResizableContainer className={styles.workspace} horizontal weights={workspaceWeights}
227-
visibles={[navigatorOpened, true, true]}
228-
onChangeWeights={weights => this.handleChangeWorkspaceWeights(weights)}>
240+
visibles={workspaceVisibles} onChangeWeights={this.handleChangeWorkspaceWeights}>
229241
<Navigator />
230242
<VisualizationViewer className={styles.visualization_viewer} />
231243
<TabContainer className={styles.editor_tab_container}>

src/frontend/components/CodeEditor/index.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class CodeEditor extends React.Component {
1717
}
1818

1919
handleResize() {
20-
this.aceEditorRef.current.editor.resize();
20+
this.aceEditorRef.current.getWrappedInstance().resize();
2121
}
2222

2323
render() {

src/frontend/components/FoldableAceEditor/index.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import 'brace/theme/tomorrow_night_eighties';
1111
import 'brace/ext/searchbox';
1212
import { actions } from '/reducers';
1313

14-
@connect(({ current }) => ({ current }), actions)
14+
@connect(({ current }) => ({ current }), actions, null, { withRef: true })
1515
class FoldableAceEditor extends AceEditor {
1616
componentDidMount() {
1717
super.componentDidMount();
@@ -40,6 +40,10 @@ class FoldableAceEditor extends AceEditor {
4040
}
4141
}
4242
}
43+
44+
resize() {
45+
this.editor.resize();
46+
}
4347
}
4448

4549
export default FoldableAceEditor;

src/frontend/components/Player/index.jsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,23 @@ class Player extends BaseComponent {
4141
}
4242
}
4343

44-
reset(traces = []) {
44+
reset(commands = []) {
4545
const chunks = [{
46-
traces: [],
46+
commands: [],
4747
lineNumber: undefined,
4848
}];
49-
while (traces.length) {
50-
const trace = traces.shift();
51-
if (trace.method === 'delay') {
52-
const [lineNumber] = trace.args;
49+
while (commands.length) {
50+
const command = commands.shift();
51+
const { key, method, args } = command;
52+
if (key === null && method === 'delay') {
53+
const [lineNumber] = args;
5354
chunks[chunks.length - 1].lineNumber = lineNumber;
5455
chunks.push({
55-
traces: [],
56+
commands: [],
5657
lineNumber: undefined,
5758
});
5859
} else {
59-
chunks[chunks.length - 1].traces.push(trace);
60+
chunks[chunks.length - 1].commands.push(command);
6061
}
6162
}
6263
this.props.setChunks(chunks);
@@ -76,10 +77,10 @@ class Player extends BaseComponent {
7677
const ext = extension(file.name);
7778
if (ext in TracerApi) {
7879
TracerApi[ext]({ code: file.content }, undefined, this.tracerApiSource.token)
79-
.then(traces => {
80+
.then(commands => {
8081
this.tracerApiSource = null;
8182
this.setState({ building: false });
82-
this.reset(traces);
83+
this.reset(commands);
8384
this.next();
8485
})
8586
.catch(error => {

src/frontend/components/ResizableContainer/index.jsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class ResizableContainer extends React.Component {
1616
let totalWeight = 0;
1717
let subtotalWeight = 0;
1818
weights.forEach((weight, i) => {
19-
if (!visibles[i]) return;
19+
if (visibles && !visibles[i]) return;
2020
totalWeight += weight;
2121
if (i < index) subtotalWeight += weight;
2222
});
@@ -34,9 +34,10 @@ class ResizableContainer extends React.Component {
3434

3535
const elements = [];
3636
let lastIndex = -1;
37-
const totalWeight = weights.filter((weight, i) => visibles[i]).reduce((sumWeight, weight) => sumWeight + weight, 0);
37+
const totalWeight = weights.filter((weight, i) => !visibles || visibles[i])
38+
.reduce((sumWeight, weight) => sumWeight + weight, 0);
3839
children.forEach((child, i) => {
39-
if (visibles[i]) {
40+
if (!visibles || visibles[i]) {
4041
if (~lastIndex) {
4142
const prevIndex = lastIndex;
4243
elements.push(

src/frontend/components/VisualizationViewer/index.jsx

Lines changed: 30 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
import React from 'react';
22
import { connect } from 'react-redux';
3-
import { classes } from '/common/util';
4-
import { BaseComponent, ResizableContainer } from '/components';
3+
import { BaseComponent } from '/components';
54
import { actions } from '/reducers';
65
import styles from './stylesheet.scss';
7-
import { Array1DData, Array2DData, ChartData, Data, GraphData, LogData, MarkdownData } from '/core/datas';
6+
import * as TracerClasses from '/core/tracers';
7+
import * as LayoutClasses from '/core/layouts';
8+
import { classes } from '/common/util';
89

910
@connect(({ player }) => ({ player }), actions)
1011
class VisualizationViewer extends BaseComponent {
1112
constructor(props) {
1213
super(props);
1314

14-
this.state = {
15-
dataWeights: {},
16-
};
15+
this.reset();
16+
}
1717

18-
this.datas = [];
18+
reset() {
19+
this.root = null;
20+
this.objects = {};
1921
}
2022

2123
componentDidMount() {
@@ -36,19 +38,11 @@ class VisualizationViewer extends BaseComponent {
3638
if (cursor > oldCursor) {
3739
applyingChunks = chunks.slice(oldCursor, cursor);
3840
} else {
39-
this.datas = [];
41+
this.reset();
4042
applyingChunks = chunks.slice(0, cursor);
4143
}
4244
applyingChunks.forEach(chunk => this.applyChunk(chunk));
4345

44-
const dataWeights = chunks === oldChunks ? { ...this.state.dataWeights } : {};
45-
this.datas.forEach(data => {
46-
if (!(data.tracerKey in dataWeights)) {
47-
dataWeights[data.tracerKey] = 1;
48-
}
49-
});
50-
this.setState({ dataWeights });
51-
5246
const lastChunk = applyingChunks[applyingChunks.length - 1];
5347
if (lastChunk && lastChunk.lineNumber !== undefined) {
5448
this.props.setLineIndicator({ lineNumber: lastChunk.lineNumber, cursor });
@@ -57,60 +51,43 @@ class VisualizationViewer extends BaseComponent {
5751
}
5852
}
5953

60-
addTracer(className, tracerKey, title) {
61-
const DataClass = {
62-
Tracer: Data,
63-
MarkdownTracer: MarkdownData,
64-
LogTracer: LogData,
65-
Array2DTracer: Array2DData,
66-
Array1DTracer: Array1DData,
67-
ChartTracer: ChartData,
68-
GraphTracer: GraphData,
69-
}[className];
70-
const data = new DataClass(tracerKey, title, this.datas);
71-
this.datas.push(data);
72-
}
73-
74-
applyTrace(trace) {
75-
const { tracerKey, method, args } = trace;
54+
applyCommand(command) {
55+
const { key, method, args } = command;
7656
try {
77-
if (method === 'construct') {
78-
const [className, title] = args;
79-
this.addTracer(className, tracerKey, title);
57+
if (key === null && method === 'setRoot') {
58+
const [root] = args;
59+
this.root = this.objects[root];
60+
} else if (method === 'destroy') {
61+
delete this.objects[key];
62+
} else if (method in LayoutClasses) {
63+
const [children] = args;
64+
const LayoutClass = LayoutClasses[method];
65+
this.objects[key] = new LayoutClass(key, key => this.objects[key], children);
66+
} else if (method in TracerClasses) {
67+
const [title] = args;
68+
const TracerClass = TracerClasses[method];
69+
this.objects[key] = new TracerClass(key, key => this.objects[key], title);
8070
} else {
81-
const data = this.datas.find(data => data.tracerKey === tracerKey);
82-
data[method](...args);
71+
this.objects[key][method](...args);
8372
}
8473
} catch (error) {
8574
this.handleError(error);
8675
}
8776
}
8877

8978
applyChunk(chunk) {
90-
chunk.traces.forEach(trace => this.applyTrace(trace));
91-
}
92-
93-
handleChangeWeights(weights) {
94-
const dataWeights = {};
95-
weights.forEach((weight, i) => {
96-
dataWeights[this.datas[i].tracerKey] = weight;
97-
});
98-
this.setState({ dataWeights });
79+
chunk.commands.forEach(command => this.applyCommand(command));
9980
}
10081

10182
render() {
10283
const { className } = this.props;
103-
const { dataWeights } = this.state;
10484

10585
return (
106-
<ResizableContainer className={classes(styles.visualization_viewer, className)}
107-
weights={this.datas.map(data => dataWeights[data.tracerKey])}
108-
visibles={this.datas.map(() => true)}
109-
onChangeWeights={weights => this.handleChangeWeights(weights)}>
86+
<div className={classes(styles.visualization_viewer, className)}>
11087
{
111-
this.datas.map(data => data.render())
88+
this.root && this.root.render()
11289
}
113-
</ResizableContainer>
90+
</div>
11491
);
11592
}
11693
}
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
@import "~/common/stylesheet/index";
22

33
.visualization_viewer {
4-
}
4+
flex: 1;
5+
display: flex;
6+
flex-direction: column;
7+
align-items: stretch;
8+
}

0 commit comments

Comments
 (0)