mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
feat: scroll to node
This commit is contained in:
parent
f768935330
commit
5f6feeff00
3 changed files with 64 additions and 62 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue