-
Notifications
You must be signed in to change notification settings - Fork 178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom Wildcard Field UI #722
base: master
Are you sure you want to change the base?
Changes from all commits
52d1cf7
2bacaf4
23df761
5ac40cd
8a42aa8
97e8665
9f73ca6
5c4637f
db4a6b1
fb0e53d
fc6e74e
efd1071
a531af5
d638fc1
5da5a02
30e124f
03c0edc
b62d6e8
deb2c27
32dba89
8e6dec5
3ccc3de
f22c1ae
16466e6
f3b3b6d
8ea9ee7
aef4b0f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,33 @@ | ||
import {ExpandedType} from 'compassql/build/src/query/expandedtype'; | ||
import {PrimitiveType, Schema} from 'compassql/build/src/schema'; | ||
import {isWildcard} from 'compassql/build/src/wildcard'; | ||
import * as stringify from 'json-stable-stringify'; | ||
import {isWildcard, isWildcardDef} from 'compassql/build/src/wildcard'; | ||
import * as React from 'react'; | ||
import * as CSSModules from 'react-css-modules'; | ||
import {connect} from 'react-redux'; | ||
import {OneOfFilter, RangeFilter} from 'vega-lite/build/src/filter'; | ||
import {FilterAction} from '../../actions'; | ||
import {CUSTOM_WILDCARD_ADD_FIELD, CustomWildcardAction} from '../../actions/custom-wildcard-field'; | ||
import {DatasetSchemaChangeFieldType} from '../../actions/dataset'; | ||
import {ActionHandler, createDispatchHandler} from '../../actions/redux-action'; | ||
import {SPEC_FIELD_AUTO_ADD, SpecFieldAutoAdd} from '../../actions/shelf'; | ||
import {FILTER_TOGGLE} from '../../actions/shelf/filter'; | ||
import {FieldParentType} from '../../constants'; | ||
import {CustomWildcardFieldDef} from '../../models/custom-wildcard-field'; | ||
import {State} from '../../models/index'; | ||
import {ShelfFieldDef} from '../../models/shelf'; | ||
import {createDefaultFilter, filterHasField} from '../../models/shelf/filter'; | ||
import {selectPresetWildcardFields, selectSchema, selectSchemaFieldDefs} from '../../selectors'; | ||
import {selectCustomWildcardFieldDefs} from '../../selectors/index'; | ||
import {selectFilters} from '../../selectors/shelf'; | ||
import {Field} from '../field'; | ||
import {DroppableField} from '../field/index'; | ||
import * as styles from './field-list.scss'; | ||
import {TypeChanger} from './type-changer'; | ||
import {CustomWildcardFieldEditor} from './wildcard-field-editor'; | ||
|
||
|
||
export interface FieldListProps extends ActionHandler<SpecFieldAutoAdd | DatasetSchemaChangeFieldType | FilterAction> { | ||
export interface FieldListProps extends ActionHandler< | ||
CustomWildcardAction | SpecFieldAutoAdd | DatasetSchemaChangeFieldType | FilterAction> { | ||
fieldDefs: ShelfFieldDef[]; | ||
schema: Schema; | ||
filters: Array<RangeFilter | OneOfFilter>; | ||
|
@@ -39,20 +44,8 @@ class FieldListBase extends React.PureComponent<FieldListProps, {}> { | |
} | ||
|
||
public render() { | ||
const {fieldDefs, schema} = this.props; | ||
const fieldItems = fieldDefs.map(fieldDef => { | ||
let primitiveType; | ||
if (!isWildcard(fieldDef.field)) { | ||
primitiveType = schema.primitiveType(fieldDef.field); | ||
} | ||
const hideTypeChanger = this.getValidTypes(primitiveType).length < 2; | ||
const key = isWildcard(fieldDef.field) ? stringify(fieldDef) : fieldDef.field; | ||
return ( | ||
<div key={key} styleName="field-list-item"> | ||
{this.renderComponent(fieldDef, hideTypeChanger, primitiveType)} | ||
</div> | ||
); | ||
}); | ||
const {fieldDefs} = this.props; | ||
const fieldItems = fieldDefs.map((fieldDef, index) => this.renderListItem(fieldDef, index)); | ||
return ( | ||
<div styleName='field-list'> | ||
{fieldItems} | ||
|
@@ -87,38 +80,69 @@ class FieldListBase extends React.PureComponent<FieldListProps, {}> { | |
return createDefaultFilter(fieldDef, domain); | ||
} | ||
|
||
private renderComponent(fieldDef: ShelfFieldDef, hideTypeChanger: boolean, primitiveType: PrimitiveType) { | ||
if (hideTypeChanger) { | ||
return this.renderField(fieldDef); | ||
} else { | ||
const popupComponent = this.renderTypeChanger(fieldDef, primitiveType); | ||
return this.renderField(fieldDef, popupComponent); | ||
} | ||
} | ||
private renderListItem(fieldDef: ShelfFieldDef, index: number) { | ||
const {schema, filters, handleAction} = this.props; | ||
|
||
let popupComponent; | ||
const isCustomWildcardField = isWildcardDef(fieldDef.field); | ||
|
||
private renderTypeChanger(fieldDef: ShelfFieldDef, primitiveType: PrimitiveType) { | ||
const {handleAction} = this.props; | ||
if (!isWildcard(fieldDef.field)) { | ||
return ( | ||
<TypeChanger | ||
field={fieldDef.field} | ||
type={fieldDef.type} | ||
validTypes={this.getValidTypes(primitiveType)} | ||
handleAction={handleAction} | ||
/> | ||
); | ||
const primitiveType = schema.primitiveType(fieldDef.field); | ||
|
||
if (this.getValidTypes(primitiveType).length < 2) { | ||
popupComponent = this.renderTypeChanger(fieldDef, primitiveType); | ||
} | ||
} else { | ||
if (isCustomWildcardField) { | ||
popupComponent = ( | ||
<CustomWildcardFieldEditor | ||
customWildcardFielddef={fieldDef as CustomWildcardFieldDef} | ||
index={index} | ||
handleAction={handleAction} | ||
/> | ||
); | ||
} | ||
} | ||
} | ||
|
||
private renderField(fieldDef: ShelfFieldDef, popupComponent?: JSX.Element) { | ||
const {schema, filters} = this.props; | ||
const filter = { | ||
active: !isWildcard(fieldDef.field) && filterHasField(filters, fieldDef.field), | ||
onToggle: this.onFilterToggle | ||
}; | ||
|
||
return ( | ||
<Field | ||
function onDrop(droppedFieldDef: ShelfFieldDef) { | ||
const type = fieldDef.type; | ||
|
||
if (droppedFieldDef.field === '*') { | ||
window.alert('Cannot add COUNT'); | ||
} else if (type === droppedFieldDef.type) { | ||
let fields: string[]; | ||
if (isWildcard(droppedFieldDef.field)) { | ||
if (droppedFieldDef.field === '?') { | ||
fields = schema.fieldNames() | ||
.filter(field => schema.vlType(field) === type); | ||
} else { | ||
fields = droppedFieldDef.field.enum.concat([]); | ||
} | ||
} else { | ||
fields = [droppedFieldDef.field]; | ||
} | ||
|
||
handleAction({ | ||
type: CUSTOM_WILDCARD_ADD_FIELD, | ||
payload: { | ||
fields, | ||
index | ||
} | ||
}); | ||
} else { | ||
window.alert('Cannot create a wildcard that mixes multiple types'); | ||
} | ||
} | ||
|
||
const FieldComponent = isCustomWildcardField ? DroppableField : Field; | ||
|
||
const field = ( | ||
<FieldComponent | ||
fieldDef={fieldDef} | ||
isPill={true} | ||
draggable={true} | ||
|
@@ -128,9 +152,30 @@ class FieldListBase extends React.PureComponent<FieldListProps, {}> { | |
popupComponent={popupComponent} | ||
onDoubleClick={this.onAdd} | ||
onAdd={this.onAdd} | ||
onDrop={isCustomWildcardField ? onDrop : undefined} | ||
schema={schema} | ||
/> | ||
); | ||
|
||
return ( | ||
<div key={index} styleName="field-list-item"> | ||
{field} | ||
</div> | ||
); | ||
} | ||
|
||
private renderTypeChanger(fieldDef: ShelfFieldDef, primitiveType: PrimitiveType) { | ||
const {handleAction} = this.props; | ||
if (!isWildcard(fieldDef.field)) { | ||
return ( | ||
<TypeChanger | ||
field={fieldDef.field} | ||
type={fieldDef.type} | ||
validTypes={this.getValidTypes(primitiveType)} | ||
handleAction={handleAction} | ||
/> | ||
); | ||
} | ||
} | ||
|
||
private getValidTypes(primitiveType: PrimitiveType): ExpandedType[] { | ||
|
@@ -170,8 +215,22 @@ export const PresetWildcardFieldList = connect( | |
(state: State) => { | ||
return { | ||
fieldDefs: selectPresetWildcardFields(state), | ||
schema: selectSchema(state) | ||
schema: selectSchema(state), | ||
filters: selectFilters(state) | ||
}; | ||
}, | ||
createDispatchHandler<SpecFieldAutoAdd>() | ||
)(FieldListRenderer); | ||
|
||
|
||
export const CustomWildcardFieldList = connect( | ||
(state: State) => { | ||
return { | ||
// Somehow TS does not infer type that CustomWildcardFieldDefs can be a ShelfFieldDef | ||
fieldDefs: selectCustomWildcardFieldDefs(state) as ShelfFieldDef[], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are so too casting in this PR -- I doubt if we really need all of them. Can you see if upgrading to the latest TS allow you to eliminate a lot of these |
||
schema: selectSchema(state), | ||
filters: selectFilters(state) | ||
}; | ||
}, | ||
createDispatchHandler<SpecFieldAutoAdd | CustomWildcardAction>() | ||
)(FieldListRenderer); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
.drop-zone { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
color: white; | ||
height: 24px; | ||
text-align: center; | ||
padding: 5px; | ||
} | ||
|
||
.drop-zone-is-over { | ||
@extend .drop-zone; | ||
border: 1px dashed #4CAF50; | ||
} | ||
|
||
.drop-zone-can-drop { | ||
@extend .drop-zone; | ||
font-size: 11px; | ||
border: 1px dashed #8ebdc7; | ||
padding: 0 .5em; | ||
background-color: rgba(187,243,255,.9); | ||
height: 20px; | ||
line-height: 20px; | ||
box-sizing: border-box; | ||
color: rgb(0, 0, 0); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why concat empty string?