refactor: extract cores

This commit is contained in:
EnixCoda 2018-06-30 12:31:21 +08:00
parent 10a239acec
commit 5695771fff
9 changed files with 203 additions and 201 deletions

View file

@ -11,6 +11,7 @@
"react"
],
"plugins": [
"transform-decorators-legacy",
"transform-class-properties",
["transform-object-rest-spread", { "useBuiltIns": true }]
]

View file

@ -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",

View file

@ -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)

View file

@ -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

View file

@ -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(<ConnectedSideBar />, SideBarElement)
ReactDOM.render(<SideBar />, SideBarElement)

View file

@ -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 */)

View file

@ -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,
}

View file

@ -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 {

2
src/driver/core/index.js Normal file
View file

@ -0,0 +1,2 @@
export { default as SideBar } from './SideBar'
export { default as FileExplorer } from './FileExplorer'