Skip to content

Commit

Permalink
Merge pull request #885 from getguesstimate/master
Browse files Browse the repository at this point in the history
10/05 Release
  • Loading branch information
mmcdermott authored Oct 6, 2016
2 parents e75c840 + 116bf8c commit 07044c8
Show file tree
Hide file tree
Showing 62 changed files with 1,716 additions and 407 deletions.
5 changes: 3 additions & 2 deletions src/components/distributions/editor/TextForm/TextForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ export class TextForm extends Component{
}

_switchMetricClickMode() {
if (this.props.guesstimate.guesstimateType === 'FUNCTION') {this.props.onChangeClickMode('FUNCTION_INPUT_SELECT')}
this.props.onFocus()
if (this.props.guesstimate.guesstimateType === 'FUNCTION') {
this.props.onChangeClickMode('FUNCTION_INPUT_SELECT')
}
}

_handleBlur() {
Expand Down
32 changes: 25 additions & 7 deletions src/components/distributions/editor/TextForm/TextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {or} from 'gEngine/utils'

import {isData, formatData} from 'lib/guesstimator/formatter/formatters/Data'

function findWithRegex(regex, contentBlock, callback) {
function findWithRegex(baseRegex, contentBlock, callback) {
const text = contentBlock.getText()
const regex = new RegExp(baseRegex.source, 'g')
let matchArr, start
while ((matchArr = regex.exec(text)) !== null) {
start = matchArr.index
Expand Down Expand Up @@ -46,7 +47,10 @@ export class TextInput extends Component{
value: PropTypes.string,
}

factRegex() { return this.props.canUseOrganizationFacts ? HANDLE_REGEX : GLOBALS_ONLY_REGEX }
factRegex() {
const baseRegex = this.props.canUseOrganizationFacts ? HANDLE_REGEX : GLOBALS_ONLY_REGEX
return new RegExp(baseRegex, 'g') // We always want a fresh, global regex.
}

decoratorList(extraDecorators=[]) {
const {validInputs, errorInputs} = this.props
Expand Down Expand Up @@ -108,6 +112,9 @@ export class TextInput extends Component{
deleteOldSuggestion(oldSuggestion) {
const freshEditorState = this.addText('', true, oldSuggestion.length)
this.setState({editorState: this.stripExtraDecorators(freshEditorState)})

const text = this.text(freshEditorState)
if (text !== this.props.value) { this.props.onChange(text) }
}

addSuggestion() {
Expand Down Expand Up @@ -179,9 +186,16 @@ export class TextInput extends Component{
}
}

acceptSuggestionIfAppropriate() {
if (!_.isEmpty(this.props.suggestion) && this.nextWord() === this.props.suggestion) {
this.acceptSuggestion()
return true
}
return false
}

handleTab(e){
if (!_.isEmpty(this.props.suggestion) && this.nextWord() === this.props.suggestion) { this.acceptSuggestion() }
else { this.props.onTab(e.shiftKey) }
if (!this.acceptSuggestionIfAppropriate()) { this.props.onTab(e.shiftKey) }
e.preventDefault()
}

Expand All @@ -202,6 +216,11 @@ export class TextInput extends Component{
this.props.onBlur()
}

handleReturn(e) {
if (!this.acceptSuggestionIfAppropriate()) { this.props.onReturn(e.shiftKey) }
return 'handled'
}

render() {
const [{hasErrors, width, value, validInputs}, {editorState}] = [this.props, this.state]
const className = `TextInput ${width}` + (_.isEmpty(value) && hasErrors ? ' hasErrors' : '')
Expand All @@ -210,13 +229,12 @@ export class TextInput extends Component{
className={className}
onClick={this.focus.bind(this)}
onKeyDown={e => {e.stopPropagation()}}
onFocus={this.handleFocus.bind(this)}
>
<Editor
onFocus={this.props.onFocus}
onFocus={this.handleFocus.bind(this)}
onEscape={this.props.onEscape}
editorState={editorState}
handleReturn={e => this.props.onReturn(e.shiftKey)}
handleReturn={this.handleReturn.bind(this)}
handlePastedText={this.handlePastedText.bind(this)}
onTab={this.handleTab.bind(this)}
onBlur={this.handleBlur.bind(this)}
Expand Down
6 changes: 4 additions & 2 deletions src/components/distributions/editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default class Guesstimate extends Component{
changeGuesstimate: PropTypes.func.isRequired,
runFormSimulations: PropTypes.func.isRequired,
changeMetricClickMode: PropTypes.func.isRequired,
metricClickMode: PropTypes.string,
guesstimate: PropTypes.object,
metricId: PropTypes.string.isRequired,
metricFocus: PropTypes.func.isRequired,
Expand Down Expand Up @@ -69,7 +70,9 @@ export default class Guesstimate extends Component{
this.changeGuesstimate({}, false, true)
}

_changeMetricClickMode(newMode) { this.props.changeMetricClickMode(newMode) }
_changeMetricClickMode(newMode) {
if (this.props.metricClickMode !== newMode) { this.props.changeMetricClickMode(newMode) }
}

handleReturn(shifted) {
if (shifted) {
Expand Down Expand Up @@ -120,7 +123,6 @@ export default class Guesstimate extends Component{
onEscape={this.props.metricFocus}
onReturn={this.handleReturn.bind(this)}
onTab={this.handleTab.bind(this)}
onFocus={this.props.onEdit}
size={size}
errors={errors}
organizationId={organizationId}
Expand Down
26 changes: 12 additions & 14 deletions src/components/facts/list/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,30 @@ import React, {Component, PropTypes} from 'react'
import {connect} from 'react-redux'

import * as organizationActions from 'gModules/organizations/actions'
import {findFacts} from 'gEngine/organization.js'
import {findFacts} from 'gEngine/organization'
import * as _collections from 'gEngine/collections'
import {orArr} from 'gEngine/utils'

import {FactList} from './list.js'

function mapStateToProps(state) {
return {
organizations: state.organizations,
organizationFacts: state.facts.organizationFacts,
}
}

@connect(mapStateToProps)
@connect(null)
export class FactListContainer extends Component{
displayName: 'FactListContainer'

render() {
const {organizationId, organizations, organizationFacts, isEditable} = this.props
const facts = findFacts(organizationId, organizationFacts)
const organization = organizations.find(u => u.id.toString() === organizationId.toString())
const {facts, existingVariableNames, categories, organization, categoryId, canMakeNewFacts, spaceId, imported_fact_ids} = this.props
return (
<FactList
onDeleteFact={fact => this.props.dispatch(organizationActions.deleteFact(organization, fact))}
onAddFact={fact => this.props.dispatch(organizationActions.addFact(organization, fact))}
onEditFact={fact => this.props.dispatch(organizationActions.editFact(organization, fact))}
onEditFact={fact => this.props.dispatch(organizationActions.editFact(organization, fact, true))}
facts={facts}
isEditable={isEditable}
existingVariableNames={existingVariableNames}
categories={orArr(categories)}
categoryId={categoryId}
canMakeNewFacts={canMakeNewFacts}
spaceId={spaceId}
imported_fact_ids={imported_fact_ids}
/>
)
}
Expand Down
90 changes: 59 additions & 31 deletions src/components/facts/list/form.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,45 @@
import React, {Component, PropTypes} from 'react'

import {simulateFact, FactPT} from 'gEngine/facts'
import {navigateFn} from 'gModules/navigation/actions'

import {spaceUrlById} from 'gEngine/space'
import {hasRequiredProperties, isExportedFromSpace, simulateFact, FactPT} from 'gEngine/facts'
import {FactCategoryPT} from 'gEngine/fact_category'
import {addStats} from 'gEngine/simulation'
import {orStr} from 'gEngine/utils'

import {isData, formatData} from 'lib/guesstimator/formatter/formatters/Data'
import {getVariableNameFromName} from 'lib/generateVariableNames/nameToVariableName'
import {withVariableName} from 'lib/generateVariableNames/generateFactVariableName'

export class FactForm extends Component {
static defaultProps = {
startingFact: {
name: '',
expression: '',
variable_name: '',
exported_from_id: null,
metric_id: null,
category_id: null,
simulation: {
sample: {
values: [],
errors: [],
},
},
}
},
categories: [],
}

static propTypes = {
categories: PropTypes.arrayOf(FactCategoryPT).isRequired,
existingVariableNames: PropTypes.arrayOf(PropTypes.string).isRequired,
onSubmit: PropTypes.func.isRequired,
onCancel: PropTypes.func,
startingFact: FactPT,
}

state = {
runningFact: this.props.startingFact,
runningFact: {...this.props.startingFact, category_id: _.get(this, 'props.startingFact.category_id') || this.props.categoryId},
variableNameManuallySet: !_.isEmpty(_.get(this.props, 'startingFact.variable_name')),
currentExpressionSimulated: true,
submissionPendingOnSimulation: false,
Expand All @@ -41,13 +51,16 @@ export class FactForm extends Component {
}
}

componentDidMount() { this.refs.name.focus() }

setFactState(newFactState, otherState = {}) { this.setState({...otherState, runningFact: {...this.state.runningFact, ...newFactState}}) }
onChangeName(e) {
const name = _.get(e, 'target.value')
this.setFactState(
this.state.variableNameManuallySet ? {name} : {name, variable_name: getVariableNameFromName(name, this.props.existingVariableNames)}
this.state.variableNameManuallySet ? {name} : withVariableName({...this.state.runningFact, name}, this.props.existingVariableNames)
)
}
onSelectCategory(c) { this.setFactState({category_id: c.target.value}) }
onChangeVariableName(e) { this.setFactState({variable_name: _.get(e, 'target.value')}, {variableNameManuallySet: true}) }
onChangeExpression(e) { this.setFactState({expression: _.get(e, 'target.value')}, {currentExpressionSimulated: false}) }
onBlurExpression() { this.simulateCurrentExpression() }
Expand All @@ -58,26 +71,18 @@ export class FactForm extends Component {
addStats(simulation)
this.setFactState({simulation}, {currentExpressionSimulated: true})
} else {
simulateFact(this.state.runningFact).then(({values, errors}) => {
let simulation = {sample: {values, errors}}
simulateFact(this.state.runningFact).then(sample => {
let simulation = {sample}
addStats(simulation)
this.setFactState({simulation}, {currentExpressionSimulated: true})
})
}
}

isExpressionValid() { return _.isEmpty(_.get(this, 'state.runningFact.simulation.sample.errors')) }
hasNoErrors() { return _.isEmpty(_.get(this, 'state.runningFact.simulation.sample.errors')) }
isVariableNameUnique() { return !_.some(this.props.existingVariableNames, n => n === this.state.runningFact.variable_name) }
isValid() {
const requiredProperties = [
'variable_name',
'expression',
'simulation.sample.values',
'simulation.stats',
]
const requiredPropertiesPresent = requiredProperties.map(prop => !_.isEmpty(_.get(this.state.runningFact, prop)))
return _.every(requiredPropertiesPresent) && this.isExpressionValid() && this.isVariableNameUnique()
}
isValid() { return hasRequiredProperties(this.state.runningFact) && this.hasNoErrors() && this.isVariableNameUnique() }

onSubmit() {
if (this.state.currentExpressionSimulated) {
this.props.onSubmit(this.state.runningFact)
Expand All @@ -86,14 +91,32 @@ export class FactForm extends Component {
}
}

submitIfEnter(e){
if (e.keyCode === 13 && this.isValid()) {this.onSubmit()}
submitIfEnter(e) { if (e.keyCode === 13 && this.isValid()) {this.onSubmit()} }

renderEditExpressionSection() {
if (isExportedFromSpace(this.state.runningFact)) {
const exported_from_url = `${spaceUrlById(_.get(this, 'state.runningFact.exported_from_id'))}?factsShown=true`
return <span className='ui button small options' onClick={navigateFn(exported_from_url)}>Edit Model</span>
} else {
return (
<div className={`field ${this.hasNoErrors() ? '' : 'error'}`}>
<input
type='text'
placeholder='value'
value={this.state.runningFact.expression}
onChange={this.onChangeExpression.bind(this)}
onBlur={this.onBlurExpression.bind(this)}
onKeyDown={this.submitIfEnter.bind(this)}
/>
</div>
)
}
}

render() {
const {
props: {buttonText, onCancel, onDelete},
state: {submissionPendingOnSimulation, runningFact: {expression, name, variable_name}}
props: {buttonText, onCancel, onDelete, categories},
state: {submissionPendingOnSimulation, runningFact: {expression, name, variable_name, category_id}}
} = this

let buttonClasses = ['ui', 'button', 'small', 'primary']
Expand All @@ -107,15 +130,7 @@ export class FactForm extends Component {
<div className='Fact--outer'>
<div className='Fact new ui form'>
<div className='section-simulation simulation-sample'>
<div className={`field ${this.isExpressionValid() ? '' : 'error'}`}>
<input
type='text'
placeholder='value'
value={expression}
onChange={this.onChangeExpression.bind(this)}
onBlur={this.onBlurExpression.bind(this)}
/>
</div>
{this.renderEditExpressionSection()}
</div>
<div className='section-name'>
<div className='fact-name'>
Expand All @@ -127,6 +142,7 @@ export class FactForm extends Component {
value={name}
onChange={this.onChangeName.bind(this)}
onKeyDown={this.submitIfEnter.bind(this)}
ref='name'
/>
</div>
</div>
Expand All @@ -142,6 +158,18 @@ export class FactForm extends Component {
/>
</div>
</div>
{!_.isEmpty(categories) &&
<div className='field'>
<div className='category-select'>
<select value={`${orStr(category_id)}`} onChange={this.onSelectCategory.bind(this)}>
<option value={''}>Uncategorized</option>
{_.map(categories, ({id, name}) => (
<option value={id} key={id}>{name}</option>
))}
</select>
</div>
</div>
}
<div className='actions'>
<span className={buttonClasses.join(' ')} onClick={this.onSubmit.bind(this)}>{buttonText}</span>
{!!onCancel && <span className='ui button small' onClick={onCancel}>Cancel</span>}
Expand Down
Loading

0 comments on commit 07044c8

Please sign in to comment.