From 5695771fffc7e3a0b68ae6ac2d060fd18f5998af Mon Sep 17 00:00:00 2001 From: EnixCoda Date: Sat, 30 Jun 2018 12:31:21 +0800 Subject: [PATCH] refactor: extract cores --- .babelrc | 1 + package.json | 1 + src/components/FileExplorer.js | 190 +----------------------- src/components/SideBar.js | 4 + src/content.js | 6 +- src/driver/connect.js | 4 - src/driver/core/FileExplorer.js | 186 +++++++++++++++++++++++ src/driver/{core.js => core/SideBar.js} | 10 +- src/driver/core/index.js | 2 + 9 files changed, 203 insertions(+), 201 deletions(-) create mode 100644 src/driver/core/FileExplorer.js rename src/driver/{core.js => core/SideBar.js} (91%) create mode 100644 src/driver/core/index.js diff --git a/.babelrc b/.babelrc index b16cc73..6464282 100644 --- a/.babelrc +++ b/.babelrc @@ -11,6 +11,7 @@ "react" ], "plugins": [ + "transform-decorators-legacy", "transform-class-properties", ["transform-object-rest-spread", { "useBuiltIns": true }] ] diff --git a/package.json b/package.json index 32057d3..f1beb95 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "babel-loader": "^7.1.2", "babel-plugin-react-require": "^3.0.0", "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-decorators-legacy": "^1.3.5", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-preset-env": "^1.6.1", "babel-preset-react": "^6.24.1", diff --git a/src/components/FileExplorer.js b/src/components/FileExplorer.js index fa72dca..69bee78 100644 --- a/src/components/FileExplorer.js +++ b/src/components/FileExplorer.js @@ -2,187 +2,15 @@ import React from 'react' import PropTypes from "prop-types"; import connect from '../driver/connect' +import { FileExplorer as FileExplorerCore } from '../driver/core' import SearchBar from './SearchBar' import Node from './Node' import cx from '../utils/cx' -import DOMHelper from '../utils/DOMHelper' -import treeParser from '../utils/treeParser' -import URLHelper from '../utils/URLHelper' -import VisibleNodesGenerator from '../utils/VisibleNodesGenerator' -function getVisibleParentNode(nodes, focusedNode, depths) { - const focusedNodeIndex = nodes.indexOf(focusedNode) - const focusedNodeDepth = depths.get(focusedNode) - let indexOfParentNode = focusedNodeIndex - 1 - while ( - indexOfParentNode !== -1 && - depths.get(nodes[indexOfParentNode]) >= focusedNodeDepth - ) { - --indexOfParentNode - } - const parentNode = nodes[indexOfParentNode] - return parentNode -} - -const tasksAfterRender = [] -const visibleNodesGenerator = new VisibleNodesGenerator() - -const init = dispatch => () => dispatch(async (state, { treeData, metaData, compressSingletonFolder }) => { - const { root } = treeParser.parse(treeData, metaData) - visibleNodesGenerator.setCompress(compressSingletonFolder) - await visibleNodesGenerator.plantTree(root) - const currentPath = URLHelper.getCurrentPath(true) - tasksAfterRender.push(DOMHelper.focusSearchInput) - if (currentPath.length) { - const nodeExpandedTo = visibleNodesGenerator.expandTo(currentPath.join('/')) - if (nodeExpandedTo) { - visibleNodesGenerator.focusNode(nodeExpandedTo) - const { nodes } = visibleNodesGenerator.visibleNodes - tasksAfterRender.push(() => DOMHelper.scrollToNodeElement(nodes.indexOf(nodeExpandedTo))) - } - } - dispatch(updateVisibleNodes) -}) - -const execAfterRender = dispatch => () => { - for (const task of tasksAfterRender) { - task() - } - tasksAfterRender.length = 0 -} - -const handleKeyDown = dispatch => ({ key }) => dispatch(({ visibleNodes: { nodes, focusedNode, expandedNodes, depths } }) => { - if (focusedNode) { - const focusedNodeIndex = nodes.indexOf(focusedNode) - switch (key) { - case 'ArrowUp': - // focus on previous node - if (focusedNodeIndex === 0) { - dispatch(focusNode, null) - tasksAfterRender.push(DOMHelper.focusSearchInput) - } else { - dispatch(focusNode, nodes[focusedNodeIndex - 1]) - } - break - - case 'ArrowDown': - // focus on next node - if (focusedNodeIndex + 1 < nodes.length) { - dispatch(focusNode, nodes[focusedNodeIndex + 1]) - } else { - dispatch(focusNode, null) - tasksAfterRender.push(DOMHelper.focusSearchInput) - } - break - - case 'ArrowLeft': - // collapse node or go to parent node - if (expandedNodes.has(focusedNode)) { - dispatch(setExpand, focusedNode, false) - } else { - // go forward to the start of the list, find the closest node with lower depth - const parentNode = getVisibleParentNode(nodes, focusedNode, depths) - if (parentNode) { - dispatch(focusNode, parentNode) - } - } - break - - // consider the two keys as 'confirm' key - case 'ArrowRight': - // expand node or focus on first content node or redirect to file page - if (focusedNode.type === 'tree') { - if (expandedNodes.has(focusedNode)) { - const nextNode = nodes[focusedNodeIndex + 1] - if (depths.get(nextNode) > depths.get(focusedNode)) { - dispatch(focusNode, nextNode) - } - } else { - dispatch(setExpand, focusedNode, true) - } - } else if (focusedNode.type === 'blob') { - DOMHelper.loadWithPJAX(focusedNode.url) - } else if (focusedNode.type === 'commit') { - // redirect to its parent folder - DOMHelper.loadWithPJAX(focusedNode.parent.url) - } - break - case 'Enter': - // expand node or redirect to file page - if (focusedNode.type === 'tree') { - dispatch(setExpand, focusedNode, true) - } else if (focusedNode.type === 'blob') { - DOMHelper.loadWithPJAX(focusedNode.url) - } else if (focusedNode.type === 'commit') { - // redirect to its parent folder - DOMHelper.loadWithPJAX(focusedNode.parent.url) - } - break - - } - } else { - // now search input is focused - if (nodes.length) { - switch (key) { - case 'ArrowDown': - dispatch(focusNode, nodes[0]) - break - case 'ArrowUp': - dispatch(focusNode, nodes[nodes.length - 1]) - break - } - } - } -}) - -const handleSearchKeyChange = dispatch => async event => { - const searchKey = event.target.value - await visibleNodesGenerator.search(searchKey) - dispatch(updateVisibleNodes) -} - -const setExpand = dispatch => (node, expand) => { - visibleNodesGenerator.setExpand(node, expand) - dispatch(focusNode, node) - tasksAfterRender.push(DOMHelper.focusSearchInput) -} - -const toggleNodeExpansion = dispatch => (node, skipScrollToNode) => { - visibleNodesGenerator.toggleExpand(node) - dispatch(focusNode, node, skipScrollToNode) - tasksAfterRender.push(DOMHelper.focusFileExplorer) -} - -const focusNode = dispatch => (node, skipScroll) => dispatch(({ visibleNodes: { nodes } }) => { - visibleNodesGenerator.focusNode(node) - if (node && !skipScroll) { - // when focus a node not in viewport(by keyboard), scroll to it - const indexOfToBeFocusedNode = nodes.indexOf(node) - tasksAfterRender.push(() => DOMHelper.scrollToNodeElement(indexOfToBeFocusedNode)) - tasksAfterRender.push(DOMHelper.focusSearchInput) - } - dispatch(updateVisibleNodes) -}) - -const onNodeClick = dispatch => (node) => { - if (node.type === 'tree') { - dispatch(toggleNodeExpansion, node, true) - } else if (node.type === 'blob') { - dispatch(focusNode, node, true) - DOMHelper.loadWithPJAX(node.url) - } else if (node.type === 'commit') { - DOMHelper.loadWithPJAX(node.parent.url) - } -} - -const updateVisibleNodes = dispatch => () => { - const { visibleNodes } = visibleNodesGenerator - dispatch({ visibleNodes }) -} - -class List extends React.Component { +@connect(FileExplorerCore) +export default class FileExplorer extends React.Component { static propTyps = { treeData: PropTypes.object, metaData: PropTypes.object, @@ -260,15 +88,3 @@ class List extends React.Component { ) } } - -export default connect({ - init, - execAfterRender, - handleKeyDown, - handleSearchKeyChange, - setExpand, - toggleNodeExpansion, - focusNode, - onNodeClick, - updateVisibleNodes, -})(List) diff --git a/src/components/SideBar.js b/src/components/SideBar.js index fbf89b8..4cf539a 100644 --- a/src/components/SideBar.js +++ b/src/components/SideBar.js @@ -1,6 +1,9 @@ import React from 'react' import PropTypes from 'prop-types' +import { SideBar as SideBarCore } from '../driver/core' +import connect from '../driver/connect' + import FileExplorer from './FileExplorer' import ToggleShowButton from './ToggleShowButton' import MetaBar from './MetaBar' @@ -12,6 +15,7 @@ import cx from '../utils/cx' const baseSize = 260 +@connect(SideBarCore) export default class Gitako extends React.PureComponent { static propTypes = { // initial width of side bar diff --git a/src/content.js b/src/content.js index 5090739..f1ed56b 100644 --- a/src/content.js +++ b/src/content.js @@ -1,15 +1,11 @@ import React from 'react' import ReactDOM from 'react-dom' -import connect from './driver/connect' -import core from './driver/core' import SideBar from './components/SideBar' import './content.less' -const ConnectedSideBar = connect(core)(SideBar) - const SideBarElement = document.createElement('div') document.body.appendChild(SideBarElement) -ReactDOM.render(, SideBarElement) +ReactDOM.render(, SideBarElement) diff --git a/src/driver/connect.js b/src/driver/connect.js index c526482..0d11a62 100644 --- a/src/driver/connect.js +++ b/src/driver/connect.js @@ -1,9 +1,5 @@ import React from 'react' -function isObject(target) { - return typeof target === 'object' && target !== null -} - function link(instance, sources) { const wrappedMethods = {/* sources[key] -> wrappedMethods.method */} const map = new Map(/* sources.creator -> wrappedMethods.method */) diff --git a/src/driver/core/FileExplorer.js b/src/driver/core/FileExplorer.js new file mode 100644 index 0000000..c876a65 --- /dev/null +++ b/src/driver/core/FileExplorer.js @@ -0,0 +1,186 @@ +import DOMHelper from '../../utils/DOMHelper' +import treeParser from '../../utils/treeParser' +import URLHelper from '../../utils/URLHelper' +import VisibleNodesGenerator from '../../utils/VisibleNodesGenerator' + +function getVisibleParentNode(nodes, focusedNode, depths) { + const focusedNodeIndex = nodes.indexOf(focusedNode) + const focusedNodeDepth = depths.get(focusedNode) + let indexOfParentNode = focusedNodeIndex - 1 + while ( + indexOfParentNode !== -1 && + depths.get(nodes[indexOfParentNode]) >= focusedNodeDepth + ) { + --indexOfParentNode + } + const parentNode = nodes[indexOfParentNode] + return parentNode +} + +const tasksAfterRender = [] +const visibleNodesGenerator = new VisibleNodesGenerator() + +const init = dispatch => () => dispatch(async (state, { treeData, metaData, compressSingletonFolder }) => { + const { root } = treeParser.parse(treeData, metaData) + visibleNodesGenerator.setCompress(compressSingletonFolder) + await visibleNodesGenerator.plantTree(root) + const currentPath = URLHelper.getCurrentPath(true) + tasksAfterRender.push(DOMHelper.focusSearchInput) + if (currentPath.length) { + const nodeExpandedTo = visibleNodesGenerator.expandTo(currentPath.join('/')) + if (nodeExpandedTo) { + visibleNodesGenerator.focusNode(nodeExpandedTo) + const { nodes } = visibleNodesGenerator.visibleNodes + tasksAfterRender.push(() => DOMHelper.scrollToNodeElement(nodes.indexOf(nodeExpandedTo))) + } + } + dispatch(updateVisibleNodes) +}) + +const execAfterRender = dispatch => () => { + for (const task of tasksAfterRender) { + task() + } + tasksAfterRender.length = 0 +} + +const handleKeyDown = dispatch => ({ key }) => dispatch(({ visibleNodes: { nodes, focusedNode, expandedNodes, depths } }) => { + if (focusedNode) { + const focusedNodeIndex = nodes.indexOf(focusedNode) + switch (key) { + case 'ArrowUp': + // focus on previous node + if (focusedNodeIndex === 0) { + dispatch(focusNode, null) + tasksAfterRender.push(DOMHelper.focusSearchInput) + } else { + dispatch(focusNode, nodes[focusedNodeIndex - 1]) + } + break + + case 'ArrowDown': + // focus on next node + if (focusedNodeIndex + 1 < nodes.length) { + dispatch(focusNode, nodes[focusedNodeIndex + 1]) + } else { + dispatch(focusNode, null) + tasksAfterRender.push(DOMHelper.focusSearchInput) + } + break + + case 'ArrowLeft': + // collapse node or go to parent node + if (expandedNodes.has(focusedNode)) { + dispatch(setExpand, focusedNode, false) + } else { + // go forward to the start of the list, find the closest node with lower depth + const parentNode = getVisibleParentNode(nodes, focusedNode, depths) + if (parentNode) { + dispatch(focusNode, parentNode) + } + } + break + + // consider the two keys as 'confirm' key + case 'ArrowRight': + // expand node or focus on first content node or redirect to file page + if (focusedNode.type === 'tree') { + if (expandedNodes.has(focusedNode)) { + const nextNode = nodes[focusedNodeIndex + 1] + if (depths.get(nextNode) > depths.get(focusedNode)) { + dispatch(focusNode, nextNode) + } + } else { + dispatch(setExpand, focusedNode, true) + } + } else if (focusedNode.type === 'blob') { + DOMHelper.loadWithPJAX(focusedNode.url) + } else if (focusedNode.type === 'commit') { + // redirect to its parent folder + DOMHelper.loadWithPJAX(focusedNode.parent.url) + } + break + case 'Enter': + // expand node or redirect to file page + if (focusedNode.type === 'tree') { + dispatch(setExpand, focusedNode, true) + } else if (focusedNode.type === 'blob') { + DOMHelper.loadWithPJAX(focusedNode.url) + } else if (focusedNode.type === 'commit') { + // redirect to its parent folder + DOMHelper.loadWithPJAX(focusedNode.parent.url) + } + break + + } + } else { + // now search input is focused + if (nodes.length) { + switch (key) { + case 'ArrowDown': + dispatch(focusNode, nodes[0]) + break + case 'ArrowUp': + dispatch(focusNode, nodes[nodes.length - 1]) + break + } + } + } +}) + +const handleSearchKeyChange = dispatch => async event => { + const searchKey = event.target.value + await visibleNodesGenerator.search(searchKey) + dispatch(updateVisibleNodes) +} + +const setExpand = dispatch => (node, expand) => { + visibleNodesGenerator.setExpand(node, expand) + dispatch(focusNode, node) + tasksAfterRender.push(DOMHelper.focusSearchInput) +} + +const toggleNodeExpansion = dispatch => (node, skipScrollToNode) => { + visibleNodesGenerator.toggleExpand(node) + dispatch(focusNode, node, skipScrollToNode) + tasksAfterRender.push(DOMHelper.focusFileExplorer) +} + +const focusNode = dispatch => (node, skipScroll) => dispatch(({ visibleNodes: { nodes } }) => { + visibleNodesGenerator.focusNode(node) + if (node && !skipScroll) { + // when focus a node not in viewport(by keyboard), scroll to it + const indexOfToBeFocusedNode = nodes.indexOf(node) + tasksAfterRender.push(() => DOMHelper.scrollToNodeElement(indexOfToBeFocusedNode)) + tasksAfterRender.push(DOMHelper.focusSearchInput) + } + dispatch(updateVisibleNodes) +}) + +const onNodeClick = dispatch => (node) => { + if (node.type === 'tree') { + dispatch(toggleNodeExpansion, node, true) + } else if (node.type === 'blob') { + dispatch(focusNode, node, true) + DOMHelper.loadWithPJAX(node.url) + } else if (node.type === 'commit') { + DOMHelper.loadWithPJAX(node.parent.url) + } +} + +const updateVisibleNodes = dispatch => () => { + const { visibleNodes } = visibleNodesGenerator + dispatch({ visibleNodes }) +} + +export default { + init, + execAfterRender, + handleKeyDown, + handleSearchKeyChange, + setExpand, + toggleNodeExpansion, + focusNode, + onNodeClick, + updateVisibleNodes, +} diff --git a/src/driver/core.js b/src/driver/core/SideBar.js similarity index 91% rename from src/driver/core.js rename to src/driver/core/SideBar.js index 298f9d7..c380c3d 100644 --- a/src/driver/core.js +++ b/src/driver/core/SideBar.js @@ -1,8 +1,8 @@ -import DOMHelper, { REPO_TYPE_PRIVATE } from '../utils/DOMHelper' -import GitHubHelper, { NOT_FOUND, BAD_CREDENTIALS } from '../utils/GitHubHelper' -import configHelper from '../utils/configHelper' -import URLHelper from '../utils/URLHelper' -import keyHelper from '../utils/keyHelper' +import DOMHelper, { REPO_TYPE_PRIVATE } from '../../utils/DOMHelper' +import GitHubHelper, { NOT_FOUND, BAD_CREDENTIALS } from '../../utils/GitHubHelper' +import configHelper from '../../utils/configHelper' +import URLHelper from '../../utils/URLHelper' +import keyHelper from '../../utils/keyHelper' const init = dispatch => async () => { try { diff --git a/src/driver/core/index.js b/src/driver/core/index.js new file mode 100644 index 0000000..071ab89 --- /dev/null +++ b/src/driver/core/index.js @@ -0,0 +1,2 @@ +export { default as SideBar } from './SideBar' +export { default as FileExplorer } from './FileExplorer'