decidim-decidim_geo 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +172 -0
  4. data/Rakefile +119 -0
  5. data/app/cells/decidim/geo/content_blocks/geo_maps/show.erb +15 -0
  6. data/app/cells/decidim/geo/content_blocks/geo_maps_cell.rb +114 -0
  7. data/app/cells/decidim/geo/content_blocks/geo_maps_settings_form_cell.rb +14 -0
  8. data/app/cells/decidim/geo/geo_collection_map/show.erb +4 -0
  9. data/app/cells/decidim/geo/geo_collection_map_cell.rb +102 -0
  10. data/app/commands/decidim/admin/create_scope.rb +56 -0
  11. data/app/commands/decidim/admin/create_scope_type.rb +47 -0
  12. data/app/commands/decidim/admin/update_scope.rb +60 -0
  13. data/app/commands/decidim/admin/update_scope_type.rb +62 -0
  14. data/app/commands/decidim/geo/admin/create_shapefile.rb +51 -0
  15. data/app/commands/decidim/geo/admin/update_geo_config.rb +54 -0
  16. data/app/commands/decidim/geo/command.rb +14 -0
  17. data/app/controllers/decidim/admin/scopes_controller.rb +115 -0
  18. data/app/controllers/decidim/geo/admin/application_controller.rb +21 -0
  19. data/app/controllers/decidim/geo/admin/geo_configs_controller.rb +44 -0
  20. data/app/controllers/decidim/geo/admin/shapefiles_controller.rb +41 -0
  21. data/app/controllers/decidim/geo/application_controller.rb +13 -0
  22. data/app/controllers/decidim/geo/shapefile_controller.rb +11 -0
  23. data/app/forms/decidim/admin/scope_form.rb +43 -0
  24. data/app/forms/decidim/admin/scope_type_form.rb +27 -0
  25. data/app/forms/decidim/geo/admin/geo_config_form.rb +20 -0
  26. data/app/forms/decidim/geo/admin/shapefile_form.rb +23 -0
  27. data/app/helpers/decidim/admin/scopes_helper.rb +72 -0
  28. data/app/jobs/decidim/geo/shp2pgsql_job.rb +14 -0
  29. data/app/models/decidim/debates/debate.rb +226 -0
  30. data/app/models/decidim/geo/application_record.rb +10 -0
  31. data/app/models/decidim/geo/geo_config.rb +28 -0
  32. data/app/models/decidim/geo/shapedata.rb +23 -0
  33. data/app/models/decidim/geo/shapefile.rb +27 -0
  34. data/app/models/decidim/geo/space_location.rb +16 -0
  35. data/app/models/decidim/scope.rb +111 -0
  36. data/app/models/decidim/scope_type.rb +26 -0
  37. data/app/overrides/decidim/assemblies/admin/assemblies/_form/insert_location_form.html.erb.deface +13 -0
  38. data/app/overrides/decidim/assemblies/assemblies/show/insert_location.html.erb.deface +7 -0
  39. data/app/overrides/decidim/participatory_processes/admin/participatory_processes/_form/insert_location_form.html.erb.deface +13 -0
  40. data/app/overrides/decidim/participatory_processes/participatory_process_groups/show/insert_map.html.erb.deface +6 -0
  41. data/app/overrides/layouts/decidim/_assembly_header/insert_map.html.erb.deface +25 -0
  42. data/app/overrides/layouts/decidim/_process_header/insert_map.html.erb.deface +22 -0
  43. data/app/overrides/layouts/decidim/_wrapper/insert_map.html.erb.deface +5 -0
  44. data/app/overrides/remove_default_meetings_map.rb +4 -0
  45. data/app/overrides/remove_default_proposals_map.rb +4 -0
  46. data/app/packs/entrypoints/decidim_geo.js +3 -0
  47. data/app/packs/images/decidim/geo/not-geolocated.svg +3 -0
  48. data/app/packs/src/decidim/geo/actions/index.js +25 -0
  49. data/app/packs/src/decidim/geo/api/index.js +53 -0
  50. data/app/packs/src/decidim/geo/api/queries.js +77 -0
  51. data/app/packs/src/decidim/geo/bootstrap.js +6 -0
  52. data/app/packs/src/decidim/geo/index.js +101 -0
  53. data/app/packs/src/decidim/geo/models/configStore.js +44 -0
  54. data/app/packs/src/decidim/geo/models/dropdownFilterStore.js +65 -0
  55. data/app/packs/src/decidim/geo/models/filterStore.js +121 -0
  56. data/app/packs/src/decidim/geo/models/geoDatasourceNode/GeoDatasourceNode.js +124 -0
  57. data/app/packs/src/decidim/geo/models/geoDatasourceNode/index.js +1 -0
  58. data/app/packs/src/decidim/geo/models/geoScope/GeoScope.js +128 -0
  59. data/app/packs/src/decidim/geo/models/geoScope/index.js +1 -0
  60. data/app/packs/src/decidim/geo/models/geoStore.js +118 -0
  61. data/app/packs/src/decidim/geo/models/pointStore.js +134 -0
  62. data/app/packs/src/decidim/geo/models/scopeDropdownStore.js +20 -0
  63. data/app/packs/src/decidim/geo/ui/DrawerDetail/fallback.js +42 -0
  64. data/app/packs/src/decidim/geo/ui/DrawerDetail/index.js +2 -0
  65. data/app/packs/src/decidim/geo/ui/DrawerDetail/meetings.js +75 -0
  66. data/app/packs/src/decidim/geo/ui/DrawerMenuItem/fallback.js +45 -0
  67. data/app/packs/src/decidim/geo/ui/DrawerMenuItem/index.js +2 -0
  68. data/app/packs/src/decidim/geo/ui/DrawerMenuItem/meetings.js +62 -0
  69. data/app/packs/src/decidim/geo/ui/createClasses.js +7 -0
  70. data/app/packs/src/decidim/geo/ui/createCustomMarker.js +14 -0
  71. data/app/packs/src/decidim/geo/ui/createDomElement.js +2 -0
  72. data/app/packs/src/decidim/geo/ui/createDrawer.js +170 -0
  73. data/app/packs/src/decidim/geo/ui/createDrawerActions.js +197 -0
  74. data/app/packs/src/decidim/geo/ui/createFilterDropdown.js +320 -0
  75. data/app/packs/src/decidim/geo/ui/createGeoScopeLayer.js +20 -0
  76. data/app/packs/src/decidim/geo/ui/createGeoScopeMenuItem.js +8 -0
  77. data/app/packs/src/decidim/geo/ui/createNodeMarker.js +9 -0
  78. data/app/packs/src/decidim/geo/ui/createNodeMenuItem.js +13 -0
  79. data/app/packs/src/decidim/geo/ui/index.js +8 -0
  80. data/app/packs/src/decidim/geo/ui/initMap.js +22 -0
  81. data/app/packs/src/decidim/geo/utils/index.js +30 -0
  82. data/app/packs/src/decidim/geo/utils/saveConfig.js +15 -0
  83. data/app/packs/stylesheets/decidim/geo/_geo.scss +2 -0
  84. data/app/packs/stylesheets/decidim/geo/geo/_decidim_geo_decidimGeo.scss +476 -0
  85. data/app/packs/stylesheets/decidim/geo/geo/_decidim_geo_override.scss +65 -0
  86. data/app/permissions/decidim/geo/admin/permissions.rb +24 -0
  87. data/app/permissions/decidim/geo/permissions.rb +15 -0
  88. data/app/uploaders/decidim/geo/shapefile_uploader.rb +14 -0
  89. data/app/validators/geocoding_validator.rb +30 -0
  90. data/app/views/decidim/admin/scope_types/_form.html.erb +10 -0
  91. data/app/views/decidim/admin/scope_types/index.html.erb +46 -0
  92. data/app/views/decidim/admin/scopes/_form.html.erb +20 -0
  93. data/app/views/decidim/admin/scopes/index.html.erb +55 -0
  94. data/app/views/decidim/geo/admin/geo_configs/_form.html.erb +47 -0
  95. data/app/views/decidim/geo/admin/geo_configs/index.html.erb +8 -0
  96. data/app/views/decidim/geo/admin/shapefiles/_form.html.erb +29 -0
  97. data/app/views/decidim/geo/admin/shapefiles/index.html.erb +38 -0
  98. data/app/views/decidim/geo/admin/shapefiles/new.html.erb +8 -0
  99. data/app/views/layouts/decidim/decidim_geo/admin/application.html.erb +19 -0
  100. data/config/assets.rb +9 -0
  101. data/config/i18n-tasks.yml +10 -0
  102. data/config/locales/de.yml +102 -0
  103. data/config/locales/en.yml +108 -0
  104. data/config/locales/fr.yml +102 -0
  105. data/db/migrate/20221019184712_add_postgis_extension_to_database.rb +5 -0
  106. data/db/migrate/20221025195520_create_decidim_geo.rb +22 -0
  107. data/db/migrate/20231013082325_create_decidim_geo_config.rb +11 -0
  108. data/db/migrate/20231206115531_add_spaces_config_to_decidim_geo_configs.rb +6 -0
  109. data/db/migrate/20240309004347_add_organization_to_shapefiles.rb +6 -0
  110. data/db/migrate/20240326052727_create_space_locations.rb +18 -0
  111. data/lib/decidim/api/geo_config_type.rb +15 -0
  112. data/lib/decidim/api/geo_coordinates_type.rb +22 -0
  113. data/lib/decidim/api/geo_datasource_type.rb +147 -0
  114. data/lib/decidim/api/geo_dates_type.rb +15 -0
  115. data/lib/decidim/api/geo_query_extension.rb +25 -0
  116. data/lib/decidim/api/geo_scope_api_type.rb +26 -0
  117. data/lib/decidim/api/geo_shapedata_type.rb +18 -0
  118. data/lib/decidim/api/geo_shapefile_type.rb +22 -0
  119. data/lib/decidim/api/input_filters/assembly_input_filter.rb +13 -0
  120. data/lib/decidim/api/input_filters/geo_datasource_input_filter.rb +18 -0
  121. data/lib/decidim/api/input_filters/geoencoded_input_filter.rb +13 -0
  122. data/lib/decidim/api/input_filters/has_scopeable_input_filter.rb +17 -0
  123. data/lib/decidim/api/input_filters/process_input_filter.rb +13 -0
  124. data/lib/decidim/api/input_filters/resource_type_input_filter.rb +13 -0
  125. data/lib/decidim/api/input_filters/scope_input_filter.rb +13 -0
  126. data/lib/decidim/api/input_filters/time_input_filter.rb +13 -0
  127. data/lib/decidim/api/meetings_input_filter.rb +14 -0
  128. data/lib/decidim/api/query_extension.rb +320 -0
  129. data/lib/decidim/api/shapefile_query_extention.rb +18 -0
  130. data/lib/decidim/api/shapefile_type.rb +18 -0
  131. data/lib/decidim/decidim_geo/admin.rb +10 -0
  132. data/lib/decidim/decidim_geo/admin_engine.rb +69 -0
  133. data/lib/decidim/decidim_geo/api.rb +23 -0
  134. data/lib/decidim/decidim_geo/engine.rb +58 -0
  135. data/lib/decidim/decidim_geo/load_shp/app_load_shp.rb +85 -0
  136. data/lib/decidim/decidim_geo/space_location/assembly_create_command_override.rb +26 -0
  137. data/lib/decidim/decidim_geo/space_location/assembly_form_override.rb +33 -0
  138. data/lib/decidim/decidim_geo/space_location/assembly_update_command_override.rb +21 -0
  139. data/lib/decidim/decidim_geo/space_location/participatory_process_command_override.rb +21 -0
  140. data/lib/decidim/decidim_geo/space_location/participatory_process_form_override.rb +33 -0
  141. data/lib/decidim/decidim_geo/space_location/space_override.rb +15 -0
  142. data/lib/decidim/decidim_geo/test/factories.rb +13 -0
  143. data/lib/decidim/decidim_geo/version.rb +14 -0
  144. data/lib/decidim/decidim_geo.rb +25 -0
  145. data/lib/tasks/decidim_geo_webpacker_tasks.rake +61 -0
  146. 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,6 @@
1
+ const { saveConfig } = require("./utils");
2
+
3
+ function bootstrap() {
4
+ saveConfig();
5
+ }
6
+ export default bootstrap;
@@ -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";