Skip to content

Commit

Permalink
geosolutions-it#10736: Interactive legend for TOC layers [WFS Layer p…
Browse files Browse the repository at this point in the history
  • Loading branch information
mahmoudadel54 authored Jan 27, 2025
1 parent 4109733 commit c09ce0a
Show file tree
Hide file tree
Showing 23 changed files with 749 additions and 45 deletions.
10 changes: 8 additions & 2 deletions web/client/api/catalog/WFS.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,12 @@ const searchAndPaginate = (json = {}, startPosition, maxRecords, text) => {
};
};

const recordToLayer = (record) => {
const recordToLayer = (record, {
service
}) => {
const {
layerOptions
} = service || {};
return {
type: record.type || "wfs",
search: {
Expand All @@ -85,7 +90,8 @@ const recordToLayer = (record) => {
description: record.description || "",
bbox: record.boundingBox,
links: getRecordLinks(record),
...record.layerOptions
...record.layerOptions,
...layerOptions
};
};

Expand Down
37 changes: 36 additions & 1 deletion web/client/components/TOC/fragments/settings/Display.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import ThreeDTilesSettings from './ThreeDTilesSettings';
import ModelTransformation from './ModelTransformation';
import StyleBasedWMSJsonLegend from '../../../../plugins/TOC/components/StyleBasedWMSJsonLegend';
import { getMiscSetting } from '../../../../utils/ConfigUtils';
import VectorLegend from '../../../../plugins/TOC/components/VectorLegend';

export default class extends React.Component {
static propTypes = {
Expand Down Expand Up @@ -325,7 +326,6 @@ export default class extends React.Component {
<div style={this.setOverFlow() && this.state.containerStyle || {}} ref={this.containerRef} >
{ enableInteractiveLegend ?
<StyleBasedWMSJsonLegend
owner="legendPreview"
style={this.setOverFlow() && {} || undefined}
layer={this.props.element}
legendHeight={
Expand All @@ -351,6 +351,41 @@ export default class extends React.Component {
</Col>
</div>
</Row>}
{this.props.element.type === "wfs" && <Row>
<div className={"legend-options"}>
{experimentalInteractiveLegend && <Col xs={12} className={"legend-label"}>
<label key="legend-options-title" className="control-label"><Message msgId="layerProperties.legendOptions.title" /></label>
</Col>}
{ experimentalInteractiveLegend && !this.props?.hideInteractiveLegendOption &&
<Col xs={12} className="first-selectize">
<Checkbox
data-qa="display-interactive-legend-option"
value="enableInteractiveLegend"
key="enableInteractiveLegend"
onChange={(e) => {
if (!e.target.checked) {
const newLayerFilter = updateLayerLegendFilter(this.props.element.layerFilter);
this.props.onChange("layerFilter", newLayerFilter );
}
this.props.onChange("enableInteractiveLegend", e.target.checked);
}}
checked={enableInteractiveLegend} >
<Message msgId="layerProperties.enableInteractiveLegendInfo.label"/>
&nbsp;<InfoPopover text={<Message msgId="layerProperties.enableInteractiveLegendInfo.infoWithoutGSNote" />} />
</Checkbox>
</Col>
}
{enableInteractiveLegend && <Col xs={12} className="legend-preview">
<ControlLabel><Message msgId="layerProperties.legendOptions.legendPreview" /></ControlLabel>
<div style={this.setOverFlow() && this.state.containerStyle || {}} ref={this.containerRef} >
<VectorLegend
layer={this.props.element}
style={this.props.element.style || {}}
/>
</div>
</Col>}
</div>
</Row>}
</Grid>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ describe('test Layer Properties Display module component', () => {
expect(labels[6].innerText).toBe("layerProperties.legendOptions.legendHeight");
expect(labels[7].innerText).toBe("layerProperties.legendOptions.legendPreview");
});
it('tests Layer Properties Legend component events', () => {
it('tests wms Layer Properties Legend component events', () => {
const l = {
name: 'layer00',
title: 'Layer',
Expand Down Expand Up @@ -369,4 +369,43 @@ describe('test Layer Properties Display module component', () => {
expect(inputs[11].value).toBe("20");
expect(inputs[12].value).toBe("40");
});
it('tests wfs Layer Properties Legend component events', () => {
const l = {
name: 'layer00',
title: 'Layer',
visibility: true,
storeIndex: 9,
type: 'wfs',
url: 'fakeurl',
legendOptions: {
legendWidth: 15,
legendHeight: 15
},
enableInteractiveLegend: false
};
const settings = {
options: {
opacity: 1
}
};
const handlers = {
onChange() {}
};
let spy = expect.spyOn(handlers, "onChange");
const comp = ReactDOM.render(<Display element={l} settings={settings} onChange={handlers.onChange}/>, document.getElementById("container"));
expect(comp).toBeTruthy();
const inputs = ReactTestUtils.scryRenderedDOMComponentsWithTag( comp, "input" );
const legendPreview = ReactTestUtils.scryRenderedDOMComponentsWithClass( comp, "legend-preview" );
expect(legendPreview).toBeTruthy();
expect(inputs).toBeTruthy();
expect(inputs.length).toBe(6);
let interactiveLegendConfig = document.querySelector(".legend-options input[data-qa='display-interactive-legend-option']");
// change enableInteractiveLegend to enable interactive legend
interactiveLegendConfig.checked = true;
ReactTestUtils.Simulate.change(interactiveLegendConfig);
expect(spy).toHaveBeenCalled();
expect(spy.calls[0].arguments[0]).toEqual("enableInteractiveLegend");
expect(spy.calls[0].arguments[1]).toEqual(true);
});

});
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { FormGroup, Checkbox } from "react-bootstrap";

import Message from "../../../I18N/Message";
import InfoPopover from '../../../widgets/widget/InfoPopover';
import { getMiscSetting } from '../../../../utils/ConfigUtils';

/**
* Common Advanced settings form WMS/CSW/WMTS/WFS
Expand All @@ -24,30 +25,41 @@ export default ({
service,
onChangeServiceProperty = () => { },
onToggleThumbnail = () => { }
}) => (
<>
<FormGroup controlId="autoload" key="autoload">
{service.autoload !== undefined && <Checkbox value="autoload" onChange={(e) => onChangeServiceProperty("autoload", e.target.checked)}
checked={!isNil(service.autoload) ? service.autoload : false}>
<Message msgId="catalog.autoload" />
</Checkbox>}
</FormGroup>
<FormGroup controlId="thumbnail" key="thumbnail">
<Checkbox
onChange={() => onToggleThumbnail()}
checked={!isNil(service.hideThumbnail) ? !service.hideThumbnail : true}>
<Message msgId="catalog.showPreview" />
</Checkbox>
</FormGroup>
}) => {
const experimentalInteractiveLegend = getMiscSetting('experimentalInteractiveLegend', false);
return (
<>
<FormGroup controlId="autoload" key="autoload">
{service.autoload !== undefined && <Checkbox value="autoload" onChange={(e) => onChangeServiceProperty("autoload", e.target.checked)}
checked={!isNil(service.autoload) ? service.autoload : false}>
<Message msgId="catalog.autoload" />
</Checkbox>}
</FormGroup>
<FormGroup controlId="thumbnail" key="thumbnail">
<Checkbox
onChange={() => onToggleThumbnail()}
checked={!isNil(service.hideThumbnail) ? !service.hideThumbnail : true}>
<Message msgId="catalog.showPreview" />
</Checkbox>
</FormGroup>

{!isNil(service.type) && service.type === "cog" &&
{!isNil(service.type) && service.type === "cog" &&
<FormGroup controlId="fetchMetadata" key="fetchMetadata">
<Checkbox
onChange={(e) => onChangeServiceProperty("fetchMetadata", e.target.checked)}
checked={!isNil(service.fetchMetadata) ? service.fetchMetadata : true}>
<Message msgId="catalog.fetchMetadata.label" />&nbsp;<InfoPopover text={<Message msgId="catalog.fetchMetadata.tooltip" />} />
</Checkbox>
</FormGroup>}
{children}
</>
);
{experimentalInteractiveLegend && ['wfs'].includes(service.type) && <FormGroup className="wfs-interactive-legend" controlId="enableInteractiveLegend" key="enableInteractiveLegend">
<Checkbox data-qa="display-interactive-legend-option"
onChange={(e) => onChangeServiceProperty("layerOptions", { ...service.layerOptions, enableInteractiveLegend: e.target.checked})}
checked={!isNil(service.layerOptions?.enableInteractiveLegend) ? service.layerOptions?.enableInteractiveLegend : false}>
<Message msgId="layerProperties.enableInteractiveLegendInfo.label" />
&nbsp;<InfoPopover text={<Message msgId="layerProperties.enableInteractiveLegendInfo.infoWithoutGSNote" />} />
</Checkbox>
</FormGroup>}
{children}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ import ReactDOM from "react-dom";
import CommonAdvancedSettings from "../CommonAdvancedSettings";
import expect from "expect";
import TestUtils from "react-dom/test-utils";
import { setConfigProp } from '../../../../../utils/ConfigUtils';

describe('Test common advanced settings', () => {
beforeEach((done) => {
document.body.innerHTML = '<div id="container"></div>';
setConfigProp('miscSettings', { experimentalInteractiveLegend: true });
setTimeout(done);
});
afterEach((done) => {
ReactDOM.unmountComponentAtNode(document.getElementById("container"));
document.body.innerHTML = '';
setConfigProp('miscSettings', { });
setTimeout(done);
});
it('creates the component with defaults', () => {
Expand All @@ -38,7 +41,7 @@ describe('Test common advanced settings', () => {
const advancedSettingPanel = document.getElementsByClassName("mapstore-switch-panel");
expect(advancedSettingPanel).toBeTruthy();
const fields = document.querySelectorAll(".form-group");
expect(fields.length).toBe(2);
expect(fields.length).toBe(3);
});
it('test wms advanced options onChangeServiceProperty autoreload', () => {
const action = {
Expand All @@ -52,7 +55,7 @@ describe('Test common advanced settings', () => {
const advancedSettingPanel = document.getElementsByClassName("mapstore-switch-panel");
expect(advancedSettingPanel).toBeTruthy();
const fields = document.querySelectorAll(".form-group");
expect(fields.length).toBe(2);
expect(fields.length).toBe(3);
const autoload = document.querySelectorAll('input[type="checkbox"]')[0];
const formGroup = document.querySelectorAll('.form-group')[0];
expect(formGroup.textContent.trim()).toBe('catalog.autoload');
Expand Down Expand Up @@ -85,4 +88,14 @@ describe('Test common advanced settings', () => {
expect(spyOn).toHaveBeenCalled();
expect(spyOn.calls[1].arguments).toEqual([ 'fetchMetadata', false ]);
});
it('test showing/hiding interactive legend checkbox', () => {
ReactDOM.render(<CommonAdvancedSettings
service={{type: "wfs"}}
/>, document.getElementById("container"));
const interactiveLegendCheckboxInput = document.querySelector(".wfs-interactive-legend .checkbox input[data-qa='display-interactive-legend-option']");
expect(interactiveLegendCheckboxInput).toBeTruthy();
const interactiveLegendLabel = document.querySelector(".wfs-interactive-legend .checkbox span");
expect(interactiveLegendLabel).toBeTruthy();
expect(interactiveLegendLabel.innerHTML).toEqual('layerProperties.enableInteractiveLegendInfo.label');
});
});
4 changes: 4 additions & 0 deletions web/client/plugins/TOC/components/DefaultLayer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ const NodeLegend = ({
<li>
{visible ? <VectorLegend
style={node?.style}
layer={node}
interactive
onChange={onChange}
/> : null}
</li>
</>
Expand All @@ -81,6 +84,7 @@ const NodeLegend = ({
language={config?.language}
{...config?.layerOptions?.legendOptions}
onChange={onChange}
interactive
/> : null}
</li>
</>
Expand Down
13 changes: 9 additions & 4 deletions web/client/plugins/TOC/components/StyleBasedWMSJsonLegend.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class StyleBasedWMSJsonLegend extends React.Component {
scaleDependent: PropTypes.bool,
language: PropTypes.string,
onChange: PropTypes.func,
owner: PropTypes.string,
interactive: PropTypes.bool, // the indicator flag that refers if this legend is interactive or not
projection: PropTypes.string,
mapSize: PropTypes.object,
mapBbox: PropTypes.object
Expand All @@ -56,7 +56,7 @@ class StyleBasedWMSJsonLegend extends React.Component {
style: {maxWidth: "100%"},
scaleDependent: true,
onChange: () => {},
owner: ''
interactive: false
};
state = {
error: false,
Expand Down Expand Up @@ -159,6 +159,7 @@ class StyleBasedWMSJsonLegend extends React.Component {
const interactiveLegendFilters = get(layerFilter, 'filters', []).find(f => f.id === INTERACTIVE_LEGEND_ID);
const legendFilters = get(interactiveLegendFilters, 'filters', []);
const showResetWarning = !this.checkPreviousFiltersAreValid(rules, legendFilters) && !layerFilter.disabled;
const isNotInteractiveLegend = !this.props.interactive;
return (
<>
{showResetWarning ? <Alert bsStyle="warning">
Expand All @@ -176,11 +177,15 @@ class StyleBasedWMSJsonLegend extends React.Component {
: rules.map((rule, idx) => {
const activeFilter = legendFilters?.some(f => f.id === rule.filter);
const isFilterDisabled = this.props?.layer?.layerFilter?.disabled;
const isLegendFilterNotApplicable = isFilterDisabled || isNotInteractiveLegend || !rule?.filter;
return (
<div
className={`wms-json-legend-rule ${isFilterDisabled || this.props.owner === 'legendPreview' || !rule?.filter ? "" : "filter-enabled "} ${activeFilter ? 'active' : ''}`}
className={`wms-json-legend-rule ${isLegendFilterNotApplicable ? "" : "filter-enabled "} ${activeFilter ? 'active' : ''}`}
key={`${rule.filter}-${idx}`}
onClick={() => this.filterWMSLayerHandler(rule.filter)}>
onClick={() => {
if (isLegendFilterNotApplicable) return;
this.filterWMSLayerHandler(rule.filter);
}}>
<WMSJsonLegendIcon rule={rule} />
<span>{rule.name || rule.title || ''}</span>
</div>
Expand Down
Loading

0 comments on commit c09ce0a

Please sign in to comment.