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

Improve workspace performance and few other things

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