Commit e6a0519f authored by Jaden DIEFENBAUGH's avatar Jaden DIEFENBAUGH
Browse files

add a bunch of comments

parent 8aee5a11
......@@ -12,22 +12,36 @@ import {
import { rxField } from '@helpers/beat';
type Props = {
// consumer-supplied func for validating input
// if the func finds the input isnt valid, it should return a react.node of text for the error message
validateFunc?: (string) => (true | React.Node),
// if the current input in the CacheInput instance is valid, this func will be called with that value
onChange: (any) => any,
// the current value for this cacheinput
value: string,
// for edge cases
children?: React.Node,
// many fields in the beat editor are restricted to a certain "slugified" format
// if this cacheinput needs that, use this flag
fieldTest?: boolean,
// delay in ms, defaults to 500
// update delay in ms, defaults to 500
// basically just simple throttling to keep from overwhelming complex updates in the parent
delay?: number,
};
type State = {
// the current string in the CacheInput input field,
// which may or may not be valid
cache: string,
// whether the cache is valid
valid: boolean,
// a text-like react node that represents an error message
invalidityText?: React.Node,
// the delay to use (default 500)
msDelay: number,
};
// test for the "fieldTest" flag, with a nice error message
const fieldTestResults = (input: string): true | React.Node => {
return (rxField.test(input) ||
<span>
......@@ -36,6 +50,8 @@ const fieldTestResults = (input: string): true | React.Node => {
);
};
// top-level validity func with some edgecase checking
// validateFunc arg lets you also check using a user-defined validate func
const isValid = (input: string, currVal: string, validateFunc: (string) => true | React.Node, fieldTest: boolean): true | React.Node => {
if(input === currVal)
return true;
......@@ -51,6 +67,8 @@ const isValid = (input: string, currVal: string, validateFunc: (string) => true
return funcRes;
};
// Provides a bootstrap-styled wrapper around <input /> that caches the raw user input until a valid input is provided.
// Also throttles the onChange event to prevent many parent state updates in a short time
class CacheInput extends React.Component<Props, State>{
constructor(props: Props){
super(props);
......@@ -64,10 +82,12 @@ class CacheInput extends React.Component<Props, State>{
msDelay: this.props.delay !== undefined ? this.props.delay : 500,
};
// check validation as soon as it mounts
componentDidMount () {
this.updateValidity(this.state.cache);
}
// if the parent's model changes, reset input
componentWillReceiveProps (nextProps: any) {
this.setState({
cache: (nextProps.value),
......@@ -75,6 +95,7 @@ class CacheInput extends React.Component<Props, State>{
this.clearTimer();
}
// gets the validity info of an input string given a curr val
updateValidity = (input: string, currVal?: string = this.props.value) => {
const func = this.props.validateFunc || (() => true);
const validRes = isValid(input, currVal, func, this.props.fieldTest || false);
......@@ -86,6 +107,8 @@ class CacheInput extends React.Component<Props, State>{
return validRes === true;
}
// the user can pass any props they want onto the inner <Input /> element
// some props shouldn't be passed though
getInputProps = () => {
const inputProps = { ...this.props };
delete inputProps['validateFunc'];
......@@ -95,12 +118,16 @@ class CacheInput extends React.Component<Props, State>{
return inputProps;
}
// throttle timer ref
onChangeTimer = null;
// clears the throttle timer
clearTimer = () => {
if(this.onChangeTimer)
clearTimeout(this.onChangeTimer);
}
// called whenever the user changes the raw input with the raw input event
// for perf reasons it really focuses on returning when possible
change = (e: any) => {
this.clearTimer();
const newInput = e.target.value;
......@@ -118,6 +145,9 @@ class CacheInput extends React.Component<Props, State>{
if(this.state.msDelay === 0){
this.props.onChange(e);
} else {
// by default in react events have a "persist" method
// that lets you keep it around for a bit longer than normal
// in testing environments the event persists by default
if(e.persist)
e.persist();
this.onChangeTimer = setTimeout(() => {
......@@ -127,6 +157,7 @@ class CacheInput extends React.Component<Props, State>{
}
}
// the Tooltips are annoying in test environments and need a react ref
target = null
render () {
......
......@@ -9,6 +9,8 @@ type Props = {
deleteFunc: (any) => any,
}
// super simple wrapper for a button that attaches to an input
// styled like a Delete button
const DeleteInputBtn = ({ deleteFunc }: Props) => (
<InputGroupAddon addonType='prepend'>
<Button
......
......@@ -37,18 +37,28 @@ import PlotterEditorContainer from './plotter';
import PlotterparameterEditorContainer from './plotterparameter';
type Props = {
// the match object from react-router
// used to find which object to render
match: any,
// the history object from react-router
// used to push new history to
history: any,
// gets the object based on the current URL
getEntityObject: () => BeatObject,
// updates the current object
updateFunc: (BeatObject) => any,
};
// 3 tabs so far: editor, docs, raw json
type Tab = '0' | '1' | '2';
type State = {
// which tab is active
activeTab: Tab,
};
// wrapper for editors
// doesnt really do much now besides the raw json tab
export class EntityDetail extends React.Component<Props, State> {
constructor(props: Props){
super(props);
......@@ -72,6 +82,7 @@ export class EntityDetail extends React.Component<Props, State> {
return (
<Container>
<Row>
{/* title line (name & validation info) */}
<Col>
<h4 style={{'textAlign': 'center'}}>
<span style={{'textTransform': 'capitalize'}}>
......@@ -114,6 +125,7 @@ export class EntityDetail extends React.Component<Props, State> {
</NavItem>
</Nav>
<TabContent activeTab={this.state.activeTab} className='mt-2'>
{/* Uses the appropriate editor, could probably be done nicer */}
<TabPane tabId='0'>
{
this.props.match.params.entity === 'algorithm' &&
......@@ -191,9 +203,13 @@ export class EntityDetail extends React.Component<Props, State> {
}
const mapStateToProps = (state, ownProps) => {
// which beat entity is currently active: 'algorithm', 'plotter', 'database', etc.
const entity = ownProps.match.params.entity;
// the name of the object ("atnt/1", "plot/isoroc/1", "user/tc/test/1/exp", etc.)
const name = ownProps.match.params.name;
const obj = {
// uses a selector based off the entity and finds the obj given the name
// if the obj doesnt exist (huge edge case) just return a default obj to not break everything
getEntityObject: (): BeatObject => Selectors[`${ entity }Get`](state).find(o => o.name === name) || getDefaultEntityObject(),
};
......@@ -201,6 +217,7 @@ const mapStateToProps = (state, ownProps) => {
};
const mapDispatchToProps = (dispatch, ownProps) => ({
// replace the obj in the Redux store with the new object
updateFunc: (obj) => {
dispatch(Actions[`${ ownProps.match.params.entity }Update`](ownProps.match.params.name, obj));
ownProps.history.push(`/${ ownProps.match.params.entity }/${ obj.name }`);
......
......@@ -40,20 +40,29 @@ type Props = {
};
type State = {
// which tab the user is viewing (endpoints, params, etc.)
activeTab: string,
// data that the user edits, needs use to click "save" to save changes
cache: any,
// algorithm-specific validity checking (alerts for common errors & warnings)
validity: AlgorithmValidatorObject,
// parameters can restrict their values to individual choices
// this string represents a choice that is being edited
choiceCache: string,
};
// represents an input or output value
// inputs/outputs are just an object with a "type" field
type IOEntry = {|
type: string,
|};
// represents a collection of inputs/outputs
type IOObject = {
[string]: IOEntry,
};
// represents a group/endpoint, optionally with outputs
type Group = {
name: string,
inputs: IOObject,
......@@ -73,6 +82,7 @@ export class AlgorithmEditor extends React.Component<Props, State> {
choiceCache: '',
}
// if the data changes behind the scenes, update the editor with these changes
componentWillReceiveProps (nextProps: Props) {
this.setState({
cache: getValidObj(nextProps.data),
......@@ -80,12 +90,16 @@ export class AlgorithmEditor extends React.Component<Props, State> {
});
}
// get the result types (analyzer result types are restricted to a subset of all available types)
getResultDfs = (): string[] => ANALYZER_RESULT_TYPES.concat(this.props.dataformats.filter(d => d.name.startsWith('plot')).map(d => d.name))
// returns a validity object based on the given algorithm cache and list of all algorithms
getValidity = (cache: BeatObject = this.state.cache, algs: BeatObject[] = this.props.algorithms): AlgorithmValidatorObject => {
return algorithmValidator(cache, algs.filter(o => o !== this.props.data));
}
// much like getValidity but just returns a bool
// doesnt worry about warnings, just errors
isValid = (cache: BeatObject): boolean => {
const isValid = Object.entries(this.getValidity(cache))
.filter(([name, bool]) => name !== (this.isAnalyzer() ? 'endpoint0OutputExists' : 'result0Exists'))
......@@ -95,10 +109,15 @@ export class AlgorithmEditor extends React.Component<Props, State> {
return isValid;
}
// change the tab
tabTo = (tab: string) => { if(this.state.activeTab !== tab) this.setState({ activeTab: tab }); }
// is the algorithm an analyzer?
// theres no flag set, so instead check for the existence of a 'splittable' field which only normal algorithms have
isAnalyzer = (): boolean => { return !Object.keys(this.state.cache.contents).includes('splittable'); }
// helper to change a value in the "contents" subobject of an algorithm
// (this is where the vast majority of change happens)
changeContentsVal = (field: string, val: any) => {
const newCache = {
...this.state.cache,
......@@ -113,6 +132,7 @@ export class AlgorithmEditor extends React.Component<Props, State> {
});
}
// updates an old group object to a new group object
updateGroup = (oldGroup: Group, newGroup: Group) => {
const gIdx = this.state.cache.contents.groups.findIndex(g => JSON.stringify(g) === JSON.stringify(oldGroup));
const newGroups = jsonClone(this.state.cache.contents.groups);
......@@ -134,12 +154,14 @@ export class AlgorithmEditor extends React.Component<Props, State> {
this.changeContentsVal('parameters', params);
}
// updates a result given the result name and the value obj
updateResult = (name: string, res: any) => {
const rs = this.state.cache.contents.results;
rs[name] = res;
this.changeContentsVal('results', rs);
}
// toggles the algorithm to an analyzer algorithm
changeToAnalyzer = () => {
const contents = jsonClone(this.state.cache.contents);
contents.groups = contents.groups.map(g => {
......@@ -159,6 +181,7 @@ export class AlgorithmEditor extends React.Component<Props, State> {
});
}
// toggles the algorithm to a normal algorithm
changeToNormal = () => {
const contents = jsonClone(this.state.cache.contents);
delete contents.results;
......@@ -176,6 +199,8 @@ export class AlgorithmEditor extends React.Component<Props, State> {
});
}
// generates a func from a given group & type that takes an input/output name
// and deletes it from the group
deleteIO = (group: Group, type: 'inputs' | 'outputs') => (name: string) => {
if(!group[type])
return;
......@@ -183,10 +208,14 @@ export class AlgorithmEditor extends React.Component<Props, State> {
this.updateGroup(group, { ...group });
}
// rendergs the endpoints tab
renderEndpoints = () => (
<TabPane tabId='0'>
{
// loop through all groups in the alg
this.state.cache.contents.groups.map((group: Group, i, groups: Group[]) => {
// func to update an input/output
// lets you change the name and/or the type
const ioUpdate = (subObj: 'inputs' | 'outputs') => (oldName: string, newName: string, newObj: IOEntry) => {
const changedObj = changeObjFieldName(group[subObj], oldName, newName);
changedObj[newName] = newObj;
......@@ -325,8 +354,9 @@ export class AlgorithmEditor extends React.Component<Props, State> {
<Button outline block
id='newGroupBtn'
onClick={(e) => {
const newGroupName = generateNewKey('group', this.state.cache.contents.groups.map(g => g.name));
const newGroup = {
name: `group ${ this.state.cache.contents.groups.length }`,
name: newGroupName,
inputs: {},
};
if(!this.isAnalyzer() && this.state.cache.contents.groups.length === 0)
......@@ -343,10 +373,12 @@ export class AlgorithmEditor extends React.Component<Props, State> {
</TabPane>
);
// renders the parameters tab
renderParameters = () => {
return (
<TabPane tabId='1'>
{
// loop through all the parameters
(Object.entries(this.state.cache.contents.parameters): [string, any][]).map(([name, param], i, params) => (
<Row key={i} className='mb-2'>
<Col sm='12'>
......@@ -382,11 +414,13 @@ export class AlgorithmEditor extends React.Component<Props, State> {
);
}
// renders the libraries tab
renderLibraries = () => (
<TabPane tabId='2'>
<Row>
<Col sm='12'>
{
// loop through all the used libs
(Object.entries(this.state.cache.contents.uses): [string, any][])
.map(([name, lib], i, lEntries) => (
<TypedField
......@@ -396,6 +430,7 @@ export class AlgorithmEditor extends React.Component<Props, State> {
types={this.props.libraries.map(l => l.name)}
existingFields={lEntries.map(([n, v]) => n)}
nameUpdateFunc={str => {
// update the alias
this.changeContentsVal('uses',
changeObjFieldName(
this.state.cache.contents.uses,
......@@ -405,6 +440,7 @@ export class AlgorithmEditor extends React.Component<Props, State> {
);
}}
typeUpdateFunc={str => {
// update the chosen library for the alias
const libs = {
...this.state.cache.contents.uses,
[name]: str
......@@ -438,9 +474,11 @@ export class AlgorithmEditor extends React.Component<Props, State> {
</TabPane>
);
// renders the results tab (only if the alg is an analyzer)
renderResults = () => (
<TabPane tabId='3'>
{
// loop through results
(Object.entries(this.state.cache.contents.results): [string, any][])
.map(([name, result], i, rEntries) => (
<TypedField
......@@ -475,6 +513,7 @@ export class AlgorithmEditor extends React.Component<Props, State> {
}}
>
<Col>
{/* display by default checkbox */}
<Label check>
<Input
type='checkbox'
......@@ -518,6 +557,7 @@ export class AlgorithmEditor extends React.Component<Props, State> {
render = () => (
<div>
{/* header content (save button & template gen button) */}
<div className='d-flex'>
<Button
className='mx-auto'
......@@ -532,6 +572,7 @@ export class AlgorithmEditor extends React.Component<Props, State> {
entity={'algorithm'}
/>
</div>
{/* simple universal stuff - description, what type of alg, if its splittable */}
<Form>
<FormGroup tag='fieldset'>
<FormGroup>
......@@ -580,6 +621,7 @@ export class AlgorithmEditor extends React.Component<Props, State> {
</FormGroup>
}
</FormGroup>
{/* most errors/warnings from the current AlgorithmValidator object are displayed here */}
{
(!this.isAnalyzer() || this.getValidity().result0Exists) ||
<Alert color='danger'>
......@@ -605,6 +647,7 @@ export class AlgorithmEditor extends React.Component<Props, State> {
You need at least 1 input in the first endpoint group.
</Alert>
}
{/* all the tabs: endpoints, params, libs, results */}
<Nav tabs className='mt-3 mb-3'>
<NavItem>
<NavLink
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment