feat: scroll to node

This commit is contained in:
EnixCoda 2019-08-11 15:38:50 +08:00
parent f768935330
commit 5f6feeff00
No known key found for this signature in database
GPG key ID: 0C1A07377913A1DD
3 changed files with 64 additions and 62 deletions

View file

@ -52,6 +52,54 @@ class FileExplorer extends React.Component<Props & ConnectorState> {
execAfterRender()
}
renderFiles(visibleNodes: VisibleNodes) {
const { nodes } = visibleNodes
const { searchKey, focusedNode } = this.props
const inSearch = searchKey !== ''
if (inSearch && nodes.length === 0) {
return <label className={'no-results'}>No results found.</label>
}
return (
<SizeObserver className={'files'}>
{({ width = 0, height = 0 }) => (
<this.ListV focusedNode={focusedNode} nodes={nodes} height={height} width={width} />
)}
</SizeObserver>
)
}
ListV = React.memo<{
nodes: TreeNode[]
height: number
width: number
focusedNode: TreeNode | null
}>(({ nodes, width, height, focusedNode }) => {
const listRef = React.useRef<List>(null)
React.useEffect(() => {
const { visibleNodes } = this.props
const nodes = visibleNodes && visibleNodes.nodes
if (nodes && focusedNode && listRef.current) {
listRef.current.scrollToItem(nodes.indexOf(focusedNode), 'smart')
}
}, [listRef.current, focusedNode])
return (
<List
ref={listRef}
itemKey={(index, { nodes }) => {
const node = nodes[index]
return node && node.path
}}
itemData={{ nodes }}
itemCount={nodes.length}
itemSize={35}
height={height}
width={width}
>
{this.VirtualNode}
</List>
)
})
VirtualNode = React.memo<ListChildComponentProps>(({ index, style }) => {
const { visibleNodes, onNodeClick } = this.props
if (!visibleNodes) return null
@ -71,37 +119,6 @@ class FileExplorer extends React.Component<Props & ConnectorState> {
)
})
renderFiles(visibleNodes: VisibleNodes) {
const { nodes } = visibleNodes
const { searchKey } = this.props
const inSearch = searchKey !== ''
if (inSearch && nodes.length === 0) {
return <label className={'no-results'}>No results found.</label>
}
return (
<SizeObserver className={'files'}>
{({ width, height }) =>
height &&
width && (
<List
itemKey={(index, { nodes }) => {
const node = nodes[index]
return node && node.path
}}
itemData={{ nodes }}
height={height}
itemCount={nodes.length}
itemSize={35}
width={width}
>
{this.VirtualNode}
</List>
)
}
</SizeObserver>
)
}
private renderActions: Node['props']['renderActions'] = node => {
const { searchKey, searched, goTo } = this.props
return (
@ -135,7 +152,6 @@ class FileExplorer extends React.Component<Props & ConnectorState> {
freeze,
handleKeyDown,
handleSearchKeyChange,
onNodeClick,
toggleShowSettings,
onFocusSearchBar,
searchKey,

View file

@ -16,6 +16,7 @@ export type ConnectorState = {
visibleNodes: VisibleNodes | null
searchKey: string
searched: boolean
focusedNode: TreeNode | null
init: () => void
execAfterRender: () => void
@ -307,14 +308,17 @@ const search: MethodCreator<Props, ConnectorState, [string]> = dispatch => {
const goTo: MethodCreator<Props, ConnectorState, [string[]]> = dispatch => async currentPath => {
await visibleNodesGenerator.search('')
tasksAfterRender.push(() => {
const nodeExpandedTo = visibleNodesGenerator.expandTo(currentPath.join('/'))
if (nodeExpandedTo) {
visibleNodesGenerator.focusNode(nodeExpandedTo)
dispatch.set({
focusedNode: nodeExpandedTo,
})
}
dispatch.call(updateVisibleNodes)
})
dispatch.set({ searchKey: '', searched: false })
const nodeExpandedTo = visibleNodesGenerator.expandTo(currentPath.join('/'))
if (nodeExpandedTo) {
visibleNodesGenerator.focusNode(nodeExpandedTo)
const { nodes } = visibleNodesGenerator.visibleNodes
tasksAfterRender.push(() => DOMHelper.scrollToNodeElement(nodes.indexOf(nodeExpandedTo)))
}
dispatch.call(updateVisibleNodes)
}
const setExpand: MethodCreator<Props, ConnectorState, [TreeNode, boolean]> = dispatch => (
@ -330,8 +334,8 @@ const toggleNodeExpansion: MethodCreator<Props, ConnectorState, [TreeNode, boole
skipScrollToNode,
) => {
visibleNodesGenerator.toggleExpand(node)
dispatch.call(focusNode, node, skipScrollToNode)
tasksAfterRender.push(DOMHelper.focusFileExplorer)
dispatch.call(focusNode, node, skipScrollToNode)
tasksAfterRender.push(DOMHelper.focusFileExplorer)
}
const focusNode: MethodCreator<Props, ConnectorState, [TreeNode | null, boolean]> = dispatch => (
@ -340,12 +344,12 @@ const focusNode: MethodCreator<Props, ConnectorState, [TreeNode | null, boolean]
) =>
dispatch.get(({ visibleNodes }) => {
if (!visibleNodes) return
const { nodes } = visibleNodes
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))
dispatch.set({
focusedNode: node,
})
}
dispatch.call(updateVisibleNodes)
})

View file

@ -118,23 +118,6 @@ function scrollToRepoContent() {
)
}
/**
* scroll to index-th element in the list
*/
function scrollToNodeElement(index: number) {
const nodeElementSelector = '.node-item'
const nodeElements = document.querySelectorAll(nodeElementSelector)
const targetElement = nodeElements[index]
if (targetElement) {
targetElement.scrollIntoView({
behavior: 'smooth',
block: 'center',
})
} else {
raiseError(new Error('cannot find DOM node to scroll to'))
}
}
const pjax = new PJAX({
elements: '.pjax-link',
selectors: ['.repository-content', 'title'],
@ -411,7 +394,6 @@ export default {
insertLogoMountPoint,
markGitakoReadyState,
setBodyIndent,
scrollToNodeElement,
scrollToRepoContent,
mountTopProgressBar,
unmountTopProgressBar,