diff --git a/browser/src/Services/DragAndDrop.tsx b/browser/src/Services/DragAndDrop.tsx
index 915643d4dc..baa67a6cfc 100644
--- a/browser/src/Services/DragAndDrop.tsx
+++ b/browser/src/Services/DragAndDrop.tsx
@@ -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
} A react class component
diff --git a/browser/src/Services/Explorer/ExplorerSplit.tsx b/browser/src/Services/Explorer/ExplorerSplit.tsx
index 8bc8fb9122..919c2e46c6 100644
--- a/browser/src/Services/Explorer/ExplorerSplit.tsx
+++ b/browser/src/Services/Explorer/ExplorerSplit.tsx
@@ -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)}
/>
)
diff --git a/browser/src/Services/Explorer/ExplorerView.tsx b/browser/src/Services/Explorer/ExplorerView.tsx
index 31addceef1..2c7a1bb55b 100644
--- a/browser/src/Services/Explorer/ExplorerView.tsx
+++ b/browser/src/Services/Explorer/ExplorerView.tsx
@@ -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"
@@ -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) => {
if (e) {
@@ -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;
@@ -94,12 +93,6 @@ interface ITransitionProps {
updated: boolean
}
-const Transition = ({ children, updated }: ITransitionProps) => (
-
- {children}
-
-)
-
const renameStyles = css`
width: 100%;
background-color: inherit;
@@ -116,7 +109,13 @@ const createStyles = css`
margin-top: 0.2em;
`
-export class NodeView extends React.PureComponent {
+const Transition = ({ children, updated }: ITransitionProps) => (
+
+ {children}
+
+)
+
+export class NodeView extends React.PureComponent {
public moveFileOrFolder = ({ drag, drop }: IMoveNode) => {
this.props.moveFileOrFolder(drag.node, drop.node)
}
@@ -125,15 +124,12 @@ export class NodeView extends React.PureComponent {
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 (
-
+
{renameInProgress ? (
{
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)
@@ -270,13 +266,67 @@ 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) => (
+ props.onClick(node.id)}>
+ props.onClick(node.id)}
+ />
+
+)
-export class ExplorerView extends React.PureComponent {
- public render(): JSX.Element {
+const ExplorerContainer = styled.div`
+ height: 100%;
+ ${enableMouse};
+`
+
+export class ExplorerView extends React.PureComponent {
+ private _list = React.createRef()
+
+ 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, 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 (
@@ -284,43 +334,57 @@ export class ExplorerView extends React.PureComponent {
active={this.props.isActive}
contentsText="Nothing to show here, yet!"
actionButtonText="Open a Folder"
- onClickButton={() => commandManager.executeCommand("workspace.openFolder")}
+ onClickButton={this.openWorkspaceFolder}
/>
)
}
return (
-
+
this.props.onClick(id)}
- render={(selectedId: string) => {
- const nodes = this.props.nodes.map(node => (
- this.props.onClick(node.id)} key={node.id}>
- this.props.onClick(node.id)}
- />
-
- ))
-
+ onSelectionChanged={this.props.onSelectionChanged}
+ render={selectedId => {
return (
-
+
+
+ {measurements => (
+ (
+
+
+
+
+
+ )}
+ />
+ )}
+
+
)
}}
/>
@@ -329,6 +393,19 @@ export class ExplorerView extends React.PureComponent {
}
}
+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,
@@ -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,
}
diff --git a/browser/src/Services/Sidebar/SidebarContentSplit.tsx b/browser/src/Services/Sidebar/SidebarContentSplit.tsx
index 0171097e79..826b7374df 100644
--- a/browser/src/Services/Sidebar/SidebarContentSplit.tsx
+++ b/browser/src/Services/Sidebar/SidebarContentSplit.tsx
@@ -127,8 +127,8 @@ export class SidebarHeaderView extends React.PureComponent(styled.div)`
flex: 1 1 auto;
- overflow-y: auto;
position: relative;
+ height: 100%;
`
export class SidebarContentView extends React.PureComponent<
diff --git a/browser/src/UI/components/SidebarItemView.tsx b/browser/src/UI/components/SidebarItemView.tsx
index 28e80a03d6..1720be2e10 100644
--- a/browser/src/UI/components/SidebarItemView.tsx
+++ b/browser/src/UI/components/SidebarItemView.tsx
@@ -6,34 +6,53 @@
import * as React from "react"
-import { styled, withProps } from "./common"
+import { OniStyledProps, pixel, styled, withProps } from "./common"
import Caret from "./../../UI/components/Caret"
import { Sneakable } from "./../../UI/components/Sneakable"
-export interface ISidebarItemViewProps {
+interface IItemProps {
yanked?: boolean
updated?: boolean
isOver?: boolean
canDrop?: boolean
didDrop?: boolean
- text: string | JSX.Element
isFocused: boolean
isContainer?: boolean
- indentationLevel: number
icon?: JSX.Element
+ text: string | JSX.Element
onClick: (e?: React.MouseEvent) => void
}
-const px = (num: number): string => num.toString() + "px"
+export interface ISidebarItemViewProps extends IItemProps {
+ indentationLevel: number
+}
-const SidebarItemStyleWrapper = withProps(styled.div)`
- padding-left: ${props => px(INDENT_AMOUNT * props.indentationLevel)};
- border-left: ${props =>
- props.isFocused
- ? `4px solid ${props.theme["highlight.mode.normal.background"]}`
- : "4px solid transparent"};
+export interface ISidebarContainerViewProps extends IItemProps {
+ isExpanded: boolean
+ indentationLevel?: number
+}
+
+type SidebarStyleProps = OniStyledProps
+
+const INDENT_AMOUNT = 12
+const getLeftBorder = (props: SidebarStyleProps) => {
+ switch (true) {
+ case props.isFocused:
+ return `4px solid ${props.theme["highlight.mode.normal.background"]}`
+ case !props.isContainer:
+ return "4px solid transparent"
+ case props.isContainer:
+ return `4px solid rgba(0, 0, 0, 0.2)`
+ default:
+ return ""
+ }
+}
+
+const SidebarItemStyleWrapper = withProps(styled.div)`
+ padding-left: ${props => pixel(INDENT_AMOUNT * props.indentationLevel)};
+ border-left: ${getLeftBorder};
${p =>
(p.isOver || p.yanked) &&
`border: 3px solid ${p.theme["highlight.mode.insert.background"]};`};
@@ -44,7 +63,6 @@ const SidebarItemStyleWrapper = withProps(styled.div)`
padding-top: 4px;
padding-bottom: 3px;
position: relative;
-
cursor: pointer;
pointer-events: all;
@@ -63,18 +81,19 @@ const SidebarItemStyleWrapper = withProps(styled.div)`
}
`
+const getSidebarBackground = (props: SidebarStyleProps) => {
+ if (props.isFocused && !props.isContainer) {
+ return props.theme["highlight.mode.normal.background"]
+ } else if (props.isContainer) {
+ return "rgb(0, 0, 0)"
+ } else {
+ return "transparent"
+ }
+}
+
const SidebarItemBackground = withProps(styled.div)`
- background-color: ${props => {
- if (props.isFocused && !props.isContainer) {
- return props.theme["highlight.mode.normal.background"]
- } else if (props.isContainer) {
- return "rgb(0, 0, 0)"
- } else {
- return "transparent"
- }
- }};
+ background-color: ${getSidebarBackground};
opacity: ${props => (props.isContainer || props.isFocused ? "0.2" : "0")};
-
position: absolute;
top: 0px;
left: 0px;
@@ -82,76 +101,38 @@ const SidebarItemBackground = withProps(styled.div)`
bottom: 0px;
`
-const INDENT_AMOUNT = 12
-
-export class SidebarItemView extends React.PureComponent {
- public render(): JSX.Element {
- const icon = this.props.icon ? {this.props.icon}
: null
- return (
-
-
-
- {icon}
- {this.props.text}
-
-
- )
- }
-}
-
-export interface ISidebarContainerViewProps extends IContainerProps {
- yanked?: boolean
- updated?: boolean
- didDrop?: boolean
- text: string
- isExpanded: boolean
- isFocused: boolean
- indentationLevel?: number
- isContainer?: boolean
- onClick: (e: React.MouseEvent) => void
+export const SidebarItemView: React.SFC = props => {
+ const icon = props.icon ? {props.icon}
: null
+ return (
+
+
+
+ {icon}
+ {props.text}
+
+
+ )
}
-
-interface IContainerProps {
- isOver?: boolean
- canDrop?: boolean
- yanked?: boolean
- updated?: boolean
-}
-
-const SidebarContainer = withProps(styled.div)`
- ${p =>
- (p.isOver || p.yanked) &&
- `border: 3px solid ${p.theme["highlight.mode.insert.background"]};`};
-`
-
-export class SidebarContainerView extends React.PureComponent {
- public render(): JSX.Element {
- const indentationlevel = this.props.indentationLevel || 0
-
- return (
-
- }
- text={this.props.text}
- isFocused={this.props.isFocused}
- isContainer={this.props.isContainer}
- onClick={this.props.onClick}
- />
- {this.props.isExpanded ? this.props.children : null}
-
- )
- }
+const SidebarContainer = styled.div``
+
+export const SidebarContainerView: React.SFC = ({
+ indentationLevel = 0,
+ ...props
+}) => {
+ return (
+
+ }
+ text={props.text}
+ isFocused={props.isFocused}
+ isContainer={props.isContainer}
+ onClick={props.onClick}
+ />
+ {props.isExpanded ? props.children : null}
+
+ )
}
diff --git a/browser/src/UI/components/VimNavigator.tsx b/browser/src/UI/components/VimNavigator.tsx
index 93c1f3f809..2fe9e1413e 100644
--- a/browser/src/UI/components/VimNavigator.tsx
+++ b/browser/src/UI/components/VimNavigator.tsx
@@ -18,6 +18,8 @@ import { Event } from "oni-types"
import { KeyboardInputView } from "./../../Input/KeyboardInput"
import { getInstance, IMenuBinding } from "./../../neovim/SharedNeovimInstance"
+import styled from "./../../UI/components/common"
+
import { CallbackCommand, commandManager } from "./../../Services/CommandManager"
export interface IVimNavigatorProps {
@@ -42,6 +44,10 @@ export interface IVimNavigatorState {
selectedId: string
}
+const NavigatorContainer = styled.div`
+ height: 100%;
+`
+
export class VimNavigator extends React.PureComponent {
private _activeBinding: IMenuBinding = null
private _activateEvent = new Event()
@@ -89,9 +95,9 @@ export class VimNavigator extends React.PureComponent
-
+
{this.props.render(this.state.selectedId, this.updateSelection)}
-
+
{this.props.active ? inputElement : null}
)
diff --git a/browser/src/UI/components/common.ts b/browser/src/UI/components/common.ts
index 06546eaa1f..6d3e45d13b 100644
--- a/browser/src/UI/components/common.ts
+++ b/browser/src/UI/components/common.ts
@@ -162,6 +162,9 @@ const fallBackFonts = `
sans-serif
`.trim()
+export type OniThemeProps = ThemeProps
+export type OniStyledProps = OniThemeProps & T
+
export {
css,
injectGlobal,
diff --git a/browser/src/units-css.d.ts b/browser/src/units-css.d.ts
new file mode 100644
index 0000000000..99976aa522
--- /dev/null
+++ b/browser/src/units-css.d.ts
@@ -0,0 +1 @@
+declare module "units-css"
diff --git a/browser/webpack.development.config.js b/browser/webpack.development.config.js
index b5ae20fff1..ab0097e965 100644
--- a/browser/webpack.development.config.js
+++ b/browser/webpack.development.config.js
@@ -1,6 +1,9 @@
var path = require("path")
var webpack = require("webpack")
+const createStyledComponentsTransformer = require("typescript-plugin-styled-components").default
+const styledComponentsTransformer = createStyledComponentsTransformer()
+
module.exports = {
mode: "development",
entry: [path.join(__dirname, "src/index.tsx")],
@@ -48,7 +51,12 @@ module.exports = {
},
{
test: /\.tsx?$/,
- use: "ts-loader",
+ use: {
+ loader: "ts-loader",
+ options: {
+ getCustomTransformers: () => ({ before: [styledComponentsTransformer] }),
+ },
+ },
exclude: /node_modules/,
},
],
diff --git a/package.json b/package.json
index e66300f5b5..c1d59dd758 100644
--- a/package.json
+++ b/package.json
@@ -919,7 +919,7 @@
"@types/react-redux": "5.0.12",
"@types/react-test-renderer": "^16.0.0",
"@types/react-transition-group": "^2.0.11",
- "@types/react-virtualized": "^9.7.10",
+ "@types/react-virtualized": "^9.18.3",
"@types/redux-batched-subscribe": "^0.1.2",
"@types/redux-mock-store": "^1.0.0",
"@types/rimraf": "^2.0.2",
@@ -980,7 +980,7 @@
"react-redux": "5.0.6",
"react-test-renderer": "^16.2.0",
"react-transition-group": "2.2.1",
- "react-virtualized": "^9.18.0",
+ "react-virtualized": "^9.19.1",
"redux": "3.7.2",
"redux-mock-store": "^1.5.3",
"redux-observable": "0.17.0",
@@ -995,6 +995,7 @@
"ts-jest": "^23.0.0",
"ts-loader": "^4.2.0",
"tslint": "5.9.1",
+ "typescript-plugin-styled-components": "^0.0.6",
"vscode-snippet-parser": "0.0.5",
"wcwidth": "1.0.1",
"webdriverio": "4.8.0",
diff --git a/test/ci/PaintPerformanceTest.config.js b/test/ci/PaintPerformanceTest.config.js
index 7b3ea89085..bcced937a4 100644
--- a/test/ci/PaintPerformanceTest.config.js
+++ b/test/ci/PaintPerformanceTest.config.js
@@ -1,9 +1,10 @@
// For more information on customizing Oni,
// check out our wiki page:
// https://github.com/onivim/oni/wiki/Configuration
-
+// tslint:disable
module.exports = {
+ "sidebar.enabled": false,
"statusbar.enabled": false,
"tabs.mode": "hidden",
"ui.animations.enabled": false,
-};
+}
diff --git a/ui-tests/NodeView.test.tsx b/ui-tests/NodeView.test.tsx
index 53f8967613..4cdf0a3222 100644
--- a/ui-tests/NodeView.test.tsx
+++ b/ui-tests/NodeView.test.tsx
@@ -1,4 +1,4 @@
-import { mount, shallow } from "enzyme"
+import { shallow } from "enzyme"
import { shallowToJson } from "enzyme-to-json"
import * as React from "react"
@@ -20,6 +20,8 @@ describe("", () => {
const Node = (
null}
+ isCreating={false}
isRenaming={testNode}
moveFileOrFolder={() => ({})}
node={testNode}
@@ -38,6 +40,8 @@ describe("", () => {
const wrapper = shallow(
null}
+ isCreating={false}
isRenaming={testNode}
moveFileOrFolder={() => ({})}
node={testNode}
diff --git a/ui-tests/__snapshots__/NodeView.test.tsx.snap b/ui-tests/__snapshots__/NodeView.test.tsx.snap
index 186a4e11f7..a593930f55 100644
--- a/ui-tests/__snapshots__/NodeView.test.tsx.snap
+++ b/ui-tests/__snapshots__/NodeView.test.tsx.snap
@@ -1,14 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[` Should match the snapshot 1`] = `
-
+