Skip to content
This repository has been archived by the owner on Apr 1, 2020. It is now read-only.

Feature/ switch explorer rendering to use react-virtualized #2182

Merged
merged 45 commits into from
Sep 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
409a05f
experiment with changing scrollIntoView fn in explorer
akinsho May 6, 2018
cabeea2
switch to using react virtualized to render explorer
akinsho May 6, 2018
201ab77
add react virtualised list and typescript Styled components plugin
akinsho May 7, 2018
76194e2
add missing style item to rowrenderer which fixes scrolling
akinsho May 7, 2018
ede1fed
add key as per docs example
akinsho May 7, 2018
751ac8f
update snapshot test for explorer
akinsho May 7, 2018
2042cdc
add ability to dynamically calculate row with inputs
akinsho May 7, 2018
0aabd00
remove overscan rows
akinsho May 7, 2018
f8f7004
re-add overscan re-trigger build
akinsho May 8, 2018
8b29760
use ui.fontSize for row sizing
akinsho May 9, 2018
0e8d707
merge upstream fix conflicts
akinsho May 9, 2018
2e9fc5d
Merge branch 'master' of https://github.com/onivim/oni into bugfix/fi…
May 13, 2018
04414cf
destructure vars (primarily to trigger re-test)
akinsho May 19, 2018
53a4248
Merge branch 'master' of https://github.com/onivim/oni into bugfix/fi…
akinsho May 19, 2018
41a7e95
switch scroll to alignment to end as center causes scroll lag
akinsho May 24, 2018
f01a470
Merge branch 'master' of https://github.com/onivim/oni into bugfix/fi…
akinsho Jun 2, 2018
f2db877
add font size in px function switch to cell measurer
akinsho Jun 3, 2018
35ea788
add the definition file for units-css
akinsho Jun 3, 2018
f67e540
update jest mocks
akinsho Jun 3, 2018
19a14ca
remove shadowed variable in explorer
akinsho Jun 3, 2018
774ad45
update react-virtualized and its types to fix type errors
akinsho Jun 3, 2018
ff509f5
prettier lint fix for performance config
akinsho Jun 4, 2018
53ac043
lint fix
akinsho Jun 4, 2018
ee0352d
Merge branch 'master' of https://github.com/onivim/oni into bugfix/fi…
akinsho Jun 7, 2018
5f9f06f
merge upstream
akinsho Jun 7, 2018
afc992f
fix double border in sidebar item component
akinsho Jun 7, 2018
02f1527
separate should measure calculation into function
akinsho Jun 13, 2018
484265f
restructure comparison function to be more readable and work hopefully
akinsho Jun 13, 2018
10804e7
Merge branch 'master' of https://github.com/onivim/oni into bugfix/fi…
akinsho Jun 13, 2018
4d72e95
use cache clear and recompute row height for list resizing
akinsho Jun 13, 2018
2d45ad1
add is or was selected to props checked component did update check
akinsho Jun 19, 2018
ed1adbb
Merge branch 'master' of https://github.com/onivim/oni into bugfix/fi…
akinsho Jul 15, 2018
8f43e82
remove outline from List, separate out should measure
akinsho Jul 16, 2018
6396751
update PR fix merge conflicts
akinsho Aug 21, 2018
b525705
switch to using _oni in explorer split
akinsho Aug 21, 2018
8dafaf7
prettier fix
akinsho Aug 21, 2018
c86e017
fix missing space on left edge of container component
akinsho Aug 21, 2018
6bb0a71
remove unnecessary type annotation
akinsho Aug 21, 2018
098a198
use pixel function from common in sidebar items
akinsho Aug 21, 2018
68f0b66
fix lint error
akinsho Aug 21, 2018
4860c58
merge upstream fix conflicts
akinsho Aug 31, 2018
e879d1d
remove unnecessary dependency and associated utility
akinsho Aug 31, 2018
d11a642
fix lint error
akinsho Aug 31, 2018
1591236
remove references to unused fontsize prop
akinsho Aug 31, 2018
fa30106
remove measurement from node entirely
akinsho Aug 31, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion browser/src/Services/DragAndDrop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const DragCollect = (connect: DND.DragSourceConnector, monitor: DND.DragSourceMo
*
* @name props
* @function
* @param {String | String[]} >props.target The target Type that responds to the drop
* @param {String | String[]} props.target The target Type that responds to the drop
* @param {Object} DragSource Object with a beginDrag which return the dragged props
* @param {React.Component} A component which is dragged onto another
* @returns {React.Component<P>} A react class component
Expand Down
2 changes: 1 addition & 1 deletion browser/src/Services/Explorer/ExplorerSplit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ export class ExplorerSplit {
onCompleteCreate={this._completeCreation}
onCompleteRename={this._completeRename}
onCancelRename={this._cancelRename}
onSelectionChanged={id => this._onSelectionChanged(id)}
onClick={id => this._onOpenItem(id)}
moveFileOrFolder={this.moveFileOrFolder}
onSelectionChanged={id => this._onSelectionChanged(id)}
/>
</Provider>
)
Expand Down
205 changes: 135 additions & 70 deletions browser/src/Services/Explorer/ExplorerView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@ import * as React from "react"
import * as DND from "react-dnd"
import HTML5Backend from "react-dnd-html5-backend"
import { connect } from "react-redux"
import { AutoSizer, CellMeasurer, CellMeasurerCache, List } from "react-virtualized"
import { compose } from "redux"

import { CSSTransition, TransitionGroup } from "react-transition-group"

import { css, styled } from "./../../UI/components/common"
import { css, enableMouse, styled } from "./../../UI/components/common"
import { TextInputView } from "./../../UI/components/LightweightText"
import { SidebarEmptyPaneView } from "./../../UI/components/SidebarEmptyPaneView"
import { SidebarContainerView, SidebarItemView } from "./../../UI/components/SidebarItemView"
import { Sneakable } from "./../../UI/components/Sneakable"
import { VimNavigator } from "./../../UI/components/VimNavigator"
import { DragAndDrop, Droppeable } from "./../DragAndDrop"

import { commandManager } from "./../CommandManager"
import { FileIcon } from "./../FileIcon"

import * as ExplorerSelectors from "./ExplorerSelectors"
Expand All @@ -38,20 +41,9 @@ export interface INodeViewProps {
updated?: string[]
isRenaming: Node
isCreating: boolean
children?: React.ReactNode
}

export const NodeWrapper = styled.div`
&:hover {
text-decoration: underline;
}
`

// tslint:disable-next-line
const noop = (elem: HTMLElement) => {}
const scrollIntoViewIfNeeded = (elem: HTMLElement) => {
// tslint:disable-next-line
elem && elem["scrollIntoViewIfNeeded"] && elem["scrollIntoViewIfNeeded"]()
}
const stopPropagation = (fn: () => void) => {
return (e?: React.MouseEvent<HTMLElement>) => {
if (e) {
Expand All @@ -75,6 +67,13 @@ interface IMoveNode {
}
}

export const NodeWrapper = styled.div`
cursor: pointer;
&:hover {
text-decoration: underline;
}
`

const NodeTransitionWrapper = styled.div`
transition: all 400ms 50ms ease-in-out;

Expand All @@ -94,12 +93,6 @@ interface ITransitionProps {
updated: boolean
}

const Transition = ({ children, updated }: ITransitionProps) => (
<CSSTransition in={updated} classNames="move" timeout={1000}>
<NodeTransitionWrapper className={updated && "move"}>{children}</NodeTransitionWrapper>
</CSSTransition>
)

const renameStyles = css`
width: 100%;
background-color: inherit;
Expand All @@ -116,7 +109,13 @@ const createStyles = css`
margin-top: 0.2em;
`

export class NodeView extends React.PureComponent<INodeViewProps, {}> {
const Transition = ({ children, updated }: ITransitionProps) => (
<CSSTransition in={updated} classNames="move" timeout={1000}>
<NodeTransitionWrapper className={updated && "move"}>{children}</NodeTransitionWrapper>
</CSSTransition>
)

export class NodeView extends React.PureComponent<INodeViewProps> {
public moveFileOrFolder = ({ drag, drop }: IMoveNode) => {
this.props.moveFileOrFolder(drag.node, drop.node)
}
Expand All @@ -125,15 +124,12 @@ export class NodeView extends React.PureComponent<INodeViewProps, {}> {
return !(drag.node.name === drop.node.name)
}

public render(): JSX.Element {
public render() {
const { isCreating, isRenaming, isSelected, node } = this.props
const renameInProgress = isRenaming.name === node.name && isSelected && !isCreating
const creationInProgress = isCreating && isSelected && !renameInProgress
return (
<NodeWrapper
style={{ cursor: "pointer" }}
innerRef={this.props.isSelected ? scrollIntoViewIfNeeded : noop}
>
<NodeWrapper>
{renameInProgress ? (
<TextInputView
styles={renameStyles}
Expand All @@ -159,7 +155,7 @@ export class NodeView extends React.PureComponent<INodeViewProps, {}> {
public hasUpdated = (path: string) =>
!!this.props.updated && this.props.updated.some(nodePath => nodePath === path)

public getElement(): JSX.Element {
public getElement() {
const { node } = this.props
const yanked = this.props.yanked.includes(node.id)

Expand Down Expand Up @@ -270,57 +266,125 @@ export interface IExplorerViewProps extends IExplorerViewContainerProps {
idToSelect: string
}

import { SidebarEmptyPaneView } from "./../../UI/components/SidebarEmptyPaneView"
interface ISneakableNode extends IExplorerViewProps {
node: Node
selectedId: string
}

import { commandManager } from "./../CommandManager"
const SneakableNode = ({ node, selectedId, ...props }: ISneakableNode) => (
<Sneakable callback={() => props.onClick(node.id)}>
<NodeView
node={node}
isSelected={node.id === selectedId}
isCreating={props.isCreating}
onCancelCreate={props.onCancelCreate}
onCompleteCreate={props.onCompleteCreate}
onCompleteRename={props.onCompleteRename}
isRenaming={props.isRenaming}
onCancelRename={props.onCancelRename}
updated={props.updated}
yanked={props.yanked}
moveFileOrFolder={props.moveFileOrFolder}
onClick={() => props.onClick(node.id)}
/>
</Sneakable>
)

export class ExplorerView extends React.PureComponent<IExplorerViewProps, {}> {
public render(): JSX.Element {
const ExplorerContainer = styled.div`
height: 100%;
${enableMouse};
`

export class ExplorerView extends React.PureComponent<IExplorerViewProps> {
private _list = React.createRef<List>()

private _cache = new CellMeasurerCache({
defaultHeight: 30,
fixedWidth: true,
})

public openWorkspaceFolder = () => {
commandManager.executeCommand("workspace.openFolder")
}

public getSelectedNode = (selectedId: string) => {
return this.props.nodes.findIndex(n => selectedId === n.id)
}

public propsChanged(keys: Array<keyof IExplorerViewProps>, prevProps: IExplorerViewProps) {
return keys.some(prop => this.props[prop] !== prevProps[prop])
}

public componentDidUpdate(prevProps: IExplorerViewProps) {
if (this.propsChanged(["isCreating", "isRenaming", "yanked"], prevProps)) {
// TODO: if we could determine which nodes actually were involved
// in the change this could potentially be optimised
this._cache.clearAll()
this._list.current.recomputeRowHeights()
}
}

public render() {
const ids = this.props.nodes.map(node => node.id)
const isActive = this.props.isActive && !this.props.isRenaming && !this.props.isCreating

if (!this.props.nodes || !this.props.nodes.length) {
return (
<SidebarEmptyPaneView
active={this.props.isActive}
contentsText="Nothing to show here, yet!"
actionButtonText="Open a Folder"
onClickButton={() => commandManager.executeCommand("workspace.openFolder")}
onClickButton={this.openWorkspaceFolder}
/>
)
}

return (
<TransitionGroup>
<TransitionGroup style={{ height: "100%" }}>
<VimNavigator
ids={ids}
active={this.props.isActive && !this.props.isRenaming && !this.props.isCreating}
active={isActive}
style={{ height: "100%" }}
idToSelect={this.props.idToSelect}
onSelectionChanged={this.props.onSelectionChanged}
onSelected={id => this.props.onClick(id)}
render={(selectedId: string) => {
const nodes = this.props.nodes.map(node => (
<Sneakable callback={() => this.props.onClick(node.id)} key={node.id}>
<NodeView
node={node}
isSelected={node.id === selectedId}
isCreating={this.props.isCreating}
onCancelCreate={this.props.onCancelCreate}
onCompleteCreate={this.props.onCompleteCreate}
onCompleteRename={this.props.onCompleteRename}
isRenaming={this.props.isRenaming}
onCancelRename={this.props.onCancelRename}
updated={this.props.updated}
yanked={this.props.yanked}
moveFileOrFolder={this.props.moveFileOrFolder}
onClick={() => this.props.onClick(node.id)}
/>
</Sneakable>
))

onSelectionChanged={this.props.onSelectionChanged}
render={selectedId => {
return (
<div className="explorer enable-mouse">
<div className="items">{nodes}</div>
</div>
<ExplorerContainer className="explorer">
<AutoSizer>
{measurements => (
<List
{...measurements}
ref={this._list}
scrollToAlignment="end"
style={{ outline: "none" }}
rowCount={this.props.nodes.length}
rowHeight={this._cache.rowHeight}
scrollToIndex={this.getSelectedNode(selectedId)}
rowRenderer={({ index, style, key, parent }) => (
<CellMeasurer
key={key}
cache={this._cache}
columnIndex={0}
parent={parent}
rowIndex={index}
>
<div
style={style}
key={this.props.nodes[index].id}
>
<SneakableNode
{...this.props}
selectedId={selectedId}
node={this.props.nodes[index]}
/>
</div>
</CellMeasurer>
)}
/>
)}
</AutoSizer>
</ExplorerContainer>
)
}}
/>
Expand All @@ -329,6 +393,19 @@ export class ExplorerView extends React.PureComponent<IExplorerViewProps, {}> {
}
}

const getIdToSelect = (fileToSelect: string, nodes: ExplorerSelectors.ExplorerNode[]) => {
// If parent has told us to select a file, attempt to convert the file path into a node ID.
if (fileToSelect) {
const [nodeToSelect] = nodes.filter(node => {
const nodePath = getPathForNode(node)
return nodePath === fileToSelect
})

return nodeToSelect ? nodeToSelect.id : null
}
return null
}

const mapStateToProps = (
state: IExplorerState,
containerProps: IExplorerViewContainerProps,
Expand All @@ -341,25 +418,13 @@ const mapStateToProps = (

const nodes: ExplorerSelectors.ExplorerNode[] = ExplorerSelectors.mapStateToNodeList(state)

let idToSelect: string = null
// If parent has told us to select a file, attempt to convert the file path into a node ID.
if (fileToSelect) {
const [nodeToSelect] = nodes.filter((node: ExplorerSelectors.ExplorerNode) => {
const nodePath: string = getPathForNode(node)
return nodePath === fileToSelect
})
if (nodeToSelect) {
idToSelect = nodeToSelect.id
}
}

return {
...containerProps,
isActive: state.hasFocus,
nodes,
updated,
yanked,
idToSelect,
idToSelect: getIdToSelect(fileToSelect, nodes),
isCreating: state.register.create.active,
isRenaming: rename.active && rename.target,
}
Expand Down
2 changes: 1 addition & 1 deletion browser/src/Services/Sidebar/SidebarContentSplit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ export class SidebarHeaderView extends React.PureComponent<ISidebarHeaderProps,

export const SidebarInnerPaneWrapper = withProps<{}>(styled.div)`
flex: 1 1 auto;
overflow-y: auto;
position: relative;
height: 100%;
`

export class SidebarContentView extends React.PureComponent<
Expand Down
Loading