CellProperty

Source

InputCell.js
import React from 'react';
import PropTypes from 'prop-types';

import style from 'PVWStyle/ReactProperties/CellProperty.mcss';

import convert from '../../../Common/Misc/Convert';
import validate from '../../../Common/Misc/Validate';

export default class InputCell extends React.Component {
constructor(props) {
super(props);
this.state = {
editing: false,
valueRep: props.value,
};

// Callback binding
this.getTooltip = this.getTooltip.bind(this);
this.applyDomains = this.applyDomains.bind(this);
this.valueChange = this.valueChange.bind(this);
this.endEditing = this.endEditing.bind(this);
}

getTooltip() {
let tooltip = '';
const idx = this.props.idx;

if (!this.props.domain) {
return tooltip;
}

// Handle range
if (
{}.hasOwnProperty.call(this.props.domain, 'range') &&
this.props.domain.range.length
) {
const size = this.props.domain.range.length;
const { min, max } = this.props.domain.range[idx % size] || {};

tooltip += min !== undefined ? `min(${min}) ` : '';
tooltip += max !== undefined ? `max(${max}) ` : '';
}

return tooltip;
}

applyDomains(idx, val) {
if (!this.props.domain) {
return val;
}

// Handle range
let newValue = val;
if (
{}.hasOwnProperty.call(this.props.domain, 'range') &&
this.props.domain.range.length
) {
const size = this.props.domain.range.length;
const { min, max, force } = this.props.domain.range[idx % size];
if (force) {
newValue = min !== undefined ? Math.max(min, newValue) : newValue;
newValue = max !== undefined ? Math.min(max, newValue) : newValue;
}
}
return newValue;
}

valueChange(e) {
const newVal = e.target.value;
const isValid = validate[this.props.type](newVal);
this.setState({
editing: true,
valueRep: newVal,
});

if (!this.props.noEmpty && newVal.length === 0 && !isValid) {
this.props.onChange(this.props.idx, undefined);
} else if (isValid) {
let propVal = convert[this.props.type](newVal);
propVal = this.applyDomains(this.props.idx, propVal);
this.props.onChange(this.props.idx, propVal);
}
}

endEditing() {
this.setState({
editing: false,
});
}

render() {
return (
<td className={style.inputCell}>
<label className={style.inputCellLabel}>{this.props.label}</label>
<input
readOnly={this.props.domain && this.props.domain.readOnly}
className={style.inputCellInput}
value={this.state.editing ? this.state.valueRep : this.props.value}
onChange={this.valueChange}
title={this.getTooltip()}
onBlur={this.endEditing}
/>
</td>
);
}
}

InputCell.propTypes = {
domain: PropTypes.object.isRequired,
idx: PropTypes.number,
label: PropTypes.string,
noEmpty: PropTypes.bool,
onChange: PropTypes.func.isRequired,
type: PropTypes.string,
value: PropTypes.any,
};

InputCell.defaultProps = {
label: '',
idx: 0,
value: '',
type: 'string',
noEmpty: false,
};
index.js
import React from 'react';
import PropTypes from 'prop-types';

import style from 'PVWStyle/ReactProperties/CellProperty.mcss';

import layouts from './layouts';
import ToggleIconButton from '../../Widgets/ToggleIconButtonWidget';

/* eslint-disable react/no-danger */
export default class CellProperty extends React.Component {
constructor(props) {
super(props);
this.state = {
data: props.data,
helpOpen: false,
};

// Callback binding
this.valueChange = this.valueChange.bind(this);
this.addValue = this.addValue.bind(this);
this.helpToggled = this.helpToggled.bind(this);
}

componentWillMount() {
const newState = {};
if (this.props.ui.default && !this.props.data.value) {
newState.data = this.state.data;
newState.data.value = this.props.ui.default;
}

if (Object.keys(newState).length > 0) {
this.setState(newState);
}
}

componentWillReceiveProps(nextProps) {
const data = nextProps.data;

if (this.state.data !== data) {
this.setState({
data,
});
}
}

valueChange(idx, newVal) {
const newData = this.state.data;
if (newVal === null) {
newData.value.splice(idx, 1);
} else {
newData.value[idx] = newVal;
}

this.setState({
data: newData,
});
if (this.props.onChange) {
this.props.onChange(newData);
}
}

addValue() {
const newData = this.state.data;
const values = newData.value;

switch (values.length) {
case 0: {
values.push(0);
break;
}
case 1: {
values.push(values[0]);
break;
}
default: {
const last = Number(values[values.length - 1]);
const beforeLast = Number(values[values.length - 2]);
const newValue = last + (last - beforeLast);
if (!Number.isNaN(newValue) && Number.isFinite(newValue)) {
values.push(newValue);
} else {
values.push(values[values.length - 1]);
}
}
}

this.setState({
data: newData,
});
if (this.props.onChange) {
this.props.onChange(newData);
}
}

helpToggled(open) {
this.setState({
helpOpen: open,
});
}

render() {
return (
<div
className={
this.props.show(this.props.viewData) ? style.container : style.hidden
}
>
<div className={style.header}>
<strong>{this.props.ui.label}</strong>
<span>
<i
className={
this.props.ui.layout === '-1' ? style.plusIcon : style.hidden
}
onClick={this.addValue}
/>
<ToggleIconButton
icon={style.helpIcon}
value={this.state.helpOpen}
toggle={!!this.props.ui.help}
onChange={this.helpToggled}
/>
</span>
</div>
<div className={style.inputBlock}>
<table className={style.inputTable}>
{layouts(this.props.data, this.props.ui, this.valueChange)}
</table>
</div>
<div
className={this.state.helpOpen ? style.helpBox : style.hidden}
dangerouslySetInnerHTML={{ __html: this.props.ui.help }}
/>
</div>
);
}
}
/* eslint-enable react/no-danger */

CellProperty.propTypes = {
data: PropTypes.object.isRequired,
help: PropTypes.string,
onChange: PropTypes.func.isRequired,
show: PropTypes.func.isRequired,
ui: PropTypes.object.isRequired,
viewData: PropTypes.object.isRequired,
};

CellProperty.defaultProps = {
help: '',
};
layouts.js
import React from 'react';
import style from 'PVWStyle/ReactProperties/CellProperty.mcss';

import InputCell from './InputCell';

function arrayFill(arr, expectedLength, filler = '') {
if (!arr) {
return Array(expectedLength).fill(filler);
}

while (arr.length < expectedLength) {
arr.push(filler);
}
return arr;
}
/* eslint-disable */
const layouts = {
1: (data, ui, callback) => {
ui.componentLabels = arrayFill(ui.componentLabels, 1);
data.value = arrayFill(data.value, 1, null);
return (
<tbody>
<tr className={style.inputRow}>
<InputCell
idx={0}
label={ui.componentLabels[0]}
type={ui.type}
value={data.value[0]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
</tr>
</tbody>
);
},
2: (data, ui, callback) => {
ui.componentLabels = arrayFill(ui.componentLabels, 2);
data.value = arrayFill(data.value, 2, null);
return (
<tbody>
<tr className={style.inputRow}>
<InputCell
idx={0}
label={ui.componentLabels[0]}
type={ui.type}
value={data.value[0]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
<InputCell
idx={1}
label={ui.componentLabels[1]}
type={ui.type}
value={data.value[1]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
</tr>
</tbody>
);
},
3: (data, ui, callback) => {
ui.componentLabels = arrayFill(ui.componentLabels, 3);
data.value = arrayFill(data.value, 3, null);
return (
<tbody>
<tr className={style.inputRow}>
<InputCell
idx={0}
label={ui.componentLabels[0]}
type={ui.type}
value={data.value[0]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
<InputCell
idx={1}
label={ui.componentLabels[1]}
type={ui.type}
value={data.value[1]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
<InputCell
idx={2}
label={ui.componentLabels[2]}
type={ui.type}
value={data.value[2]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
</tr>
</tbody>
);
},
'2x3': (data, ui, callback) => {
ui.componentLabels = arrayFill(ui.componentLabels, 6);
data.value = arrayFill(data.value, 6, null);
return (
<tbody>
<tr className={style.inputRow} key={data.id + '_0'}>
<InputCell
idx={0}
label={ui.componentLabels[0]}
type={ui.type}
value={data.value[0]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
<InputCell
idx={1}
label={ui.componentLabels[1]}
type={ui.type}
value={data.value[1]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
<InputCell
idx={2}
label={ui.componentLabels[2]}
type={ui.type}
value={data.value[2]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
</tr>
<tr className={style.inputRow} key={data.id + '_1'}>
<InputCell
idx={3}
label={ui.componentLabels[3]}
type={ui.type}
value={data.value[3]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
<InputCell
idx={4}
label={ui.componentLabels[4]}
type={ui.type}
value={data.value[4]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
<InputCell
idx={5}
label={ui.componentLabels[5]}
type={ui.type}
value={data.value[5]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
</tr>
</tbody>
);
},
'3x2': (data, ui, callback) => {
ui.componentLabels = arrayFill(ui.componentLabels, 6);
data.value = arrayFill(data.value, 6, null);
return (
<tbody>
<tr className={style.inputRow} key={data.id + '_0'}>
<InputCell
idx={0}
label={ui.componentLabels[0]}
type={ui.type}
value={data.value[0]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
<InputCell
idx={1}
label={ui.componentLabels[1]}
type={ui.type}
value={data.value[1]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
</tr>
<tr className={style.inputRow} key={data.id + '_1'}>
<InputCell
idx={2}
label={ui.componentLabels[2]}
type={ui.type}
value={data.value[2]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
<InputCell
idx={3}
label={ui.componentLabels[3]}
type={ui.type}
value={data.value[3]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
</tr>
<tr className={style.inputRow} key={data.id + '_2'}>
<InputCell
idx={4}
label={ui.componentLabels[4]}
type={ui.type}
value={data.value[4]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
<InputCell
idx={5}
label={ui.componentLabels[5]}
type={ui.type}
value={data.value[5]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
</tr>
</tbody>
);
},
n: (data, ui, callback) => {
ui.componentLabels = arrayFill(ui.componentLabels, ui.size);
data.value = arrayFill(data.value, ui.size, null);
return (
<tbody>
<tr className={style.inputRow}>
{data.value.map((value, idx) => (
<InputCell
idx={idx}
key={idx}
label={ui.componentLabels[idx]}
type={ui.type}
value={value}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
))}
</tr>
</tbody>
);
},
m6: (data, ui, callback) => {
ui.componentLabels = arrayFill(ui.componentLabels, 6);
data.value = arrayFill(data.value, 6, null);
return (
<tbody>
<tr className={style.inputRow} key={data.id + '_0'}>
<InputCell
idx={0}
label={ui.componentLabels[0]}
type={ui.type}
value={data.value[0]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
<InputCell
idx={1}
label={ui.componentLabels[1]}
type={ui.type}
value={data.value[1]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
<InputCell
idx={2}
label={ui.componentLabels[2]}
type={ui.type}
value={data.value[2]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
</tr>
<tr className={style.inputRow} key={data.id + '_1'}>
<td />
<InputCell
idx={3}
label={ui.componentLabels[3]}
type={ui.type}
value={data.value[3]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
<InputCell
idx={4}
label={ui.componentLabels[4]}
type={ui.type}
value={data.value[4]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
</tr>
<tr className={style.inputRow} key={data.id + '_2'}>
<td />
<td />
<InputCell
idx={5}
label={ui.componentLabels[5]}
type={ui.type}
value={data.value[5]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
</tr>
</tbody>
);
},
'-1': (data, ui, callback) => {
return (
<tbody>
{data.value.map((value, index) => {
return (
<tr key={[data.id, index].join('_')} className={style.inputRow}>
<td>
<i
className={index ? style.deleteIcon : style.hidden}
onClick={() => {
callback(index, null);
}}
/>
</td>
<InputCell
idx={index}
label=""
type={ui.type}
value={value}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
</tr>
);
})}
</tbody>
);
},
NO_LAYOUT: (data, ui, callback) => {
return (
<tbody>
<tr className={style.inputRow}>
<InputCell
idx={0}
label={ui.componentLabels[0]}
type={ui.type}
value={data.value[0]}
name={data.name}
domain={ui.domain}
onChange={callback}
/>
</tr>
</tbody>
);
},
};
/* eslint-enable */

export default function(data, ui, callback) {
if (!{}.hasOwnProperty.call(ui, 'layout')) {
ui.layout = 'NO_LAYOUT';
}

if (!{}.hasOwnProperty.call(ui, 'size')) {
ui.size = 1;
}

if (!{}.hasOwnProperty.call(ui, 'type')) {
ui.type = 'string';
}

if (!{}.hasOwnProperty.call(ui, 'domain')) {
ui.domain = {};
}

const fn = layouts[ui.layout];
if (fn) {
return fn(data, ui, callback);
}
return null;
}