From a3529a3142752b54a9b0390dddead133ba690266 Mon Sep 17 00:00:00 2001 From: Jason Park Date: Sat, 30 Jun 2018 17:54:33 +0900 Subject: [PATCH] Improve workspace performance and few other things --- src/backend/controllers/hierarchy.js | 16 ++-- src/backend/public/algorithms | 2 +- src/frontend/apis/index.js | 3 +- src/frontend/common/config.js | 7 +- src/frontend/components/App/index.jsx | 68 ++++++++-------- src/frontend/components/App/stylesheet.scss | 1 - src/frontend/components/CodeEditor/index.jsx | 46 +++-------- .../components/DescriptionViewer/index.jsx | 45 +---------- src/frontend/components/Header/index.jsx | 18 +++-- .../components/MarkdownViewer/index.jsx | 2 +- src/frontend/components/Navigator/index.jsx | 65 +++++++++++---- src/frontend/core/tracerManager.jsx | 7 +- .../components/WSSectionContainer/index.jsx | 10 +-- .../components/WSTabContainer/index.jsx | 2 +- .../workspace/components/Workspace/index.jsx | 79 ++++++++++++++++--- src/frontend/workspace/core/Child.js | 53 ------------- src/frontend/workspace/core/Parent.js | 44 +++++++++++ src/frontend/workspace/core/Section.js | 39 +++++++-- .../workspace/core/SectionContainer.js | 7 +- src/frontend/workspace/core/TabContainer.js | 15 ++-- src/frontend/workspace/core/draggingData.js | 25 ------ src/frontend/workspace/core/index.js | 5 +- src/frontend/workspace/core/mixins/index.js | 1 - .../workspace/core/mixins/parentMixin.js | 47 ----------- 24 files changed, 288 insertions(+), 319 deletions(-) delete mode 100644 src/frontend/workspace/core/Child.js create mode 100644 src/frontend/workspace/core/Parent.js delete mode 100644 src/frontend/workspace/core/draggingData.js delete mode 100644 src/frontend/workspace/core/mixins/index.js delete mode 100644 src/frontend/workspace/core/mixins/parentMixin.js diff --git a/src/backend/controllers/hierarchy.js b/src/backend/controllers/hierarchy.js index dc9d36f..cfc8d3d 100644 --- a/src/backend/controllers/hierarchy.js +++ b/src/backend/controllers/hierarchy.js @@ -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 diff --git a/src/backend/public/algorithms b/src/backend/public/algorithms index f17c570..c9b0606 160000 --- a/src/backend/public/algorithms +++ b/src/backend/public/algorithms @@ -1 +1 @@ -Subproject commit f17c57048f65f287d12e13e6f35b606034aeeb31 +Subproject commit c9b0606bd6c80e02bc12c0215722cbbd9751965e diff --git a/src/frontend/apis/index.js b/src/frontend/apis/index.js index 7a3d67b..6916fb5 100644 --- a/src/frontend/apis/index.js +++ b/src/frontend/apis/index.js @@ -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 { diff --git a/src/frontend/common/config.js b/src/frontend/common/config.js index fce5e00..932f87f 100644 --- a/src/frontend/common/config.js +++ b/src/frontend/common/config.js @@ -1,5 +1,10 @@ -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 diff --git a/src/frontend/components/App/index.jsx b/src/frontend/components/App/index.jsx index bc56c5f..a3db569 100644 --- a/src/frontend/components/App/index.jsx +++ b/src/frontend/components/App/index.jsx @@ -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 (
- +
this.navigatorReference.core.setVisible(!this.navigatorReference.core.visible)} - navigatorOpened={navigatorOpened} /> + onClickTitleBar={() => this.navigator.setVisible(!this.navigator.visible)} + navigatorOpened={true /* TODO: fix */} /> @@ -116,12 +111,13 @@ class App extends React.Component { title: 'Visualization', removable: false, horizontal: false, - reference: this.spawnReference - }} /> + }}> + {renderers} + - - + + diff --git a/src/frontend/components/App/stylesheet.scss b/src/frontend/components/App/stylesheet.scss index 3a72a15..75d7285 100644 --- a/src/frontend/components/App/stylesheet.scss +++ b/src/frontend/components/App/stylesheet.scss @@ -15,7 +15,6 @@ body { user-select: none; color: $color-font; font-size: $font-size-normal; - background-color: $theme-normal; } a { diff --git a/src/frontend/components/CodeEditor/index.jsx b/src/frontend/components/CodeEditor/index.jsx index 0170e74..2d51f34 100644 --- a/src/frontend/components/CodeEditor/index.jsx +++ b/src/frontend/components/CodeEditor/index.jsx @@ -1,46 +1,33 @@ 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 && (
@@ -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} /> -
// TODO: trick to update on resize +
// TODO: need resizing when parent resizes ); } } diff --git a/src/frontend/components/DescriptionViewer/index.jsx b/src/frontend/components/DescriptionViewer/index.jsx index 9124baf..75201bd 100644 --- a/src/frontend/components/DescriptionViewer/index.jsx +++ b/src/frontend/components/DescriptionViewer/index.jsx @@ -1,55 +1,14 @@ 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 && (
- this.loadFile(href)} /> +
); diff --git a/src/frontend/components/Header/index.jsx b/src/frontend/components/Header/index.jsx index 93ee1c5..648480a 100644 --- a/src/frontend/components/Header/index.jsx +++ b/src/frontend/components/Header/index.jsx @@ -72,15 +72,23 @@ class Header extends React.Component { const { className, onClickTitleBar, navigatorOpened } = this.props; const { hierarchy, categoryKey, algorithmKey, signedIn } = this.props.env; - const category = hierarchy.find(category => category.key === categoryKey); - const algorithm = category.algorithms.find(algorithm => algorithm.key === algorithmKey); + 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 (
diff --git a/src/frontend/components/MarkdownViewer/index.jsx b/src/frontend/components/MarkdownViewer/index.jsx index f85b2c4..f2d2e23 100644 --- a/src/frontend/components/MarkdownViewer/index.jsx +++ b/src/frontend/components/MarkdownViewer/index.jsx @@ -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) ? ( ) : ( onClickLink(href)} {...rest} /> diff --git a/src/frontend/components/Navigator/index.jsx b/src/frontend/components/Navigator/index.jsx index d03fdd4..e3bae9d 100644 --- a/src/frontend/components/Navigator/index.jsx +++ b/src/frontend/components/Navigator/index.jsx @@ -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 ( ); diff --git a/src/frontend/core/tracerManager.jsx b/src/frontend/core/tracerManager.jsx index 156f761..9bd4a0c 100644 --- a/src/frontend/core/tracerManager.jsx +++ b/src/frontend/core/tracerManager.jsx @@ -103,10 +103,9 @@ class TracerManager { }[className]; const data = new DataClass(options); this.datas[tracerKey] = data; - const renderer = { - tracerKey, - element: - }; + const renderer = ( + + ); this.renderers.push(renderer); } diff --git a/src/frontend/workspace/components/WSSectionContainer/index.jsx b/src/frontend/workspace/components/WSSectionContainer/index.jsx index 4a8c8c2..bf8051a 100644 --- a/src/frontend/workspace/components/WSSectionContainer/index.jsx +++ b/src/frontend/workspace/components/WSSectionContainer/index.jsx @@ -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( - 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( -
+
{child.element}
); } else { elements.push( -
+
{ !child.fixed && this.handleDropTabToContainer(tab, index + 1)} onDropSection={section => this.handleDropSectionToContainer(section, index + 1)} disableDrop={child.fixed} /> diff --git a/src/frontend/workspace/components/WSTabContainer/index.jsx b/src/frontend/workspace/components/WSTabContainer/index.jsx index be4632d..e1f5a0e 100644 --- a/src/frontend/workspace/components/WSTabContainer/index.jsx +++ b/src/frontend/workspace/components/WSTabContainer/index.jsx @@ -22,7 +22,7 @@ class WSTabContainer extends React.Component { super(props); const { core } = props; - core.reference.component = this; + core.component = this; this.core = core; } diff --git a/src/frontend/workspace/components/Workspace/index.jsx b/src/frontend/workspace/components/Workspace/index.jsx index 1e284da..b4d9894 100644 --- a/src/frontend/workspace/components/Workspace/index.jsx +++ b/src/frontend/workspace/components/Workspace/index.jsx @@ -1,29 +1,82 @@ 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 ( - {children} - + 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; } diff --git a/src/frontend/workspace/core/Child.js b/src/frontend/workspace/core/Child.js deleted file mode 100644 index 7fe6138..0000000 --- a/src/frontend/workspace/core/Child.js +++ /dev/null @@ -1,53 +0,0 @@ -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 ( -
- ); - } - - 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 diff --git a/src/frontend/workspace/core/Parent.js b/src/frontend/workspace/core/Parent.js new file mode 100644 index 0000000..75d918b --- /dev/null +++ b/src/frontend/workspace/core/Parent.js @@ -0,0 +1,44 @@ +import React from 'react'; +import { Section } from '/workspace/core'; + +class Parent extends Section { + constructor(element) { + super(element); + this.children = []; + } + + setElement(element) { + super.setElement(React.cloneElement(element, { core: this })); + } + + childify(element) { + return new Section(element); + } + + addChild(child, index = this.children.length) { + if (child.parent === this) { + const oldIndex = this.children.indexOf(child); + this.children[oldIndex] = null; + this.children.splice(index, 0, child); + this.children = this.children.filter(child => child); + } else { + this.children.splice(index, 0, child); + child.setParent(this); + } + } + + removeChild(index) { + this.children.splice(index, 1); + if (this.children.length === 0) this.remove(); + } + + findIndex(child) { + return this.children.indexOf(child); + } + + render() { + if (this.component) this.component.forceUpdate(); + } +} + +export default Parent; \ No newline at end of file diff --git a/src/frontend/workspace/core/Section.js b/src/frontend/workspace/core/Section.js index 6b9dbff..f601372 100644 --- a/src/frontend/workspace/core/Section.js +++ b/src/frontend/workspace/core/Section.js @@ -1,9 +1,10 @@ -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 ( +
+ ); + } + + 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); + } } } diff --git a/src/frontend/workspace/core/SectionContainer.js b/src/frontend/workspace/core/SectionContainer.js index 3ecf541..44b7992 100644 --- a/src/frontend/workspace/core/SectionContainer.js +++ b/src/frontend/workspace/core/SectionContainer.js @@ -1,9 +1,8 @@ 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); } } diff --git a/src/frontend/workspace/core/TabContainer.js b/src/frontend/workspace/core/TabContainer.js index 6f78c15..2c2267b 100644 --- a/src/frontend/workspace/core/TabContainer.js +++ b/src/frontend/workspace/core/TabContainer.js @@ -1,9 +1,8 @@ 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, () => { - this.setTabIndex(Math.min(index, this.children.length - 1)); - }); + super.addChild(child, index); + this.setTabIndex(Math.min(index, this.children.length - 1)); } removeChild(index) { - super.removeChild(index, () => { - this.setTabIndex(Math.min(this.tabIndex, this.children.length - 1)); - }); + super.removeChild(index); + this.setTabIndex(Math.min(this.tabIndex, this.children.length - 1)); } setTabIndex(tabIndex) { diff --git a/src/frontend/workspace/core/draggingData.js b/src/frontend/workspace/core/draggingData.js deleted file mode 100644 index 921764a..0000000 --- a/src/frontend/workspace/core/draggingData.js +++ /dev/null @@ -1,25 +0,0 @@ -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 diff --git a/src/frontend/workspace/core/index.js b/src/frontend/workspace/core/index.js index 49aa770..62a7f4d 100644 --- a/src/frontend/workspace/core/index.js +++ b/src/frontend/workspace/core/index.js @@ -1,6 +1,5 @@ -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 TabContainer } from './TabContainer'; export { default as Tab } from './Tab'; -export { default as TabContainer } from './TabContainer'; \ No newline at end of file diff --git a/src/frontend/workspace/core/mixins/index.js b/src/frontend/workspace/core/mixins/index.js deleted file mode 100644 index df009bf..0000000 --- a/src/frontend/workspace/core/mixins/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default as parentMixin } from './parentMixin'; \ No newline at end of file diff --git a/src/frontend/workspace/core/mixins/parentMixin.js b/src/frontend/workspace/core/mixins/parentMixin.js deleted file mode 100644 index abc55d9..0000000 --- a/src/frontend/workspace/core/mixins/parentMixin.js +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { Child } from '/workspace/core'; - -const parentMixin = (Base = Child) => class Parent extends Base { - constructor(element) { - super(element); - const { children = [] } = this.element.props; - this.children = []; - React.Children.forEach(children, element => this.addChild(this.childify(element))); - } - - childify(element) { - return new Child(element); - } - - addChild(child, index = this.children.length, beforeRender) { - if (child.parent === this) { - const oldIndex = this.children.indexOf(child); - this.children[oldIndex] = null; - this.children.splice(index, 0, child); - this.children = this.children.filter(child => child); - } else { - this.children.splice(index, 0, child); - child.setParent(this); - } - if(beforeRender) beforeRender(); - this.render(); - } - - removeChild(index, beforeRender) { - 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); - } - - render() { - const { component } = this.reference; - if (component) component.forceUpdate(); - } -}; - -export default parentMixin; \ No newline at end of file -- GitLab