Skip to content

Commit

Permalink
Merge branch '150-move-scope-selection-with-other-filters' into 'main'
Browse files Browse the repository at this point in the history
feat: move scope selection with other filters

See merge request decidim/decidim-module-geo!133
  • Loading branch information
Hadrien Froger committed Oct 10, 2024
2 parents e1ed842 + eefbcd1 commit 9ac85dd
Show file tree
Hide file tree
Showing 27 changed files with 782 additions and 726 deletions.
4 changes: 3 additions & 1 deletion app/controllers/decidim/geo/points_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def index
etag = query.cache_key_with_version + "/params-" + Digest::MD5.hexdigest(permitted_params.to_json)
if stale?(last_modified: last_modified.utc, etag: etag)
last_id = query.maximum(:id)
geo_scope_ids = query.select(:geo_scope_id).group(:geo_scope_id).pluck(:geo_scope_id).select {|id| !id.nil?}
render json: {
meta: {
fields: permitted_fields_params,
Expand All @@ -28,7 +29,8 @@ def index
default_locale: current_organization.default_locale,
first: first_params,
after: after_params,
has_more: results.last && results.last.id != last_id
has_more: results.last && results.last.id != last_id,
geo_scope_ids: geo_scope_ids || []
},
data: results
}
Expand Down
13 changes: 5 additions & 8 deletions app/packs/src/decidim/geo/api/getGeoDataSource.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { _getGeoDataSource, _getGeoDataSourceIds } from "./queries";



const getGeoDataSource = async (params = {}, fetchAll = true, callback) => {
const { filters = [], locale, isIndex = false, after = 0, first = 50 } = params;
const fields = fetchAll
Expand All @@ -21,7 +19,7 @@ const getGeoDataSource = async (params = {}, fetchAll = true, callback) => {
"imageUrl",
"latitude",
"longitude",
"scopeId",
"geoScopeId",
"lonlat",
"extendedData"
]
Expand All @@ -34,8 +32,8 @@ const getGeoDataSource = async (params = {}, fetchAll = true, callback) => {
first
});
filters.forEach((f) => {
searchParams.append("filters[]", JSON.stringify(f))
})
searchParams.append("filters[]", JSON.stringify(f));
});
fields.forEach((f) => searchParams.append("fields[]", f));
let page;
try {
Expand All @@ -48,7 +46,7 @@ const getGeoDataSource = async (params = {}, fetchAll = true, callback) => {
}
if (!page) return { nodes: [], edges: [] };
const { has_more: hasMoreThanOne, end_cursor: endCursor } = page?.meta || {};
callback(page.data, hasMoreThanOne)
callback(page.data, hasMoreThanOne, page?.meta || {});
if (!hasMoreThanOne) return;

searchParams.set("after", endCursor);
Expand All @@ -62,10 +60,9 @@ const getGeoDataSource = async (params = {}, fetchAll = true, callback) => {
return;
}
const { end_cursor: endCursor, has_more: hasMore } = page.meta || {};
callback(page.data, hasMore)
callback(page.data, hasMore, page?.meta || {});
if (!hasMore) break;
searchParams.set("after", endCursor);
}

};
export default getGeoDataSource;
88 changes: 54 additions & 34 deletions app/packs/src/decidim/geo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import filterStore from "./stores/filterStore";
import geoStore from "./stores/geoStore";
import memoryStore from "./stores/memoryStore";
import dropdownFilterStore from "./stores/dropdownFilterStore";
import { initMap, ScopeDropdown, Drawer, aside } from "./ui";
import { initMap, DrawerHeader, Drawer, aside } from "./ui";
import bootstrap from "./bootstrap";
import registerMobile from "./ui/mobile/registerMobile";
import FilterControl from "./ui/FilterControl";

window.debug = window.debug || {};
window.debug.stores = () => ({
Expand All @@ -28,112 +29,131 @@ async function prepareLeaflet(isSmallScreen) {
configStore.setState(() => ({ map, tile, isSmallScreen }));
}
async function fetchData() {
const { addProcess, removeProcess, fetchAll } = pointStore.getState();
const { addProcess, removeProcess, fetchAll, pointsForFilters } = pointStore.getState();
const { defaultFilters, activeFilters } = filterStore.getState();
addProcess();
// Fetch all the data
await fetchAll(filterStore.getState().defaultFilters)
await Promise.all([fetchAll(defaultFilters), pointsForFilters(activeFilters)]);
removeProcess();
}
async function displayMap() {
try {
const { addProcess, removeProcess } = pointStore.getState();
const { moveHandler, setSavedPosition } = memoryStore.getState();
const { map } = configStore.getState();
const { map, pointsLayer, scopeLayer } = configStore.getState();
addProcess();

// Be sure to fit all the points whenever you change
// any filter.
const pointsLayer = L.layerGroup();
map.addLayer(scopeLayer);
map.addLayer(pointsLayer);

map.whenReady(async () => {
// Add the aside to the map
aside([DrawerHeader, Drawer]);

map.addControl(new FilterControl());

// Save the first loaded position.
await new Promise((resolve) => setTimeout(resolve, 120));
setSavedPosition();
configStore.getState().setReady();
removeProcess();
setTimeout(setSavedPosition, 320);
});

pointStore.subscribe(
(state) => [!!state.isLoading, state.getFilteredPoints, state.fetchesRunning, state._lastResponse],
(state) => [
!!state.isLoading,
state.getFilteredPoints,
state.fetchesRunning,
state._lastResponse
],
([isLoading, getFilteredPoints, fetchesRunning]) => {
if (isLoading > 0) return;
const { space_ids: spaceIds, map } = configStore.getState();
if (isLoading > 0) {
return;
}
const { map, pointsLayer } = configStore.getState();
const { selectedPoint, selectedScope } = geoStore.getState();
const { savedCenter } = memoryStore.getState();
pointsLayer.clearLayers();
let boudingBoxFilter = () => true;
if (!fetchesRunning && !selectedPoint && !selectedScope && spaceIds) {
boudingBoxFilter = (node) =>
node.isGeoLocated() && spaceIds.includes(`${node.scopeId}`);
}
if (!fetchesRunning && selectedScope?.layer && selectedScope && !selectedPoint) {
map.fitBounds(selectedScope.layer.getBounds(), { padding: [64, 64] });
if (
fetchesRunning === 0 &&
selectedScope?.layer &&
selectedScope &&
!selectedPoint
) {
map.setView(selectedScope.layer.getBounds().getCenter(), map.getZoom(), {
animation: true
});
}

const pointInMap = getFilteredPoints().filter(
(node) => typeof node.isGeoLocated !== "undefined" && node.isGeoLocated()
);

if (pointInMap.length > 0) {
pointInMap.forEach(({ marker }) => {
pointsLayer.addLayer(marker);
});
const idealBoundingBox = pointInMap
.filter(boudingBoxFilter)
.map(({ marker }) => marker);
}
if (
pointInMap.length > 0 &&
!fetchesRunning &&
!savedCenter &&
!selectedScope &&
!selectedPoint
) {
const idealBoundingBox = pointInMap.map(({ marker }) => marker);
const boundingBox = L.featureGroup(
_.isEmpty(idealBoundingBox)
? pointInMap.map(({ marker }) => marker)
: idealBoundingBox,
{ updateWhenZooming: true }
);
if (!fetchesRunning && !savedCenter && boundingBox && !selectedScope && !selectedPoint) {
map.fitBounds(boundingBox.getBounds(), { padding: [64, 64] });
}

map.fitBounds(boundingBox.getBounds(), { padding: [64, 64] });
}
}
);
aside([ScopeDropdown, Drawer]);

// Set active class on dropdown element
geoStore.subscribe(
(state) => [state.selectedScope],
([geoScope]) => {
// Remove all active classes.
const activeList = document.getElementsByClassName(
"decidimGeo__scopesDropdown__listItem--active"
"decidimGeo__drawerHeader__listItem--active"
);
for (const domEl of activeList) {
domEl.className = domEl.className
.replace("decidimGeo__scopesDropdown__listItem--active", "")
.replace("decidimGeo__drawerHeader__listItem--active", "")
.trim();
}
const [container] = document.querySelectorAll(
".decidimGeo__scopesDropdown__list"
);
const [container] = document.querySelectorAll(".decidimGeo__drawerHeader__list");
if (!container) return;
if (geoScope?.id) {
const [active] = document.querySelectorAll(
`.decidimGeo__scopesDropdown__listItem[data-scope='${geoScope.id}']`
`.decidimGeo__drawerHeader__listItem[data-scope='${geoScope.id}']`
);
if (active) {
active.className += " decidimGeo__scopesDropdown__listItem--active";
active.className += " decidimGeo__drawerHeader__listItem--active";
container.scrollBy(active.getBoundingClientRect().left, 0);
}
} else {
const [active] = document.querySelectorAll(
`.decidimGeo__scopesDropdown__listItem[data-scope='all']`
`.decidimGeo__drawerHeader__listItem[data-scope='all']`
);
if (active) {
active.className += " decidimGeo__scopesDropdown__listItem--active";
active.className += " decidimGeo__drawerHeader__listItem--active";
container.scrollBy(active.getBoundingClientRect().left, 0);
}
}
}
);
const [active] = document.querySelectorAll(
`.decidimGeo__scopesDropdown__listItem[data-scope='all']`
`.decidimGeo__drawerHeader__listItem[data-scope='all']`
);
if (active) active.className += " decidimGeo__scopesDropdown__listItem--active";
if (active) active.className += " decidimGeo__drawerHeader__listItem--active";

map.on("moveend", moveHandler);
} catch (e) {
Expand Down
7 changes: 5 additions & 2 deletions app/packs/src/decidim/geo/models/GeoDatasourceNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default class GeoDatasourceNode {
const { map } = configStore.getState();
map.eachLayer((layer) => {
if (layer.feature) {
if (layer.feature.geometry.properties.id === this.scopeId) {
if (layer.feature.geometry.properties.id === this.geoScopeId) {
layer.setStyle(this.selectedState);
} else {
layer.setStyle(this.staledState);
Expand All @@ -75,7 +75,10 @@ export default class GeoDatasourceNode {
}

get scopeId() {
return parseInt(`${this.data.scopeId}`) || undefined;
return parseInt(`${this.data.geoScopeId}`) || undefined;
}
get geoScopeId() {
return this.scopeId;
}

get selectedState() {
Expand Down
22 changes: 13 additions & 9 deletions app/packs/src/decidim/geo/models/GeoScope.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { scopeDropdownItem, scopeGeoLayer } from "./../ui";
import geoStore from "../stores/geoStore";
import pointStore from "../stores/pointStore";
import configStore from "../stores/configStore";
import scopeDropdownStore from "../stores/scopeDropdownStore";
import polylabel from "polylabel";
import _ from "lodash";

Expand All @@ -19,11 +18,11 @@ export default class GeoScope {
return { fillColor: "#2952A370", color: "#2952A3" };
}

isEmpty() {
isEmpty(points = undefined) {
if (!points) points = pointStore.getState().points;
if (this.data.geom === null) return true;
const { points } = pointStore.getState();
const currentScopeId = this.id;
return !points.find(({ scopeId }) => `${scopeId}` === `${currentScopeId}`);
return !points.find(({ geoScopeId }) => `${geoScopeId}` === `${currentScopeId}`);
}

isLoading() {
Expand All @@ -46,7 +45,7 @@ export default class GeoScope {
*/
isActive() {
const { selectedScope } = geoStore.getState();
if (selectedScope) return selectedScope === this;
if (selectedScope) return selectedScope.id === this.id;
return false;
}

Expand All @@ -72,7 +71,9 @@ export default class GeoScope {
nodesForScope() {
const { points } = pointStore.getState();
const currentScopeId = this.id;
return points.filter(({ scopeId }) => scopeId === currentScopeId).filter(Boolean);
return points
.filter(({ geoScopeId }) => `${geoScopeId}` === `${currentScopeId}`)
.filter(Boolean);
}

markersForScope() {
Expand All @@ -89,17 +90,20 @@ export default class GeoScope {
.filter(Boolean)
);
}

scopeClickHandler() {
this.select("layer");
}
init() {
remove() {
const { map } = configStore.getState();
map.removeLayer(this.layer);
}
init(mapLayer) {
this.markers_group = this.markersForScope();
const [itm, repaintItm] = scopeDropdownItem({
scopeId: this.id,
label: this.name,
onClick: () => {
scopeDropdownStore.getState().toggleOpen();
geoStore.getState().selectScope(this);
this.repaint();
}
Expand All @@ -116,7 +120,7 @@ export default class GeoScope {
onClick: this.scopeClickHandler.bind(this)
});
this.layer.bringToBack();
map.addLayer(this.layer);
mapLayer.addLayer(this.layer);

// Add the layer only when we are sure there is some point
// in the layer.
Expand Down
11 changes: 10 additions & 1 deletion app/packs/src/decidim/geo/stores/configStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const store = createStore(
tile: undefined,
map: undefined,
mapReady: false,
pointsLayer: L.layerGroup(),
scopeLayer: L.layerGroup(),
isIndex: false,
mapID: "Generic",
map_config: {},
Expand All @@ -23,6 +25,7 @@ const store = createStore(
isSmallScreen: false,
activeManifests: [],
closeAside: () => set({ isAsideOpen: false }),
openAside: () => set({ isAsideOpen: true }),
setFullscreen: (fullscreen) => set({ isFullscreen: !!fullscreen }),
setReady: () => set({ mapReady: true }),
setConfig: (mapConfig) => {
Expand All @@ -44,12 +47,18 @@ const store = createStore(
setFilters(mapConfig.filters);

dropdownFilterStore.getState().setDefaultFilters({
GeoScopeFilter: toFilterOptions("GeoScopeFilter", mapConfig.filters),
GeoShowFilter: toFilterOptions("GeoShowFilter", mapConfig.filters),
GeoTimeFilter: toFilterOptions("GeoTimeFilter", mapConfig.filters),
GeoType: toFilterOptions("GeoType", mapConfig.filters)
});
}
}))
);

store.subscribe(
({ isAsideOpen }) => [isAsideOpen],
([isAsideOpen], [wasAsideOpen]) => {
if (isAsideOpen && !wasAsideOpen) dropdownFilterStore.getState().close();
}
);
export default store;
Loading

0 comments on commit 9ac85dd

Please sign in to comment.