Commit 575bfc34 authored by Jaden DIEFENBAUGH's avatar Jaden DIEFENBAUGH
Browse files

refactored parameter handling from editors, closes #82

parent 8d7fc3af
// @flow
import * as React from 'react';
import {
Label, Input,
} from 'reactstrap';
import { expGetDefaultParameterValue as getDefaultParameterValue } from '@helpers/beat';
import type { ParameterValue } from '@helpers/beat';
type Props = {
parameter: any,
updateFunc: (ParameterValue) => any,
currVal?: ParameterValue,
};
// given a parameter obj, its current value, and an update function,
// return an input for the specific type of parameter
const ParameterConsume = ({ parameter, updateFunc, currVal }: Props) => {
if((parameter === null || parameter === undefined) || !parameter.hasOwnProperty('type'))
return '';
const parseVal = (v: ParameterValue): ParameterValue => {
if(v === true || v === false)
return v;
if(parameter.type.includes('float'))
return Number.parseFloat(`${ v }`);
if(parameter.type.includes('int'))
return Number.parseInt(`${ v }`);
return v;
};
const pVal = currVal || getDefaultParameterValue(parameter);
if(parameter.hasOwnProperty('choice'))
return (
<Input
type='select'
className='custom-select'
value={pVal}
onChange={e => updateFunc(parseVal(e.target.value))}
>
{ parameter.choice.map((c, i) => <option key={i} value={c}>{c}</option>) }
</Input>
);
if(parameter.hasOwnProperty('range'))
return (
<Input
type='number'
min={parameter.range[0]}
max={parameter.range[1]}
value={pVal}
onChange={e => updateFunc(parseVal(e.target.value))}
/>
);
if(parameter.type === 'bool')
return (
<Label>
<Input
className='ml-0'
type='checkbox'
value={pVal}
onChange={e => updateFunc(parseVal(e.target.checked))}
/>
</Label>
);
if(parameter.type === 'string')
return (
<Input
type='text'
value={pVal}
onChange={e => updateFunc(parseVal(e.target.value))}
/>
);
else {
const step = parameter.type.includes('float') ? 'any' : 1;
return (
<Input
type='number'
min={-1000000}
max={1000000}
step={step}
value={pVal}
onChange={e => updateFunc(parseVal(e.target.value))}
/>
);
}
};
export default ParameterConsume;
// @flow
import * as React from 'react';
import {
Row,
Col,
Button,
FormGroup,
Label, Input,
UncontrolledDropdown, DropdownToggle, DropdownMenu,
InputGroup, InputGroupAddon,
} from 'reactstrap';
import { BUILTIN_TYPES, ANALYZER_RESULT_TYPES } from '@helpers/beat';
import TypedField from './TypedField.jsx';
type Props = {
name: string,
param: any,
params: any[],
updateParameter: (name: string | null, parameter: any | null, oldName: ?string) => any,
};
type State = {
choiceCache: string
};
export default class ParameterCreate extends React.Component<Props, State> {
constructor(props: Props){
super(props);
}
state = {
choiceCache: '',
}
shouldComponentUpdate = (nextProps: Props, nextState: State) => {
// basically PureComponent but not checking updateParameter func which wont change by itself
// props check
if(nextProps.name !== this.props.name || nextProps.param !== this.props.param || nextProps.params !== this.props.params)
return true;
// state check
if(nextState.choiceCache !== this.state.choiceCache)
return true;
return false;
}
render = () => {
const name = this.props.name;
const param = this.props.param;
const params = this.props.params;
const updateParameter = this.props.updateParameter;
return (
<React.Fragment>
<TypedField
placeholder='Parameter name...'
name={name}
type={param.type}
types={BUILTIN_TYPES.filter(d => !d.startsWith('complex'))}
existingFields={params.map(([n, v]) => n)}
nameUpdateFunc={str => updateParameter(str, param, name)}
typeUpdateFunc={(str) => {
const newParam = {
...param,
type: str
};
switch(newParam.type){
case 'string':
delete newParam['range'];
break;
case 'bool':
delete newParam['range'];
delete newParam['choice'];
break;
default:
break;
}
updateParameter(name, newParam);
}}
deleteFunc={() => {
updateParameter(null, null, name);
}}
>
<Col>
<Input
type='text'
placeholder='Description'
value={param[name] || param.description || ''}
onChange={(e) => updateParameter(name, {
...param,
description: e.target.value
})}
/>
</Col>
</TypedField>
<FormGroup row className='ml-3'>
{
param.type !== 'bool' &&
<Col sm='4'>
<Label>Restrict to...</Label>
<FormGroup check>
<Row>
<Col>
<Label check>
<Input
type='checkbox'
checked={Array.isArray(param.choice)}
onChange={e => {
const b = e.target.checked;
const newParam = {...param};
if(b){
delete newParam['range'];
newParam['choice'] = [];
} else {
delete newParam['choice'];
}
updateParameter(name, newParam);
}}
/>{' '}
Choices
</Label>
</Col>
{
param.type !== 'string' &&
<Col>
<Label check className='ml-3'>
<Input
type='checkbox'
checked={Array.isArray(param.range)}
onChange={e => {
const b = e.target.checked;
const newParam = {...param};
if(b){
delete newParam['choice'];
newParam['range'] = [0, 1];
} else {
delete newParam['range'];
}
updateParameter(name, newParam);
}}
/>{' '}
Range
</Label>
</Col>
}
</Row>
</FormGroup>
</Col>
}
{
Array.isArray(param.choice) &&
<Col sm='auto'>
<Label>
Choices
</Label>
<UncontrolledDropdown>
<DropdownToggle caret>
Choices List
</DropdownToggle>
<DropdownMenu className='p-0'>
{
param.choice
.map((c, j) =>
<InputGroup key={j}>
<Input
defaultValue={c}
className='border-top-0 border-left-0 border-right-0 rounded-0'
onSubmit={e => {
e.preventDefault();
}}
onChange={e => {
updateParameter(
name,
{
...param,
choice: param.choice.map((c, k) => k === j ? e.target.value : c)
});
}}
/>
<InputGroupAddon addonType='append'>
<Button
className='border-top-0 border-left-0 border-right-0 rounded-0'
color='danger'
onClick={e => {
updateParameter(name,
{
...param,
choice: param.choice.filter((c, k) => k !== j)
}
);
}}
>
x
</Button>
</InputGroupAddon>
</InputGroup>
)
}
<Input
placeholder='New Choice...'
className='border-top-0 border-left-0 border-right-0 rounded-0'
value={this.state.choiceCache}
onSubmit={e => {
e.preventDefault();
}}
onBlur={e => {
if(this.state.choiceCache.length > 0)
updateParameter(name,
{
...param,
choice: [
...param.choice,
this.state.choiceCache
]
});
this.setState({ choiceCache: '' });
}}
onChange={e => {
this.setState({ choiceCache: e.target.value });
}}
onKeyPress={e => {
if(e.key === 'Enter'){
if(this.state.choiceCache.length > 0)
updateParameter(name,
{
...param,
choice: [
...param.choice,
this.state.choiceCache
]
}
);
this.setState({ choiceCache: '' });
}
}}
/>
</DropdownMenu>
</UncontrolledDropdown>
</Col>
}
{
Array.isArray(param.range) &&
<Col sm='4'>
<Label>
Range
</Label>
<FormGroup row>
<Col>
<InputGroup>
<InputGroupAddon>Min</InputGroupAddon>
<Input
type='number'
step={param.type.startsWith('float') ? 'any' : '1'}
value={param.range[0]}
onChange={e => {
const num = e.target.value;
const newParam = {
...param,
range: [
num,
param.range[1],
]
};
updateParameter(
name,
newParam
);
}}
required
/>
</InputGroup>
</Col>
<Col>
<InputGroup>
<InputGroupAddon>Max</InputGroupAddon>
<Input
type='number'
step={param.type.startsWith('float') ? 'any' : '1'}
value={param.range[1]}
required
onChange={e => {
const num = e.target.value;
const newParam = {
...param,
range: [
param.range[0],
num,
]
};
updateParameter(
name,
newParam
);
}}
/>
</InputGroup>
</Col>
</FormGroup>
</Col>
}
{ param.type !== 'bool' &&
<Col>
<Label for={`default${ name }`}>
Default
</Label>
<Input
name={`default${ name }`}
type='text'
placeholder='Default'
value={param.default}
onChange={(e) => updateParameter(name, {
...param,
default: e.target.value
})}
/>
</Col>
||
<Col sm='auto'>
<Label>
Default
</Label>
<FormGroup check>
<Row>
<Col>
<Label check>
<Input
name={`default${ name }`}
type='radio'
/>
True
</Label>
</Col>
<Col>
<Label check className='ml-1'>
<Input
name={`default${ name }`}
type='radio'
/>
False
</Label>
</Col>
</Row>
</FormGroup>
</Col>
}
</FormGroup>
</React.Fragment>
);
}
}
......@@ -29,6 +29,7 @@ import DeleteInputBtn from '../DeleteInputBtn.jsx';
import TypedField from '../TypedField.jsx';
import TemplateButton from '../EntityTemplateGenerationButton.jsx';
import InfoTooltip from '../InfoTooltip.jsx';
import ParameterCreate from '../ParameterCreate.jsx';
type Props = {
data: BeatObject,
......@@ -119,10 +120,18 @@ export class AlgorithmEditor extends React.Component<Props, State> {
this.changeContentsVal('groups', newGroups);
}
updateParameter = (name: string, param: any) => {
const ps = this.state.cache.contents.parameters;
ps[name] = param;
this.changeContentsVal('parameters', ps);
// 4 different functions:
// creates a parameter (provide name & value)
// updates a parameter (provide name & value)
// renames a parameter (provide name & value & the old name)
// deletes a parameter (provide null as name, value is optional, the name)
updateParameter = (name: null | string, value: any, oldName: ?string) => {
const params = jsonClone(this.state.cache.contents.parameters);
if(oldName)
delete params[oldName];
if(name !== null)
params[name] = value;
this.changeContentsVal('parameters', params);
}
updateResult = (name: string, res: any) => {
......@@ -341,303 +350,12 @@ export class AlgorithmEditor extends React.Component<Props, State> {
(Object.entries(this.state.cache.contents.parameters): [string, any][]).map(([name, param], i, params) => (
<Row key={i} className='mb-2'>
<Col sm='12'>
<TypedField
placeholder='Parameter name...'
<ParameterCreate
name={name}
type={param.type}
types={BUILTIN_TYPES.filter(d => !d.startsWith('complex'))}
existingFields={params.map(([n, v]) => n)}
nameUpdateFunc={str => this.changeContentsVal(
'parameters',
changeObjFieldName(
this.state.cache.contents.parameters,
name,
str
)
)}
typeUpdateFunc={(str) => {
const newParam = {
...param,
type: str
};
switch(newParam.type){
case 'string':
delete newParam['range'];
break;
case 'bool':
delete newParam['range'];
delete newParam['choice'];
break;
default:
break;
}
this.updateParameter(name, newParam);
}}
deleteFunc={() => {
const ps = {
...this.state.cache.contents.parameters
};
delete ps[name];
this.changeContentsVal('parameters', ps);
}}
>
<Col>
<Input
type='text'
placeholder='Description'
value={param[name] || param.description || ''}
onChange={(e) => this.updateParameter(name, {
...param,
description: e.target.value
})}
/>
</Col>
</TypedField>
<FormGroup row className='ml-3'>
{
param.type !== 'bool' &&
<Col sm='auto'>
<Label>Restrict to...</Label>
<FormGroup check>
<Label check>
<Input
type='checkbox'
checked={Array.isArray(param.choice)}
onChange={e => {
const b = e.target.checked;
const newParam = {...param};
if(b){
delete newParam['range'];
newParam['choice'] = [];
} else {
delete newParam['choice'];
}
this.updateParameter(name, newParam);
}}
/>{' '}
Choices
</Label>
{
param.type !== 'string' &&
<Label check className='ml-1'>
<Input
type='checkbox'
checked={Array.isArray(param.range)}
onChange={e => {
const b = e.target.checked;
const newParam = {...param};
if(b){
delete newParam['choice'];
newParam['range'] = [0, 1];
} else {
delete newParam['range'];
}
this.updateParameter(name, newParam);
}}
/>{' '}
Range
</Label>
}
</FormGroup>
</Col>
}
{
Array.isArray(param.choice) &&
<Col sm='auto'>
<Label>
Choices
</Label>
<UncontrolledDropdown>
<DropdownToggle caret>
Choices List
</DropdownToggle>
<DropdownMenu className='p-0'>
{
param.choice
.map((c, j) =>
<InputGroup key={j}>
<Input
defaultValue={c}
className='border-top-0 border-left-0 border-right-0 rounded-0'
onSubmit={e => {