-
Notifications
You must be signed in to change notification settings - Fork 14.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into john-bodley-flake8-F8
- Loading branch information
Showing
21 changed files
with
470 additions
and
125 deletions.
There are no files selected for viewing
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import Select from 'react-select'; | ||
|
||
export default class OnPasteSelect extends React.Component { | ||
onPaste(evt) { | ||
if (!this.props.multi) { | ||
return; | ||
} | ||
evt.preventDefault(); | ||
const clipboard = evt.clipboardData.getData('Text'); | ||
if (!clipboard) { | ||
return; | ||
} | ||
const regex = `[${this.props.separator}]+`; | ||
const values = clipboard.split(new RegExp(regex)).map(v => v.trim()); | ||
const validator = this.props.isValidNewOption; | ||
const selected = this.props.value || []; | ||
const existingOptions = {}; | ||
const existing = {}; | ||
this.props.options.forEach((v) => { | ||
existingOptions[v[this.props.valueKey]] = 1; | ||
}); | ||
let options = []; | ||
selected.forEach((v) => { | ||
options.push({ [this.props.labelKey]: v, [this.props.valueKey]: v }); | ||
existing[v] = 1; | ||
}); | ||
options = options.concat(values | ||
.filter((v) => { | ||
const notExists = !existing[v]; | ||
existing[v] = 1; | ||
return notExists && (validator ? validator({ [this.props.labelKey]: v }) : !!v); | ||
}) | ||
.map((v) => { | ||
const opt = { [this.props.labelKey]: v, [this.props.valueKey]: v }; | ||
if (!existingOptions[v]) { | ||
this.props.options.unshift(opt); | ||
} | ||
return opt; | ||
}), | ||
); | ||
if (options.length) { | ||
if (this.props.onChange) { | ||
this.props.onChange(options); | ||
} | ||
} | ||
} | ||
render() { | ||
const SelectComponent = this.props.selectWrap; | ||
const refFunc = (ref) => { | ||
if (this.props.ref) { | ||
this.props.ref(ref); | ||
} | ||
this.pasteInput = ref; | ||
}; | ||
const inputProps = { onPaste: this.onPaste.bind(this) }; | ||
return ( | ||
<SelectComponent | ||
{...this.props} | ||
ref={refFunc} | ||
inputProps={inputProps} | ||
/> | ||
); | ||
} | ||
} | ||
|
||
OnPasteSelect.propTypes = { | ||
separator: PropTypes.string.isRequired, | ||
selectWrap: PropTypes.func.isRequired, | ||
ref: PropTypes.func, | ||
onChange: PropTypes.func.isRequired, | ||
valueKey: PropTypes.string.isRequired, | ||
labelKey: PropTypes.string.isRequired, | ||
options: PropTypes.array, | ||
multi: PropTypes.bool.isRequired, | ||
value: PropTypes.any, | ||
isValidNewOption: PropTypes.func, | ||
}; | ||
OnPasteSelect.defaultProps = { | ||
separator: ',', | ||
selectWrap: Select, | ||
valueKey: 'value', | ||
labelKey: 'label', | ||
options: [], | ||
multi: false, | ||
}; |
56 changes: 56 additions & 0 deletions
56
superset/assets/javascripts/components/VirtualizedRendererWrap.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
export default function VirtualizedRendererWrap(renderer) { | ||
function WrapperRenderer({ | ||
focusedOption, | ||
focusOption, | ||
key, | ||
option, | ||
selectValue, | ||
style, | ||
valueArray, | ||
}) { | ||
if (!option) { | ||
return null; | ||
} | ||
const className = ['VirtualizedSelectOption']; | ||
if (option === focusedOption) { | ||
className.push('VirtualizedSelectFocusedOption'); | ||
} | ||
if (option.disabled) { | ||
className.push('VirtualizedSelectDisabledOption'); | ||
} | ||
if (valueArray && valueArray.indexOf(option) >= 0) { | ||
className.push('VirtualizedSelectSelectedOption'); | ||
} | ||
if (option.className) { | ||
className.push(option.className); | ||
} | ||
const events = option.disabled ? {} : { | ||
onClick: () => selectValue(option), | ||
onMouseEnter: () => focusOption(option), | ||
}; | ||
return ( | ||
<div | ||
className={className.join(' ')} | ||
key={key} | ||
style={Object.assign(option.style || {}, style)} | ||
title={option.title} | ||
{...events} | ||
> | ||
{renderer(option)} | ||
</div> | ||
); | ||
} | ||
WrapperRenderer.propTypes = { | ||
focusedOption: PropTypes.object.isRequired, | ||
focusOption: PropTypes.func.isRequired, | ||
key: PropTypes.string, | ||
option: PropTypes.object, | ||
selectValue: PropTypes.func.isRequired, | ||
style: PropTypes.object, | ||
valueArray: PropTypes.array, | ||
}; | ||
return WrapperRenderer; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 105 additions & 0 deletions
105
superset/assets/spec/javascripts/components/OnPasteSelect_spec.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* eslint-disable no-unused-expressions */ | ||
import React from 'react'; | ||
import sinon from 'sinon'; | ||
import { expect } from 'chai'; | ||
import { shallow } from 'enzyme'; | ||
import { describe, it } from 'mocha'; | ||
import VirtualizedSelect from 'react-virtualized-select'; | ||
import Select, { Creatable } from 'react-select'; | ||
|
||
import OnPasteSelect from '../../../javascripts/components/OnPasteSelect'; | ||
|
||
const defaultProps = { | ||
onChange: sinon.spy(), | ||
multi: true, | ||
isValidNewOption: sinon.spy(s => !!s.label), | ||
value: [], | ||
options: [ | ||
{ value: 'United States', label: 'United States' }, | ||
{ value: 'China', label: 'China' }, | ||
{ value: 'India', label: 'India' }, | ||
{ value: 'Canada', label: 'Canada' }, | ||
{ value: 'Russian Federation', label: 'Russian Federation' }, | ||
{ value: 'Japan', label: 'Japan' }, | ||
{ value: 'Mexico', label: 'Mexico' }, | ||
], | ||
}; | ||
|
||
const defaultEvt = { | ||
preventDefault: sinon.spy(), | ||
clipboardData: { | ||
getData: sinon.spy(() => ' United States, China , India, Canada, '), | ||
}, | ||
}; | ||
|
||
describe('OnPasteSelect', () => { | ||
let wrapper; | ||
let props; | ||
let evt; | ||
let expected; | ||
beforeEach(() => { | ||
props = Object.assign({}, defaultProps); | ||
wrapper = shallow(<OnPasteSelect {...props} />); | ||
evt = Object.assign({}, defaultEvt); | ||
}); | ||
|
||
it('renders the supplied selectWrap component', () => { | ||
const select = wrapper.find(Select); | ||
expect(select).to.have.lengthOf(1); | ||
}); | ||
|
||
it('renders custom selectWrap components', () => { | ||
props.selectWrap = Creatable; | ||
wrapper = shallow(<OnPasteSelect {...props} />); | ||
expect(wrapper.find(Creatable)).to.have.lengthOf(1); | ||
props.selectWrap = VirtualizedSelect; | ||
wrapper = shallow(<OnPasteSelect {...props} />); | ||
expect(wrapper.find(VirtualizedSelect)).to.have.lengthOf(1); | ||
}); | ||
|
||
describe('onPaste', () => { | ||
it('calls onChange with pasted values', () => { | ||
wrapper.instance().onPaste(evt); | ||
expected = props.options.slice(0, 4); | ||
expect(props.onChange.calledWith(expected)).to.be.true; | ||
expect(evt.preventDefault.called).to.be.true; | ||
expect(props.isValidNewOption.callCount).to.equal(5); | ||
}); | ||
|
||
it('calls onChange without any duplicate values and adds new values', () => { | ||
evt.clipboardData.getData = sinon.spy(() => | ||
'China, China, China, China, Mexico, Mexico, Chi na, Mexico, ', | ||
); | ||
expected = [ | ||
props.options[1], | ||
props.options[6], | ||
{ label: 'Chi na', value: 'Chi na' }, | ||
]; | ||
wrapper.instance().onPaste(evt); | ||
expect(props.onChange.calledWith(expected)).to.be.true; | ||
expect(evt.preventDefault.called).to.be.true; | ||
expect(props.isValidNewOption.callCount).to.equal(9); | ||
expect(props.options[0].value).to.equal(expected[2].value); | ||
props.options.splice(0, 1); | ||
}); | ||
|
||
it('calls onChange with currently selected values and new values', () => { | ||
props.value = ['United States', 'Canada', 'Mexico']; | ||
evt.clipboardData.getData = sinon.spy(() => | ||
'United States, Canada, Japan, India', | ||
); | ||
wrapper = shallow(<OnPasteSelect {...props} />); | ||
expected = [ | ||
props.options[0], | ||
props.options[3], | ||
props.options[6], | ||
props.options[5], | ||
props.options[2], | ||
]; | ||
wrapper.instance().onPaste(evt); | ||
expect(props.onChange.calledWith(expected)).to.be.true; | ||
expect(evt.preventDefault.called).to.be.true; | ||
expect(props.isValidNewOption.callCount).to.equal(11); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.