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

Improve workspace performance and few other things

上级 b4801513
...@@ -65,7 +65,7 @@ const cacheHierarchy = () => { ...@@ -65,7 +65,7 @@ const cacheHierarchy = () => {
const file = allFiles[fileIndex]; const file = allFiles[fileIndex];
if (file) { if (file) {
const cwd = getPath(); const cwd = getPath();
exec(`git --no-pager log --follow --format="%H" "${file.path}"`, { cwd }, (error, stdout, stderr) => { exec(`git --no-pager log --follow --no-merges --format="%H" "${file.path}"`, { cwd }, (error, stdout, stderr) => {
if (!error && !stderr) { if (!error && !stderr) {
const output = stdout.toString().replace(/\n$/, ''); const output = stdout.toString().replace(/\n$/, '');
const shas = output.split('\n').reverse(); const shas = output.split('\n').reverse();
...@@ -92,24 +92,22 @@ const getHierarchy = (req, res, next) => { ...@@ -92,24 +92,22 @@ const getHierarchy = (req, res, next) => {
res.json({ hierarchy: cachedHierarchy }); res.json({ hierarchy: cachedHierarchy });
}; };
const getFile = (req, res, next) => { const getAlgorithm = (req, res, next) => {
const { categoryKey, algorithmKey, fileName } = req.params; const { categoryKey, algorithmKey } = req.params;
const category = cachedHierarchy.find(category => category.key === categoryKey); const category = cachedHierarchy.find(category => category.key === categoryKey);
if (!category) return next(new NotFoundError()); if (!category) return next(new NotFoundError());
const algorithm = category.algorithms.find(algorithm => algorithm.key === algorithmKey); const algorithm = category.algorithms.find(algorithm => algorithm.key === algorithmKey);
if (!algorithm) return next(new NotFoundError()); if (!algorithm) return next(new NotFoundError());
const file = algorithm.files.find(file => file.name === fileName);
if (!file) return next(new NotFoundError());
const { content, contributors } = file; const files = algorithm.files.map(({ name, content, contributors }) => ({ name, content, contributors }));
res.json({ file: { content, contributors } }); res.json({ algorithm: { ...algorithm, files } });
}; };
router.route('/') router.route('/')
.get(getHierarchy); .get(getHierarchy);
router.route('/:categoryKey/:algorithmKey/:fileName') router.route('/:categoryKey/:algorithmKey')
.get(getFile); .get(getAlgorithm);
export default router; export default router;
\ No newline at end of file
Subproject commit f17c57048f65f287d12e13e6f35b606034aeeb31 Subproject commit c9b0606bd6c80e02bc12c0215722cbbd9751965e
...@@ -51,7 +51,7 @@ const PUT = URL => { ...@@ -51,7 +51,7 @@ const PUT = URL => {
const HierarchyApi = { const HierarchyApi = {
getHierarchy: GET('/hierarchy'), getHierarchy: GET('/hierarchy'),
getFile: GET('/hierarchy/:categoryKey/:algorithmKey/:fileName'), getAlgorithm: GET('/hierarchy/:categoryKey/:algorithmKey'),
}; };
const WikiApi = { const WikiApi = {
...@@ -62,6 +62,7 @@ const WikiApi = { ...@@ -62,6 +62,7 @@ const WikiApi = {
const GitHubApi = { const GitHubApi = {
auth: token => gh = new GitHub({ token }), auth: token => gh = new GitHub({ token }),
getProfile: () => gh.getUser().getProfile(), getProfile: () => gh.getUser().getProfile(),
listGists: () => gh.getUser().listGists(),
}; };
export { export {
......
const stepLimit = 1e6; const stepLimit = 1e6; // TODO: limit number of traces
const CATEGORY_SCRATCH_PAPER = 'scratch-paper';
const ALGORITHM_NEW = 'new';
export { export {
stepLimit, stepLimit,
CATEGORY_SCRATCH_PAPER,
ALGORITHM_NEW,
}; };
\ No newline at end of file
...@@ -3,7 +3,6 @@ import { connect } from 'react-redux'; ...@@ -3,7 +3,6 @@ import { connect } from 'react-redux';
import { loadProgressBar } from 'axios-progress-bar' import { loadProgressBar } from 'axios-progress-bar'
import { CodeEditor, DescriptionViewer, Header, Navigator, ToastContainer, WikiViewer, } from '/components'; import { CodeEditor, DescriptionViewer, Header, Navigator, ToastContainer, WikiViewer, } from '/components';
import { Workspace, WSSectionContainer, WSTabContainer } from '/workspace/components'; import { Workspace, WSSectionContainer, WSTabContainer } from '/workspace/components';
import { Section } from '/workspace/core';
import { actions as toastActions } from '/reducers/toast'; import { actions as toastActions } from '/reducers/toast';
import { actions as envActions } from '/reducers/env'; import { actions as envActions } from '/reducers/env';
import { GitHubApi, HierarchyApi } from '/apis'; import { GitHubApi, HierarchyApi } from '/apis';
...@@ -26,11 +25,21 @@ class App extends React.Component { ...@@ -26,11 +25,21 @@ class App extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.spawnReference = Workspace.createReference(); this.workspaceRef = React.createRef();
this.navigatorReference = Workspace.createReference(); this.navigator = null;
this.state = {
files: [],
codeFile: null,
descFile: null,
renderers: [],
};
} }
componentDidMount() { componentDidMount() {
const workspace = this.workspaceRef.current;
this.navigator = workspace.findSectionById('navigator');
this.updateDirectory(this.props.match.params); this.updateDirectory(this.props.match.params);
HierarchyApi.getHierarchy() HierarchyApi.getHierarchy()
...@@ -45,7 +54,7 @@ class App extends React.Component { ...@@ -45,7 +54,7 @@ class App extends React.Component {
const { signedIn, accessToken } = this.props.env; const { signedIn, accessToken } = this.props.env;
if (signedIn) GitHubApi.auth(accessToken); if (signedIn) GitHubApi.auth(accessToken);
tracerManager.setOnRender(renderers => this.handleChangeRenderers(renderers)); tracerManager.setOnRender(renderers => this.setState({ renderers }));
tracerManager.setOnError(error => this.props.showErrorToast(error.message)); tracerManager.setOnError(error => this.props.showErrorToast(error.message));
} }
...@@ -63,51 +72,37 @@ class App extends React.Component { ...@@ -63,51 +72,37 @@ class App extends React.Component {
updateDirectory({ categoryKey = null, algorithmKey = null }) { updateDirectory({ categoryKey = null, algorithmKey = null }) {
if (categoryKey && algorithmKey) { if (categoryKey && algorithmKey) {
this.props.setDirectory(categoryKey, algorithmKey); this.props.setDirectory(categoryKey, algorithmKey);
HierarchyApi.getAlgorithm(categoryKey, algorithmKey)
.then(({ algorithm }) => {
const { files } = algorithm;
const codeFile = files.find(file => file.name === 'code.js') || null;
const descFile = files.find(file => file.name === 'desc.md') || null;
this.setState({ files, codeFile, descFile });
})
.catch(() => this.setState({ files: [] }));
} }
} }
handleChangeRenderers(renderers) {
const oldSections = this.rendererSections || {};
const newSections = {};
for (const renderer of renderers) {
const { tracerKey, element } = renderer;
let section = null;
if (tracerKey in oldSections) {
section = oldSections[tracerKey];
section.setElement(element);
delete oldSections[tracerKey];
} else {
section = new Section(element);
this.spawnReference.core.addChild(section);
}
newSections[tracerKey] = section;
}
Object.values(oldSections).forEach(tab => tab.remove());
this.rendererSections = newSections;
}
render() { render() {
const { hierarchy, categoryKey, algorithmKey } = this.props.env; const { codeFile, descFile, renderers } = this.state;
const navigatorOpened = true;
return hierarchy && categoryKey && algorithmKey && ( return (
<div className={styles.app}> <div className={styles.app}>
<Workspace className={styles.workspace} wsProps={{ horizontal: false }}> <Workspace className={styles.workspace} wsProps={{ horizontal: false }} ref={this.workspaceRef}>
<Header wsProps={{ <Header wsProps={{
removable: false, removable: false,
size: 32, size: 32,
fixed: true, fixed: true,
resizable: false, resizable: false,
}} }}
onClickTitleBar={() => this.navigatorReference.core.setVisible(!this.navigatorReference.core.visible)} onClickTitleBar={() => this.navigator.setVisible(!this.navigator.visible)}
navigatorOpened={navigatorOpened} /> navigatorOpened={true /* TODO: fix */} />
<WSSectionContainer wsProps={{ fixed: true }}> <WSSectionContainer wsProps={{ fixed: true }}>
<Navigator wsProps={{ <Navigator wsProps={{
id: 'navigator',
removable: false, removable: false,
size: 240, size: 240,
minSize: 120, minSize: 120,
reference: this.navigatorReference,
fixed: true, fixed: true,
}} /> }} />
<WSTabContainer> <WSTabContainer>
...@@ -116,12 +111,13 @@ class App extends React.Component { ...@@ -116,12 +111,13 @@ class App extends React.Component {
title: 'Visualization', title: 'Visualization',
removable: false, removable: false,
horizontal: false, horizontal: false,
reference: this.spawnReference }}>
}} /> {renderers}
</WSSectionContainer>
</WSTabContainer> </WSTabContainer>
<WSTabContainer> <WSTabContainer>
<DescriptionViewer wsProps={{ title: 'Description' }} /> <DescriptionViewer wsProps={{ title: 'Description' }} file={descFile} />
<CodeEditor wsProps={{ title: 'code.js' }} /> <CodeEditor wsProps={{ title: 'code.js' }} file={codeFile} />
</WSTabContainer> </WSTabContainer>
</WSSectionContainer> </WSSectionContainer>
</Workspace> </Workspace>
......
...@@ -15,7 +15,6 @@ body { ...@@ -15,7 +15,6 @@ body {
user-select: none; user-select: none;
color: $color-font; color: $color-font;
font-size: $font-size-normal; font-size: $font-size-normal;
background-color: $theme-normal;
} }
a { a {
......
import React from 'react'; import React from 'react';
import AceEditor from 'react-ace'; import AceEditor from 'react-ace';
import { connect } from 'react-redux';
import 'brace/mode/javascript'; import 'brace/mode/javascript';
import 'brace/theme/tomorrow_night_eighties'; import 'brace/theme/tomorrow_night_eighties';
import 'brace/ext/searchbox';
import { tracerManager } from '/core'; import { tracerManager } from '/core';
import { classes } from '/common/util'; import { classes } from '/common/util';
import styles from './stylesheet.scss'; import styles from './stylesheet.scss';
import { HierarchyApi } from '/apis';
import { ContributorsViewer } from '/components'; import { ContributorsViewer } from '/components';
import { actions as envActions } from '/reducers/env';
// TODO: code should not be reloaded when reopening tab
@connect(
({ env }) => ({
env
}), {
...envActions
}
)
class CodeEditor extends React.Component { class CodeEditor extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
const { file } = props;
const { lineIndicator } = tracerManager; const { lineIndicator } = tracerManager;
this.state = { this.state = {
lineMarker: this.createLineMarker(lineIndicator), lineMarker: this.createLineMarker(lineIndicator),
file: null, code: file && file.content,
}; };
} }
componentDidMount() { componentDidMount() {
const { categoryKey, algorithmKey } = this.props.env;
this.loadFile(categoryKey, algorithmKey);
tracerManager.setOnUpdateLineIndicator(lineIndicator => this.setState({ lineMarker: this.createLineMarker(lineIndicator) })); tracerManager.setOnUpdateLineIndicator(lineIndicator => this.setState({ lineMarker: this.createLineMarker(lineIndicator) }));
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
const { categoryKey, algorithmKey } = nextProps.env; const { file } = nextProps;
if (categoryKey !== this.props.env.categoryKey || if (file !== this.props.file) {
algorithmKey !== this.props.env.algorithmKey) { this.handleChangeCode(file && file.content);
this.loadFile(categoryKey, algorithmKey);
} }
} }
...@@ -48,15 +35,6 @@ class CodeEditor extends React.Component { ...@@ -48,15 +35,6 @@ class CodeEditor extends React.Component {
tracerManager.setOnUpdateLineIndicator(null); tracerManager.setOnUpdateLineIndicator(null);
} }
loadFile(categoryKey, algorithmKey) {
HierarchyApi.getFile(categoryKey, algorithmKey, 'code.js')
.then(({ file }) => {
this.setState({ file });
tracerManager.setCode(file.content);
})
.catch(() => this.setState({ file: null }));
}
createLineMarker(lineIndicator) { createLineMarker(lineIndicator) {
if (lineIndicator === null) return null; if (lineIndicator === null) return null;
const { lineNumber, cursor } = lineIndicator; const { lineNumber, cursor } = lineIndicator;
...@@ -73,14 +51,13 @@ class CodeEditor extends React.Component { ...@@ -73,14 +51,13 @@ class CodeEditor extends React.Component {
} }
handleChangeCode(code) { handleChangeCode(code) {
const file = { ...this.state.file, content: code }; this.setState({ code });
this.setState({ file });
tracerManager.setCode(code); tracerManager.setCode(code);
} }
render() { render() {
const { lineMarker, file } = this.state; const { className, file } = this.props;
const { className, relativeWeight } = this.props; const { lineMarker, code } = this.state;
return file && ( return file && (
<div className={classes(styles.code_editor, className)}> <div className={classes(styles.code_editor, className)}>
...@@ -92,10 +69,9 @@ class CodeEditor extends React.Component { ...@@ -92,10 +69,9 @@ class CodeEditor extends React.Component {
editorProps={{ $blockScrolling: true }} editorProps={{ $blockScrolling: true }}
onChange={code => this.handleChangeCode(code)} onChange={code => this.handleChangeCode(code)}
markers={lineMarker ? [lineMarker] : []} markers={lineMarker ? [lineMarker] : []}
value={file.content} value={code} />
width={`${relativeWeight}`} />
<ContributorsViewer contributors={file.contributors} /> <ContributorsViewer contributors={file.contributors} />
</div> // TODO: trick to update on resize </div> // TODO: need resizing when parent resizes
); );
} }
} }
......
import React from 'react'; import React from 'react';
import { connect } from 'react-redux';
import { actions as envActions } from '/reducers/env';
import { HierarchyApi } from '/apis/index';
import { ContributorsViewer, MarkdownViewer } from '/components'; import { ContributorsViewer, MarkdownViewer } from '/components';
import styles from './stylesheet.scss'; import styles from './stylesheet.scss';
import { classes } from '/common/util'; import { classes } from '/common/util';
@connect(
({ env }) => ({
env
}), {
...envActions
}
)
class DescriptionViewer extends React.Component { class DescriptionViewer extends React.Component {
constructor(props) {
super(props);
this.state = {
file: null,
};
}
componentDidMount() {
const { categoryKey, algorithmKey } = this.props.env;
const href = `/algorithm/${categoryKey}/${algorithmKey}`;
this.loadFile(href);
}
componentWillReceiveProps(nextProps) {
const { categoryKey, algorithmKey } = nextProps.env;
if (categoryKey !== this.props.env.categoryKey ||
algorithmKey !== this.props.env.algorithmKey) {
const href = `/algorithm/${categoryKey}/${algorithmKey}`;
this.loadFile(href);
}
}
loadFile(href) {
const [, , categoryKey, algorithmKey] = href.split('/');
HierarchyApi.getFile(categoryKey, algorithmKey, 'desc.md')
.then(({ file }) => this.setState({ file }))
.catch(() => this.setState({ file: null }));
}
render() { render() {
const { className } = this.props; const { className, file } = this.props;
const { file } = this.state;
return file && ( return file && (
<div className={classes(styles.description_viewer, className)}> <div className={classes(styles.description_viewer, className)}>
<MarkdownViewer className={styles.markdown_viewer} source={file.content} onClickLink={href => this.loadFile(href)} /> <MarkdownViewer className={styles.markdown_viewer} source={file.content} />
<ContributorsViewer contributors={file.contributors} /> <ContributorsViewer contributors={file.contributors} />
</div> </div>
); );
......
...@@ -72,15 +72,23 @@ class Header extends React.Component { ...@@ -72,15 +72,23 @@ class Header extends React.Component {
const { className, onClickTitleBar, navigatorOpened } = this.props; const { className, onClickTitleBar, navigatorOpened } = this.props;
const { hierarchy, categoryKey, algorithmKey, signedIn } = this.props.env; const { hierarchy, categoryKey, algorithmKey, signedIn } = this.props.env;
let directory = ['Algorithm Visualizer'];
if (hierarchy && categoryKey && algorithmKey) {
const category = hierarchy.find(category => category.key === categoryKey); const category = hierarchy.find(category => category.key === categoryKey);
const algorithm = category.algorithms.find(algorithm => algorithm.key === algorithmKey); const algorithm = category.algorithms.find(algorithm => algorithm.key === algorithmKey);
directory = [category.name, algorithm.name];
}
return ( return (
<header className={classes(styles.header, className)}> <header className={classes(styles.header, className)}>
<Button className={styles.title_bar} onClick={onClickTitleBar}> <Button className={styles.title_bar} onClick={onClickTitleBar}>
<Ellipsis>{category.name}</Ellipsis> {
<FontAwesomeIcon className={styles.nav_arrow} fixedWidth icon={faAngleRight} /> directory.map((path, i) => [
<Ellipsis>{algorithm.name}</Ellipsis> <Ellipsis key={`path-${i}`}>{path}</Ellipsis>,
i < directory.length - 1 &&
<FontAwesomeIcon className={styles.nav_arrow} fixedWidth icon={faAngleRight} key={`arrow-${i}`} />
])
}
<FontAwesomeIcon className={styles.nav_caret} fixedWidth <FontAwesomeIcon className={styles.nav_caret} fixedWidth
icon={navigatorOpened ? faCaretDown : faCaretRight} /> icon={navigatorOpened ? faCaretDown : faCaretRight} />
</Button> </Button>
......
...@@ -8,7 +8,7 @@ class MarkdownViewer extends React.Component { ...@@ -8,7 +8,7 @@ class MarkdownViewer extends React.Component {
const { className, source, onClickLink } = this.props; const { className, source, onClickLink } = this.props;
const link = ({ href, ...rest }) => { const link = ({ href, ...rest }) => {
return /^https?:\/\//i.test(href) ? ( return !onClickLink || /^https?:\/\//i.test(href) ? (
<a href={href} rel="noopener" target="_blank" {...rest} /> <a href={href} rel="noopener" target="_blank" {...rest} />
) : ( ) : (
<a onClick={() => onClickLink(href)} {...rest} /> <a onClick={() => onClickLink(href)} {...rest} />
......
...@@ -8,29 +8,44 @@ import faGithub from '@fortawesome/fontawesome-free-brands/faGithub'; ...@@ -8,29 +8,44 @@ import faGithub from '@fortawesome/fontawesome-free-brands/faGithub';
import { ExpandableListItem, ListItem } from '/components'; import { ExpandableListItem, ListItem } from '/components';
import { classes } from '/common/util'; import { classes } from '/common/util';
import { actions as envActions } from '/reducers/env'; import { actions as envActions } from '/reducers/env';
import { actions as toastActions } from '/reducers/toast';
import styles from './stylesheet.scss'; import styles from './stylesheet.scss';
import { ALGORITHM_NEW, CATEGORY_SCRATCH_PAPER } from '/common/config';
@connect( @connect(
({ env }) => ({ ({ env }) => ({
env env
}), { }), {
...envActions ...envActions,
...toastActions,
} }
) )
class Navigator extends React.Component { class Navigator extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
const { categoryKey } = this.props.env;
this.state = { this.state = {
categoriesOpened: { categoriesOpened: {},
[categoryKey]: true, scratchPaperOpened: false,
}, favoritesOpened: false,
query: '', query: '',
} }
} }
componentDidMount() {
const { categoryKey } = this.props.env;
if (categoryKey) {
this.toggleCategory(categoryKey, true);
}
}
componentWillReceiveProps(nextProps) {
const { categoryKey } = nextProps.env;
if (categoryKey) {
this.toggleCategory(categoryKey, true);
}
}
toggleCategory(key, categoryOpened = !this.state.categoriesOpened[key]) { toggleCategory(key, categoryOpened = !this.state.categoriesOpened[key]) {
const categoriesOpened = { const categoriesOpened = {
...this.state.categoriesOpened, ...this.state.categoriesOpened,
...@@ -39,6 +54,14 @@ class Navigator extends React.Component { ...@@ -39,6 +54,14 @@ class Navigator extends React.Component {
this.setState({ categoriesOpened }); this.setState({ categoriesOpened });
} }
toggleScratchPaper(scratchPaperOpened = !this.state.scratchPaperOpened) {
this.setState({ scratchPaperOpened });
}
toggleFavorites(favoritesOpened = !this.state.favoritesOpened) {
this.setState({ favoritesOpened });
}
handleChangeQuery(e) { handleChangeQuery(e) {
const { hierarchy } = this.props.env; const { hierarchy } = this.props.env;
const categoriesOpened = {}; const categoriesOpened = {};
...@@ -48,7 +71,6 @@ class Navigator extends React.Component { ...@@ -48,7 +71,6 @@ class Navigator extends React.Component {
categoriesOpened[category.key] = true; categoriesOpened[category.key] = true;
} }
}); });
this.setState({ categoriesOpened, query }); this.setState({ categoriesOpened, query });
} }
...@@ -58,9 +80,9 @@ class Navigator extends React.Component { ...@@ -58,9 +80,9 @@ class Navigator extends React.Component {
} }
render() { render() {
const { categoriesOpened, query } = this.state; const { categoriesOpened, scratchPaperOpened, favoritesOpened, query } = this.state;
const { className, style } = this.props; const { className, style } = this.props;
const { hierarchy, categoryKey: selectedCategoryKey, algorithmKey: selectedAlgorithmKey } = this.props.env; const { hierarchy, categoryKey, algorithmKey, signedIn } = this.props.env;
return ( return (
<nav className={classes(styles.navigator, className)} style={style}> <nav className={classes(styles.navigator, className)} style={style}>
...@@ -71,7 +93,7 @@ class Navigator extends React.Component { ...@@ -71,7 +93,7 @@ class Navigator extends React.Component {
</div> </div>
<div className={styles.algorithm_list}> <div className={styles.algorithm_list}>
{ {
hierarchy.map(category => { hierarchy && hierarchy.map(category => {
const categoryOpened = categoriesOpened[category.key]; const categoryOpened = categoriesOpened[category.key];
let algorithms = category.algorithms; let algorithms = category.algorithms;
if (!this.testQuery(category.name)) { if (!this.testQuery(category.name)) {
...@@ -84,7 +106,7 @@ class Navigator extends React.Component { ...@@ -84,7 +106,7 @@ class Navigator extends React.Component {
opened={categoryOpened}> opened={categoryOpened}>
{ {
algorithms.map(algorithm => { algorithms.map(algorithm => {
const selected = category.key === selectedCategoryKey && algorithm.key === selectedAlgorithmKey; const selected = category.key === categoryKey && algorithm.key === algorithmKey;
return ( return (
<ListItem indent key={algorithm.key} selected={selected} <ListItem indent key={algorithm.key} selected={selected}
to={`/${category.key}/${algorithm.key}`} label={algorithm.name} /> to={`/${category.key}/${algorithm.key}`} label={algorithm.name} />
...@@ -97,9 +119,24 @@ class Navigator extends React.Component { ...@@ -97,9 +119,24 @@ class Navigator extends React.Component {
} }
</div> </div>
<div className={styles.footer}> <div className={styles.footer}>
<ExpandableListItem icon={faCode} label="Scratch Paper" /> {
<ExpandableListItem icon={faStar} label="Favorites" /> signedIn ?
<ListItem icon={faGithub} label="Fork me on GitHub" href="https://github.com/parkjs814/AlgorithmVisualizer" /> <ExpandableListItem icon={faCode} label="Scratch Paper" onClick={() => this.toggleScratchPaper()}
opened={scratchPaperOpened}>
<ListItem indent label="New ..." to={`/${CATEGORY_SCRATCH_PAPER}/${ALGORITHM_NEW}`} />
</ExpandableListItem> :
<ListItem icon={faCode} label="Scratch Paper"
onClick={() => this.props.showSuccessToast('Sign In Required')} />
}
{
signedIn ?
<ExpandableListItem icon={faStar} label="Favorites" onClick={() => this.toggleFavorites()}
opened={favoritesOpened} /> :
<ListItem icon={faStar} label="Favorites"
onClick={() => this.props.showSuccessToast('Sign In Required')} />
}
<ListItem icon={faGithub} label="Fork me on GitHub"
href="https://github.com/algorithm-visualizer/algorithm-visualizer" />
</div> </div>
</nav> </nav>
); );
......
...@@ -103,10 +103,9 @@ class TracerManager { ...@@ -103,10 +103,9 @@ class TracerManager {
}[className]; }[className];
const data = new DataClass(options); const data = new DataClass(options);
this.datas[tracerKey] = data; this.datas[tracerKey] = data;
const renderer = { const renderer = (
tracerKey, <RendererClass key={tracerKey} title={title} data={data} wsProps={{ fixed: true }} />
element: <RendererClass title={title} data={data} wsProps={{ fixed: true }} /> );
};
this.renderers.push(renderer); this.renderers.push(renderer);
} }
......
...@@ -9,7 +9,7 @@ class WSSectionContainer extends React.Component { ...@@ -9,7 +9,7 @@ class WSSectionContainer extends React.Component {
super(props); super(props);
const { core } = props; const { core } = props;
core.reference.component = this; core.component = this;
this.core = core; this.core = core;
} }
...@@ -57,7 +57,7 @@ class WSSectionContainer extends React.Component { ...@@ -57,7 +57,7 @@ class WSSectionContainer extends React.Component {
visibleChildren.forEach((child, visibleIndex) => { visibleChildren.forEach((child, visibleIndex) => {
const index = children.indexOf(child); const index = children.indexOf(child);
elements.push( elements.push(
<Divider key={`divider-before-${child.key}`} horizontal={horizontal} <Divider key={`divider-${index}`} horizontal={horizontal}
onResize={visibleIndex > 0 && ((target, dx, dy) => this.handleResize(visibleIndex, target, dx, dy))} onResize={visibleIndex > 0 && ((target, dx, dy) => this.handleResize(visibleIndex, target, dx, dy))}
onDropTab={tab => this.handleDropTabToContainer(tab, index)} onDropTab={tab => this.handleDropTabToContainer(tab, index)}
onDropSection={section => this.handleDropSectionToContainer(section, index)} onDropSection={section => this.handleDropSectionToContainer(section, index)}
...@@ -71,13 +71,13 @@ class WSSectionContainer extends React.Component { ...@@ -71,13 +71,13 @@ class WSSectionContainer extends React.Component {
}; };
if (children.length === 1) { if (children.length === 1) {
elements.push( elements.push(
<div key={child.key} className={classes(styles.wrapper)} style={style}> <div key={child.id} className={classes(styles.wrapper)} style={style}>
{child.element} {child.element}
</div> </div>
); );
} else { } else {
elements.push( elements.push(
<div key={child.key} className={classes(styles.wrapper, !horizontal && styles.horizontal)} style={style}> <div key={child.id} className={classes(styles.wrapper, !horizontal && styles.horizontal)} style={style}>
{ {
!child.fixed && !child.fixed &&
<Divider horizontal={!horizontal} <Divider horizontal={!horizontal}
...@@ -96,7 +96,7 @@ class WSSectionContainer extends React.Component { ...@@ -96,7 +96,7 @@ class WSSectionContainer extends React.Component {
} }
if (visibleIndex === visibleChildren.length - 1) { if (visibleIndex === visibleChildren.length - 1) {
elements.push( elements.push(
<Divider key={`divider-after-${child.key}`} horizontal={horizontal} <Divider key={`divider-last`} horizontal={horizontal}
onDropTab={tab => this.handleDropTabToContainer(tab, index + 1)} onDropTab={tab => this.handleDropTabToContainer(tab, index + 1)}
onDropSection={section => this.handleDropSectionToContainer(section, index + 1)} onDropSection={section => this.handleDropSectionToContainer(section, index + 1)}
disableDrop={child.fixed} /> disableDrop={child.fixed} />
......
...@@ -22,7 +22,7 @@ class WSTabContainer extends React.Component { ...@@ -22,7 +22,7 @@ class WSTabContainer extends React.Component {
super(props); super(props);
const { core } = props; const { core } = props;
core.reference.component = this; core.component = this;
this.core = core; this.core = core;
} }
......
import React from 'react'; import React from 'react';
import { classes } from '/common/util'; import { classes } from '/common/util';
import { WSSectionContainer } from '/workspace/components'; import { WSSectionContainer } from '/workspace/components';
import { SectionContainer } from '/workspace/core'; import { Parent, SectionContainer } from '/workspace/core';
import styles from './stylesheet.scss'; import styles from './stylesheet.scss';
class Workspace extends React.Component { class Workspace extends React.Component {
static createReference() {
return {
core: null,
component: null,
};
}
constructor(props) { constructor(props) {
super(props); super(props);
const { className, children, wsProps } = props; this.sectionContainer = new SectionContainer(this.getElement(props));
this.sectionContainer = new SectionContainer( }
componentDidMount() {
this.handleChangeElement(this.props);
}
componentWillReceiveProps(nextProps) {
this.handleChangeElement(nextProps);
}
getElement(props) {
const { className, wsProps, ...rest } = props;
return (
<WSSectionContainer className={classes(styles.workspace, className)} <WSSectionContainer className={classes(styles.workspace, className)}
wsProps={{ removable: false, ...wsProps }}> wsProps={{ id: 'workspace', removable: false, ...wsProps }} {...rest} />
{children}
</WSSectionContainer>
); );
} }
handleChangeElement(props) {
const element = this.getElement(props);
const unmark = section => {
section.updated = false;
if (section instanceof Parent) {
section.children.forEach(unmark);
}
};
unmark(this.sectionContainer);
console.log('----');
const update = (element, parentSection) => {
const { children = [], wsProps = {} } = element.props;
const id = wsProps.id || `${parentSection.id}-${element.key}`;
if (id.startsWith('workspace-.1-.1-.1-')) console.log(id.slice('workspace-.1-.1-.1-'.length));
let section = this.findSectionById(id);
if (section) {
section.setElement(element);
} else {
section = parentSection.childify(React.cloneElement(element, { wsProps: { ...wsProps, id } }));
parentSection.addChild(section);
}
section.updated = true;
if (section instanceof Parent) {
React.Children.toArray(children).forEach(element => update(element, section));
}
};
update(element);
const removeUnmarked = section => {
if (!section.updated) {
section.remove();
} else if (section instanceof Parent) {
section.children.forEach(removeUnmarked);
}
};
removeUnmarked(this.sectionContainer);
}
findSectionById(id, section = this.sectionContainer) {
if (section.id === id) return section;
if (section instanceof Parent) {
for (const childSection of section.children) {
const foundSection = this.findSectionById(id, childSection);
if (foundSection) return foundSection;
}
}
}
render() { render() {
return this.sectionContainer.element; return this.sectionContainer.element;
} }
......
import React from 'react';
import uuid from 'uuid';
import { Workspace } from '/workspace/components';
class Child {
getDefaultProps() {
return {
reference: Workspace.createReference(),
removable: true,
};
}
createElement(wsProps) {
return (
<div wsProps={wsProps} />
);
}
constructor(element) {
if (!React.isValidElement(element)) {
element = this.createElement(element);
}
const { wsProps = {} } = element.props;
Object.assign(this, this.getDefaultProps(), wsProps);
this.reference.core = this;
this.key = uuid.v4();
this.parent = null;
this.init(element);
}
init(element) {
this.element = React.cloneElement(element, { core: this });
}
setParent(parent) {
if (this.parent) this.remove(true);
this.parent = parent;
}
setElement(element) {
this.init(element);
this.parent.render();
}
remove(moving = false) {
if (this.removable || moving) {
const index = this.parent.findIndex(this.key);
this.parent.removeChild(index);
}
}
}
export default Child;
\ No newline at end of file
import React from 'react'; import React from 'react';
import { Child } from '/workspace/core'; import { Section } from '/workspace/core';
const parentMixin = (Base = Child) => class Parent extends Base { class Parent extends Section {
constructor(element) { constructor(element) {
super(element); super(element);
const { children = [] } = this.element.props;
this.children = []; this.children = [];
React.Children.forEach(children, element => this.addChild(this.childify(element))); }
setElement(element) {
super.setElement(React.cloneElement(element, { core: this }));
} }
childify(element) { childify(element) {
return new Child(element); return new Section(element);
} }
addChild(child, index = this.children.length, beforeRender) { addChild(child, index = this.children.length) {
if (child.parent === this) { if (child.parent === this) {
const oldIndex = this.children.indexOf(child); const oldIndex = this.children.indexOf(child);
this.children[oldIndex] = null; this.children[oldIndex] = null;
...@@ -23,25 +25,20 @@ const parentMixin = (Base = Child) => class Parent extends Base { ...@@ -23,25 +25,20 @@ const parentMixin = (Base = Child) => class Parent extends Base {
this.children.splice(index, 0, child); this.children.splice(index, 0, child);
child.setParent(this); child.setParent(this);
} }
if(beforeRender) beforeRender();
this.render();
} }
removeChild(index, beforeRender) { removeChild(index) {
this.children.splice(index, 1); this.children.splice(index, 1);
if (this.children.length === 0) this.remove(); if (this.children.length === 0) this.remove();
if(beforeRender) beforeRender();
this.render();
} }
findIndex(key) { findIndex(child) {
return this.children.findIndex(child => child.key === key); return this.children.indexOf(child);
} }
render() { render() {
const { component } = this.reference; if (this.component) this.component.forceUpdate();
if (component) component.forceUpdate();
} }
}; }
export default parentMixin; export default Parent;
\ No newline at end of file \ No newline at end of file
import { Child } from '/workspace/core'; import React from 'react';
class Section extends Child { class Section {
getDefaultProps() { getDefaultProps() {
return { return {
...super.getDefaultProps(), id: null,
removable: true,
visible: true, visible: true,
resizable: true, resizable: true,
weight: 1, weight: 1,
...@@ -17,13 +18,37 @@ class Section extends Child { ...@@ -17,13 +18,37 @@ class Section extends Child {
} }
constructor(element) { constructor(element) {
super(element); if (!React.isValidElement(element)) {
element = this.createElement(element);
}
this.parent = null;
Object.assign(this, this.getDefaultProps());
this.setElement(element);
}
createElement(wsProps) {
return (
<div wsProps={wsProps} />
);
}
setElement(element) {
const { wsProps = {} } = element.props;
Object.assign(this, wsProps);
this.relative = this.size === -1; this.relative = this.size === -1;
this.element = element;
}
setParent(parent) {
if (this.parent) this.remove(true);
this.parent = parent;
} }
setVisible(visible) { remove(moving = false) {
this.visible = visible; if (this.removable || moving) {
this.parent.render(); const index = this.parent.findIndex(this);
this.parent.removeChild(index);
}
} }
} }
......
import React from 'react'; import React from 'react';
import { Section, TabContainer } from '/workspace/core'; import { Parent, Section, TabContainer } from '/workspace/core';
import { parentMixin } from '/workspace/core/mixins';
import { WSSectionContainer, WSTabContainer } from '/workspace/components'; import { WSSectionContainer, WSTabContainer } from '/workspace/components';
class SectionContainer extends parentMixin(Section) { class SectionContainer extends Parent {
getDefaultProps() { getDefaultProps() {
return { return {
...super.getDefaultProps(), ...super.getDefaultProps(),
...@@ -32,7 +31,7 @@ class SectionContainer extends parentMixin(Section) { ...@@ -32,7 +31,7 @@ class SectionContainer extends parentMixin(Section) {
super.removeChild(index); super.removeChild(index);
if (this.removable && this.children.length === 1) { if (this.removable && this.children.length === 1) {
const [child] = this.children; const [child] = this.children;
const index = this.parent.findIndex(this.key); const index = this.parent.findIndex(this);
this.parent.addChild(child, index); this.parent.addChild(child, index);
} }
} }
......
import React from 'react'; import React from 'react';
import { Section, Tab } from '/workspace/core'; import { Parent, Tab } from '/workspace/core';
import { WSTabContainer } from '/workspace/components'; import { WSTabContainer } from '/workspace/components';
import { parentMixin } from '/workspace/core/mixins';
class TabContainer extends parentMixin(Section) { class TabContainer extends Parent {
getDefaultProps() { getDefaultProps() {
return { return {
...super.getDefaultProps(), ...super.getDefaultProps(),
...@@ -22,15 +21,13 @@ class TabContainer extends parentMixin(Section) { ...@@ -22,15 +21,13 @@ class TabContainer extends parentMixin(Section) {
} }
addChild(child, index = this.children.length) { addChild(child, index = this.children.length) {
super.addChild(child, index, () => { super.addChild(child, index);
this.setTabIndex(Math.min(index, this.children.length - 1)); this.setTabIndex(Math.min(index, this.children.length - 1));
});
} }
removeChild(index) { removeChild(index) {
super.removeChild(index, () => { super.removeChild(index);
this.setTabIndex(Math.min(this.tabIndex, this.children.length - 1)); this.setTabIndex(Math.min(this.tabIndex, this.children.length - 1));
});
} }
setTabIndex(tabIndex) { setTabIndex(tabIndex) {
......
import uuid from 'uuid';
const key = 'dragging-data-id';
class DraggingData {
constructor() {
this.id = null;
this.data = null;
}
set(e, type, child) {
this.id = uuid.v4();
this.data = { type, child };
e.dataTransfer.dropEffect = 'move';
e.dataTransfer.setData(key, this.id);
}
get(e) {
const id = e.dataTransfer.getData(key);
if (id === this.id) return this.data;
}
}
const draggingData = new DraggingData();
export default draggingData;
\ No newline at end of file
export { default as Child } from './Child';
export { default as draggingData } from './draggingData';
export { default as Section } from './Section'; export { default as Section } from './Section';
export { default as Parent } from './Parent';
export { default as SectionContainer } from './SectionContainer'; export { default as SectionContainer } from './SectionContainer';
export { default as Tab } from './Tab';
export { default as TabContainer } from './TabContainer'; export { default as TabContainer } from './TabContainer';
export { default as Tab } from './Tab';
export { default as parentMixin } from './parentMixin';
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册