State Management - digitalcityscience/TOSCA-2 GitHub Wiki
This project uses Pinia for state management with Vue 3 Composition API. Pinia is a lightweight, modular, and TypeScript-compatible alternative to Vuex.
Each function within the store modules is documented using TSDoc comments. For detailed explanations of function parameters, return values, and usage, refer directly to the corresponding TypeScript files in the store/
directory.
For example:
-
Map-related state functions → Check
store/map.ts
-
Filtering state functions → See
store/filter.ts
-
Geoserver API state functions → Refer to
store/geoserver.ts
For a complete breakdown of how each function works, refer to its respective file and TSDoc comments.
All Pinia stores are located in the src/store/
directory. Each store manages state for a specific part of the application.
📂 store
┣ 📜 buffer.ts # Manages buffer-related state
┣ 📜 draw.ts # Handles drawing tools state
┣ 📜 filter.ts # Manages filtering state
┣ 📜 geoserver.ts # Handles Geoserver interactions
┣ 📜 map.ts # Manages map-related state
┣ 📜 participation.ts # Manages participation-related state
- Manages map state (zoom, center, active layers)
- Handles interactions with MapLibre
import { defineStore } from "pinia";
import { ref } from "vue";
export const useMapStore = defineStore("map", () => {
const map = ref<any>();
const layersOnMap = ref<LayerObjectWithAttributes[]>([]);
async function addMapLayer(
params: LayerParams
): Promise<AddLayerObject | undefined> {
const {
sourceType,
identifier,
layerType,
layerStyle,
displayName,
sourceIdentifier,
showOnLayerList = true,
} = params;
if (isNullOrEmpty(map.value)) {
throw new Error("There is no map to add layer");
}
if (identifier === "") {
throw new Error("Identifier is required to add layer");
}
// Additional validation for geoserver source type
if (sourceType === "geoserver" && !("geoserverLayerDetails" in params)) {
throw new Error("Layer details required to add geoserver layers");
}
// Additional validation for geojson source type
if (sourceType === "geojson" && !("geoJSONSrc" in params)) {
throw new Error("GeoJSON data required to add GeoJSON layers");
}
const styling = generateStyling(layerType, layerStyle);
const source = sourceIdentifier ?? identifier;
const layerObject: CustomAddLayerObject = {
id: identifier,
source,
sourceType,
type: layerType,
showOnLayerList,
...styling,
// Conditional properties
...(sourceType === "geoserver" && params.sourceLayer != null
? { "source-layer": params.sourceLayer }
: {}),
...(sourceType === "geojson" && params.isFilterLayer
? {
filterLayer: params.isFilterLayer,
}
: {}),
...(sourceType === "geojson" &&
(params.isFilterLayer ||
(params.isDrawnLayer !== undefined && params.isDrawnLayer))
? {
layerData: params.geoJSONSrc,
}
: {}),
...(displayName !== undefined && displayName !== ""
? { displayName }
: {}),
};
// check if layer is raster type and add before vector layers
let beforeId;
let index;
if (layerType === "raster") {
const firstVectorLayer = layersOnMap.value.find((layer) => {
return layer.type !== "raster";
});
const indexOfFirstVectorLayer = layersOnMap.value.findIndex((layer) => {
return layer.type !== "raster";
});
if (indexOfFirstVectorLayer !== -1) {
index = indexOfFirstVectorLayer;
}
if (firstVectorLayer !== undefined) {
beforeId = firstVectorLayer.id;
}
}
// add layer object to map
map.value?.addLayer(layerObject as AddLayerObject, beforeId);
if (map.value?.getLayer(identifier) === undefined) {
throw new Error(`Couldn't add requested layer: ${identifier}`);
}
if (sourceType === "geoserver") {
(layerObject as LayerObjectWithAttributes).details =
params.geoserverLayerDetails;
if (params.workspaceName !== undefined) {
(layerObject as LayerObjectWithAttributes).workspaceName =
params.workspaceName;
}
}
add2MapLayerList(layerObject as LayerObjectWithAttributes, index);
return map.value.getLayer(identifier) as AddLayerObject;
}
return {
map,
layersOnMap,
addMapDataSource,
deleteMapDataSource,
addMapLayer,
deleteMapLayer,
removeFromMapLayerList,
resetMapData,
geometryConversion,
};
});
- Manages active attribute and geometry filters.
- Uses Turf.js for spatial filtering.
import { defineStore } from "pinia";
import { ref } from "vue";
import booleanWithin from "@turf/boolean-within";
export const useFilterStore = defineStore("filter", () => {
const appliedFiltersList = ref([]);
async function populateLayerFilter(appliedFilters: AppliedFiltersListItem, attributeRelation: RelationTypes): Promise<any[]> {
const expressionBlock: any[] = ["all"]
if (appliedFilters.attributeFilters !== undefined){
if (appliedFilters.attributeFilters.length > 0){
const appliedFilterBlock: any[] = []
if (attributeRelation === "AND"){
appliedFilterBlock.push("all")
} else {
appliedFilterBlock.push("any")
}
appliedFilters.attributeFilters.forEach((filter)=>{
if (filter.attribute.binding==="java.lang.String") {
appliedFilterBlock.push([filter.operand, ["downcase", ["get", filter.attribute.name]], filter.value.toLowerCase()])
} else {
appliedFilterBlock.push([filter.operand, ["get", filter.attribute.name], parseFloat(filter.value)])
}
})
if (appliedFilterBlock.length>0){
expressionBlock.push(appliedFilterBlock)
}
}
}
if (appliedFilters.geometryFilters !== undefined){
if (appliedFilters.geometryFilters.targetLayerSourceType === "fill"||appliedFilters.geometryFilters.targetLayerSourceType === "circle" || appliedFilters.geometryFilters.targetLayerSourceType === "line"){
if (appliedFilters.geometryFilters.filterArray!.length>0){
const geomFiltetExpression = ["in", ["get", appliedFilters.geometryFilters.identifier], ["literal", appliedFilters.geometryFilters.filterArray]]
expressionBlock.push(geomFiltetExpression)
}
}
}
if (expressionBlock.length>1){
return await Promise.resolve(expressionBlock)
} else {
return await Promise.resolve(expressionBlock)
}
}
return {
attributeList,
integerFilters,
stringFilters,
filterNames,
allowedBindings,
allowedIDBindings,
appliedFiltersList,
addAttributeFilter,
removeAttributeFilter,
addGeometryFilter,
removeGeometryFilter,
createGeometryFilter,
populateLayerFilter
};
});
- Fetches metadata and feature attributes from GeoServer.
- Retrieves bounding boxes and workspace layers.
import { defineStore } from "pinia";
import { ref } from "vue";
export const useGeoserverStore = defineStore("geoserver", () => {
const layerList = ref<GeoserverLayerListItem[]>();
const workspaceList = ref<WorkspaceListItem[]>();
async function getLayerList(
workspaceName?: string
): Promise<GeoserverLayerListResponse> {
let url = new URL(`${import.meta.env.VITE_GEOSERVER_REST_URL}/layers`);
/* eslint-disable */
if (workspaceName) {
url = new URL(
`${
import.meta.env.VITE_GEOSERVER_REST_URL
}/workspaces/${workspaceName}/layers`
);
}
const response = await fetch(url, {
method: "GET",
redirect: "follow",
headers: new Headers({
"Content-Type": "application/json",
Authorization: `Basic ${auth}`,
}),
});
return await response.json();
}
return {
pointData,
layerList,
workspaceList,
getLayerList,
getWorkspaceList,
getLayerInformation,
getLayerDetail,
getGeoJSONLayerSource,
getLayerStyling
};
});
- Handles user participation in campaigns.
- Communicates with the Django backend for participation data.
import { defineStore } from "pinia";
import { ref } from "vue";
export const useParticipationStore = defineStore("participation", () => {
const campaigns = ref([]);
function populateCampaignList(): void {
getActiveCampaigns().then((campaigns) => {
activeCampaigns.value = campaigns;
}).catch((error) => {
console.error(error);
});
}
return {
feedbackOnProgress,
feedbackStep,
sendFeedback,
isLocationSelected,
isCampaignRated,
locationSelectionOnProgress,
pointOfInterest,
startCenterSelection,
cancelCenterSelection,
finishCenterSelection,
selectedDrawnGeometry,
addToSelectedDrawnGeometry,
removeFromSelectedDrawnGeometry,
createSelectedGeometryGeoJSON,
createSelectedAreasTempLayer,
updateSelectedAreasTempLayer,
deleteSelectedAreasTempLayer,
resetSelectedAreas,
drawMode,
getCampaignDetail,
getActiveCampaigns,
activeCampaigns,
populateCampaignList
};
});
-
Use the Composition API with
defineStore
for better reactivity. -
Keep state reactive using
ref
. - Define actions as functions inside the store.
- Use Pinia DevTools for debugging state changes in Vue DevTools.
For more details, refer to the Vue 3 Pinia documentation.