decidim-decidim_geo 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE-AGPLv3.txt +661 -0
- data/README.md +172 -0
- data/Rakefile +119 -0
- data/app/cells/decidim/geo/content_blocks/geo_maps/show.erb +15 -0
- data/app/cells/decidim/geo/content_blocks/geo_maps_cell.rb +114 -0
- data/app/cells/decidim/geo/content_blocks/geo_maps_settings_form_cell.rb +14 -0
- data/app/cells/decidim/geo/geo_collection_map/show.erb +4 -0
- data/app/cells/decidim/geo/geo_collection_map_cell.rb +102 -0
- data/app/commands/decidim/admin/create_scope.rb +56 -0
- data/app/commands/decidim/admin/create_scope_type.rb +47 -0
- data/app/commands/decidim/admin/update_scope.rb +60 -0
- data/app/commands/decidim/admin/update_scope_type.rb +62 -0
- data/app/commands/decidim/geo/admin/create_shapefile.rb +51 -0
- data/app/commands/decidim/geo/admin/update_geo_config.rb +54 -0
- data/app/commands/decidim/geo/command.rb +14 -0
- data/app/controllers/decidim/admin/scopes_controller.rb +115 -0
- data/app/controllers/decidim/geo/admin/application_controller.rb +21 -0
- data/app/controllers/decidim/geo/admin/geo_configs_controller.rb +44 -0
- data/app/controllers/decidim/geo/admin/shapefiles_controller.rb +41 -0
- data/app/controllers/decidim/geo/application_controller.rb +13 -0
- data/app/controllers/decidim/geo/shapefile_controller.rb +11 -0
- data/app/forms/decidim/admin/scope_form.rb +43 -0
- data/app/forms/decidim/admin/scope_type_form.rb +27 -0
- data/app/forms/decidim/geo/admin/geo_config_form.rb +20 -0
- data/app/forms/decidim/geo/admin/shapefile_form.rb +23 -0
- data/app/helpers/decidim/admin/scopes_helper.rb +72 -0
- data/app/jobs/decidim/geo/shp2pgsql_job.rb +14 -0
- data/app/models/decidim/debates/debate.rb +226 -0
- data/app/models/decidim/geo/application_record.rb +10 -0
- data/app/models/decidim/geo/geo_config.rb +28 -0
- data/app/models/decidim/geo/shapedata.rb +23 -0
- data/app/models/decidim/geo/shapefile.rb +27 -0
- data/app/models/decidim/geo/space_location.rb +16 -0
- data/app/models/decidim/scope.rb +111 -0
- data/app/models/decidim/scope_type.rb +26 -0
- data/app/overrides/decidim/assemblies/admin/assemblies/_form/insert_location_form.html.erb.deface +13 -0
- data/app/overrides/decidim/assemblies/assemblies/show/insert_location.html.erb.deface +7 -0
- data/app/overrides/decidim/participatory_processes/admin/participatory_processes/_form/insert_location_form.html.erb.deface +13 -0
- data/app/overrides/decidim/participatory_processes/participatory_process_groups/show/insert_map.html.erb.deface +6 -0
- data/app/overrides/layouts/decidim/_assembly_header/insert_map.html.erb.deface +25 -0
- data/app/overrides/layouts/decidim/_process_header/insert_map.html.erb.deface +22 -0
- data/app/overrides/layouts/decidim/_wrapper/insert_map.html.erb.deface +5 -0
- data/app/overrides/remove_default_meetings_map.rb +4 -0
- data/app/overrides/remove_default_proposals_map.rb +4 -0
- data/app/packs/entrypoints/decidim_geo.js +3 -0
- data/app/packs/images/decidim/geo/not-geolocated.svg +3 -0
- data/app/packs/src/decidim/geo/actions/index.js +25 -0
- data/app/packs/src/decidim/geo/api/index.js +53 -0
- data/app/packs/src/decidim/geo/api/queries.js +77 -0
- data/app/packs/src/decidim/geo/bootstrap.js +6 -0
- data/app/packs/src/decidim/geo/index.js +101 -0
- data/app/packs/src/decidim/geo/models/configStore.js +44 -0
- data/app/packs/src/decidim/geo/models/dropdownFilterStore.js +65 -0
- data/app/packs/src/decidim/geo/models/filterStore.js +121 -0
- data/app/packs/src/decidim/geo/models/geoDatasourceNode/GeoDatasourceNode.js +124 -0
- data/app/packs/src/decidim/geo/models/geoDatasourceNode/index.js +1 -0
- data/app/packs/src/decidim/geo/models/geoScope/GeoScope.js +128 -0
- data/app/packs/src/decidim/geo/models/geoScope/index.js +1 -0
- data/app/packs/src/decidim/geo/models/geoStore.js +118 -0
- data/app/packs/src/decidim/geo/models/pointStore.js +134 -0
- data/app/packs/src/decidim/geo/models/scopeDropdownStore.js +20 -0
- data/app/packs/src/decidim/geo/ui/DrawerDetail/fallback.js +42 -0
- data/app/packs/src/decidim/geo/ui/DrawerDetail/index.js +2 -0
- data/app/packs/src/decidim/geo/ui/DrawerDetail/meetings.js +75 -0
- data/app/packs/src/decidim/geo/ui/DrawerMenuItem/fallback.js +45 -0
- data/app/packs/src/decidim/geo/ui/DrawerMenuItem/index.js +2 -0
- data/app/packs/src/decidim/geo/ui/DrawerMenuItem/meetings.js +62 -0
- data/app/packs/src/decidim/geo/ui/createClasses.js +7 -0
- data/app/packs/src/decidim/geo/ui/createCustomMarker.js +14 -0
- data/app/packs/src/decidim/geo/ui/createDomElement.js +2 -0
- data/app/packs/src/decidim/geo/ui/createDrawer.js +170 -0
- data/app/packs/src/decidim/geo/ui/createDrawerActions.js +197 -0
- data/app/packs/src/decidim/geo/ui/createFilterDropdown.js +320 -0
- data/app/packs/src/decidim/geo/ui/createGeoScopeLayer.js +20 -0
- data/app/packs/src/decidim/geo/ui/createGeoScopeMenuItem.js +8 -0
- data/app/packs/src/decidim/geo/ui/createNodeMarker.js +9 -0
- data/app/packs/src/decidim/geo/ui/createNodeMenuItem.js +13 -0
- data/app/packs/src/decidim/geo/ui/index.js +8 -0
- data/app/packs/src/decidim/geo/ui/initMap.js +22 -0
- data/app/packs/src/decidim/geo/utils/index.js +30 -0
- data/app/packs/src/decidim/geo/utils/saveConfig.js +15 -0
- data/app/packs/stylesheets/decidim/geo/_geo.scss +2 -0
- data/app/packs/stylesheets/decidim/geo/geo/_decidim_geo_decidimGeo.scss +476 -0
- data/app/packs/stylesheets/decidim/geo/geo/_decidim_geo_override.scss +65 -0
- data/app/permissions/decidim/geo/admin/permissions.rb +24 -0
- data/app/permissions/decidim/geo/permissions.rb +15 -0
- data/app/uploaders/decidim/geo/shapefile_uploader.rb +14 -0
- data/app/validators/geocoding_validator.rb +30 -0
- data/app/views/decidim/admin/scope_types/_form.html.erb +10 -0
- data/app/views/decidim/admin/scope_types/index.html.erb +46 -0
- data/app/views/decidim/admin/scopes/_form.html.erb +20 -0
- data/app/views/decidim/admin/scopes/index.html.erb +55 -0
- data/app/views/decidim/geo/admin/geo_configs/_form.html.erb +47 -0
- data/app/views/decidim/geo/admin/geo_configs/index.html.erb +8 -0
- data/app/views/decidim/geo/admin/shapefiles/_form.html.erb +29 -0
- data/app/views/decidim/geo/admin/shapefiles/index.html.erb +38 -0
- data/app/views/decidim/geo/admin/shapefiles/new.html.erb +8 -0
- data/app/views/layouts/decidim/decidim_geo/admin/application.html.erb +19 -0
- data/config/assets.rb +9 -0
- data/config/i18n-tasks.yml +10 -0
- data/config/locales/de.yml +102 -0
- data/config/locales/en.yml +108 -0
- data/config/locales/fr.yml +102 -0
- data/db/migrate/20221019184712_add_postgis_extension_to_database.rb +5 -0
- data/db/migrate/20221025195520_create_decidim_geo.rb +22 -0
- data/db/migrate/20231013082325_create_decidim_geo_config.rb +11 -0
- data/db/migrate/20231206115531_add_spaces_config_to_decidim_geo_configs.rb +6 -0
- data/db/migrate/20240309004347_add_organization_to_shapefiles.rb +6 -0
- data/db/migrate/20240326052727_create_space_locations.rb +18 -0
- data/lib/decidim/api/geo_config_type.rb +15 -0
- data/lib/decidim/api/geo_coordinates_type.rb +22 -0
- data/lib/decidim/api/geo_datasource_type.rb +147 -0
- data/lib/decidim/api/geo_dates_type.rb +15 -0
- data/lib/decidim/api/geo_query_extension.rb +25 -0
- data/lib/decidim/api/geo_scope_api_type.rb +26 -0
- data/lib/decidim/api/geo_shapedata_type.rb +18 -0
- data/lib/decidim/api/geo_shapefile_type.rb +22 -0
- data/lib/decidim/api/input_filters/assembly_input_filter.rb +13 -0
- data/lib/decidim/api/input_filters/geo_datasource_input_filter.rb +18 -0
- data/lib/decidim/api/input_filters/geoencoded_input_filter.rb +13 -0
- data/lib/decidim/api/input_filters/has_scopeable_input_filter.rb +17 -0
- data/lib/decidim/api/input_filters/process_input_filter.rb +13 -0
- data/lib/decidim/api/input_filters/resource_type_input_filter.rb +13 -0
- data/lib/decidim/api/input_filters/scope_input_filter.rb +13 -0
- data/lib/decidim/api/input_filters/time_input_filter.rb +13 -0
- data/lib/decidim/api/meetings_input_filter.rb +14 -0
- data/lib/decidim/api/query_extension.rb +320 -0
- data/lib/decidim/api/shapefile_query_extention.rb +18 -0
- data/lib/decidim/api/shapefile_type.rb +18 -0
- data/lib/decidim/decidim_geo/admin.rb +10 -0
- data/lib/decidim/decidim_geo/admin_engine.rb +69 -0
- data/lib/decidim/decidim_geo/api.rb +23 -0
- data/lib/decidim/decidim_geo/engine.rb +58 -0
- data/lib/decidim/decidim_geo/load_shp/app_load_shp.rb +85 -0
- data/lib/decidim/decidim_geo/space_location/assembly_create_command_override.rb +26 -0
- data/lib/decidim/decidim_geo/space_location/assembly_form_override.rb +33 -0
- data/lib/decidim/decidim_geo/space_location/assembly_update_command_override.rb +21 -0
- data/lib/decidim/decidim_geo/space_location/participatory_process_command_override.rb +21 -0
- data/lib/decidim/decidim_geo/space_location/participatory_process_form_override.rb +33 -0
- data/lib/decidim/decidim_geo/space_location/space_override.rb +15 -0
- data/lib/decidim/decidim_geo/test/factories.rb +13 -0
- data/lib/decidim/decidim_geo/version.rb +14 -0
- data/lib/decidim/decidim_geo.rb +25 -0
- data/lib/tasks/decidim_geo_webpacker_tasks.rake +61 -0
- metadata +335 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
export const geoDatasource = `
|
2
|
+
query geoDatasourceQuery ($locale: String!, $filters: [GeoDatasourceFilter!], $after: String) {
|
3
|
+
geoDatasource(filters: $filters, locale: $locale, after: $after){
|
4
|
+
pageInfo {
|
5
|
+
hasPreviousPage
|
6
|
+
startCursor
|
7
|
+
endCursor
|
8
|
+
hasNextPage
|
9
|
+
}
|
10
|
+
nodes {
|
11
|
+
id
|
12
|
+
link
|
13
|
+
participatorySpaceId
|
14
|
+
participatorySpaceType
|
15
|
+
componentId
|
16
|
+
type
|
17
|
+
startTime
|
18
|
+
endTime
|
19
|
+
title{
|
20
|
+
translation(locale: $locale)
|
21
|
+
}
|
22
|
+
shortDescription {
|
23
|
+
translation(locale: $locale)
|
24
|
+
}
|
25
|
+
description {
|
26
|
+
translation(locale: $locale)
|
27
|
+
}
|
28
|
+
bannerImage
|
29
|
+
coordinates{
|
30
|
+
latitude
|
31
|
+
longitude
|
32
|
+
}
|
33
|
+
scope {
|
34
|
+
id
|
35
|
+
name {
|
36
|
+
translation(locale: "en")
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}`;
|
42
|
+
export const geoDatasourceIds = `
|
43
|
+
query geoDatasourceQueryIds ($locale: String!, $filters: [GeoDatasourceFilter!], $after: String) {
|
44
|
+
geoDatasource(filters: $filters, locale: $locale, after: $after){
|
45
|
+
pageInfo {
|
46
|
+
hasPreviousPage
|
47
|
+
startCursor
|
48
|
+
endCursor
|
49
|
+
hasNextPage
|
50
|
+
}
|
51
|
+
nodes {
|
52
|
+
id
|
53
|
+
type
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}`;
|
57
|
+
|
58
|
+
export const geoConfig = `{
|
59
|
+
geoConfig {
|
60
|
+
latitude
|
61
|
+
longitude
|
62
|
+
zoom
|
63
|
+
tile
|
64
|
+
}
|
65
|
+
}`;
|
66
|
+
|
67
|
+
export const geoScope = `
|
68
|
+
query geoScopeQuery ($locale: String!) {
|
69
|
+
geoScope {
|
70
|
+
id
|
71
|
+
name {
|
72
|
+
translation(locale: $locale)
|
73
|
+
}
|
74
|
+
geom
|
75
|
+
}
|
76
|
+
}
|
77
|
+
`;
|
@@ -0,0 +1,101 @@
|
|
1
|
+
import "src/decidim/map/controller/markers";
|
2
|
+
import "src/decidim/map/icon";
|
3
|
+
import configStore from "./models/configStore";
|
4
|
+
import pointStore from "./models/pointStore";
|
5
|
+
import filterStore from "./models/filterStore";
|
6
|
+
import geoStore from "./models/geoStore";
|
7
|
+
import dropdownFilterStore from "./models/dropdownFilterStore";
|
8
|
+
|
9
|
+
import { initMap, createDrawerActions, createDrawer } from "./ui";
|
10
|
+
import bootstrap from "./bootstrap";
|
11
|
+
|
12
|
+
window.debug = window.debug || {};
|
13
|
+
window.debug.stores = () => ({
|
14
|
+
config: configStore.getState(),
|
15
|
+
filter: filterStore.getState(),
|
16
|
+
geo: geoStore.getState(),
|
17
|
+
point: pointStore.getState(),
|
18
|
+
dropdownFilter: dropdownFilterStore.getState()
|
19
|
+
});
|
20
|
+
|
21
|
+
async function main() {
|
22
|
+
try {
|
23
|
+
// Parse and save server-side information.
|
24
|
+
bootstrap();
|
25
|
+
const { addProcess, removeProcess } = pointStore.getState();
|
26
|
+
addProcess();
|
27
|
+
// Create Leaflet map
|
28
|
+
const map = await initMap();
|
29
|
+
configStore.setState(() => ({ map: map }));
|
30
|
+
|
31
|
+
// Be sure to fit all the points whenever you change
|
32
|
+
// any filter.
|
33
|
+
const pointsLayer = L.layerGroup();
|
34
|
+
map.addLayer(pointsLayer);
|
35
|
+
map.whenReady(() => {
|
36
|
+
configStore.getState().setReady();
|
37
|
+
});
|
38
|
+
pointStore.subscribe(
|
39
|
+
(state) => [!!state.isLoading, state.getFilteredPoints, state._lastResponse],
|
40
|
+
([isLoading, getFilteredPoints]) => {
|
41
|
+
if (isLoading) return;
|
42
|
+
const { space_ids: spaceIds, map } = configStore.getState();
|
43
|
+
const { selectedPoint, selectedScope } = geoStore.getState();
|
44
|
+
pointsLayer.clearLayers();
|
45
|
+
let boudingBoxFilter = () => true;
|
46
|
+
if (!selectedPoint && !selectedScope && spaceIds) {
|
47
|
+
boudingBoxFilter = (node) =>
|
48
|
+
node.isGeoLocated() && spaceIds.includes(`${node.scopeId}`);
|
49
|
+
}
|
50
|
+
if (selectedScope?.layer && selectedScope && !selectedPoint) {
|
51
|
+
map.fitBounds(selectedScope.layer.getBounds(), { padding: [64, 64] });
|
52
|
+
}
|
53
|
+
|
54
|
+
const pointInMap = getFilteredPoints().filter((node) => node.isGeoLocated());
|
55
|
+
if (pointInMap.length > 0) {
|
56
|
+
pointInMap.forEach(({ marker }) => {
|
57
|
+
pointsLayer.addLayer(marker);
|
58
|
+
});
|
59
|
+
const idealBoundingBox = pointInMap
|
60
|
+
.filter(boudingBoxFilter)
|
61
|
+
.map(({ marker }) => marker);
|
62
|
+
const boundingBox = L.featureGroup(
|
63
|
+
_.isEmpty(idealBoundingBox)
|
64
|
+
? pointInMap.map(({ marker }) => marker)
|
65
|
+
: idealBoundingBox,
|
66
|
+
{ updateWhenZooming: true }
|
67
|
+
);
|
68
|
+
if (boundingBox && !selectedScope && !selectedPoint) {
|
69
|
+
map.fitBounds(boundingBox.getBounds(), { padding: [64, 64] });
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
);
|
74
|
+
|
75
|
+
// Create the drawer menu
|
76
|
+
await createDrawerActions();
|
77
|
+
// Create the drawer
|
78
|
+
await createDrawer();
|
79
|
+
// Fetch all the data
|
80
|
+
const { fetchAll, pointsForFilters, clearCache } = pointStore.getState();
|
81
|
+
await fetchAll(filterStore.getState().defaultFilters);
|
82
|
+
clearCache();
|
83
|
+
await pointsForFilters(filterStore.getState().defaultFilters);
|
84
|
+
removeProcess();
|
85
|
+
} catch (e) {
|
86
|
+
console.error(e);
|
87
|
+
const { map, mapID } = configStore.getState();
|
88
|
+
|
89
|
+
// If there is anything that happens,
|
90
|
+
// we don't want to see the map.
|
91
|
+
if (map?.remove) {
|
92
|
+
map.remove();
|
93
|
+
}
|
94
|
+
if (map?.off) {
|
95
|
+
map.off();
|
96
|
+
}
|
97
|
+
document.getElementById(mapID)?.remove();
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
main();
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import { createStore } from "zustand/vanilla";
|
2
|
+
import { subscribeWithSelector } from "zustand/middleware";
|
3
|
+
import filterStore from "./filterStore";
|
4
|
+
import geoStore from "./geoStore";
|
5
|
+
import dropdownFilterStore from "./dropdownFilterStore";
|
6
|
+
|
7
|
+
const store = createStore(
|
8
|
+
subscribeWithSelector((set) => ({
|
9
|
+
locale: "en",
|
10
|
+
selected_component: undefined,
|
11
|
+
selected_point: undefined,
|
12
|
+
space_ids: [],
|
13
|
+
images: {},
|
14
|
+
map: undefined,
|
15
|
+
mapReady: false,
|
16
|
+
mapID: "Generic",
|
17
|
+
map_config: {},
|
18
|
+
i18n: {},
|
19
|
+
setReady: () => set({ mapReady: true }),
|
20
|
+
setConfig: (mapConfig) => {
|
21
|
+
set(() => ({
|
22
|
+
locale: mapConfig.locale,
|
23
|
+
selected_component: mapConfig.selected_component,
|
24
|
+
space_ids: mapConfig.space_ids,
|
25
|
+
selected_point: mapConfig.selected_point,
|
26
|
+
mapID: mapConfig.mapID,
|
27
|
+
map_config: mapConfig.map_config,
|
28
|
+
i18n: mapConfig.i18n,
|
29
|
+
images: mapConfig.images || {}
|
30
|
+
}));
|
31
|
+
const { setDefaultFilters, setFilters, toFilterOptions } = filterStore.getState();
|
32
|
+
setDefaultFilters(mapConfig.filters);
|
33
|
+
setFilters(mapConfig.filters);
|
34
|
+
|
35
|
+
dropdownFilterStore.getState().setDefaultFilters({
|
36
|
+
GeoShowFilter: toFilterOptions("GeoShowFilter", mapConfig.filters),
|
37
|
+
GeoTimeFilter: toFilterOptions("GeoTimeFilter", mapConfig.filters),
|
38
|
+
GeoType: toFilterOptions("GeoType", mapConfig.filters)
|
39
|
+
});
|
40
|
+
}
|
41
|
+
}))
|
42
|
+
);
|
43
|
+
|
44
|
+
export default store;
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import { createStore } from "zustand/vanilla";
|
2
|
+
import { subscribeWithSelector } from "zustand/middleware";
|
3
|
+
import scopeDropdownStore from "./scopeDropdownStore";
|
4
|
+
const store = createStore(
|
5
|
+
subscribeWithSelector((set) => ({
|
6
|
+
isOpen: false,
|
7
|
+
defaultFilters: {
|
8
|
+
GeoShowFilter: "all",
|
9
|
+
GeoTimeFilter: "only_active",
|
10
|
+
GeoType: "all"
|
11
|
+
},
|
12
|
+
selectedFilters: {
|
13
|
+
GeoShowFilter: "all",
|
14
|
+
GeoTimeFilter: "only_active",
|
15
|
+
GeoType: "all"
|
16
|
+
},
|
17
|
+
nextFilters: undefined,
|
18
|
+
filterCount: () => {
|
19
|
+
const { selectedFilters, defaultFilters } = store.getState();
|
20
|
+
return Object.entries(selectedFilters).filter(([key, value]) => {
|
21
|
+
return defaultFilters[key] !== value;
|
22
|
+
}).length;
|
23
|
+
},
|
24
|
+
setNextFilter: (name, value) => {
|
25
|
+
set((state) => ({
|
26
|
+
nextFilters: {
|
27
|
+
...state.nextFilters,
|
28
|
+
[`${name}`]: value || state.defaultFilters[`${name}`]
|
29
|
+
}
|
30
|
+
}));
|
31
|
+
},
|
32
|
+
applyNextFilters: () => {
|
33
|
+
set(({ nextFilters }) => (nextFilters ? { selectedFilters: nextFilters } : {}));
|
34
|
+
},
|
35
|
+
resetFilters: () => {
|
36
|
+
set(({ defaultFilters }) => ({
|
37
|
+
selectedFilters: defaultFilters,
|
38
|
+
nextFilters: defaultFilters
|
39
|
+
}));
|
40
|
+
},
|
41
|
+
setDefaultFilters: (newFilters) => {
|
42
|
+
set(() => ({ defaultFilters: newFilters, selectedFilters: newFilters }));
|
43
|
+
},
|
44
|
+
setFilter: (name, value) => {
|
45
|
+
set((state) => ({
|
46
|
+
selectedFilters: {
|
47
|
+
...state.selectedFilters,
|
48
|
+
[`${name}`]: value || state.defaultFilters[`${name}`]
|
49
|
+
}
|
50
|
+
}));
|
51
|
+
},
|
52
|
+
toggleOpen: () => set((state) => ({ isOpen: !state.isOpen })),
|
53
|
+
close: () => set(() => ({ isOpen: false }))
|
54
|
+
}))
|
55
|
+
);
|
56
|
+
|
57
|
+
store.subscribe(
|
58
|
+
(state) => [state.isOpen],
|
59
|
+
([isOpen]) => {
|
60
|
+
if (isOpen) {
|
61
|
+
scopeDropdownStore.getState().close();
|
62
|
+
}
|
63
|
+
}
|
64
|
+
);
|
65
|
+
export default store;
|
@@ -0,0 +1,121 @@
|
|
1
|
+
import { createStore } from "zustand/vanilla";
|
2
|
+
import _ from "lodash";
|
3
|
+
import { subscribeWithSelector } from "zustand/middleware";
|
4
|
+
import pointStore from "./pointStore";
|
5
|
+
import geoStore from "./geoStore";
|
6
|
+
import dropdownFilterStore from "./dropdownFilterStore";
|
7
|
+
|
8
|
+
const sortingIteratee = (filter) => JSON.stringify(filter);
|
9
|
+
const store = createStore(
|
10
|
+
subscribeWithSelector((set) => ({
|
11
|
+
defaultFilters: [],
|
12
|
+
activeFilters: [],
|
13
|
+
setDefaultFilters: (filters = []) => {
|
14
|
+
const sortedFilters = _.sortBy(filters, [sortingIteratee]);
|
15
|
+
set(() => ({
|
16
|
+
defaultFilters: sortedFilters
|
17
|
+
}));
|
18
|
+
},
|
19
|
+
isFilteredByScope: (filters = undefined) => {
|
20
|
+
const { scopeFilter } = store.getState();
|
21
|
+
return !!scopeFilter(filters);
|
22
|
+
},
|
23
|
+
scopeFilter: (filters = undefined) => {
|
24
|
+
if (!filters) {
|
25
|
+
const { activeFilters } = store.getState();
|
26
|
+
filters = activeFilters;
|
27
|
+
}
|
28
|
+
return filters.find(({ scopeFilter }) => scopeFilter)?.scopeFilter?.scopeId;
|
29
|
+
},
|
30
|
+
isFilteredByTime: (filters = undefined) => {
|
31
|
+
if (!filters) {
|
32
|
+
const { activeFilters } = store.getState();
|
33
|
+
filters = activeFilters;
|
34
|
+
}
|
35
|
+
return !!filters.find(({ timeFilter }) => timeFilter);
|
36
|
+
},
|
37
|
+
hasUserFilters: () => {
|
38
|
+
const { defaultFilters, activeFilters } = store.getState();
|
39
|
+
return _.isEqual(defaultFilters, activeFilters);
|
40
|
+
},
|
41
|
+
setFilters: (filters = []) => {
|
42
|
+
const activeFilters = _.sortBy(filters, [sortingIteratee]);
|
43
|
+
set((state) => {
|
44
|
+
if (_.isEqual(state.activeFilters, activeFilters)) return {};
|
45
|
+
return { activeFilters: activeFilters };
|
46
|
+
});
|
47
|
+
},
|
48
|
+
resetFilters: () => {
|
49
|
+
set((state) => ({
|
50
|
+
activeFilters: state.defaultFilters
|
51
|
+
}));
|
52
|
+
},
|
53
|
+
toFilterOptions: (name, filters) => {
|
54
|
+
const { defaultFilters } = dropdownFilterStore.getState();
|
55
|
+
switch (name) {
|
56
|
+
case "GeoShowFilter":
|
57
|
+
const showFilterMatch = filters.find(
|
58
|
+
({ geoencodedFilter = undefined }) => geoencodedFilter
|
59
|
+
);
|
60
|
+
if (!showFilterMatch) return defaultFilters.GeoShowFilter;
|
61
|
+
const geoencodedFilter = showFilterMatch.geoencodedFilter.geoencoded;
|
62
|
+
if (geoencodedFilter === true) return "only_geoencoded";
|
63
|
+
if (geoencodedFilter === false) return "only_virtual";
|
64
|
+
return defaultFilters.GeoShowFilter;
|
65
|
+
case "GeoTimeFilter":
|
66
|
+
const timeFilterMatch = filters.find(
|
67
|
+
({ timeFilter = undefined }) => timeFilter
|
68
|
+
);
|
69
|
+
if (!timeFilterMatch) return defaultFilters.GeoTimeFilter;
|
70
|
+
const timeFilter = timeFilterMatch.timeFilter.time;
|
71
|
+
if (timeFilter === "past") return "only_past";
|
72
|
+
if (timeFilter === "active") return "only_active";
|
73
|
+
if (timeFilter === "future") return "only_future";
|
74
|
+
return defaultFilters.GeoTimeFilter;
|
75
|
+
case "GeoType":
|
76
|
+
const typeFilterMatch = filters.find(
|
77
|
+
({ resourceTypeFilter = undefined }) => resourceTypeFilter
|
78
|
+
);
|
79
|
+
if (!typeFilterMatch) return defaultFilters.GeoType;
|
80
|
+
const resourceType = typeFilterMatch.resourceTypeFilter.resourceType;
|
81
|
+
if (resourceType === "Decidim::Assembly") return "only_assemblies";
|
82
|
+
if (resourceType === "Decidim::Proposals::Proposal") return "only_proposals";
|
83
|
+
if (resourceType === "Decidim::Meetings::Meeting") return "only_meetings";
|
84
|
+
if (resourceType === "Decidim::ParticipatoryProcess") return "only_processes";
|
85
|
+
if (resourceType === "all") return "all";
|
86
|
+
return defaultFilters.GeoType;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
}))
|
90
|
+
);
|
91
|
+
|
92
|
+
const onFilteredByScope = (filters) => {
|
93
|
+
const { scopeFilter } = store.getState();
|
94
|
+
const { scopeForId } = pointStore.getState();
|
95
|
+
let scopeId;
|
96
|
+
if ((scopeId = scopeFilter(filters))) {
|
97
|
+
const { selectScope, selectedScope: previousScope } = geoStore.getState();
|
98
|
+
if (previousScope && `${scopeId}` === `${previousScope?.id}`) return;
|
99
|
+
const selectedScope = scopeForId(scopeId);
|
100
|
+
if (selectedScope) selectScope(selectedScope);
|
101
|
+
}
|
102
|
+
};
|
103
|
+
|
104
|
+
// If you update the active filters, we need to fetch
|
105
|
+
// the point matching this filter.
|
106
|
+
store.subscribe(
|
107
|
+
(state) => [state.activeFilters],
|
108
|
+
async ([activeFilters], [previousActiveFilter]) => {
|
109
|
+
if (!previousActiveFilter || !_.isEqual(activeFilters, previousActiveFilter)) {
|
110
|
+
await pointStore.getState().pointsForFilters(activeFilters);
|
111
|
+
onFilteredByScope(activeFilters);
|
112
|
+
}
|
113
|
+
const { toFilterOptions } = store.getState();
|
114
|
+
const { setFilter } = dropdownFilterStore.getState();
|
115
|
+
setFilter("GeoShowFilter", toFilterOptions("GeoShowFilter", activeFilters));
|
116
|
+
setFilter("GeoTimeFilter", toFilterOptions("GeoTimeFilter", activeFilters));
|
117
|
+
setFilter("GeoType", toFilterOptions("GeoType", activeFilters));
|
118
|
+
}
|
119
|
+
);
|
120
|
+
|
121
|
+
export default store;
|
@@ -0,0 +1,124 @@
|
|
1
|
+
import { createNodeMenuItem, createNodeMarker } from "../../ui";
|
2
|
+
import geoStore from "../geoStore";
|
3
|
+
import configStore from "../configStore";
|
4
|
+
|
5
|
+
export default class GeoDatasourceNode {
|
6
|
+
constructor({ node, map }) {
|
7
|
+
//Model
|
8
|
+
this.data = node;
|
9
|
+
this.map = map;
|
10
|
+
this.marker = undefined;
|
11
|
+
}
|
12
|
+
|
13
|
+
isGeoLocated() {
|
14
|
+
return !!this.marker;
|
15
|
+
}
|
16
|
+
|
17
|
+
repaint() {
|
18
|
+
const { selectedPoint } = geoStore.getState();
|
19
|
+
const { selected_component, selected_point: pinPoint } = configStore.getState();
|
20
|
+
if (this.isGeoLocated()) {
|
21
|
+
if (pinPoint) {
|
22
|
+
if (selectedPoint === this) {
|
23
|
+
this.marker.setStyle(this.selectedState);
|
24
|
+
} else if (
|
25
|
+
!selectedPoint &&
|
26
|
+
`${this.data.componentId}` == `${selected_component}` &&
|
27
|
+
pinPoint == `${this.data.id}`
|
28
|
+
) {
|
29
|
+
this.marker.setStyle(this.selectedState);
|
30
|
+
} else {
|
31
|
+
this.marker.setStyle(this.staledState);
|
32
|
+
}
|
33
|
+
} else {
|
34
|
+
if (selectedPoint === this) {
|
35
|
+
this.marker.setStyle(this.selectedState);
|
36
|
+
} else if (
|
37
|
+
!selectedPoint &&
|
38
|
+
`${this.data.componentId}` === `${selected_component}`
|
39
|
+
) {
|
40
|
+
this.marker.setStyle(this.selectedState);
|
41
|
+
} else {
|
42
|
+
this.marker.setStyle(this.staledState);
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
colorLayers() {
|
49
|
+
if (!this.isGeoLocated()) return;
|
50
|
+
const { map } = configStore.getState();
|
51
|
+
map.eachLayer((layer) => {
|
52
|
+
if (layer.feature) {
|
53
|
+
if (layer.feature.geometry.properties.id === this.scopeId) {
|
54
|
+
layer.setStyle(this.selectedState);
|
55
|
+
} else {
|
56
|
+
layer.setStyle(this.staledState);
|
57
|
+
}
|
58
|
+
}
|
59
|
+
});
|
60
|
+
}
|
61
|
+
|
62
|
+
get id() {
|
63
|
+
if (!this.data.id || !this.data.type) return undefined;
|
64
|
+
return `${this.data.type}::${this.data.id}`;
|
65
|
+
}
|
66
|
+
|
67
|
+
get scopeId() {
|
68
|
+
return parseInt(`${this.data.scope?.id}`) || undefined;
|
69
|
+
}
|
70
|
+
|
71
|
+
get selectedState() {
|
72
|
+
return { color: "var(--primary)" };
|
73
|
+
}
|
74
|
+
|
75
|
+
get staledState() {
|
76
|
+
return { color: "#404040" };
|
77
|
+
}
|
78
|
+
|
79
|
+
async panToMarker(zoom = 21) {
|
80
|
+
if (!this.isGeoLocated()) return;
|
81
|
+
const { map } = configStore.getState();
|
82
|
+
return new Promise((resolve) => {
|
83
|
+
map
|
84
|
+
.flyTo(this.marker.getLatLng(), zoom, {
|
85
|
+
animate: false,
|
86
|
+
noMoveStart: true
|
87
|
+
})
|
88
|
+
.once("moveend", () => {
|
89
|
+
this.repaint();
|
90
|
+
resolve();
|
91
|
+
});
|
92
|
+
});
|
93
|
+
}
|
94
|
+
|
95
|
+
select() {
|
96
|
+
geoStore.getState().selectPoint(this);
|
97
|
+
}
|
98
|
+
|
99
|
+
init() {
|
100
|
+
if (this.data?.coordinates?.latitude && this.data?.coordinates?.longitude) {
|
101
|
+
this.marker = createNodeMarker(this.data);
|
102
|
+
this.marker.on("click", this.select.bind(this));
|
103
|
+
this.marker.bringToFront();
|
104
|
+
}
|
105
|
+
try {
|
106
|
+
this.repaint();
|
107
|
+
this.menuItem = createNodeMenuItem(this.data);
|
108
|
+
this.menuItem.onclick = () => {
|
109
|
+
this.select("sidebar");
|
110
|
+
};
|
111
|
+
|
112
|
+
geoStore.subscribe(
|
113
|
+
(state) => [state.selectedPoint],
|
114
|
+
(_np, [unselectedPoint]) => {
|
115
|
+
if (unselectedPoint) unselectedPoint.repaint();
|
116
|
+
this.repaint();
|
117
|
+
}
|
118
|
+
);
|
119
|
+
} catch (error) {
|
120
|
+
console.error("ERROR: decidim-geo can't initialize ", { error });
|
121
|
+
}
|
122
|
+
return this;
|
123
|
+
}
|
124
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default } from "./GeoDatasourceNode";
|
@@ -0,0 +1,128 @@
|
|
1
|
+
import { createGeoScopeMenuItem, createGeoScopeLayer } from "../../ui";
|
2
|
+
import geoStore from "../geoStore";
|
3
|
+
import pointStore from "../pointStore";
|
4
|
+
import configStore from "../configStore";
|
5
|
+
import scopeDropdownStore from "../scopeDropdownStore";
|
6
|
+
import polylabel from "polylabel";
|
7
|
+
import _ from "lodash";
|
8
|
+
|
9
|
+
export default class GeoScope {
|
10
|
+
constructor({ geoScope }) {
|
11
|
+
//Model
|
12
|
+
this.data = geoScope;
|
13
|
+
this.markers_group = [];
|
14
|
+
this.menuItem = null;
|
15
|
+
}
|
16
|
+
|
17
|
+
get activeState() {
|
18
|
+
return { fillColor: "#2952A370", color: "#2952A3" };
|
19
|
+
}
|
20
|
+
|
21
|
+
isEmpty() {
|
22
|
+
const { points } = pointStore.getState();
|
23
|
+
const currentScopeId = this.id;
|
24
|
+
return !points.find(({ scopeId }) => `${scopeId}` === `${currentScopeId}`);
|
25
|
+
}
|
26
|
+
|
27
|
+
isLoading() {
|
28
|
+
return pointStore.getState().isLoading;
|
29
|
+
}
|
30
|
+
|
31
|
+
get staledState() {
|
32
|
+
return { fillColor: "#cccccc", color: "#999999" };
|
33
|
+
}
|
34
|
+
|
35
|
+
select() {
|
36
|
+
const { selectedScope: previousScope } = geoStore.getState();
|
37
|
+
if (previousScope === this) return;
|
38
|
+
geoStore.getState().selectScope(this);
|
39
|
+
}
|
40
|
+
|
41
|
+
/**
|
42
|
+
* If the current marker is selected (active)
|
43
|
+
* @returns boolean
|
44
|
+
*/
|
45
|
+
isActive() {
|
46
|
+
const { selectedScope } = geoStore.getState();
|
47
|
+
if (selectedScope) return selectedScope === this;
|
48
|
+
return false;
|
49
|
+
}
|
50
|
+
|
51
|
+
repaint() {
|
52
|
+
if (this.isActive()) {
|
53
|
+
this.layer?.setStyle(this.activeState);
|
54
|
+
} else {
|
55
|
+
this.layer?.setStyle(this.staledState);
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
get name() {
|
60
|
+
return this.data.name.translation;
|
61
|
+
}
|
62
|
+
|
63
|
+
get id() {
|
64
|
+
return parseInt(`${this.data.id}`);
|
65
|
+
}
|
66
|
+
|
67
|
+
nodesForScope() {
|
68
|
+
const { points } = pointStore.getState();
|
69
|
+
const currentScopeId = this.id;
|
70
|
+
return points.filter(({ scopeId }) => scopeId === currentScopeId).filter(Boolean);
|
71
|
+
}
|
72
|
+
|
73
|
+
markersForScope() {
|
74
|
+
if (this.markers_group.length > 0) return this.markers_group;
|
75
|
+
return this.nodesForScope()
|
76
|
+
.filter((node) => node.isGeoLocated())
|
77
|
+
.map(({ marker }) => marker);
|
78
|
+
}
|
79
|
+
|
80
|
+
componentIds() {
|
81
|
+
return _.uniq(
|
82
|
+
this.nodesForScope()
|
83
|
+
.map(({ data }) => (data.componentId ? `${data.componentId}` : false))
|
84
|
+
.filter(Boolean)
|
85
|
+
);
|
86
|
+
}
|
87
|
+
|
88
|
+
init() {
|
89
|
+
this.markers_group = this.markersForScope();
|
90
|
+
this.menuItem = createGeoScopeMenuItem({
|
91
|
+
label: this.name,
|
92
|
+
onClick: () => {
|
93
|
+
scopeDropdownStore.getState().toggleOpen();
|
94
|
+
geoStore.getState().selectScope(this);
|
95
|
+
this.repaint();
|
96
|
+
}
|
97
|
+
});
|
98
|
+
if (this.data.geom?.type) {
|
99
|
+
if (this.data.geom.type === "MultiPolygon") {
|
100
|
+
this.centroid = polylabel(this.data.geom.coordinates[0], 1.0);
|
101
|
+
}
|
102
|
+
this.layer = createGeoScopeLayer({
|
103
|
+
geoScope: this.data,
|
104
|
+
centroid: this.centroid,
|
105
|
+
onClick: () => this.select("layer")
|
106
|
+
});
|
107
|
+
this.layer.bringToBack();
|
108
|
+
// Add the layer only when we are sure there is some point
|
109
|
+
// in the layer.
|
110
|
+
pointStore.subscribe(
|
111
|
+
(state) => [state.isLoading],
|
112
|
+
([isLoading]) => {
|
113
|
+
const { map, mapReady } = configStore.getState();
|
114
|
+
if (isLoading) return;
|
115
|
+
if (this.isEmpty()) {
|
116
|
+
if (map.hasLayer(this.layer) && mapReady) {
|
117
|
+
map.removeLayer(this.layer);
|
118
|
+
}
|
119
|
+
} else {
|
120
|
+
map.addLayer(this.layer);
|
121
|
+
}
|
122
|
+
}
|
123
|
+
);
|
124
|
+
}
|
125
|
+
this.repaint();
|
126
|
+
return this;
|
127
|
+
}
|
128
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default } from "./GeoScope";
|