Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR Feature/1570-layercomparerselection #1573

Merged
merged 13 commits into from
Jan 7, 2025
Merged
159 changes: 159 additions & 0 deletions apps/admin/src/views/components/LayerComparerLayerList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import React from "react";

class LayerComparerLayerList extends React.Component {
constructor(props) {
super(props);
this.state = {
filterText: "",
sortAsc: true,
chosenLayers: this.props.chosenLayers || [],
};
}

componentDidUpdate(prevProps) {
if (prevProps.chosenLayers !== this.props.chosenLayers) {
this.setState({ chosenLayers: this.props.chosenLayers });
}
}

handleFilterChange = (e) => {
this.setState({ filterText: e.target.value });
};

handleSortToggle = () => {
this.setState((prevState) => ({ sortAsc: !prevState.sortAsc }));
};

handleLayerToggle = (layer) => {
this.setState((prevState) => {
const { chosenLayers } = prevState;
const exists = chosenLayers.some((l) => l.id === layer.id);
let updated;
if (exists) {
updated = chosenLayers.filter((l) => l.id !== layer.id);
} else {
updated = [...chosenLayers, layer];
}

if (this.props.onChosenLayersChange) {
this.props.onChosenLayersChange(updated);
}

return { chosenLayers: updated };
});
};

getFilteredAndSortedLayers() {
const { allLayers = [] } = this.props;
const { filterText, sortAsc } = this.state;

const filteredLayers = allLayers
.filter((layer) =>
(layer.caption || layer.id || "")
.toLowerCase()
.includes(filterText.toLowerCase())
)
.sort((a, b) => {
const aVal = (a.caption || a.id || "").toLowerCase();
const bVal = (b.caption || b.id || "").toLowerCase();
if (aVal < bVal) return sortAsc ? -1 : 1;
if (aVal > bVal) return sortAsc ? 1 : -1;
return 0;
});

return filteredLayers;
}

selectAllLayers = () => {
const filteredLayers = this.getFilteredAndSortedLayers();
const { chosenLayers } = this.state;

const allSelected =
filteredLayers.length > 0 &&
filteredLayers.every((layer) =>
chosenLayers.some((chosen) => chosen.id === layer.id)
);

let updated;
if (allSelected) {
updated = chosenLayers.filter(
(chosen) => !filteredLayers.some((fLayer) => fLayer.id === chosen.id)
);
} else {
const missing = filteredLayers.filter(
(l) => !chosenLayers.some((chosen) => chosen.id === l.id)
);
updated = [...chosenLayers, ...missing];
}

this.setState({ chosenLayers: updated });
if (this.props.onChosenLayersChange) {
this.props.onChosenLayersChange(updated);
}
};

render() {
const { chosenLayers, filterText, sortAsc } = this.state;
const filteredLayers = this.getFilteredAndSortedLayers();

const containerStyle = {
maxWidth: "600px",
border: "1px solid #ccc",
padding: "10px",
textAlign: "left",
};

const listContainerStyle = {
maxHeight: "200px",
overflowY: "auto",
border: "1px solid #ddd",
padding: "5px",
marginTop: "10px",
};

return (
<div style={containerStyle}>
<h3>Lager</h3>
<div style={{ marginBottom: "10px" }}>
<input
type="text"
placeholder="Filtrera"
value={filterText}
onChange={this.handleFilterChange}
style={{ marginRight: "10px", width: "80%" }}
/>
<button
type="button"
onClick={this.handleSortToggle}
style={{ marginRight: "10px" }}
>
Sortera {sortAsc ? "A-Ö" : "Ö-A"}
</button>
<button type="button" onClick={this.selectAllLayers}>
Välj alla / rensa alla
</button>
</div>

<div style={listContainerStyle}>
<ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
{filteredLayers.map((layer) => {
const isChecked = chosenLayers.some((l) => l.id === layer.id);
return (
<li key={layer.id} style={{ marginBottom: "5px" }}>
<input
type="checkbox"
checked={isChecked}
onChange={() => this.handleLayerToggle(layer)}
/>{" "}
{layer.caption || layer.id}
</li>
);
})}
</ul>
</div>
</div>
);
}
}

export default LayerComparerLayerList;
117 changes: 117 additions & 0 deletions apps/admin/src/views/tools/layercomparer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Button from "@material-ui/core/Button";
import SaveIcon from "@material-ui/icons/SaveSharp";
import { withStyles } from "@material-ui/core/styles";
import { blue } from "@material-ui/core/colors";
import LayerComparerLayerList from "../components/LayerComparerLayerList";

const ColorButtonBlue = withStyles((theme) => ({
root: {
Expand All @@ -24,6 +25,9 @@ const defaultState = {
instruction: "",
visibleAtStart: false,
visibleForGroups: [],
chosenLayers: [],
selectChosenLayers: false,
layers: [],
};

class LayerComparer extends Component {
Expand All @@ -49,14 +53,99 @@ class LayerComparer extends Component {
visibleForGroups: tool.options.visibleForGroups
? tool.options.visibleForGroups
: [],
chosenLayers: tool.options.chosenLayers
? tool.options.chosenLayers
: [],
selectChosenLayers: tool.options.selectChosenLayers,
});
this.loadLayers();
} else {
this.setState({
active: false,
});
}
}

handleSelectChosenLayersChange = (checked) => {
this.setState((prevState) => ({
selectChosenLayers: checked,
chosenLayers: checked ? prevState.chosenLayers : [],
showNonBaseLayersInSelect: checked
? false
: prevState.showNonBaseLayersInSelect,
}));

if (checked) {
this.loadLayers();
}
};

/* Extracts all layers from the provided options, including both grouped layers and baselayers.
Initializes an empty array to store the collected layers.
Defines a helper function to recursively collect layers from each group.
If a group contains layers, those layers are added to the layers array.
If a group contains subgroups, the helper function is called recursively for each subgroup.
Iterates over each group in options.groups and collects their layers using the helper function.
Adds all baselayers from options.baselayers to the layers array.
Returns the complete array of collected layers.*/
getLayersFromGroupsAndBaselayers(options) {
const layers = [];

function collectLayersFromGroup(group) {
if (group.layers) {
layers.push(...group.layers);
}
if (group.groups) {
group.groups.forEach(collectLayersFromGroup);
}
}

if (options.groups) {
options.groups.forEach(collectLayersFromGroup);
}

if (options.baselayers) {
layers.push(...options.baselayers);
}

return layers;
}

/*
* Loads layers based on the current map configuration.
* 1. Retrieves the layer menu configuration from the model.
* 2. Logs an error and exits if the configuration is missing.
* 3. Extracts layers from groups and baselayers using a helper method.
* 4. Retrieves all available layers from the model.
* 5. Filters and maps the extracted layers to match the available layers.
* 6. Updates the component's state with the filtered layers.
*/
loadLayers() {
const mapConfig = this.props.model.get("layerMenuConfig");

if (!mapConfig) {
console.error("Map configuration could not be loaded.");
return;
}

const layerMenuConfigLayers =
this.getLayersFromGroupsAndBaselayers(mapConfig);

const allLayers = this.props.model.get("layers");

const comparedLayers = layerMenuConfigLayers
.filter((lsLayer) => allLayers.some((al) => al.id === lsLayer.id))
.map((lsLayer) => {
const realLayer = allLayers.find((al) => al.id === lsLayer.id);
return {
id: realLayer.id,
caption: realLayer.caption || "Okänt lager",
};
});

this.setState({ layers: comparedLayers });
}

/**
*
*/
Expand Down Expand Up @@ -118,6 +207,8 @@ class LayerComparer extends Component {
Function.prototype.call,
String.prototype.trim
),
chosenLayers: this.state.chosenLayers,
selectChosenLayers: this.state.selectChosenLayers,
},
};

Expand Down Expand Up @@ -202,6 +293,7 @@ class LayerComparer extends Component {
*
*/
render() {
const { layers } = this.state;
return (
<div>
<form>
Expand Down Expand Up @@ -286,6 +378,7 @@ class LayerComparer extends Component {
this.handleInputChange(e);
}}
checked={this.state.showNonBaseLayersInSelect}
disabled={this.state.selectChosenLayers}
/>
&nbsp;
<label
Expand Down Expand Up @@ -315,6 +408,30 @@ class LayerComparer extends Component {
value={this.state.instruction ? atob(this.state.instruction) : ""}
/>
</div>
<div>
<label style={{ paddingBottom: "20px" }}>
<input
type="checkbox"
checked={this.state.selectChosenLayers}
onChange={(e) =>
this.handleSelectChosenLayersChange(e.target.checked)
}
/>
Aktivera "Välj lager"
</label>
</div>
<div>
{this.state.selectChosenLayers && (
<LayerComparerLayerList
allLayers={layers}
chosenLayers={this.state.chosenLayers}
onChosenLayersChange={(updatedLayers) =>
this.setState({ chosenLayers: updatedLayers })
}
/>
)}
</div>

{this.renderVisibleForGroups()}
</form>
</div>
Expand Down
29 changes: 29 additions & 0 deletions apps/backend/server/apis/v2/services/settings.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,24 @@ class SettingsService {
return group;
};

const removeLayerIdFromLayerComparer = (layerComparerOptions) => {
// Remove the layerId from the chosenLayers array
if (Array.isArray(layerComparerOptions.chosenLayers)) {
const initialLength = layerComparerOptions.chosenLayers.length;
layerComparerOptions.chosenLayers =
layerComparerOptions.chosenLayers.filter(
(layer) => layer.id !== layerId
);

// If any layers were removed, mark as modified
if (layerComparerOptions.chosenLayers.length !== initialLength) {
modified = true;
}
}

return layerComparerOptions;
};

// Helper function, used to remove references to a layer from Search tool's options
const removeLayerIdFromSearchSources = (searchSources) => {
const index = searchSources.findIndex((l) => l === layerId);
Expand Down Expand Up @@ -251,6 +269,17 @@ class SettingsService {
);
}

// LayerComparer processing
const layerComparerToolIndex = json.tools.findIndex(
(t) => t.type === "layercomparer"
);
const layerComparerOptions = json.tools[layerComparerToolIndex]?.options;

if (layerComparerOptions) {
json.tools[layerComparerToolIndex].options =
removeLayerIdFromLayerComparer(layerComparerOptions);
}

// If any of the above resulted in modified file, write the changes.
// Make sure to first check that we really have new options to write
// as we don't want to write an undefined "object".
Expand Down
Loading