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

Commit

Permalink
Enable use of middle click to close tabs (#2190)
Browse files Browse the repository at this point in the history
* Add middle click tab closing (#2069)

by adding a onMouseDown directive to both tab file icon and
tab name and checking the resulting React.MouseEvent for a
middle click

* Fix unused imports and whitespace in Tabs ui test (#2069)

* Update Tabs component test snapshot (#2069)

* Fix Tabs ui test to correctly retrieve children (#2069)

* Differentiate between single und multiple tabs in test (#2069)

* Use mousedown event for tab selection and closing (#2069)

by middle clicking and let the tab itself handle the logic
for it by checking React.MouseEvent.button value

* Update classnames dependency to lastest master branch

and write own @types module declaration so that it can be used in
testing nstead of using a mock. The mock does not suffice as the
way the actual module previously had to be imported was
"import * as classNames" where classNames was the actual function.

It is not possible to build a module in typescript/es6 imports, which
will directly return a function in the same way the dependency did
in commonjs module syntax. Instead when defining a function to be
returned as default export it is returned as an Object like this
"{ default: [Function] }", which is correctly resolved when importing
with "import classNames from 'classnames'".

This only previously worked in production as webpacks ts-loader,
handles this issue, whereas when testing the sources are only compiled
with tsc.

There is an update to the classnames dependency on the current master,
but there hasn't been a release since 2006. So options were to setup
webpack for tests as well or add updated classnames dependency which
sets a "default" value on its commonjs exports.

Links for reference:
JedWatson/classnames#152
JedWatson/classnames#106
DefinitelyTyped/DefinitelyTyped#25206
microsoft/TypeScript#2719

* Fix tab click onMouseDown callback

* Test tab clicks to select/close trigger callbacks

* Mock child react components directly to test smaller unit

* Reset calls to callback mocks in each test case

* Add tests for tabs interaction with FileIcon/Sneakable
  • Loading branch information
texhnolyze authored and bryphe committed May 16, 2018
1 parent 0ce4fb1 commit 94d6cbf
Show file tree
Hide file tree
Showing 11 changed files with 249 additions and 85 deletions.
20 changes: 20 additions & 0 deletions @types/classnames/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Custom type definitions for classnames master branch
// Project: https://github.com/JedWatson/classnames

declare module "classnames" {
type ClassValue = string | number | ClassDictionary | ClassArray | undefined | null | false

interface ClassDictionary {
[id: string]: boolean | undefined | null
}

// This is the only way I found to break circular references between ClassArray and ClassValue
// https://github.com/Microsoft/TypeScript/issues/3496#issuecomment-128553540
interface ClassArray extends Array<ClassValue> {} // tslint:disable-line no-empty-interface

type ClassNamesFn = (...classes: ClassValue[]) => string

const classNames: ClassNamesFn

export default classNames
}
70 changes: 36 additions & 34 deletions browser/src/UI/components/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as path from "path"
import * as React from "react"
import { connect } from "react-redux"

import * as classNames from "classnames"
import classNames from "classnames"
import { keyframes } from "styled-components"

import * as BufferSelectors from "./../../Editor/NeovimEditor/NeovimEditorSelectors"
Expand Down Expand Up @@ -40,8 +40,8 @@ export interface ITabContainerProps {
}

export interface ITabsProps {
onSelect?: (id: number) => void
onClose?: (id: number) => void
onTabSelect?: (id: number) => void
onTabClose?: (id: number) => void

visible: boolean
tabs: ITabProps[]
Expand All @@ -65,16 +65,6 @@ const InnerName = styled.span`
`

export class Tabs extends React.PureComponent<ITabsProps, {}> {
private _boundOnSelect: (tabId: number) => void
private _boundOnClose: (tabId: number) => void

constructor(props: ITabsProps) {
super(props)

this._boundOnSelect = (id: number) => this._onSelect(id)
this._boundOnClose = (id: number) => this._onClickClose(id)
}

public render(): JSX.Element {
if (!this.props.visible) {
return null
Expand All @@ -98,8 +88,8 @@ export class Tabs extends React.PureComponent<ITabsProps, {}> {
<Tab
key={t.id}
{...t}
onClickName={this._boundOnSelect}
onClickClose={this._boundOnClose}
onSelect={this.props.onTabSelect}
onClose={this.props.onTabClose}
backgroundColor={this.props.backgroundColor}
foregroundColor={this.props.foregroundColor}
height={this.props.height}
Expand All @@ -114,19 +104,11 @@ export class Tabs extends React.PureComponent<ITabsProps, {}> {
</div>
)
}

private _onSelect(id: number): void {
this.props.onSelect(id)
}

private _onClickClose(id: number): void {
this.props.onClose(id)
}
}

export interface ITabPropsWithClick extends ITabProps {
onClickName: (id: number) => void
onClickClose: (id: number) => void
onSelect: (id: number) => void
onClose: (id: number) => void

backgroundColor: string
foregroundColor: string
Expand Down Expand Up @@ -175,28 +157,28 @@ export class Tab extends React.PureComponent<ITabPropsWithClick> {
borderTop: "2px solid " + this.props.highlightColor,
}

const handleTitleClick = this._handleTitleClick.bind(this)
const handleCloseButtonClick = this._handleCloseButtonClick.bind(this)

return (
<Sneakable callback={() => this.props.onClickName(this.props.id)} tag={this.props.name}>
<Sneakable callback={() => this.props.onSelect(this.props.id)} tag={this.props.name}>
<TabWrapper
innerRef={(e: IChromeDivElement) => (this._tab = e)}
className={cssClasses}
title={this.props.description}
style={style}
>
<div className="corner" onClick={() => this.props.onClickName(this.props.id)}>
<div className="corner" onMouseDown={handleTitleClick}>
<FileIcon
fileName={this.props.iconFileName}
isLarge={true}
playAppearAnimation={true}
/>
</div>
<div className="name" onClick={() => this.props.onClickName(this.props.id)}>
<div className="name" onMouseDown={handleTitleClick}>
<InnerName>{this.props.name}</InnerName>
</div>
<div
className="corner enable-hover"
onClick={() => this.props.onClickClose(this.props.id)}
>
<div className="corner enable-hover" onClick={handleCloseButtonClick}>
<div className="icon-container x-icon-container">
<Icon name="times" />
</div>
Expand All @@ -220,6 +202,26 @@ export class Tab extends React.PureComponent<ITabPropsWithClick> {
}
}
}

private _handleTitleClick(event: React.MouseEvent<HTMLElement>): void {
if (this._isLeftClick(event)) {
this.props.onSelect(this.props.id)
} else if (this._isMiddleClick(event)) {
this.props.onClose(this.props.id)
}
}

private _handleCloseButtonClick(): void {
this.props.onClose(this.props.id)
}

private _isMiddleClick(event: React.MouseEvent<HTMLElement>): boolean {
return event.button === 1
}

private _isLeftClick(event: React.MouseEvent<HTMLElement>): boolean {
return event.button === 0
}
}

export const getTabName = (name: string, isDuplicate?: boolean): string => {
Expand Down Expand Up @@ -365,8 +367,8 @@ const mapStateToProps = (state: State.IState, ownProps: ITabContainerProps): ITa
fontSize: addDefaultUnitIfNeeded(state.configuration["ui.fontSize"]),
backgroundColor: state.colors["tabs.background"],
foregroundColor: state.colors["tabs.foreground"],
onSelect: selectFunc,
onClose: closeFunc,
onTabSelect: selectFunc,
onTabClose: closeFunc,
height,
maxWidth,
shouldWrap,
Expand Down
2 changes: 1 addition & 1 deletion browser/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@
"sourceMap": true,
"types": ["mocha", "webgl2"]
},
"include": ["src/**/*.ts", "test/**/*.ts"],
"include": ["../@types/**/*.d.ts", "src/**/*.ts", "test/**/*.ts"],
"exclude": ["node_modules"]
}
2 changes: 1 addition & 1 deletion browser/tsconfig.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@
"sourceMap": true,
"types": ["mocha", "webgl2"]
},
"include": ["src/**/*.ts", "src/**/*.tsx", "test/**/*.ts"],
"include": ["../@types/**/*.d.ts", "src/**/*.ts", "src/**/*.tsx", "test/**/*.ts"],
"exclude": ["node_modules"]
}
1 change: 0 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ module.exports = {
PersistentSettings: "<rootDir>/ui-tests/mocks/PersistentSettings.ts",
Utility: "<rootDir>/ui-tests/mocks/Utility.ts",
Configuration: "<rootDir>/ui-tests/mocks/Configuration.ts",
classnames: "<rootDir>/ui-tests/mocks/classnames.ts",
KeyboardLayout: "<rootDir>/ui-tests/mocks/keyboardLayout.ts",
},
snapshotSerializers: ["enzyme-to-json/serializer"],
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,6 @@
},
"devDependencies": {
"@types/chokidar": "^1.7.5",
"@types/classnames": "0.0.32",
"@types/color": "2.0.0",
"@types/detect-indent": "^5.0.0",
"@types/dompurify": "^0.0.31",
Expand Down Expand Up @@ -759,7 +758,7 @@
"babel-minify-webpack-plugin": "^0.3.1",
"babel-plugin-dynamic-import-node": "^1.2.0",
"bs-platform": "2.1.0",
"classnames": "2.2.5",
"classnames": "JedWatson/classnames",
"codecov": "^3.0.0",
"color": "2.0.0",
"concurrently": "3.1.0",
Expand Down
Loading

0 comments on commit 94d6cbf

Please sign in to comment.