Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@
"@carnesen/redux-add-action-listener-enhancer": "0.0.1",
"@geosolutions/geostyler-geocss-parser": "1.0.0",
"@geosolutions/geostyler-sld-parser": "2.0.1-2",
"proj4": "2.19.10",
"@geosolutions/react-joyride": "1.10.2",
"@googlemaps/js-api-loader": "1.12.9",
"@mapbox/geojsonhint": "3.3.0",
Expand All @@ -142,6 +141,7 @@
"@turf/point-on-surface": "4.1.0",
"@turf/polygon-to-linestring": "4.1.0",
"@znemz/cesium-navigation": "4.0.0",
"ajv": "8.17.1",
"assert": "2.0.0",
"axios": "0.30.0",
"babel-polyfill": "6.8.0",
Expand Down Expand Up @@ -201,6 +201,7 @@
"ol": "7.4.0",
"pdfmake": "0.2.7",
"plotly.js-cartesian-dist": "2.35.2",
"proj4": "2.19.10",
"prop-types": "15.7.2",
"qrcode.react": "0.9.3",
"query-string": "6.9.0",
Expand Down
4 changes: 2 additions & 2 deletions web/client/components/I18N/IntlNumberFormControl.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class IntlNumberFormControl extends React.Component {
parse = value => {
let formatValue = value;
// eslint-disable-next-line use-isnan
if (formatValue !== NaN && formatValue !== "NaN") { // Allow locale string to parse
if (formatValue !== '' && formatValue !== NaN && formatValue !== "NaN") { // Allow locale string to parse
const locale = this.context && this.context.intl && this.context.intl.locale || "en-US";
const format = new Intl.NumberFormat(locale);
const parts = format.formatToParts(12345.6);
Expand All @@ -164,7 +164,7 @@ class IntlNumberFormControl extends React.Component {
};

format = val => {
if (!isNaN(val) && val !== "NaN") {
if (val !== '' && !isNaN(val) && val !== "NaN") {
const locale = this.context && this.context.intl && this.context.intl.locale || "en-US";
const formatter = new Intl.NumberFormat(locale, {minimumFractionDigits: 0, maximumFractionDigits: 20});
return formatter.format(val);
Expand Down
13 changes: 12 additions & 1 deletion web/client/components/data/featuregrid/FeatureGrid.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,18 @@ class FeatureGrid extends React.PureComponent {
this.props.changes[id].hasOwnProperty(key);
},
isProperty: (k) => k === "geometry" || isProperty(k, this.props.describeFeatureType),
isValid: (val, key) => this.props.describeFeatureType ? isValidValueForPropertyName(val, key, this.props.describeFeatureType) : true
isValid: (val, key, rowId) => {
const { errors = [], changed } = (this.props?.validationErrors?.[rowId] || {});
// Extract field name from instancePath or dataPath (e.g., "/fid" -> "fid")
const error = errors.find((err) => {
const path = err.instancePath || err.dataPath || '';
return path.replace(/^[./]/, '') === key;
});
if (error) {
return { valid: false, message: error?.message, changed };
}
return { valid: this.props.describeFeatureType ? isValidValueForPropertyName(val, key, this.props.describeFeatureType) : false };
}
};
}
render() {
Expand Down
74 changes: 74 additions & 0 deletions web/client/components/data/featuregrid/editors/EnumerateEditor.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2025, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import PropTypes from 'prop-types';
import { Combobox } from 'react-widgets';
import AttributeEditor from './AttributeEditor';
import { isNil } from 'lodash';

const EnumerateEditorItem = (props) => {
const { value, label } = props.item || {};
return value === null ? <span style={{ display: 'inline-block', height: '1em' }} /> : label;
};
/**
* Editor of the FeatureGrid, that allows to enumerate options for current property
* @memberof components.data.featuregrid.editors
* @name EnumerateEditor
* @class
*/
export default class EnumerateEditor extends AttributeEditor {
static propTypes = {
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.null
]),
schema: PropTypes.object,
column: PropTypes.object,
onTemporaryChanges: PropTypes.func
};

static defaultProps = {
column: {}
};

constructor(props) {
super(props);
this.state = { selected: this.getOption(props.value) };
}

getOption = (value) => {
return { value, label: isNil(value) ? '' : `${value}` };
}

getValue = () => {
return {
[this.props.column.key]: this.state?.selected?.value
};
}

render() {
const options = (this.props?.schema?.enum || []);
const isValid = options.includes(this.state?.selected?.value);
return (
<div className={`ms-cell-editor${isValid ? '' : ' invalid'}`}>
<Combobox
value={this.state.selected}
data={options.map(this.getOption)}
valueField="value"
textField="label"
itemComponent={EnumerateEditorItem}
onChange={(selected) => {
this.setState({ selected: selected ? selected : this.getOption(null) });
}}
/>
</div>
);
}
}
49 changes: 31 additions & 18 deletions web/client/components/data/featuregrid/editors/NumberEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import React from 'react';
import PropTypes from 'prop-types';
import {isNumber} from 'lodash';
import { isNumber, castArray } from 'lodash';
import IntlNumberFormControl from '../../../I18N/IntlNumberFormControl';
import { editors } from 'react-data-grid';

Expand All @@ -29,7 +29,9 @@ export default class NumberEditor extends editors.SimpleTextEditor {
static propTypes = {
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number]),
PropTypes.number,
PropTypes.null
]),
inputProps: PropTypes.object,
dataType: PropTypes.string,
minValue: PropTypes.number,
Expand All @@ -45,12 +47,14 @@ export default class NumberEditor extends editors.SimpleTextEditor {

constructor(props) {
super(props);

this.state = {inputText: props.value?.toString?.() ?? ''};
const value = props.value?.toString?.() ?? '';
this.state = {
inputText: value,
isValid: this.validateTextValue(value),
validated: true
};
}

state = {inputText: ''};

componentDidMount() {
this.props.onTemporaryChanges?.(true);
}
Expand All @@ -62,9 +66,9 @@ export default class NumberEditor extends editors.SimpleTextEditor {

getValue() {
try {
const numberValue = parsers[this.props.dataType](this.state.inputText);
const numberValue = this.state.inputText === '' ? null : parsers[this.props.dataType](this.state.inputText);
return {
[this.props.column.key]: this.validateNumberValue(numberValue) ? numberValue : this.props.value
[this.props.column.key]: numberValue
};
} catch (e) {
return {
Expand All @@ -73,16 +77,21 @@ export default class NumberEditor extends editors.SimpleTextEditor {
}
}

getMinValue() {
return this.props?.column?.schema?.minimum ?? this.props.minValue;
}

getMaxValue() {
return this.props?.column?.schema?.maximum ?? this.props.maxValue;
}

render() {
return (<IntlNumberFormControl
return (<div className={`ms-cell-editor ${!this.state.validated || this.state.isValid ? '' : 'invalid'}`}><IntlNumberFormControl
{...this.props.inputProps}
style={!this.state.validated || this.state.isValid ? {} : {
borderColor: 'red'
}}
value={this.state.inputText}
type="number"
min={this.props.minValue}
max={this.props.maxValue}
min={this.getMinValue()}
max={this.getMaxValue()}
className="form-control"
defaultValue={this.props.value}
onKeyDown={this.props.onKeyDown}
Expand All @@ -94,14 +103,16 @@ export default class NumberEditor extends editors.SimpleTextEditor {
validated: true
});
}}
/>);
/></div>);
}

validateTextValue = (value) => {
if (value === '') {
return castArray(this.props?.column?.schema?.type || []).includes('null');
}
if (!parsers[this.props.dataType]) {
return false;
}

try {
const numberValue = parsers[this.props.dataType](value);

Expand All @@ -112,9 +123,11 @@ export default class NumberEditor extends editors.SimpleTextEditor {
};

validateNumberValue = (value) => {
const minValue = this.getMinValue();
const maxValue = this.getMaxValue();
return isNumber(value) &&
!isNaN(value) &&
(!isNumber(this.props.minValue) || this.props.minValue <= value) &&
(!isNumber(this.props.maxValue) || this.props.maxValue >= value);
(!isNumber(minValue) || minValue <= value) &&
(!isNumber(maxValue) || maxValue >= value);
};
}
Loading