geoblacklight 6.0.0.pre.alpha.7 → 6.0.0.pre.beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +14 -3
  3. data/README.md +2 -2
  4. data/Rakefile +1 -17
  5. data/app/assets/images/geoblacklight/logo-dark.svg +31 -0
  6. data/app/assets/images/geoblacklight/logo.svg +10 -10
  7. data/app/assets/stylesheets/geoblacklight/global.css +44 -7
  8. data/app/assets/stylesheets/geoblacklight/index_maps.css +0 -7
  9. data/app/assets/stylesheets/geoblacklight/record.css +33 -9
  10. data/app/assets/stylesheets/geoblacklight/search.css +66 -17
  11. data/app/assets/stylesheets/geoblacklight/viewers.css +18 -111
  12. data/app/assets/stylesheets/geoblacklight.css +0 -5
  13. data/app/components/blacklight/icons/georeferenced_component.rb +23 -0
  14. data/app/components/geoblacklight/document/download_link_component.html.erb +1 -1
  15. data/app/components/geoblacklight/document/preview_component.html.erb +0 -2
  16. data/app/components/geoblacklight/document_component.html.erb +0 -4
  17. data/app/components/geoblacklight/facets/bbox_field_component.html.erb +2 -1
  18. data/app/components/geoblacklight/georeferenced_header_badge_component.rb +16 -0
  19. data/app/components/geoblacklight/header_badge_component.html.erb +4 -2
  20. data/app/components/geoblacklight/header_badge_component.rb +1 -1
  21. data/app/components/geoblacklight/header_badges_component.html.erb +5 -0
  22. data/app/components/geoblacklight/header_badges_component.rb +34 -0
  23. data/app/components/geoblacklight/iiif_drag_drop_component.html.erb +1 -1
  24. data/app/components/geoblacklight/item_map_viewer_component.html.erb +0 -58
  25. data/app/components/geoblacklight/item_map_viewer_component.rb +19 -70
  26. data/app/components/geoblacklight/locator_map_component.rb +31 -0
  27. data/app/components/geoblacklight/login_link_component.html.erb +1 -1
  28. data/app/components/geoblacklight/login_link_component.rb +17 -1
  29. data/app/components/geoblacklight/metadata_description_markdown_component.rb +3 -9
  30. data/app/components/geoblacklight/overview_map_component.html.erb +2 -0
  31. data/app/components/geoblacklight/overview_map_component.rb +86 -0
  32. data/app/components/geoblacklight/relations/relation_component.html.erb +12 -0
  33. data/app/components/geoblacklight/relations/relation_component.rb +30 -0
  34. data/app/components/geoblacklight/relations/relations_component.html.erb +15 -0
  35. data/app/components/geoblacklight/relations/relations_component.rb +59 -0
  36. data/app/components/geoblacklight/resource_header_badge_component.rb +1 -1
  37. data/app/components/geoblacklight/search_result_component.html.erb +14 -33
  38. data/app/components/geoblacklight/search_result_component.rb +2 -14
  39. data/app/components/geoblacklight/static_map_component.rb +3 -14
  40. data/app/components/geoblacklight/truncatable_metadata_field_layout_component.html.erb +7 -0
  41. data/app/components/geoblacklight/truncatable_metadata_field_layout_component.rb +45 -0
  42. data/app/controllers/{relation_controller.rb → relations_controller.rb} +4 -2
  43. data/app/controllers/viewer_record_controller.rb +28 -0
  44. data/app/helpers/geoblacklight_helper.rb +57 -6
  45. data/app/javascript/geoblacklight/controllers/overview_map_controller.js +143 -0
  46. data/app/javascript/geoblacklight/core.js +8 -10
  47. data/app/javascript/geoblacklight/index.js +9 -12
  48. data/app/javascript/geoblacklight/initializers/field_truncation.js +62 -0
  49. data/app/javascript/geoblacklight/initializers/metadata_download.js +3 -8
  50. data/app/javascript/geoblacklight/initializers/truncation.js +7 -3
  51. data/app/javascript/geoblacklight/initializers/viewer_requests.js +61 -0
  52. data/app/javascript/geoblacklight/initializers/viewer_theme.js +28 -0
  53. data/app/models/concerns/geoblacklight/solr_document/thumbnail.rb +38 -0
  54. data/app/models/concerns/geoblacklight/solr_document.rb +2 -0
  55. data/app/models/concerns/geoblacklight/suppressed_records_search_behavior.rb +1 -1
  56. data/app/views/catalog/_document_split.html.erb +10 -10
  57. data/app/views/catalog/_home_text.html.erb +1 -1
  58. data/app/views/catalog/metadata.html.erb +1 -1
  59. data/app/views/{relation → relations}/index.html.erb +1 -1
  60. data/app/views/relations/index.json.jbuilder +9 -0
  61. data/config/importmap.rb +8 -8
  62. data/config/locales/geoblacklight.en.yml +48 -99
  63. data/geoblacklight.gemspec +3 -3
  64. data/lib/generators/geoblacklight/assets_generator.rb +2 -2
  65. data/lib/generators/geoblacklight/install_generator.rb +3 -6
  66. data/lib/generators/geoblacklight/templates/assets/customizations.css +5 -1
  67. data/lib/generators/geoblacklight/templates/catalog_controller.rb +17 -27
  68. data/lib/generators/geoblacklight/templates/settings.yml +15 -95
  69. data/lib/geoblacklight/configuration/display_note_shown_config.rb +3 -0
  70. data/lib/geoblacklight/configuration/relationship_config.rb +7 -1
  71. data/lib/geoblacklight/configuration/relationships_config.rb +15 -15
  72. data/lib/geoblacklight/configuration/settings_attributes.rb +85 -0
  73. data/lib/geoblacklight/configuration/settings_builder.rb +3 -14
  74. data/lib/geoblacklight/configuration.rb +23 -62
  75. data/lib/geoblacklight/exceptions.rb +4 -0
  76. data/lib/geoblacklight/reference.rb +0 -2
  77. data/lib/geoblacklight/references.rb +17 -5
  78. data/lib/geoblacklight/relations/ancestors.rb +12 -0
  79. data/lib/geoblacklight/relations/descendants.rb +11 -0
  80. data/lib/geoblacklight/relations/query.rb +39 -0
  81. data/lib/geoblacklight/{relation → relations}/relation_response.rb +5 -4
  82. data/lib/geoblacklight/routes/exportable.rb +2 -1
  83. data/lib/geoblacklight/version.rb +1 -1
  84. data/lib/geoblacklight/viewer_record.rb +39 -0
  85. data/lib/geoblacklight.rb +1 -1
  86. data/lib/tasks/geoblacklight.rake +1 -17
  87. data/lib/tasks/solr.rb +37 -0
  88. data/schema/readme.md +1 -1
  89. data/script/wait-for-solr +36 -0
  90. data/solr/conf/solrconfig.xml +10 -16
  91. data/spec/components/geoblacklight/document/sidebar_component_spec.rb +3 -3
  92. data/spec/components/geoblacklight/document_component_spec.rb +8 -34
  93. data/spec/components/geoblacklight/{header_icons_component_spec.rb → header_badges_component_spec.rb} +20 -2
  94. data/spec/components/geoblacklight/item_map_viewer_component_spec.rb +65 -15
  95. data/spec/components/geoblacklight/locator_map_component_spec.rb +54 -0
  96. data/spec/components/geoblacklight/login_link_component_spec.rb +20 -12
  97. data/spec/components/geoblacklight/metadata_description_markdown_component_spec.rb +1 -6
  98. data/spec/components/geoblacklight/overview_map_component_spec.rb +136 -0
  99. data/spec/components/geoblacklight/relations/relation_component_spec.rb +98 -0
  100. data/spec/components/geoblacklight/relations/relations_component_spec.rb +146 -0
  101. data/spec/components/geoblacklight/search_result_component_spec.rb +66 -39
  102. data/spec/components/geoblacklight/static_map_component_spec.rb +8 -2
  103. data/spec/components/geoblacklight/truncatable_metadata_field_layout_component_spec.rb +83 -0
  104. data/spec/controllers/catalog_controller_spec.rb +22 -7
  105. data/spec/controllers/{relation_controller_spec.rb → relations_controller_spec.rb} +1 -1
  106. data/spec/factories/user.rb +6 -1
  107. data/spec/features/axe_spec.rb +5 -3
  108. data/spec/features/home_page_spec.rb +8 -7
  109. data/spec/features/index_view_spec.rb +6 -20
  110. data/spec/features/layer_preview_spec.rb +4 -11
  111. data/spec/features/locator_map_spec.rb +31 -0
  112. data/spec/features/relations_spec.rb +22 -11
  113. data/spec/features/restricted_viewer_spec.rb +14 -0
  114. data/spec/features/search_results_map_spec.rb +89 -13
  115. data/spec/features/search_spec.rb +6 -2
  116. data/spec/features/sign_in_spec.rb +26 -0
  117. data/spec/features/tilejson_spec.rb +1 -3
  118. data/spec/features/tms_spec.rb +2 -2
  119. data/spec/features/truncated_metadata_spec.rb +77 -0
  120. data/spec/features/viewer_requests_spec.rb +40 -0
  121. data/spec/features/viewer_theme_spec.rb +51 -0
  122. data/spec/features/wmts_spec.rb +6 -6
  123. data/spec/features/xyz_spec.rb +3 -3
  124. data/spec/fixtures/solr_documents/README.md +18 -14
  125. data/spec/fixtures/solr_documents/actual-polygon1.json +22 -18
  126. data/spec/fixtures/solr_documents/b1g_wabash_child_15.json +14 -15
  127. data/spec/fixtures/solr_documents/b1g_wabash_child_16.json +14 -15
  128. data/spec/fixtures/solr_documents/b1g_wabash_child_17.json +14 -15
  129. data/spec/fixtures/solr_documents/b1g_wabash_child_18.json +14 -15
  130. data/spec/fixtures/solr_documents/b1g_wabash_parent.json +14 -15
  131. data/spec/fixtures/solr_documents/esri-dynamic-layer-single-layer.json +2 -2
  132. data/spec/fixtures/solr_documents/esri-feature-layer.json +1 -1
  133. data/spec/fixtures/solr_documents/esri-image-map-layer.json +20 -20
  134. data/spec/fixtures/solr_documents/esri-wms-layer.json +22 -24
  135. data/spec/fixtures/solr_documents/index_map_point.json +3 -3
  136. data/spec/fixtures/solr_documents/public_polygon.json +28 -0
  137. data/spec/helpers/geoblacklight_helper_spec.rb +103 -25
  138. data/spec/lib/geoblacklight/bounding_box_spec.rb +16 -0
  139. data/spec/lib/geoblacklight/configuration/relationships_config_spec.rb +121 -0
  140. data/spec/lib/geoblacklight/configuration/settings_attributes_spec.rb +113 -0
  141. data/spec/lib/geoblacklight/configuration/settings_builder_spec.rb +33 -4
  142. data/spec/lib/geoblacklight/references_spec.rb +35 -0
  143. data/spec/lib/geoblacklight/{relation → relations}/ancestors_spec.rb +17 -6
  144. data/spec/lib/geoblacklight/{relation → relations}/descendants_spec.rb +14 -7
  145. data/spec/lib/geoblacklight/{relation → relations}/relation_response_spec.rb +31 -17
  146. data/spec/lib/geoblacklight/viewer_record_spec.rb +69 -0
  147. data/spec/models/concerns/geoblacklight/solr_document/thumbnail_spec.rb +149 -0
  148. data/spec/models/concerns/geoblacklight/solr_document_spec.rb +1 -1
  149. data/spec/requests/bookmarks_spec.rb +2 -1
  150. data/spec/requests/catalog_show_spec.rb +24 -2
  151. data/spec/requests/index_view_spec.rb +50 -2
  152. data/spec/requests/missing_metadata_spec.rb +1 -1
  153. data/spec/requests/relations_spec.rb +9 -2
  154. data/spec/requests/search_results_spec.rb +6 -6
  155. data/spec/requests/viewer_record_spec.rb +37 -0
  156. data/spec/requests/viewer_requests_spec.rb +52 -0
  157. data/spec/solr_config_spec.rb +41 -0
  158. data/spec/solr_fixtures_spec.rb +23 -0
  159. data/spec/spec_helper.rb +13 -3
  160. data/spec/support/authentication.rb +37 -0
  161. data/spec/support/features/map_helpers.rb +68 -0
  162. data/spec/support/features/session_helpers.rb +18 -13
  163. data/spec/support/features/viewer_helpers.rb +20 -0
  164. data/spec/support/features.rb +4 -0
  165. data/spec/test_app_templates/lib/generators/test_app_generator.rb +1 -1
  166. data/template.rb +1 -1
  167. metadata +72 -84
  168. data/app/components/geoblacklight/accordion_component.html.erb +0 -16
  169. data/app/components/geoblacklight/accordion_component.rb +0 -15
  170. data/app/components/geoblacklight/attribute_table_component.html.erb +0 -15
  171. data/app/components/geoblacklight/attribute_table_component.rb +0 -17
  172. data/app/components/geoblacklight/header_icons_component.html.erb +0 -3
  173. data/app/components/geoblacklight/header_icons_component.rb +0 -27
  174. data/app/components/geoblacklight/index_map_inspect_component.html.erb +0 -12
  175. data/app/components/geoblacklight/index_map_inspect_component.rb +0 -15
  176. data/app/components/geoblacklight/index_map_legend_component.html.erb +0 -14
  177. data/app/components/geoblacklight/index_map_legend_component.rb +0 -24
  178. data/app/components/geoblacklight/location_leaflet_map_component.rb +0 -68
  179. data/app/components/geoblacklight/relations_component.html.erb +0 -23
  180. data/app/components/geoblacklight/relations_component.rb +0 -25
  181. data/app/components/geoblacklight/viewer_help_text_component.html.erb +0 -1
  182. data/app/components/geoblacklight/viewer_help_text_component.rb +0 -46
  183. data/app/controllers/wms_controller.rb +0 -17
  184. data/app/javascript/geoblacklight/controllers/leaflet_viewer_controller.js +0 -240
  185. data/app/javascript/geoblacklight/controllers/openlayers_viewer_controller.js +0 -74
  186. data/app/javascript/geoblacklight/controllers/search_results_controller.js +0 -50
  187. data/app/javascript/geoblacklight/initializers/popovers.js +0 -11
  188. data/app/javascript/geoblacklight/initializers/tooltips.js +0 -51
  189. data/app/javascript/geoblacklight/leaflet/basemaps.js +0 -73
  190. data/app/javascript/geoblacklight/leaflet/constants.js +0 -18
  191. data/app/javascript/geoblacklight/leaflet/controls/geosearch.js +0 -168
  192. data/app/javascript/geoblacklight/leaflet/controls/layer_opacity.js +0 -121
  193. data/app/javascript/geoblacklight/leaflet/controls/sidebar.js +0 -49
  194. data/app/javascript/geoblacklight/leaflet/controls/sleep.js +0 -101
  195. data/app/javascript/geoblacklight/leaflet/inspection.js +0 -210
  196. data/app/javascript/geoblacklight/leaflet/layer_index_map.js +0 -128
  197. data/app/javascript/geoblacklight/leaflet/layer_wmts.js +0 -57
  198. data/app/javascript/geoblacklight/leaflet/layers.js +0 -146
  199. data/app/javascript/geoblacklight/leaflet/utils.js +0 -83
  200. data/app/javascript/geoblacklight/openlayers/basemaps.js +0 -55
  201. data/app/javascript/geoblacklight/openlayers/inspection.js +0 -42
  202. data/app/javascript/geoblacklight/openlayers/layers.js +0 -44
  203. data/app/views/relation/index.json.jbuilder +0 -8
  204. data/lib/geoblacklight/configuration/layer_config.rb +0 -28
  205. data/lib/geoblacklight/configuration/leaflet_config.rb +0 -73
  206. data/lib/geoblacklight/configuration/leaflet_layers_config.rb +0 -33
  207. data/lib/geoblacklight/configuration/leaflet_sleep_config.rb +0 -20
  208. data/lib/geoblacklight/relation/ancestors.rb +0 -31
  209. data/lib/geoblacklight/relation/descendants.rb +0 -31
  210. data/lib/geoblacklight/routes/wms.rb +0 -15
  211. data/lib/geoblacklight/wms_layer/feature_info_response.rb +0 -37
  212. data/lib/geoblacklight/wms_layer.rb +0 -34
  213. data/spec/components/geoblacklight/attribute_table_component_spec.rb +0 -36
  214. data/spec/components/geoblacklight/index_map_legend_component_spec.rb +0 -29
  215. data/spec/components/geoblacklight/viewer_help_text_component_spec.rb +0 -27
  216. data/spec/controllers/wms_controller_spec.rb +0 -34
  217. data/spec/features/configurable_basemap_spec.rb +0 -40
  218. data/spec/features/full_screen_control_spec.rb +0 -15
  219. data/spec/features/layer_inspection_spec.rb +0 -12
  220. data/spec/features/layer_opacity_spec.rb +0 -16
  221. data/spec/fixtures/solr_documents/harvard_raster.json +0 -39
  222. data/spec/fixtures/solr_documents/public_polygon_mit.json +0 -23
  223. data/spec/fixtures/solr_documents/uva_slug_colon.json +0 -29
  224. data/spec/javascripts/geoblacklight_spec.js +0 -11
  225. data/spec/lib/geoblacklight/configuration/layer_config_spec.rb +0 -71
  226. data/spec/lib/geoblacklight/configuration/leaflet_config_spec.rb +0 -129
  227. data/spec/lib/geoblacklight/configuration/leaflet_layers_config_spec.rb +0 -63
  228. data/spec/lib/geoblacklight/configuration_spec.rb +0 -48
  229. data/spec/lib/geoblacklight/wms_layer/feature_info_response_spec.rb +0 -54
  230. data/spec/lib/geoblacklight/wms_layer_spec.rb +0 -115
  231. /data/app/components/geoblacklight/{location_leaflet_map_component.html.erb → locator_map_component.html.erb} +0 -0
@@ -1,23 +0,0 @@
1
- <div class="card relations relationship-<%= relationship_type.downcase %>">
2
- <div class="card-header">
3
- <h2 class="mb-0 h6"><%= t("#{rel_type_info.label}") %></h2>
4
- </div>
5
-
6
- <ul class="list-group list-group-flush">
7
- <% relationship_type_results['docs'][0..2].map { |hash| SolrDocument.new(hash) }.each do |doc| %>
8
- <li class="list-group-item">
9
- <%= link_to solr_document_path(doc) do %>
10
- <%= helpers.geoblacklight_icon(rel_type_info.icon) unless rel_type_info.icon.nil? %>
11
- <%= doc.title %>
12
- <% end %>
13
- </li>
14
- <% end %>
15
- <% unless (relationship_type_results['numFound'].to_i <= 3) %>
16
- <li class="list-group-item">
17
- <%= link_to search_catalog_path({f: {"#{Geoblacklight.configuration.relationships_shown.public_send(relationship_type).field}" => [relations.link_id]}}) do %>
18
- <%= t('geoblacklight.relations.browse_all', count: relationship_type_results['numFound']) %>
19
- <% end %>
20
- </li>
21
- <% end %>
22
- </ul>
23
- </div>
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Geoblacklight
4
- # Display expandable file download links in sidebar
5
- class RelationsComponent < ViewComponent::Base
6
- attr_reader :relations, :relationship_type, :rel_type_info
7
-
8
- def initialize(relations:, relationship_type:, rel_type_info:)
9
- @relations = relations
10
- @relationship_type = relationship_type
11
- @rel_type_info = rel_type_info
12
- super()
13
- end
14
-
15
- def render?
16
- relationship_type_results["numFound"].to_i.positive?
17
- end
18
-
19
- private
20
-
21
- def relationship_type_results
22
- @relationship_type_results ||= relations.public_send(relationship_type)
23
- end
24
- end
25
- end
@@ -1 +0,0 @@
1
- <%= display_text %>
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Geoblacklight
4
- ##
5
- # A component for rendering the help text for the viewer container
6
- #
7
- class ViewerHelpTextComponent < ViewComponent::Base
8
- def initialize(feature, key)
9
- @feature = feature
10
- @key = key
11
- super()
12
- end
13
-
14
- # Retrieve i18n value
15
- def help_text_exists?
16
- I18n.exists?("geoblacklight.help_text.#{@feature}.#{@key}", helpers.locale)
17
- end
18
-
19
- def help_text_value
20
- I18n.t("geoblacklight.help_text.#{@feature}.#{@key}")
21
- end
22
-
23
- def help_text_tag
24
- help_text = help_text_value
25
- tag.h2 class: "help-text viewer_protocol h6" do
26
- tag.a data: {bs_toggle: "popover", bs_title: help_text[:title], bs_content: help_text[:content]} do
27
- help_text[:title]
28
- end
29
- end
30
- end
31
-
32
- def help_text_missing_tag
33
- tag.span class: "help-text translation-missing"
34
- end
35
-
36
- def display_text
37
- return help_text_tag if help_text_exists?
38
-
39
- help_text_missing_tag
40
- end
41
-
42
- def render?
43
- Geoblacklight.configuration.help_text.fetch(@feature.to_sym, []).include?(@key)
44
- end
45
- end
46
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class WmsController < ApplicationController
4
- def handle
5
- response = Geoblacklight::WmsLayer.new(wms_params).feature_info
6
-
7
- respond_to do |format|
8
- format.json { render json: response }
9
- end
10
- end
11
-
12
- private
13
-
14
- def wms_params
15
- params.permit(Geoblacklight.configuration.gbl_params)
16
- end
17
- end
@@ -1,240 +0,0 @@
1
- import { Icon, Control } from "leaflet"
2
- import { FullScreen } from "leaflet.fullscreen"
3
- import { layerGroup, polygon, map, tileLayer } from "leaflet"
4
- import { imageMapLayer } from "esri-leaflet"
5
- import { Controller } from "@hotwired/stimulus"
6
- import basemaps from "geoblacklight/leaflet/basemaps"
7
- import LayerOpacityControl from "geoblacklight/leaflet/controls/layer_opacity"
8
- import GeoSearchControl from "geoblacklight/leaflet/controls/geosearch"
9
- import Sleep from "geoblacklight/leaflet/controls/sleep"
10
- import {
11
- wmsInspection,
12
- tiledMapLayerInspection,
13
- featureLayerInspection,
14
- dynamicMapLayerInspection,
15
- } from "geoblacklight/leaflet/inspection"
16
- import {
17
- esriDynamicMapLayer,
18
- esriFeatureLayer,
19
- esriTiledMapLayer,
20
- tileJsonLayer,
21
- wmsLayer,
22
- wmtsLayer,
23
- indexMapLayer,
24
- } from "geoblacklight/leaflet/layers"
25
- import { geoJSONToBounds } from "geoblacklight/leaflet/utils"
26
- import { DEFAULT_BOUNDS, DEFAULT_OPACITY, DEFAULT_GEOM_OVERLAY_OPTIONS } from "geoblacklight/leaflet/constants"
27
- import sidebarControl from "geoblacklight/leaflet/controls/sidebar"
28
-
29
- export default class LeafletViewerController extends Controller {
30
- static values = {
31
- url: String,
32
- catalogBaseUrl: String,
33
- protocol: String,
34
- available: Boolean,
35
- options: Object,
36
- basemap: String,
37
- darkBasemap: String,
38
- mapGeom: Object,
39
- page: String,
40
- layerId: String,
41
- drawInitialBounds: Boolean,
42
- }
43
-
44
- connect() {
45
- // Use leaflet icon images from CDN
46
- Icon.Default.imagePath = "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/"
47
-
48
- // The map can be turbo-permanent; if we're reconnected but the map already
49
- // exists, no need to destroy and recreate the layers and controls.
50
- if (this.map) return
51
-
52
- // Set up layers
53
- this.basemap = this.getBasemap()
54
- this.overlay = layerGroup()
55
-
56
- // Calculate bounds
57
- this.bounds = DEFAULT_BOUNDS
58
- if (this.hasMapGeomValue) this.bounds = geoJSONToBounds(this.mapGeomValue)
59
-
60
- // Load the map
61
- this.loadMap()
62
-
63
- // Check for changes to dark/light mode and update the basemap accordingly
64
- const observer = new MutationObserver(() => {
65
- this.map.removeLayer(this.basemap)
66
- this.map.addLayer(this.getBasemap())
67
- })
68
- observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-bs-theme"] })
69
- }
70
-
71
- // Create the map, add layers, fit the bounds
72
- async loadMap() {
73
- if (this.map) return
74
-
75
- // Set up the map and fit to bounds
76
- const sleepSettings = this.optionsValue.sleep || { sleep: false }
77
- this.map = map(this.element, sleepSettings)
78
- if (sleepSettings.sleep) this.map.addHandler("SLEEP", Sleep)
79
-
80
- this.map.addLayer(this.basemap)
81
- this.map.addLayer(this.overlay)
82
- this.fitBounds(this.bounds)
83
- this.map.options.selected_color = this.optionsValue.selected_color || "blue"
84
-
85
- // If the data is available, add the preview and set up inspection
86
- // Otherwise just draw the bounds, if configured to do so
87
- if (this.availableValue && this.protocolValue) {
88
- await this.addPreviewOverlay()
89
- this.addInspection()
90
- } else if (this.drawInitialBoundsValue && this.bounds) {
91
- this.addBoundsOverlay(this.bounds)
92
- }
93
-
94
- // Add configured controls
95
- this.addControls()
96
-
97
- if (this.protocolValue == "IndexMap") {
98
- this.fitBounds(this.previewOverlay.getBounds())
99
- }
100
-
101
- // Enable geosearch handler, if available
102
- if (this.map.geosearch) this.map.geosearch.enable()
103
-
104
- // Emit an event for other controllers to listen to
105
- this.dispatch("loaded")
106
- }
107
-
108
- // Set the bounds of the map to an L.LatLngBounds object
109
- fitBounds(bounds) {
110
- // prevent map from moving when setting bounds, if relevant
111
- if (this.map.geosearch) this.map.geosearch.disable()
112
- this.map.fitBounds(bounds, { animate: false, noMoveStart: true })
113
- this.bounds = bounds
114
- if (this.map.geosearch) this.map.geosearch.enable()
115
- this.element.dataset.bounds = this.bounds.toBBoxString()
116
- }
117
-
118
- // Select the configured basemaps to use
119
- getBasemap() {
120
- if (document.documentElement.getAttribute("data-bs-theme") === "dark")
121
- this.basemapName = this.darkBasemapValue || "dark_matter"
122
- else this.basemapName = this.basemapValue || "positron"
123
-
124
- return tileLayer(basemaps[this.basemapName].url, {
125
- ...basemaps[this.basemapName],
126
- detectRetina: this.optionsValue.layers.detect_retina || false,
127
- })
128
- }
129
-
130
- // Add the configured controls to the map
131
- addControls() {
132
- // Add any top-level controls (e.g. geosearch) first
133
- const globalControls = this.optionsValue.controls || {}
134
- Object.entries(globalControls).forEach(([controlName, controlOptions]) => {
135
- const control = this.getControl(controlName, controlOptions)
136
- if (control) this.addControl(control)
137
- })
138
-
139
- // Add opacity and fullscreen controls for all layers
140
- if (this.hasProtocolValue) {
141
- if (this.availableValue) {
142
- const opacityControl = this.getControl("Opacity")
143
- this.addControl(opacityControl)
144
- }
145
- const fullscreenControl = this.getControl("Fullscreen")
146
- this.addControl(fullscreenControl)
147
- }
148
- }
149
-
150
- // Add a pre-configured L.Control instance to the map
151
- addControl(control) {
152
- this.map.addControl(control)
153
- }
154
-
155
- // Look up the control name and return the corresponding L.Control instance
156
- getControl(controlName, controlOptions) {
157
- if (controlName == "Opacity") return new LayerOpacityControl(this.previewOverlay)
158
- if (controlName == "Fullscreen") return new FullScreen({ position: "bottomleft", ...controlOptions })
159
- if (controlName == "Geosearch")
160
- return new GeoSearchControl({
161
- baseUrl: this.catalogBaseUrlValue,
162
- ...controlOptions,
163
- })
164
- console.error(`Unsupported control name: "${controlName}"`)
165
- }
166
-
167
- // Add the bounding box to the map
168
- addBoundsOverlay(bounds) {
169
- const hasMapGeomValue = Object.keys(this.mapGeomValue).length > 0 && !this.map.geosearch
170
- var defaultOptions = hasMapGeomValue ? DEFAULT_GEOM_OVERLAY_OPTIONS : {}
171
- const overlayOptions = this.optionsValue.bounds_overlay || {}
172
- const options = { ...defaultOptions, ...overlayOptions[this.pageValue] }
173
- const boundsOverlay = polygon(
174
- [bounds.getSouthWest(), bounds.getSouthEast(), bounds.getNorthEast(), bounds.getNorthWest()],
175
- options,
176
- )
177
-
178
- // Render geometry layer if available
179
- if (hasMapGeomValue) this.map.addLayer(L.geoJSON(this.mapGeomValue, overlayOptions[this.pageValue]))
180
-
181
- // Render the bounds overlay
182
- this.boundsOverlay = boundsOverlay
183
- this.overlay.addLayer(boundsOverlay)
184
- }
185
-
186
- // Remove the bounding box overlay
187
- removeBoundsOverlay() {
188
- if (this.boundsOverlay) this.overlay.removeLayer(this.boundsOverlay)
189
- }
190
-
191
- setUpSidebar() {
192
- const sidebar = document.querySelector("[data-sidebar]")
193
- if (!sidebar || sidebar.dataset.sidebar != "true") return
194
- const control = new sidebarControl({
195
- position: "topright",
196
- sidebar,
197
- })
198
- this.map.addControl(control)
199
- }
200
-
201
- addInspection() {
202
- const inspectionProtocols = ["Wms", "FeatureLayer", "DynamicMapLayer", "IndexMap"]
203
- if (!inspectionProtocols.includes(this.protocolValue)) return
204
- this.setUpSidebar()
205
- if (this.protocolValue == "Wms")
206
- return wmsInspection(this.map, this.urlValue, this.layerIdValue, this.previewOverlay)
207
- if (this.protocolValue == "FeatureLayer") return featureLayerInspection(this.map, this.previewOverlay)
208
- if (this.protocolValue == "DynamicMapLayer")
209
- return dynamicMapLayerInspection(this.map, this.previewOverlay, this.layerIdValue)
210
- // TODO: TiledMapLayer is converted but seems busted -- see layers.js. Don't know what to test it with, need a fixture.
211
- // if (this.protocolValue == "TiledMapLayer") return tiledMapLayerInspection(this.map, this.previewOverlay);
212
- }
213
-
214
- // Add the actual data to the map as a layer
215
- async addPreviewOverlay() {
216
- this.previewOverlay = await this.getPreviewOverlay(this.protocolValue, this.urlValue, {
217
- layerId: this.layerIdValue,
218
- opacity: this.optionsValue.opacity || DEFAULT_OPACITY,
219
- detectRetina: this.optionsValue.layers.detect_retina || false,
220
- })
221
- if (this.previewOverlay) this.overlay.addLayer(this.previewOverlay)
222
- }
223
-
224
- // Generate a layer based on the protocol
225
- async getPreviewOverlay(protocol, url, options) {
226
- if (protocol == "DynamicMapLayer") return esriDynamicMapLayer(url, options)
227
- if (protocol == "FeatureLayer") return esriFeatureLayer(url, options)
228
- // imageMapLayer is a built-in method from esri-leaflet
229
- if (protocol == "ImageMapLayer") return imageMapLayer({ url, ...options })
230
- if (protocol == "IndexMap") return await indexMapLayer(url, this.optionsValue)
231
- if (protocol == "TiledMapLayer") return await esriTiledMapLayer(url, options)
232
- if (protocol == "Tilejson") return await tileJsonLayer(url, options)
233
- // tileLayer is a built-in method from leaflet
234
- if (protocol == "Tms") return tileLayer(url, { tms: true, ...options })
235
- if (protocol == "Wms") return wmsLayer(url, options)
236
- if (protocol == "Wmts") return await wmtsLayer(url, options)
237
- if (protocol == "Xyz") return tileLayer(url, options)
238
- console.error(`Unsupported protocol name: "${protocol}"`)
239
- }
240
- }
@@ -1,74 +0,0 @@
1
- import { Map } from "ol"
2
- import TileLayer from "ol/layer/Tile"
3
- import XYZ from "ol/source/XYZ"
4
- import GeoJSON from "ol/format/GeoJSON"
5
- import { FullScreen, defaults as defaultControls } from "ol/control"
6
- import { pmTilesLayer, cogLayer } from "geoblacklight/openlayers/layers"
7
- import basemaps from "geoblacklight/openlayers/basemaps"
8
- import { Controller } from "@hotwired/stimulus"
9
- import { pmTilesInspection } from "geoblacklight/openlayers/inspection"
10
-
11
- export default class OpenlayersViewerController extends Controller {
12
- static values = {
13
- url: String,
14
- protocol: String,
15
- basemap: String,
16
- darkBasemap: String,
17
- mapGeom: String,
18
- }
19
-
20
- async connect() {
21
- // Set up layers
22
- this.basemap = this.getBasemap()
23
- this.overlay = this.getPreviewOverlay(this.protocolValue, this.urlValue)
24
-
25
- await this.getBounds()
26
- // Load the map
27
- this.loadMap()
28
-
29
- // Check for changes to dark/light mode and update the basemap accordingly
30
- const observer = new MutationObserver(() => this.map.getLayers().setAt(0, this.getBasemap()))
31
- observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-bs-theme"] })
32
- }
33
-
34
- // Create the map, add layers, and fit the bounds
35
- loadMap() {
36
- this.map = new Map({
37
- target: this.element,
38
- controls: defaultControls().extend([new FullScreen()]),
39
- layers: [this.basemap, this.overlay],
40
- })
41
- this.map.getView().fit(this.extent, this.map.getSize())
42
- this.addInspection()
43
- }
44
-
45
- addInspection() {
46
- if (this.protocolValue == "Pmtiles") return pmTilesInspection(this.map)
47
- }
48
-
49
- async getBounds() {
50
- if (this.protocolValue == "Cog") {
51
- const view = await this.overlay.getSource().getView()
52
- this.extent = view.extent
53
- } else {
54
- this.extent = new GeoJSON().readFeatures(this.mapGeomValue)[0].getGeometry().getExtent()
55
- }
56
- }
57
-
58
- // Select the configured basemap to use
59
- getBasemap() {
60
- if (document.documentElement.getAttribute("data-bs-theme") === "dark")
61
- this.basemapName = this.darkBasemapValue || "dark_matter"
62
- else this.basemapName = this.basemapValue || "positron"
63
-
64
- const layer = new TileLayer({ source: new XYZ(basemaps[this.basemapName]) })
65
- return layer
66
- }
67
-
68
- // Generate a layer based on the protocol
69
- getPreviewOverlay(protocol, url) {
70
- if (protocol === "Pmtiles") return pmTilesLayer(url)
71
- if (protocol === "Cog") return cogLayer(url)
72
- console.error(`Unsupported protocol name: "${protocol}"`)
73
- }
74
- }
@@ -1,50 +0,0 @@
1
- import { Controller } from "@hotwired/stimulus"
2
- import { geoJSONToBounds } from "geoblacklight/leaflet/utils"
3
- import { DEFAULT_BOUNDS } from "geoblacklight/leaflet/constants"
4
-
5
- export default class SearchResultsController extends Controller {
6
- static targets = ["result"]
7
- static outlets = ["leaflet-viewer"]
8
-
9
- // Fit the map to the combined bounding box of all search results
10
- // If a bounding box query is already active, do not override it
11
- fitResultBounds() {
12
- if (new URL(window.location).searchParams.get("bbox")) return
13
- this.leafletViewerOutlet.fitBounds(this.getMaximalBounds())
14
- }
15
-
16
- // Get the bounding box of a search result from its data attributes
17
- getResultBounds(resultElement) {
18
- const geomElement = resultElement.querySelector("[data-geom]")
19
- if (geomElement && geomElement.dataset.geom) {
20
- return geoJSONToBounds(JSON.parse(geomElement.dataset.geom))
21
- }
22
- }
23
-
24
- // Find the combined bounding box of all search results
25
- getMaximalBounds() {
26
- const bounds = this.resultTargets.map(this.getResultBounds)
27
- const nonNullBounds = bounds.filter((b) => b)
28
-
29
- if (nonNullBounds.length > 0) {
30
- return nonNullBounds.reduce((max, b) => max.extend(b))
31
- }
32
-
33
- return DEFAULT_BOUNDS
34
- }
35
-
36
- // Preview the selected search result's bounding box on the map
37
- previewResultOnMap(event) {
38
- const geomElement = event.target.querySelector("[data-geom]")
39
- if (geomElement && geomElement.dataset.geom !== "") {
40
- const geom = JSON.parse(geomElement.dataset.geom)
41
- const bbox = geoJSONToBounds(geom)
42
- this.leafletViewerOutlet.addBoundsOverlay(bbox)
43
- }
44
- }
45
-
46
- // Remove the previewed bounding box
47
- clearPreview() {
48
- this.leafletViewerOutlet.removeBoundsOverlay()
49
- }
50
- }
@@ -1,11 +0,0 @@
1
- import { Popover } from "bootstrap"
2
-
3
- // Initialize popovers
4
- export default function initializePopovers() {
5
- const popoverElements = document.querySelectorAll('[data-bs-toggle="popover"]') // Bootstrap 5 uses 'data-bs-toggle'
6
- popoverElements.forEach((element) => {
7
- const popover = new Popover(element, {
8
- trigger: "hover",
9
- })
10
- })
11
- }
@@ -1,51 +0,0 @@
1
- import { Tooltip } from "bootstrap"
2
-
3
- export default function initializeTooltips() {
4
- document.body.addEventListener(
5
- "mouseenter",
6
- (event) => {
7
- if (event.target.closest(".blacklight-icons.svg_tooltip svg")) {
8
- const svgElement = event.target.closest(".blacklight-icons.svg_tooltip svg")
9
- const svgTitleElement = svgElement.querySelector("title")
10
- const titleText = svgTitleElement ? svgTitleElement.textContent : ""
11
-
12
- if (titleText !== undefined && titleText !== "") {
13
- // Initialize Bootstrap tooltip with native JavaScript
14
- const tooltip = new Tooltip(svgElement, {
15
- placement: "bottom",
16
- title: titleText,
17
- })
18
- tooltip.show()
19
-
20
- // Store the original title in the data-original-title attribute
21
- // and remove the title element to prevent it from interfering with Bootstrap's tooltip.
22
- svgElement.setAttribute("data-original-title", titleText)
23
- svgTitleElement.remove()
24
- }
25
- }
26
- },
27
- true,
28
- ) // Use capture to ensure the event is caught as it bubbles up
29
-
30
- document.body.addEventListener(
31
- "mouseleave",
32
- (event) => {
33
- if (event.target.closest(".blacklight-icons.svg_tooltip svg")) {
34
- const svgElement = event.target.closest(".blacklight-icons.svg_tooltip svg")
35
- const originalTitle = svgElement.getAttribute("data-original-title")
36
-
37
- if (originalTitle !== undefined && originalTitle !== "") {
38
- // Restore the SVG title element from data-original-title
39
- const newTitleElement = document.createElement("title")
40
- newTitleElement.textContent = originalTitle
41
- svgElement.prepend(newTitleElement)
42
- svgElement.setAttribute("data-original-title", "")
43
- }
44
- }
45
-
46
- // Remove tooltips
47
- document.querySelectorAll(".tooltip").forEach((elem) => elem.remove())
48
- },
49
- true,
50
- ) // Use capture to ensure the event is caught as it bubbles up
51
- }
@@ -1,73 +0,0 @@
1
- export default {
2
- dark_matter: {
3
- url: "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{retina}.png",
4
- attribution:
5
- '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://carto.com/attributions">Carto</a>',
6
- maxZoom: 18,
7
- worldCopyJump: true,
8
- retina: "@2x",
9
- },
10
- positron: {
11
- url: "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{retina}.png",
12
- attribution:
13
- '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://carto.com/attributions">Carto</a>',
14
- maxZoom: 18,
15
- worldCopyJump: true,
16
- retina: "@2x",
17
- },
18
- positron_lite: {
19
- url: "https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{retina}.png",
20
- attribution:
21
- '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://carto.com/attributions">Carto</a>',
22
- maxZoom: 18,
23
- worldCopyJump: true,
24
- retina: "@2x",
25
- },
26
- world_antique: {
27
- url: "https://cartocdn_{s}.global.ssl.fastly.net/base-antique/{z}/{x}/{y}{retina}.png",
28
- attribution:
29
- '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://carto.com/attributions">Carto</a>',
30
- maxZoom: 18,
31
- worldCopyJump: true,
32
- retina: "@2x",
33
- },
34
- world_eco: {
35
- url: "https://cartocdn_{s}.global.ssl.fastly.net/base-eco/{z}/{x}/{y}{retina}.png",
36
- attribution:
37
- '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://carto.com/attributions">Carto</a>',
38
- maxZoom: 18,
39
- worldCopyJump: true,
40
- retina: "@2x",
41
- },
42
- flat_blue: {
43
- url: "https://cartocdn_{s}.global.ssl.fastly.net/base-flatblue/{z}/{x}/{y}{retina}.png",
44
- attribution:
45
- '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://carto.com/attributions">Carto</a>',
46
- maxZoom: 18,
47
- worldCopyJump: true,
48
- retina: "@2x",
49
- },
50
- midnight_commander: {
51
- url: "https://cartocdn_{s}.global.ssl.fastly.net/base-midnight/{z}/{x}/{y}{retina}.png",
52
- attribution:
53
- '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://carto.com/attributions">Carto</a>',
54
- maxZoom: 18,
55
- worldCopyJump: true,
56
- retina: "@2x",
57
- },
58
- openstreetmap_hot: {
59
- url: "https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png",
60
- attribution:
61
- '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>, Tiles courtesy of <a href="http://hot.openstreetmap.org/" target="_blank">Humanitarian OpenStreetMap Team</a>',
62
- maxZoom: 19,
63
- worldCopyJump: true,
64
- retina: "@2x",
65
- },
66
- openstreetmap_standard: {
67
- url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
68
- attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
69
- maxZoom: 19,
70
- worldCopyJump: true,
71
- retina: "@2x",
72
- },
73
- }
@@ -1,18 +0,0 @@
1
- import { LatLngBounds } from "leaflet"
2
-
3
- export const DEFAULT_BOUNDS = new LatLngBounds([
4
- [-30, -100],
5
- [30, 100],
6
- ])
7
-
8
- export const DEFAULT_OPACITY = 0.75
9
-
10
- export const DEFAULT_BASEMAP = "positron"
11
-
12
- export const DEFAULT_GEOM_OVERLAY_OPTIONS = {
13
- color: "#3388ff",
14
- dashArray: "5 5",
15
- weight: 2,
16
- opacity: 1,
17
- fillOpacity: 0,
18
- }