basic signal value editor

close #109
main
Joost Wooning 2022-03-18 16:11:42 +01:00
parent 5952632816
commit a9803f335f
8 changed files with 114 additions and 18 deletions

View File

@ -95,15 +95,6 @@
}
&-form {
&-field {
&-error {
input,
select {
border-color: rgb(220, 0, 0);
&:focus {
border-color: rgb(220, 0, 0);
}
}
}
min-height: $input-height;
padding: 1px 0;
@extend %clearfix;
@ -131,3 +122,13 @@
}
}
}
.form-field-error {
input,
select {
border-color: rgb(220, 0, 0);
&:focus {
border-color: rgb(220, 0, 0);
}
}
}

View File

@ -106,5 +106,10 @@ export default [
field: 'max',
title: 'Maximum value',
type: 'number'
},
{
field: 'valueDescriptions',
title: 'Value descriptions',
type: 'map'
}
];

View File

@ -2,9 +2,10 @@ import React, { Component } from 'react';
export default class Field extends Component {
render() {
const { title, htmlFor, children } = this.props;
const { title, htmlFor, children, valid } = this.props;
const errorCls = valid === false ? ' form-field-error' : '';
return (
<div className="form-field form-field--small">
<div className={ `form-field form-field--small${errorCls}` }>
<label htmlFor={htmlFor}>{title}</label>
{children}
</div>

View File

@ -0,0 +1,81 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Field from './Field';
export default class MapField extends Component {
static propTypes = {
fieldSpec: PropTypes.any,
signal: PropTypes.any,
isExpanded: PropTypes.any,
signalEdited: PropTypes.any,
updateField: PropTypes.any,
valid: PropTypes.any,
};
constructor(props) {
super(props);
this.state = {
valid: true,
mapString: '',
};
this.onChange = this.onChange.bind(this);
}
componentDidMount() {
this.componentDidUpdate({}, {});
}
componentDidUpdate(prevProps) {
if (prevProps.isExpanded !== this.props.isExpanded) {
const entryPairs = Array.from(this.props.signalEdited.entries());
const mapString = entryPairs.reduce((str, [value, desc]) => `${str + value} "${desc}" `, '').trim();
this.setState({ mapString });
}
}
onChange(ev) {
const mapString = ev.target.value;
this.setState({ mapString });
if ((mapString.split('"').length - 1) % 2 !== 0) {
this.setState({ valid: false });
return;
}
let splitted = Array.from(mapString.matchAll(/[^\s"]+|"([^"]*)"/g));
if (splitted.length % 2 !== 0) {
this.setState({ valid: false });
return;
}
splitted = splitted.map(([match, group]) => group || match);
const res = new Map();
for (let i = 0; i < splitted.length; i += 2) {
res.set(splitted[i], splitted[i+1]);
}
this.setState({ valid: true });
this.props.updateField(this.props.fieldSpec, res);
}
render() {
const { fieldSpec, signal, isExpanded } = this.props;
const htmlFor = `${signal.name}_${fieldSpec.field}`;
let valueCol;
if (isExpanded) {
valueCol = <input id={htmlFor} type="text" value={this.state.mapString} onChange={this.onChange} />;
} else {
valueCol = <span>{signal[fieldSpec.field]}</span>;
}
return (
<Field title={typeof fieldSpec.title === 'function' ? fieldSpec.title(signal) : fieldSpec.title}
htmlFor={htmlFor} valid={this.props.valid && this.state.valid}>
{valueCol}
</Field>
);
}
}

View File

@ -6,7 +6,8 @@ export default ({
signal,
isExpanded,
signalEdited,
updateField
updateField,
valid
}) => {
const { field, title } = fieldSpec;
const htmlFor = `${signal.name}_${field}`;
@ -36,6 +37,7 @@ export default ({
<Field
title={typeof title === 'function' ? title(signal) : title}
htmlFor={htmlFor}
valid={valid}
>
{valueCol}
</Field>

View File

@ -8,7 +8,8 @@ export default ({
signal,
isExpanded,
signalEdited,
updateField
updateField,
valid
}) => {
let valueCol;
const { field, title } = fieldSpec;
@ -41,6 +42,7 @@ export default ({
<Field
title={typeof title === 'function' ? title(signal) : title}
htmlFor={htmlFor}
valid={valid}
>
{valueCol}
</Field>

View File

@ -1,15 +1,16 @@
import React from 'react';
import cx from 'classnames';
import FIELDS from './FIELDS';
import NumberField from './NumberField';
import StringField from './StringField';
import OptionField from './OptionField';
import MapField from './MapField';
const FieldMap = {
number: NumberField,
option: OptionField,
string: StringField
string: StringField,
map: MapField,
};
export default ({
@ -23,15 +24,16 @@ export default ({
<div className="signals-legend-entry-form">
{FIELDS.map((field) => {
const Node = FieldMap[field.type];
const errorClass = fieldError === field.field ? 'signals-legend-entry-form-field-error' : null;
const valid = fieldError !== field.field;
return (
<div className={cx("signals-legend-entry-form-field", errorClass)} key={field.field}>
<div className="signals-legend-entry-form-field" key={field.field}>
<Node
fieldSpec={field}
signal={signal}
isExpanded={isExpanded}
signalEdited={getSignalEdited(field.field)}
updateField={update}
valid={valid}
/>
</div>
);

View File

@ -6,7 +6,8 @@ export default ({
signal,
isExpanded,
signalEdited,
updateField
updateField,
valid
}) => {
const { field, title } = fieldSpec;
const htmlFor = `${signal.name}_${field}`;
@ -30,6 +31,7 @@ export default ({
<Field
title={typeof title === 'function' ? title(signal) : title}
htmlFor={htmlFor}
valid={valid}
>
{valueCol}
</Field>