src/components/expression.js
import React, {Component, PropTypes} from 'react'
import CourseExpression from './expression--course'
import ResultIndicator from './result-indicator'
import map from 'lodash/collection/map'
import sortByOrder from 'lodash/collection/sortByOrder'
import cx from 'classnames'
import plur from 'plur'
import humanizeOperator from '../lib/humanize-operator'
import './expression.scss'
const joiners = {
$and: 'AND',
$or: 'OR',
}
function makeBooleanExpression({expr, ctx}) {
let kind = '$invalid'
if ('$and' in expr) {
kind = '$and'
}
else if ('$or' in expr) {
kind = '$or'
}
const contents = expr[kind].reduce((acc, exp, i) => {
if (i > 0) {
acc.push(<span key={`${i}-joiner`} className='joiner'>{joiners[kind]}</span>)
}
acc.push(<Expression key={i} expr={exp} ctx={ctx} />)
return acc
}, [])
return {contents}
}
const ofLookup = {
all: `All of`,
any: `Any of`,
none: `None of`,
}
function makeOfExpression({expr, ctx}) {
const description = ofLookup[expr.$count.$was] || `${expr._counted || 0} of ${humanizeOperator(expr.$count.$operator)} ${expr.$count.$num} from among`
const contents = map(sortByOrder(expr.$of, ['_result'], ['desc']), (ex, i) =>
<Expression key={i} expr={ex} ctx={ctx} />)
return {description, contents}
}
function makeModifierExpression({expr}) {
const needs = `${humanizeOperator(expr.$count.$operator)} ${expr.$count.$num} ${plur(expr.$what, expr.$count.$num)}`
const description = `${expr._counted} of ${needs} from ${expr.$from}`
return {description}
}
function makeWhereQualifier(where) {
return `${where.$key} ${where.$operator} ${where.$value}`
}
function makeWhereExpression({expr}) {
// console.log(expr)
const needs = `${humanizeOperator(expr.$count.$operator)} ${expr.$count.$num}`
const qualifier = makeWhereQualifier(expr.$where)
const description = `${expr._counted} of ${needs} from ${qualifier}`
let contents = map(expr._matches, (course, i) =>
<Expression key={i} expr={{$type: 'course', $course: course}} hideIndicator={true} />)
if (!contents.length) {
contents = null
}
return {description, contents}
}
function makeOccurrenceExpression({expr}) {
const description = `${expr._counted} of ${humanizeOperator(expr.$count.$operator)} ${expr.$count.$num} ${plur('occurrence', expr.$count.$num)} of `
const contents = <Expression expr={{...expr, type: 'course'}} />
return {description, contents}
}
export default class Expression extends Component {
static propTypes = {
ctx: PropTypes.object,
expr: PropTypes.shape({
$type: PropTypes.string,
}).isRequired,
hideIndicator: PropTypes.bool,
}
render() {
const {expr} = this.props
const {$type} = expr
if (!$type) {
return null
}
const wasComputed = expr.hasOwnProperty('_result')
const computationResult = expr._result
let contents = null
let description = null
let result = null
if ($type === 'boolean') {
({contents} = makeBooleanExpression({...this.props}))
}
else if ($type === 'course') {
contents = <CourseExpression {...expr.$course} />
result = <ResultIndicator result={computationResult} />
}
else if ($type === 'reference') {
contents = expr.$requirement
result = <ResultIndicator result={computationResult} />
}
else if ($type === 'of') {
({contents, description} = makeOfExpression({...this.props}))
}
else if ($type === 'modifier') {
({description} = makeModifierExpression({...this.props}))
result = <ResultIndicator result={computationResult} />
}
else if ($type === 'where') {
({description, contents} = makeWhereExpression({...this.props}))
}
else if ($type === 'occurrence') {
({description, contents} = makeOccurrenceExpression({...this.props}))
}
else {
console.warn(`<Expression />: type not handled: ${$type}`)
console.log(this.props)
contents = JSON.stringify(expr, null, 2)
}
const className = cx(
'expression',
`expression--${$type}`,
wasComputed ? 'computed' : 'computed--not',
computationResult ? 'computed-success' : 'computed-failure')
return (
<span className={className}>
{description &&
<span className='expression--description'>
{description}{!this.props.hideIndicator && result}
</span>}
{contents &&
<span className='expression--contents'>
{contents}
{!this.props.hideIndicator && result}
</span>}
</span>
)
}
}