blacklight-spotlight 0.32.0 → 0.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (202) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -0
  3. data/app/assets/images/spotlight/default_browse_thumbnail.jpg +0 -0
  4. data/app/assets/javascripts/spotlight/application.js +6 -1
  5. data/app/assets/javascripts/spotlight/blocks/pages_block.js +1 -1
  6. data/app/assets/javascripts/spotlight/blocks/resources_block.js +7 -4
  7. data/app/assets/javascripts/spotlight/blocks/solr_documents_base_block.js +108 -0
  8. data/app/assets/javascripts/spotlight/blocks/solr_documents_block.js +12 -56
  9. data/app/assets/javascripts/spotlight/blocks/solr_documents_carousel_block.js +3 -3
  10. data/app/assets/javascripts/spotlight/blocks/solr_documents_embed_block.js +3 -3
  11. data/app/assets/javascripts/spotlight/blocks/solr_documents_features_block.js +3 -3
  12. data/app/assets/javascripts/spotlight/blocks/solr_documents_grid_block.js +3 -3
  13. data/app/assets/javascripts/spotlight/crop.es6 +205 -0
  14. data/app/assets/javascripts/spotlight/croppable.js +7 -104
  15. data/app/assets/javascripts/spotlight/iiif.es6 +54 -0
  16. data/app/assets/javascripts/spotlight/multi_image_selector.js +34 -16
  17. data/app/assets/javascripts/spotlight/pages.js.erb +1 -1
  18. data/app/assets/javascripts/spotlight/reindex_monitor.js +5 -1
  19. data/app/assets/javascripts/spotlight/search_typeahead.js +33 -47
  20. data/app/assets/javascripts/spotlight/sir-trevor/locales.js +5 -2
  21. data/app/assets/javascripts/spotlight/zpr_links.js.erb +30 -0
  22. data/app/assets/stylesheets/spotlight/_croppable.scss +8 -13
  23. data/app/assets/stylesheets/spotlight/_multi_image_selector.scss +1 -1
  24. data/app/assets/stylesheets/spotlight/_pages.scss +5 -0
  25. data/app/assets/stylesheets/spotlight/typeahead.css +0 -1
  26. data/app/controllers/concerns/spotlight/base.rb +3 -5
  27. data/app/controllers/spotlight/appearances_controller.rb +6 -4
  28. data/app/controllers/spotlight/catalog_controller.rb +10 -0
  29. data/app/controllers/spotlight/contacts_controller.rb +2 -6
  30. data/app/controllers/spotlight/featured_images_controller.rb +26 -0
  31. data/app/controllers/spotlight/pages_controller.rb +3 -2
  32. data/app/controllers/spotlight/resources/iiif_harvester_controller.rb +10 -0
  33. data/app/controllers/spotlight/resources/upload_controller.rb +4 -3
  34. data/app/controllers/spotlight/searches_controller.rb +20 -6
  35. data/app/controllers/spotlight/sites_controller.rb +2 -5
  36. data/app/helpers/spotlight/application_helper.rb +14 -1
  37. data/app/helpers/spotlight/crop_helper.rb +37 -0
  38. data/app/helpers/spotlight/main_app_helpers.rb +13 -0
  39. data/app/helpers/spotlight/meta_helper.rb +10 -20
  40. data/app/models/concerns/spotlight/solr_document.rb +1 -2
  41. data/app/models/concerns/spotlight/solr_document/uploaded_resource.rb +1 -23
  42. data/app/models/sir_trevor_rails/blocks/browse_block.rb +10 -0
  43. data/app/models/sir_trevor_rails/blocks/featured_pages_block.rb +10 -0
  44. data/app/models/sir_trevor_rails/blocks/solr_documents_block.rb +5 -0
  45. data/app/models/spotlight/analytics/ga.rb +1 -1
  46. data/app/models/spotlight/blacklight_configuration.rb +16 -8
  47. data/app/models/spotlight/contact.rb +2 -13
  48. data/app/models/spotlight/contact_image.rb +11 -0
  49. data/app/models/spotlight/exhibit.rb +11 -8
  50. data/app/models/spotlight/exhibit_thumbnail.rb +12 -0
  51. data/app/models/spotlight/feature_page.rb +3 -5
  52. data/app/models/spotlight/featured_image.rb +28 -9
  53. data/app/models/spotlight/home_page.rb +2 -0
  54. data/app/models/spotlight/masthead.rb +5 -11
  55. data/app/models/spotlight/page.rb +5 -0
  56. data/app/models/spotlight/reindex_progress.rb +10 -18
  57. data/app/models/spotlight/reindexing_log_entry.rb +1 -0
  58. data/app/models/spotlight/resources/iiif_harvester.rb +33 -0
  59. data/app/models/spotlight/resources/iiif_manifest.rb +211 -0
  60. data/app/models/spotlight/resources/iiif_service.rb +93 -0
  61. data/app/models/spotlight/resources/upload.rb +1 -2
  62. data/app/models/spotlight/search.rb +5 -34
  63. data/app/presenters/spotlight/iiif_manifest_presenter.rb +79 -0
  64. data/app/serializers/spotlight/exhibit_export_serializer.rb +9 -41
  65. data/app/services/spotlight/carrierwave_file_resolver.rb +3 -1
  66. data/app/services/spotlight/iiif_resource_resolver.rb +73 -0
  67. data/app/services/spotlight/resources/iiif_builder.rb +17 -0
  68. data/app/services/spotlight/upload_solr_document_builder.rb +23 -23
  69. data/app/uploaders/spotlight/attachment_uploader.rb +0 -48
  70. data/app/uploaders/spotlight/featured_image_uploader.rb +2 -16
  71. data/app/views/_user_util_links.html.erb +8 -5
  72. data/app/views/catalog/_save_search.html.erb +4 -2
  73. data/app/views/layouts/spotlight/spotlight.html.erb +5 -1
  74. data/app/views/shared/_masthead.html.erb +1 -1
  75. data/app/views/spotlight/about_pages/_contact_properties.html.erb +1 -1
  76. data/app/views/spotlight/appearances/edit.html.erb +26 -6
  77. data/app/views/spotlight/browse/_search.html.erb +1 -1
  78. data/app/views/spotlight/contacts/_form.html.erb +12 -7
  79. data/app/views/spotlight/dashboards/_reindexing_activity.html.erb +1 -1
  80. data/app/views/spotlight/exhibits/_exhibit_card_front.html.erb +2 -2
  81. data/app/views/spotlight/featured_images/_form.html.erb +12 -15
  82. data/app/views/spotlight/featured_images/_upload_form.html.erb +6 -12
  83. data/app/views/spotlight/metadata_configurations/_metadata_field.html.erb +3 -3
  84. data/app/views/spotlight/pages/_form.html.erb +3 -3
  85. data/app/views/spotlight/pages/edit.html.erb +4 -2
  86. data/app/views/spotlight/resources/iiif/_form.html.erb +9 -0
  87. data/app/views/spotlight/searches/_form.html.erb +3 -3
  88. data/app/views/spotlight/searches/_search.html.erb +4 -2
  89. data/app/views/spotlight/sir_trevor/blocks/_browse_block.html.erb +2 -2
  90. data/app/views/spotlight/sir_trevor/blocks/_featured_pages_block.html.erb +1 -1
  91. data/app/views/spotlight/sir_trevor/blocks/_solr_documents_block.html.erb +7 -2
  92. data/app/views/spotlight/sir_trevor/blocks/_solr_documents_carousel_block.html.erb +4 -2
  93. data/app/views/spotlight/sir_trevor/blocks/_solr_documents_features_block.html.erb +4 -2
  94. data/app/views/spotlight/sir_trevor/blocks/_solr_documents_grid_block.html.erb +4 -2
  95. data/app/views/spotlight/sites/edit.html.erb +1 -1
  96. data/config/locales/spotlight.en.yml +31 -6
  97. data/config/routes.rb +9 -0
  98. data/db/migrate/20160714144125_add_iiif_urls_to_featured_image.rb +9 -0
  99. data/db/migrate/20160718194010_add_iiif_url_to_contact.rb +6 -0
  100. data/db/migrate/20160805143841_add_upload_id_to_resources.rb +6 -0
  101. data/db/migrate/20170204091234_add_theme_to_spotlight_exhibits.rb +5 -0
  102. data/lib/generators/spotlight/install_generator.rb +5 -2
  103. data/lib/generators/spotlight/templates/config/initializers/spotlight_initializer.rb +0 -1
  104. data/lib/generators/spotlight/templates/spotlight.scss +3 -2
  105. data/lib/migration/iiif.rb +82 -0
  106. data/lib/spotlight/engine.rb +22 -10
  107. data/lib/spotlight/version.rb +1 -1
  108. data/lib/tasks/spotlight_tasks.rake +10 -0
  109. data/spec/controllers/spotlight/about_pages_controller_spec.rb +1 -1
  110. data/spec/controllers/spotlight/appearances_controller_spec.rb +31 -18
  111. data/spec/controllers/spotlight/catalog_controller_spec.rb +40 -0
  112. data/spec/controllers/spotlight/contacts_controller_spec.rb +20 -1
  113. data/spec/controllers/spotlight/feature_pages_controller_spec.rb +2 -6
  114. data/spec/controllers/spotlight/featured_images_controller_spec.rb +74 -0
  115. data/spec/controllers/spotlight/home_pages_controller_spec.rb +1 -1
  116. data/spec/controllers/spotlight/searches_controller_spec.rb +3 -1
  117. data/spec/controllers/spotlight/sites_controller_spec.rb +6 -1
  118. data/spec/examples.txt +1118 -1059
  119. data/spec/factories/contact_images.rb +6 -0
  120. data/spec/factories/contacts.rb +4 -1
  121. data/spec/factories/exhibit_thumbnails.rb +6 -0
  122. data/spec/factories/exhibits.rb +4 -0
  123. data/spec/factories/featured_images.rb +1 -0
  124. data/spec/factories/resources.rb +2 -1
  125. data/spec/features/add_contacts_spec.rb +5 -5
  126. data/spec/features/add_iiif_manifest_spec.rb +41 -0
  127. data/spec/features/add_items_spec.rb +2 -2
  128. data/spec/features/autocomplete_typeahead_spec.rb +86 -0
  129. data/spec/features/browse_category_admin_spec.rb +27 -6
  130. data/spec/features/browse_category_spec.rb +2 -2
  131. data/spec/features/create_exhibit_spec.rb +3 -3
  132. data/spec/features/exhibit_masthead_spec.rb +20 -9
  133. data/spec/features/exhibit_themes_spec.rb +25 -0
  134. data/spec/features/home_page_spec.rb +1 -1
  135. data/spec/features/javascript/blocks/solr_documents_block_spec.rb +42 -0
  136. data/spec/features/javascript/blocks/uploaded_items_block_spec.rb +5 -3
  137. data/spec/features/javascript/feature_page_admin_spec.rb +1 -1
  138. data/spec/features/javascript/multi_image_select_spec.rb +5 -6
  139. data/spec/features/javascript/search_config_admin_spec.rb +1 -1
  140. data/spec/features/site_masthead_spec.rb +14 -4
  141. data/spec/fixtures/gk446cj2442-manifest.json +58 -0
  142. data/spec/fixtures/iiif_responses.rb +274 -0
  143. data/spec/fixtures/sample_solr_documents.yml +106 -0
  144. data/spec/helpers/spotlight/crop_helper_spec.rb +9 -0
  145. data/spec/helpers/spotlight/main_app_helpers_spec.rb +45 -0
  146. data/spec/helpers/spotlight/meta_helper_spec.rb +2 -15
  147. data/spec/lib/migration/iiif_spec.rb +70 -0
  148. data/spec/models/spotlight/blacklight_configuration_spec.rb +17 -5
  149. data/spec/models/spotlight/contact_image_spec.rb +9 -0
  150. data/spec/models/spotlight/exhibit_spec.rb +17 -20
  151. data/spec/models/spotlight/exhibit_thumbnail_spec.rb +8 -0
  152. data/spec/models/spotlight/featured_image_spec.rb +59 -10
  153. data/spec/models/spotlight/masthead_spec.rb +33 -17
  154. data/spec/models/spotlight/page_spec.rb +14 -0
  155. data/spec/models/spotlight/reindex_progress_spec.rb +22 -73
  156. data/spec/models/spotlight/resources/iiif_harvester_spec.rb +30 -0
  157. data/spec/models/spotlight/resources/iiif_manifest_spec.rb +107 -0
  158. data/spec/models/spotlight/resources/iiif_service_spec.rb +52 -0
  159. data/spec/models/spotlight/resources/upload_spec.rb +7 -3
  160. data/spec/models/spotlight/search_spec.rb +0 -45
  161. data/spec/models/spotlight/solr_document/uploaded_resource_spec.rb +11 -29
  162. data/spec/presenters/spotlight/iiif_manifest_presenter_spec.rb +123 -0
  163. data/spec/routing/spotlight/exhibit_catalog_spec.rb +4 -0
  164. data/spec/routing/spotlight/featured_images_spec.rb +21 -0
  165. data/spec/serializers/spotlight/exhibit_export_serializer_spec.rb +15 -18
  166. data/spec/services/spotlight/iiif_resource_resolver_spec.rb +90 -0
  167. data/spec/spec_helper.rb +3 -0
  168. data/spec/support/features/test_features_helpers.rb +3 -2
  169. data/spec/support/stub_iiif_response.rb +24 -0
  170. data/spec/support/views/test_view_helpers.rb +1 -0
  171. data/spec/test_app_templates/Gemfile.extra +0 -1
  172. data/spec/uploaders/spotlight/attachment_uploader_spec.rb +24 -0
  173. data/spec/uploaders/spotlight/featured_image_uploader_spec.rb +30 -0
  174. data/spec/views/_user_util_links.html.erb_spec.rb +9 -5
  175. data/spec/views/shared/_masthead.html.erb_spec.rb +5 -2
  176. data/spec/views/spotlight/browse/_search.html.erb_spec.rb +2 -2
  177. data/spec/views/spotlight/contacts/edit.html.erb_spec.rb +4 -7
  178. data/spec/views/spotlight/metadata_configurations/_metadata_field.html.erb_spec.rb +7 -2
  179. data/spec/views/spotlight/pages/edit.html.erb_spec.rb +3 -1
  180. data/spec/views/spotlight/pages/new.html.erb_spec.rb +3 -1
  181. data/spec/views/spotlight/searches/_search.html.erb_spec.rb +3 -2
  182. data/spec/views/spotlight/searches/edit.html.erb_spec.rb +3 -2
  183. data/vendor/assets/javascripts/leaflet-areaselect.js +184 -0
  184. data/vendor/assets/javascripts/leaflet-iiif.js +230 -0
  185. data/vendor/assets/javascripts/leaflet.js +9 -0
  186. data/vendor/assets/javascripts/polyfill.min.js +4 -0
  187. data/vendor/assets/stylesheets/leaflet-areaselect.css +15 -0
  188. data/vendor/assets/stylesheets/leaflet.css +624 -0
  189. metadata +136 -62
  190. data/app/assets/javascripts/spotlight/jcrop.js +0 -1696
  191. data/app/helpers/spotlight/jcrop_helper.rb +0 -37
  192. data/app/models/concerns/spotlight/default_thumbnailable.rb +0 -25
  193. data/app/models/concerns/spotlight/image_derivatives.rb +0 -58
  194. data/app/models/concerns/spotlight/solr_document/spotlight_images.rb +0 -55
  195. data/app/uploaders/spotlight/avatar_uploader.rb +0 -24
  196. data/app/uploaders/spotlight/item_uploader.rb +0 -25
  197. data/app/uploaders/spotlight/masthead_uploader.rb +0 -22
  198. data/spec/helpers/spotlight/jcrop_helper_spec.rb +0 -33
  199. data/spec/models/spotlight/default_thumbnailable_concern_spec.rb +0 -16
  200. data/spec/models/spotlight/image_derivatives_spec.rb +0 -15
  201. data/spec/models/spotlight/solr_document/spotlight_images_spec.rb +0 -42
  202. data/spec/uploaders/spotlight/item_uploader_spec.rb +0 -67
@@ -10,6 +10,7 @@ module Spotlight
10
10
  # null start times sort to the top, to more easily surface pending reindexing
11
11
  default_scope { order('start_time IS NOT NULL, start_time DESC') }
12
12
  scope :recent, -> { limit(5) }
13
+ scope :started_or_completed, -> { where.not(job_status: 'unstarted') }
13
14
 
14
15
  def duration
15
16
  end_time - start_time if end_time
@@ -0,0 +1,33 @@
1
+ require 'iiif/presentation'
2
+
3
+ module Spotlight
4
+ module Resources
5
+ # harvest Images from IIIF Manifest and turn them into a Spotlight::Resource
6
+ # Note: IIIF API : http://iiif.io/api/presentation/2.0
7
+ class IiifHarvester < Spotlight::Resource
8
+ self.document_builder_class = Spotlight::Resources::IiifBuilder
9
+ self.weight = -5000
10
+
11
+ validate :valid_url?
12
+
13
+ def iiif_manifests
14
+ @iiif_manifests ||= IiifService.parse(url)
15
+ end
16
+
17
+ private
18
+
19
+ def valid_url?
20
+ errors.add(:url, 'Invalid IIIF URL') unless url_is_iiif?(url)
21
+ end
22
+
23
+ def url_is_iiif?(url)
24
+ valid_content_types = ['application/json', 'application/ld+json']
25
+ req = Faraday.head(url)
26
+ return unless req.success?
27
+ valid_content_types.any? do |valid_type|
28
+ req.headers['content-type'].include?(valid_type)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,211 @@
1
+ module Spotlight
2
+ module Resources
3
+ ##
4
+ # A PORO to construct a solr hash for a given IiifManifest
5
+ class IiifManifest
6
+ attr_reader :collection
7
+ def initialize(attrs = {})
8
+ @url = attrs[:url]
9
+ @manifest = attrs[:manifest]
10
+ @collection = attrs[:collection]
11
+ @solr_hash = {}
12
+ end
13
+
14
+ def to_solr
15
+ add_document_id
16
+ add_label
17
+ add_thumbnail_url
18
+ add_full_image_urls
19
+ add_manifest_url
20
+ add_image_urls
21
+ add_metadata
22
+ add_collection_id
23
+ solr_hash
24
+ end
25
+
26
+ def with_exhibit(e)
27
+ @exhibit = e
28
+ end
29
+
30
+ def compound_id
31
+ Digest::MD5.hexdigest("#{exhibit.id}-#{url}")
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :url, :manifest, :exhibit, :solr_hash
37
+ delegate :blacklight_config, to: :exhibit
38
+
39
+ def add_document_id
40
+ solr_hash[exhibit.blacklight_config.document_model.unique_key.to_sym] = compound_id
41
+ end
42
+
43
+ def add_collection_id
44
+ solr_hash[collection_id_field] = [collection.compound_id] if collection
45
+ end
46
+
47
+ def collection_id_field
48
+ Spotlight::Engine.config.iiif_collection_id_field
49
+ end
50
+
51
+ def add_manifest_url
52
+ solr_hash[Spotlight::Engine.config.iiif_manifest_field] = url
53
+ end
54
+
55
+ def add_thumbnail_url
56
+ return unless thumbnail_field && manifest['thumbnail'].present?
57
+ solr_hash[thumbnail_field] = manifest['thumbnail']['@id']
58
+ end
59
+
60
+ def add_full_image_urls
61
+ return unless full_image_field && full_image_url
62
+ solr_hash[full_image_field] = full_image_url
63
+ end
64
+
65
+ def add_label
66
+ return unless title_field && manifest.try(:label)
67
+ solr_hash[title_field] = manifest.label
68
+ end
69
+
70
+ def add_image_urls
71
+ solr_hash[tile_source_field] = image_urls
72
+ end
73
+
74
+ def add_metadata
75
+ solr_hash.merge!(manifest_metadata)
76
+ sidecar.update(data: sidecar.data.merge(manifest_metadata))
77
+ end
78
+
79
+ def manifest_metadata
80
+ metadata = metadata_class.new(manifest).to_solr
81
+ return {} unless metadata.present?
82
+ create_sidecars_for(*metadata.keys)
83
+
84
+ metadata.each_with_object({}) do |(key, value), hash|
85
+ next unless (field = exhibit_custom_fields[key])
86
+ hash[field.field] = value
87
+ end
88
+ end
89
+
90
+ def create_sidecars_for(*keys)
91
+ missing_keys(keys).each do |k|
92
+ exhibit.custom_fields.create! label: k, readonly_field: true
93
+ end
94
+ @exhibit_custom_fields = nil
95
+ end
96
+
97
+ def missing_keys(keys)
98
+ custom_field_keys = exhibit_custom_fields.keys.map(&:downcase)
99
+ keys.reject do |key|
100
+ custom_field_keys.include?(key.downcase)
101
+ end
102
+ end
103
+
104
+ def exhibit_custom_fields
105
+ @exhibit_custom_fields ||= exhibit.custom_fields.each_with_object({}) do |value, hash|
106
+ hash[value.label] = value
107
+ end
108
+ end
109
+
110
+ def image_urls
111
+ @image_urls ||= resources.map do |resource|
112
+ next unless resource && !resource.service.empty?
113
+ image_url = resource.service['@id']
114
+ image_url << '/info.json' unless image_url.downcase.ends_with?('/info.json')
115
+ image_url
116
+ end
117
+ end
118
+
119
+ def full_image_url
120
+ resources.first.try(:[], '@id')
121
+ end
122
+
123
+ def resources
124
+ @resources ||= sequences
125
+ .flat_map(&:canvases)
126
+ .flat_map(&:images)
127
+ .flat_map(&:resource)
128
+ end
129
+
130
+ def sequences
131
+ manifest.try(:sequences) || []
132
+ end
133
+
134
+ def thumbnail_field
135
+ blacklight_config.index.try(:thumbnail_field)
136
+ end
137
+
138
+ def full_image_field
139
+ Spotlight::Engine.config.full_image_field
140
+ end
141
+
142
+ def tile_source_field
143
+ blacklight_config.show.try(:tile_source_field)
144
+ end
145
+
146
+ def title_field
147
+ blacklight_config.index.try(:title_field)
148
+ end
149
+
150
+ def sidecar
151
+ @sidecar ||= document_model.new(id: compound_id).sidecar(exhibit)
152
+ end
153
+
154
+ def document_model
155
+ exhibit.blacklight_config.document_model
156
+ end
157
+
158
+ def metadata_class
159
+ Spotlight::Engine.config.iiif_metadata_class.call
160
+ end
161
+
162
+ ###
163
+ # A simple class to map the metadata field
164
+ # in a IIIF document to label/value pairs
165
+ # This is intended to be overriden by an
166
+ # application if a different metadata
167
+ # strucure is used by the consumer
168
+ class Metadata
169
+ def initialize(manifest)
170
+ @manifest = manifest
171
+ end
172
+
173
+ def to_solr
174
+ metadata_hash.merge(manifest_level_metadata)
175
+ end
176
+
177
+ private
178
+
179
+ attr_reader :manifest
180
+
181
+ def metadata
182
+ manifest.try(:metadata) || []
183
+ end
184
+
185
+ def metadata_hash
186
+ return {} unless metadata.present?
187
+ return {} unless metadata.is_a?(Array)
188
+
189
+ metadata.each_with_object({}) do |md, hash|
190
+ next unless md['label'] && md['value']
191
+ hash[md['label']] ||= []
192
+ hash[md['label']] += Array(md['value'])
193
+ end
194
+ end
195
+
196
+ def manifest_level_metadata
197
+ manifest_fields.each_with_object({}) do |field, hash|
198
+ next unless manifest.respond_to?(field) &&
199
+ manifest.send(field).present?
200
+ hash[field.capitalize] ||= []
201
+ hash[field.capitalize] += Array(manifest.send(field))
202
+ end
203
+ end
204
+
205
+ def manifest_fields
206
+ %w(attribution description license)
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,93 @@
1
+ require 'iiif/presentation'
2
+ module Spotlight
3
+ module Resources
4
+ ###
5
+ # Wrapper around IIIF-Presentation's IIIF::Service that provides the
6
+ # ability to recursively traverse through all collections and manifests
7
+ class IiifService
8
+ def initialize(url)
9
+ @url = url
10
+ end
11
+
12
+ def collections
13
+ @collections ||= (object.try(:collections) || []).map do |collection|
14
+ self.class.new(collection['@id'])
15
+ end
16
+ end
17
+
18
+ def manifests
19
+ @manifests ||= if manifest?
20
+ [create_iiif_manifest(object)]
21
+ else
22
+ build_collection_manifest.to_a
23
+ end
24
+ end
25
+
26
+ def self.parse(url)
27
+ recursive_manifests(new(url))
28
+ end
29
+
30
+ protected
31
+
32
+ def object
33
+ @object ||= IIIF::Service.parse(response)
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :url
39
+
40
+ class << self
41
+ def iiif_response(url)
42
+ Faraday.get(url).body
43
+ rescue Faraday::Error::ConnectionFailed, Faraday::TimeoutError => e
44
+ Rails.logger.warn("HTTP GET for #{url} failed with #{e}")
45
+ {}.to_json
46
+ end
47
+
48
+ private
49
+
50
+ def recursive_manifests(thing, &block)
51
+ return to_enum(:recursive_manifests, thing) unless block_given?
52
+
53
+ thing.manifests.each(&block)
54
+
55
+ return unless thing.collections.present?
56
+
57
+ thing.collections.each do |collection|
58
+ recursive_manifests(collection, &block)
59
+ end
60
+ end
61
+ end
62
+
63
+ def create_iiif_manifest(manifest, collection = nil)
64
+ IiifManifest.new(url: manifest['@id'], manifest: manifest, collection: collection)
65
+ end
66
+
67
+ def manifest?
68
+ object.is_a?(IIIF::Presentation::Manifest)
69
+ end
70
+
71
+ def collection?
72
+ object.is_a?(IIIF::Presentation::Collection)
73
+ end
74
+
75
+ def response
76
+ @response ||= self.class.iiif_response(url)
77
+ end
78
+
79
+ def build_collection_manifest
80
+ return to_enum(:build_collection_manifest) unless block_given?
81
+
82
+ if collection?
83
+ self_manifest = create_iiif_manifest(object)
84
+ yield self_manifest
85
+ end
86
+
87
+ (object.try(:manifests) || []).each do |manifest|
88
+ yield create_iiif_manifest(self.class.new(manifest['@id']).object, self_manifest)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -4,8 +4,7 @@ module Spotlight
4
4
  ##
5
5
  # Exhibit-specific resources, created using uploaded and custom fields
6
6
  class Upload < Spotlight::Resource
7
- mount_uploader :url, Spotlight::ItemUploader
8
- include Spotlight::ImageDerivatives
7
+ belongs_to :upload, class_name: 'Spotlight::FeaturedImage'
9
8
 
10
9
  # we want to do this before reindexing
11
10
  after_create :update_document_sidecar
@@ -2,7 +2,6 @@ module Spotlight
2
2
  ##
3
3
  # Exhibit saved searches
4
4
  class Search < ActiveRecord::Base
5
- include DefaultThumbnailable
6
5
  extend FriendlyId
7
6
  friendly_id :title, use: [:slugged, :scoped, :finders, :history], scope: :exhibit
8
7
 
@@ -16,23 +15,12 @@ module Spotlight
16
15
 
17
16
  belongs_to :masthead, dependent: :destroy
18
17
  belongs_to :thumbnail, class_name: 'Spotlight::FeaturedImage', dependent: :destroy
19
- accepts_nested_attributes_for :thumbnail, update_only: true
20
- accepts_nested_attributes_for :masthead, update_only: true
18
+ accepts_nested_attributes_for :thumbnail, update_only: true, reject_if: proc { |attr| attr['iiif_tilesource'].blank? }
19
+ accepts_nested_attributes_for :masthead, update_only: true, reject_if: proc { |attr| attr['iiif_tilesource'].blank? }
21
20
 
22
21
  def thumbnail_image_url
23
- thumbnail.image.thumb.url if thumbnail && thumbnail.image
24
- end
25
-
26
- def images
27
- return enum_for(:images) { documents.size } unless block_given?
28
-
29
- documents.each do |doc|
30
- yield [
31
- doc.first(blacklight_config.document_model.unique_key),
32
- doc.first(blacklight_config.index.title_field),
33
- doc.first(blacklight_config.index.thumbnail_field)
34
- ]
35
- end
22
+ return unless thumbnail && thumbnail.iiif_url
23
+ thumbnail.iiif_url
36
24
  end
37
25
 
38
26
  def documents
@@ -58,22 +46,6 @@ module Spotlight
58
46
  masthead && masthead.display?
59
47
  end
60
48
 
61
- # rubocop:disable Metrics/MethodLength
62
- def set_default_thumbnail
63
- self.thumbnail ||= begin
64
- return unless Spotlight::Engine.config.full_image_field
65
- doc = documents.detect { |x| x.first(Spotlight::Engine.config.full_image_field) }
66
- if doc
67
- create_thumbnail(
68
- source: 'exhibit',
69
- document_global_id: doc.to_global_id.to_s,
70
- remote_image_url: doc.first(Spotlight::Engine.config.full_image_field)
71
- )
72
- end
73
- end
74
- end
75
- # rubocop:enable Metrics/MethodLength
76
-
77
49
  def search_params
78
50
  search_builder.with(query_params.with_indifferent_access).merge(facet: false, fl: default_search_fields)
79
51
  end
@@ -102,8 +74,7 @@ module Spotlight
102
74
  [
103
75
  blacklight_config.document_model.unique_key,
104
76
  blacklight_config.index.title_field,
105
- blacklight_config.index.thumbnail_field,
106
- Spotlight::Engine.config.full_image_field
77
+ blacklight_config.index.thumbnail_field
107
78
  ].compact
108
79
  end
109
80
 
@@ -0,0 +1,79 @@
1
+ module Spotlight
2
+ ##
3
+ # A presenter class that provides the methods that IIIFManifest expects, as well as convenience methods
4
+ # that will generate a IIIFManifest object, and the actual JSON manifest from the IIIFManifest object.
5
+ # Instances of this class represent IIIF leaf nodes. We do not currently generate manifests with interstitial
6
+ # nodes.
7
+ #
8
+ # IIIFManifest expects the following methods: #file_set_presenters, #work_presenters, #manifest_url, #description.
9
+ # see: https://github.com/projecthydra-labs/iiif_manifest/blob/master/README.md
10
+ class IiifManifestPresenter
11
+ require 'iiif_manifest'
12
+
13
+ # The class that represents the leaf nodes must implement #id (here implemented
14
+ # via delegation to the resource, since this class represents leaf nodes).
15
+ delegate :id, :uploaded_resource, to: :resource
16
+ delegate :blacklight_config, to: :controller
17
+
18
+ attr_accessor :resource, :controller
19
+
20
+ def initialize(resource, controller)
21
+ @resource = resource
22
+ @controller = controller
23
+ end
24
+
25
+ # IIIFManifest expects leaf nodes to implement #display_image, which returns an instance of IIIFManifest::DisplayImage.
26
+ def display_image
27
+ IIIFManifest::DisplayImage.new(id,
28
+ width: resource.first(:spotlight_full_image_width_ssm),
29
+ height: resource.first(:spotlight_full_image_height_ssm),
30
+ format: 'image/jpeg',
31
+ iiif_endpoint: endpoint)
32
+ end
33
+
34
+ # Returns an array of leaf nodes. Currently, this is a single element array containing this presenter
35
+ # instance, since we're only building a single-image manifest for the given resource.
36
+ def file_set_presenters
37
+ [self]
38
+ end
39
+
40
+ # This is an empty array, since we're not building manifests for works at the moment.
41
+ def work_presenters
42
+ []
43
+ end
44
+
45
+ # where this manifest can be found
46
+ def manifest_url
47
+ controller.spotlight.manifest_exhibit_solr_document_url(uploaded_resource.exhibit, resource)
48
+ end
49
+
50
+ # a description of the manifest
51
+ def description
52
+ resource.first(Spotlight::Engine.config.upload_description_field)
53
+ end
54
+
55
+ # IIIFManifest will call #to_s on each leaf node to get its respective label (not called out in README).
56
+ def to_s
57
+ resource.first(blacklight_config.view_config(:show).title_field)
58
+ end
59
+
60
+ def iiif_manifest
61
+ IIIFManifest::ManifestFactory.new(self)
62
+ end
63
+
64
+ def iiif_manifest_json
65
+ iiif_manifest.to_h.to_json
66
+ end
67
+
68
+ private
69
+
70
+ def endpoint
71
+ IIIFManifest::IIIFEndpoint.new(iiif_url, profile: 'http://iiif.io/api/image/2/level2.json')
72
+ end
73
+
74
+ def iiif_url
75
+ # yes this is hacky, and we are appropriately ashamed.
76
+ controller.riiif.info_url.sub(%r{/info\.json\Z}, '')
77
+ end
78
+ end
79
+ end