hyrax 2.8.0 → 2.9.0

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +1 -1
  4. data/app/assets/javascripts/hyrax/autocomplete.es6 +29 -0
  5. data/app/assets/javascripts/hyrax/editor.es6 +9 -10
  6. data/app/controllers/concerns/hyrax/works_controller_behavior.rb +18 -7
  7. data/app/helpers/hyrax/hyrax_helper_behavior.rb +1 -0
  8. data/app/helpers/hyrax/work_form_helper.rb +48 -0
  9. data/app/jobs/iiif_manifest_cache_prewarm_job.rb +16 -0
  10. data/app/models/concerns/hyrax/solr_document/metadata.rb +1 -0
  11. data/app/models/concerns/hyrax/solr_document/ordered_members.rb +46 -0
  12. data/app/models/concerns/hyrax/solr_document_behavior.rb +10 -0
  13. data/app/presenters/hyrax/displays_image.rb +25 -21
  14. data/app/presenters/hyrax/iiif_manifest_presenter.rb +232 -0
  15. data/app/presenters/hyrax/member_presenter_factory.rb +1 -7
  16. data/app/services/hyrax/caching_iiif_manifest_builder.rb +53 -0
  17. data/app/services/hyrax/identifier/builder.rb +45 -0
  18. data/app/services/hyrax/identifier/dispatcher.rb +61 -0
  19. data/app/services/hyrax/identifier/registrar.rb +41 -0
  20. data/app/services/hyrax/manifest_builder_service.rb +88 -0
  21. data/app/services/hyrax/versioning_service.rb +9 -0
  22. data/app/views/hyrax/base/_form.html.erb +1 -1
  23. data/app/views/hyrax/base/_form_progress.html.erb +4 -0
  24. data/app/views/hyrax/base/_guts4form.html.erb +7 -1
  25. data/app/views/hyrax/batch_uploads/_form.html.erb +1 -1
  26. data/app/views/hyrax/dashboard/_sidebar.html.erb +1 -1
  27. data/config/features.rb +4 -0
  28. data/hyrax.gemspec +1 -0
  29. data/lib/generators/hyrax/templates/config/initializers/hyrax.rb +5 -0
  30. data/lib/hyrax.rb +1 -0
  31. data/lib/hyrax/configuration.rb +15 -0
  32. data/lib/hyrax/engine.rb +1 -0
  33. data/lib/hyrax/specs/shared_specs.rb +1 -0
  34. data/lib/hyrax/specs/shared_specs/identifiers.rb +27 -0
  35. data/lib/hyrax/version.rb +1 -1
  36. data/template.rb +1 -1
  37. metadata +26 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 140c7caa34751f7a525eda269abcee6331a9e7d200863e2cbee29d315966de65
4
- data.tar.gz: 7085c394edd0aa1e90baef7d13f90245d8fb0fb65659e54549cedda0487445e9
3
+ metadata.gz: c050894909dcdbe63b34640d24f9f665e3818850cf2f4c5a0d92af23ae51cf62
4
+ data.tar.gz: 42a5a185ee2d26c9ab54d544a8261fc2bd24c01b99903092e5d54a9c5dcee054
5
5
  SHA512:
6
- metadata.gz: 691b84f363281552cbd84f85b767bb5b460ba749e0449aed0498594143767a6cd4dae187a5dd9c88bf3b7bd06a568375da70367b3b8faa674a59f39deaf4df06
7
- data.tar.gz: e19605afaa51b569ea79e7e99d65fd0576b172cd6dfeb38f815c4dd3a7ac18678bb6b3ff4ac4a5af2b6358bf55c459baba0c4a98b393d20f7d64388f576c894f
6
+ metadata.gz: a0fab1e2979598561c1f4afd61e2434c0af3529005c7f6394f9605d5c30e4e95df45a7c23cf665ac6ee647360a27ef286870a7456b8b57a0ec17e905f8d474af
7
+ data.tar.gz: 55d6ffdace586396f41b327dceca10da11eeedbcee3d0be0abeafa3635574425e79fa6b8dd0ba831870b9f6d464e0067da889d46e5318051d63f00a502cff892
data/.gitignore CHANGED
@@ -78,3 +78,4 @@ _yardoc
78
78
  lib/bundler/man
79
79
  spec/reports
80
80
  /spec/examples.txt
81
+ node_modules
data/README.md CHANGED
@@ -158,7 +158,7 @@ NOTE: The steps need to be done in order to create a new Hyrax based app.
158
158
  Generate a new Rails application using the template.
159
159
 
160
160
  ```
161
- rails _5.2.4.3_ new my_app -m https://raw.githubusercontent.com/samvera/hyrax/v2.8.0/template.rb
161
+ rails _5.2.4.3_ new my_app -m https://raw.githubusercontent.com/samvera/hyrax/v2.9.0/template.rb
162
162
  ```
163
163
 
164
164
  Generating a new Rails application using Hyrax's template above takes cares of a number of steps for you, including:
@@ -10,6 +10,34 @@ export default class Autocomplete {
10
10
  * @param {string} url - The url for the autocompete search endpoint
11
11
  */
12
12
  setup (element, fieldName, url) {
13
+ if(element.data('autocomplete-type') && element.data('autocomplete-type').length > 0) {
14
+ this.byDataAttribute(element, url)
15
+ } else {
16
+ this.byFieldName(element, fieldName, url)
17
+ }
18
+ }
19
+
20
+ byDataAttribute(element, url) {
21
+ let type = element.data('autocomplete-type')
22
+ let exlude = element.data('exclude-work')
23
+ if(type === 'resource' && exclude.length > 0) {
24
+ new Resource(
25
+ element,
26
+ url,
27
+ { excluding: exclude }
28
+ )
29
+ } else if(type === 'resource' ) {
30
+ new Resource(
31
+ element,
32
+ url)
33
+ } else if(type === 'linked') {
34
+ new LinkedData(element, url)
35
+ } else {
36
+ new Default(element, url)
37
+ }
38
+ }
39
+
40
+ byFieldName(element, fieldName, url) {
13
41
  switch (fieldName) {
14
42
  case 'work':
15
43
  new Resource(
@@ -30,4 +58,5 @@ export default class Autocomplete {
30
58
  break
31
59
  }
32
60
  }
61
+
33
62
  }
@@ -53,17 +53,16 @@ export default class {
53
53
  $('[data-autocomplete]').each((function() {
54
54
  var elem = $(this)
55
55
  autocomplete.setup(elem, elem.data('autocomplete'), elem.data('autocompleteUrl'))
56
+ elem.parents('.multi_value.form-group').manage_fields({
57
+ add: function(e, element) {
58
+ var elem = $(element)
59
+ // Don't mark an added element as readonly even if previous element was
60
+ // Enable before initializing, as otherwise LinkedData fields remain disabled
61
+ elem.attr('readonly', false)
62
+ autocomplete.setup(elem, elem.data('autocomplete'), elem.data('autocompleteUrl'))
63
+ }
64
+ })
56
65
  }))
57
-
58
- $('.multi_value.form-group').manage_fields({
59
- add: function(e, element) {
60
- var elem = $(element)
61
- // Don't mark an added element as readonly even if previous element was
62
- // Enable before initializing, as otherwise LinkedData fields remain disabled
63
- elem.attr('readonly', false)
64
- autocomplete.setup(elem, elem.data('autocomplete'), elem.data('autocompleteUrl'))
65
- }
66
- })
67
66
  }
68
67
 
69
68
  // initialize any controlled vocabulary widgets
@@ -10,10 +10,11 @@ module Hyrax
10
10
  with_themed_layout :decide_layout
11
11
  copy_blacklight_config_from(::CatalogController)
12
12
 
13
- class_attribute :_curation_concern_type, :show_presenter, :work_form_service, :search_builder_class
13
+ class_attribute :_curation_concern_type, :show_presenter, :work_form_service, :search_builder_class, :iiif_manifest_builder
14
14
  self.show_presenter = Hyrax::WorkShowPresenter
15
15
  self.work_form_service = Hyrax::WorkFormService
16
16
  self.search_builder_class = WorkSearchBuilder
17
+ self.iiif_manifest_builder = (Flipflop.cache_work_iiif_manifest? ? Hyrax::CachingIiifManifestBuilder.new : Hyrax::ManifestBuilderService.new)
17
18
  attr_accessor :curation_concern
18
19
  helper_method :curation_concern, :contextual_path
19
20
 
@@ -127,14 +128,28 @@ module Hyrax
127
128
 
128
129
  def manifest
129
130
  headers['Access-Control-Allow-Origin'] = '*'
131
+
132
+ json = iiif_manifest_builder.manifest_for(presenter: iiif_manifest_presenter)
133
+
130
134
  respond_to do |wants|
131
- wants.json { render json: manifest_builder.to_h }
132
- wants.html { render json: manifest_builder.to_h }
135
+ wants.json { render json: json }
136
+ wants.html { render json: json }
133
137
  end
134
138
  end
135
139
 
136
140
  private
137
141
 
142
+ def iiif_manifest_builder
143
+ self.class.iiif_manifest_builder
144
+ end
145
+
146
+ def iiif_manifest_presenter
147
+ IiifManifestPresenter.new(curation_concern_from_search_results).tap do |p|
148
+ p.hostname = request.hostname
149
+ p.ability = current_ability
150
+ end
151
+ end
152
+
138
153
  def user_collections
139
154
  collections_service.search_results(:deposit)
140
155
  end
@@ -157,10 +172,6 @@ module Hyrax
157
172
  @form = work_form_service.build(curation_concern, current_ability, self)
158
173
  end
159
174
 
160
- def manifest_builder
161
- ::IIIFManifest::ManifestFactory.new(presenter)
162
- end
163
-
164
175
  def actor
165
176
  @actor ||= Hyrax::CurationConcern.actor
166
177
  end
@@ -12,6 +12,7 @@ module Hyrax
12
12
  include Hyrax::ChartsHelper
13
13
  include Hyrax::DashboardHelperBehavior
14
14
  include Hyrax::IiifHelper
15
+ include Hyrax::WorkFormHelper
15
16
 
16
17
  # Which translations are available for the user to select
17
18
  # @return [Hash<String,String>] locale abbreviations as keys and flags as values
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module WorkFormHelper
4
+ ##
5
+ # This helper allows downstream applications and engines to add/remove/reorder the tabs to be
6
+ # rendered on the work form.
7
+ #
8
+ # @example with additional tabs
9
+ # Override this helper and ensure that it loads after Hyrax's helpers.
10
+ # module WorksHelper
11
+ # def form_tabs_for(form:)
12
+ # super + ["my_new_tab"]
13
+ # end
14
+ # end
15
+ # Add the new section partial at app/views/hyrax/base/_form_my_new_tab.html.erb
16
+ #
17
+ # @todo The share tab isn't included because it wasn't in guts4form. guts4form should be
18
+ # cleaned up so share is treated the same as other tabs and can be included below.
19
+ # @param form [Hyrax::Forms::WorkForm]
20
+ # @return [Array<String>] the list of names of tabs to be rendered in the form
21
+ def form_tabs_for(form:)
22
+ if form.instance_of? Hyrax::Forms::BatchUploadForm
23
+ %w[files metadata relationships]
24
+ else
25
+ %w[metadata files relationships]
26
+ end
27
+ end
28
+
29
+ ##
30
+ # This helper allows downstream applications and engines to add additional sections to be
31
+ # rendered after the visibility section in the Save Work panel on the work form.
32
+ #
33
+ # @example with additional sections
34
+ # Override this helper and ensure that it loads after Hyrax's helpers.
35
+ # module WorksHelper
36
+ # def form_progress_sections_for(*)
37
+ # super + ["my_new_section"]
38
+ # end
39
+ # end
40
+ # Add the new section partial at app/views/hyrax/base/_form_progress_my_new_section.html.erb
41
+ #
42
+ # @param form [Hyrax::Forms::WorkForm]
43
+ # @return [Array<String>] the list of names of sections to be rendered in the form_progress panel
44
+ def form_progress_sections_for(*)
45
+ []
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class IiifManifestCachePrewarmJob < Hyrax::ApplicationJob
4
+ ##
5
+ # @param work [ActiveFedora::Base]
6
+ def perform(work)
7
+ presenter = Hyrax::IiifManifestPresenter.new(work)
8
+ manifest_builder.manifest_for(presenter: presenter)
9
+ end
10
+
11
+ private
12
+
13
+ def manifest_builder
14
+ Hyrax::CachingIiifManifestBuilder.new
15
+ end
16
+ end
@@ -54,6 +54,7 @@ module Hyrax
54
54
  attribute :read_groups, Solr::Array, ::Ability.read_group_field
55
55
  attribute :collection_ids, Solr::Array, 'collection_ids_tesim'
56
56
  attribute :admin_set, Solr::Array, solr_name('admin_set')
57
+ attribute :member_ids, Solr::Array, "member_ids_ssim"
57
58
  attribute :member_of_collection_ids, Solr::Array, solr_name('member_of_collection_ids', :symbol)
58
59
  attribute :description, Solr::Array, solr_name('description')
59
60
  attribute :title, Solr::Array, solr_name('title')
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module SolrDocument
4
+ ##
5
+ # Decorates an object responding to `#id` with an `#ordered_member_ids` method.
6
+ #
7
+ # @note this decorator is intended for use with data representations other
8
+ # than the core model objects, as an alternative to a direct query of the
9
+ # canonical database. for example, it can be used with `SolrDocument` to
10
+ # quickly retrieve member order in a way that is compatible with the
11
+ # fast access required in Blacklight's search contexts.
12
+ #
13
+ # @example
14
+ # base_document = SolrDocument.new(my_work.to_solr)
15
+ # solr_document = Hyrax::SolrDocument::OrderedMembers.decorate(base_document)
16
+ #
17
+ # solr_document.ordered_member_ids # => ['abc', '123']
18
+ #
19
+ class OrderedMembers < Draper::Decorator
20
+ delegate_all
21
+
22
+ ##
23
+ # @note the purpose of this method is to provide fast access to member
24
+ # order. currently this is achieved by accessing indexed list proxies
25
+ # from Solr. however, this strategy may change in the future.
26
+ #
27
+ # @return [Enumerable<String>] ids in the order of their membership,
28
+ # only includes ids of ordered members.
29
+ def ordered_member_ids
30
+ return [] if id.blank?
31
+ @ordered_member_ids ||= query_for_ordered_ids
32
+ end
33
+
34
+ private
35
+
36
+ def query_for_ordered_ids(limit: 10_000,
37
+ proxy_field: 'proxy_in_ssi',
38
+ target_field: 'ordered_targets_ssim')
39
+ ActiveFedora::SolrService
40
+ .query("#{proxy_field}:#{id}", rows: limit, fl: target_field)
41
+ .flat_map { |x| x.fetch(target_field, nil) }
42
+ .compact
43
+ end
44
+ end
45
+ end
46
+ end
@@ -61,10 +61,20 @@ module Hyrax
61
61
  @model ||= ModelWrapper.new(hydra_model, id)
62
62
  end
63
63
 
64
+ ##
65
+ # @return [Boolean]
64
66
  def collection?
65
67
  hydra_model == ::Collection
66
68
  end
67
69
 
70
+ ##
71
+ # @return [Boolean]
72
+ def file_set?
73
+ hydra_model == ::FileSet
74
+ end
75
+
76
+ ##
77
+ # @return [Boolean]
68
78
  def admin_set?
69
79
  hydra_model == ::AdminSet
70
80
  end
@@ -11,20 +11,11 @@ module Hyrax
11
11
  # @return [IIIFManifest::DisplayImage] the display image required by the manifest builder.
12
12
  def display_image
13
13
  return nil unless solr_document.image? && current_ability.can?(:read, solr_document)
14
-
15
- latest_file_id = lookup_original_file_id
16
-
17
14
  return nil unless latest_file_id
18
15
 
19
- url = Hyrax.config.iiif_image_url_builder.call(
20
- latest_file_id,
21
- request.base_url,
22
- Hyrax.config.iiif_image_size_default
23
- )
24
-
25
16
  # @see https://github.com/samvera-labs/iiif_manifest
26
- IIIFManifest::DisplayImage.new(url,
27
- format: image_format([]),
17
+ IIIFManifest::DisplayImage.new(display_image_url(request.base_url),
18
+ format: image_format(alpha_channels),
28
19
  width: width,
29
20
  height: height,
30
21
  iiif_endpoint: iiif_endpoint(latest_file_id))
@@ -32,16 +23,24 @@ module Hyrax
32
23
 
33
24
  private
34
25
 
35
- def iiif_endpoint(file_id)
26
+ def display_image_url(base_url)
27
+ Hyrax.config.iiif_image_url_builder.call(
28
+ latest_file_id,
29
+ base_url,
30
+ Hyrax.config.iiif_image_size_default
31
+ )
32
+ end
33
+
34
+ def iiif_endpoint(file_id, base_url: request.base_url)
36
35
  return unless Hyrax.config.iiif_image_server?
37
36
  IIIFManifest::IIIFEndpoint.new(
38
- Hyrax.config.iiif_info_url_builder.call(file_id, request.base_url),
37
+ Hyrax.config.iiif_info_url_builder.call(file_id, base_url),
39
38
  profile: Hyrax.config.iiif_image_compliance_level_uri
40
39
  )
41
40
  end
42
41
 
43
42
  def image_format(channels)
44
- channels.find { |c| c.include?('rgba') }.nil? ? 'jpg' : 'png'
43
+ channels&.find { |c| c.include?('rgba') }.nil? ? 'jpg' : 'png'
45
44
  end
46
45
 
47
46
  def unindexed_current_file_version
@@ -49,13 +48,18 @@ module Hyrax
49
48
  ActiveFedora::File.uri_to_id(::FileSet.find(id).current_content_version_uri)
50
49
  end
51
50
 
52
- def lookup_original_file_id
53
- result = original_file_id
54
- if result.blank?
55
- Rails.logger.warn "original_file_id for #{id} not found, falling back to Fedora."
56
- result = ActiveFedora::File.uri_to_id(::FileSet.find(id).current_content_version_uri)
57
- end
58
- result
51
+ def latest_file_id
52
+ @latest_file_id ||=
53
+ begin
54
+ result = original_file_id
55
+
56
+ if result.blank?
57
+ Rails.logger.warn "original_file_id for #{id} not found, falling back to Fedora."
58
+ result = Hyrax::VersioningService.versioned_file_id ::FileSet.find(id).original_file
59
+ end
60
+
61
+ result
62
+ end
59
63
  end
60
64
  end
61
65
  end
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyrax
4
+ ##
5
+ # This presenter wraps objects in the interface required by `IIIFManifiest`.
6
+ # It will accept either a Work-like resource or a SolrDocument.
7
+ #
8
+ # @example with a work
9
+ #
10
+ # monograph = Monograph.new
11
+ # presenter = IiifManifestPresenter.new(monograph)
12
+ # presenter.title # => []
13
+ #
14
+ # monograph.title = ['Comet in Moominland']
15
+ # presenter.title # => ['Comet in Moominland']
16
+ #
17
+ # @see https://www.rubydoc.info/gems/iiif_manifest
18
+ class IiifManifestPresenter < Draper::Decorator
19
+ delegate_all
20
+
21
+ ##
22
+ # @!attribute [w] ability
23
+ # @return [Ability]
24
+ # @!attribute [w] hostname
25
+ # @return [String]
26
+ attr_writer :ability, :hostname
27
+
28
+ class << self
29
+ ##
30
+ # @param [Hyrax::Resource, SolrDocument]
31
+ def for(model)
32
+ klass = model.file_set? ? DisplayImagePresenter : IiifManifestPresenter
33
+
34
+ klass.new(model)
35
+ end
36
+ end
37
+
38
+ ##
39
+ # @return [#can?]
40
+ def ability
41
+ @ability ||= NullAbility.new
42
+ end
43
+
44
+ ##
45
+ # @return [String]
46
+ def description
47
+ Array(super).first || ''
48
+ end
49
+
50
+ ##
51
+ # @return [Boolean]
52
+ def file_set?
53
+ model.try(:file_set?) || Array(model[:has_model_ssim]).include?('FileSet')
54
+ end
55
+
56
+ ##
57
+ # @return [Array<DisplayImagePresenter>]
58
+ def file_set_presenters
59
+ member_presenters.select(&:file_set?)
60
+ end
61
+
62
+ ##
63
+ # IIIF metadata for inclusion in the manifest
64
+ # Called by the `iiif_manifest` gem to add metadata
65
+ #
66
+ # @todo should this use the simple_form i18n keys?! maybe the manifest
67
+ # needs its own?
68
+ #
69
+ # @return [Array<Hash{String => String}>] array of metadata hashes
70
+ def manifest_metadata
71
+ metadata_fields.map do |field_name|
72
+ {
73
+ 'label' => I18n.t("simple_form.labels.defaults.#{field_name}"),
74
+ 'value' => Array(self[field_name]).map { |value| scrub(value.to_s) }
75
+ }
76
+ end
77
+ end
78
+
79
+ ##
80
+ # @return [String] the URL where the manifest can be found
81
+ def manifest_url
82
+ return '' if id.blank?
83
+
84
+ Rails.application.routes.url_helpers.polymorphic_url([:manifest, model], host: hostname)
85
+ end
86
+
87
+ ##
88
+ # @return [Array<#to_s>]
89
+ def member_ids
90
+ Hyrax::SolrDocument::OrderedMembers.decorate(model).ordered_member_ids
91
+ end
92
+
93
+ ##
94
+ # @note cache member presenters to avoid querying repeatedly; we expect this
95
+ # presenter to live only as long as the request.
96
+ #
97
+ # @note skips presenters for objects the current `@ability` cannot read.
98
+ # the default ability has all permissions.
99
+ #
100
+ # @return [Array<IiifManifestPresenter>]
101
+ def member_presenters
102
+ @member_presenters_cache ||= Factory.build_for(ids: member_ids, presenter_class: self.class).map do |presenter|
103
+ next unless ability.can?(:read, presenter.model)
104
+
105
+ presenter.hostname = hostname
106
+ presenter.ability = ability
107
+ presenter
108
+ end.compact
109
+ end
110
+
111
+ ##
112
+ # @return [Array<Hash{String => String}>]
113
+ def sequence_rendering
114
+ Array(try(:rendering_ids)).map do |file_set_id|
115
+ rendering = file_set_presenters.find { |p| p.id == file_set_id }
116
+ next unless rendering
117
+
118
+ { '@id' => Hyrax::Engine.routes.url_helpers.download_url(rendering.id, host: hostname),
119
+ 'format' => rendering.mime_type.present? ? rendering.mime_type : I18n.t("hyrax.manifest.unknown_mime_text"),
120
+ 'label' => I18n.t("hyrax.manifest.download_text") + (rendering.label || '') }
121
+ end.flatten
122
+ end
123
+
124
+ ##
125
+ # @return [Boolean]
126
+ def work?
127
+ object.try(:work?) || !file_set?
128
+ end
129
+
130
+ ##
131
+ # @return [Array<IiifManifestPresenter>]
132
+ def work_presenters
133
+ member_presenters.select(&:work?)
134
+ end
135
+
136
+ ##
137
+ # @note ideally, this value will be cheap to retrieve, and will reliably
138
+ # change any time the manifest JSON will change. the current implementation
139
+ # is more blunt than this, changing only when the work itself changes.
140
+ #
141
+ # @return [String] a string tag suitable for cache keys for this manifiest
142
+ def version
143
+ object.try(:modified_date)&.to_s || ''
144
+ end
145
+
146
+ ##
147
+ # An Ability-like object that gives `true` for all `can?` requests
148
+ class NullAbility
149
+ ##
150
+ # @return [Boolean] true
151
+ def can?(*)
152
+ true
153
+ end
154
+ end
155
+
156
+ class Factory < PresenterFactory
157
+ ##
158
+ # @return [Array]
159
+ def build
160
+ ids.map do |id|
161
+ solr_doc = load_docs.find { |doc| doc.id == id }
162
+ presenter_class.for(solr_doc) if solr_doc
163
+ end.compact
164
+ end
165
+
166
+ private
167
+
168
+ ##
169
+ # cache the docs in this method, rather than #build;
170
+ # this can probably be pushed up to the parent class
171
+ def load_docs
172
+ @cached_docs ||= super
173
+ end
174
+ end
175
+
176
+ ##
177
+ # a Presenter for producing `IIIFManifest::DisplayImage` objects
178
+ #
179
+ class DisplayImagePresenter < Draper::Decorator
180
+ delegate_all
181
+
182
+ include Hyrax::DisplaysImage
183
+
184
+ ##
185
+ # @!attribute [w] ability
186
+ # @return [Ability]
187
+ # @!attribute [w] hostname
188
+ # @return [String]
189
+ attr_writer :ability, :hostname
190
+
191
+ ##
192
+ # Creates a display image only where #model is an image.
193
+ #
194
+ # @return [IIIFManifest::DisplayImage] the display image required by the manifest builder.
195
+ def display_image
196
+ return nil unless model.image?
197
+ return nil unless latest_file_id
198
+
199
+ IIIFManifest::DisplayImage
200
+ .new(display_image_url(hostname),
201
+ format: image_format(alpha_channels),
202
+ width: width,
203
+ height: height,
204
+ iiif_endpoint: iiif_endpoint(latest_file_id, base_url: hostname))
205
+ end
206
+
207
+ def hostname
208
+ @hostname || 'localhost'
209
+ end
210
+
211
+ ##
212
+ # @return [Boolean] false
213
+ def work?
214
+ false
215
+ end
216
+ end
217
+
218
+ private
219
+
220
+ def hostname
221
+ @hostname || 'localhost'
222
+ end
223
+
224
+ def metadata_fields
225
+ Hyrax.config.iiif_metadata_fields
226
+ end
227
+
228
+ def scrub(value)
229
+ Loofah.fragment(value).scrub!(:whitewash).to_s
230
+ end
231
+ end
232
+ end
@@ -36,14 +36,8 @@ module Hyrax
36
36
  @work_presenters ||= member_presenters(ordered_ids - file_set_ids, work_presenter_class)
37
37
  end
38
38
 
39
- # TODO: Extract this to ActiveFedora::Aggregations::ListSource
40
39
  def ordered_ids
41
- @ordered_ids ||= begin
42
- ActiveFedora::SolrService.query("proxy_in_ssi:#{id}",
43
- rows: 10_000,
44
- fl: "ordered_targets_ssim")
45
- .flat_map { |x| x.fetch("ordered_targets_ssim", []) }
46
- end
40
+ @ordered_ids ||= Hyrax::SolrDocument::OrderedMembers.decorate(@work).ordered_member_ids
47
41
  end
48
42
 
49
43
  private
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyrax
4
+ ##
5
+ # constructs IIIF Manifests and holds them in the Rails cache,
6
+ # this approach avoids long manifest build times for some kinds of requests,
7
+ # at the cost of introducing cache invalidation issues.
8
+ class CachingIiifManifestBuilder < ManifestBuilderService
9
+ KEY_PREFIX = 'iiif-cache-v1'
10
+
11
+ attr_accessor :expires_in
12
+
13
+ ##
14
+ # @api public
15
+ #
16
+ # @param iiif_manifest_factory [Class] a class that initializes with presenter
17
+ # object and returns an object that responds to `#to_h`
18
+ # @param expires_in [Integer] the number of seconds until the cache expires
19
+ # @see Hyrax::Configuration#iiif_manifest_cache_duration
20
+ def initialize(iiif_manifest_factory: ::IIIFManifest::ManifestFactory, expires_in: Hyrax.config.iiif_manifest_cache_duration)
21
+ self.expires_in = expires_in
22
+
23
+ super(iiif_manifest_factory: iiif_manifest_factory)
24
+ end
25
+
26
+ ##
27
+ # @see ManifestBuilderService#as_json
28
+ def manifest_for(presenter:)
29
+ Rails.cache.fetch(manifest_cache_key(presenter: presenter), expires_in: expires_in) do
30
+ super
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ ##
37
+ # @note adding a version_for suffix helps us manage cache expiration,
38
+ # reducing false cache hits
39
+ #
40
+ # @param presenter [Hyrax::IiifManifestPresenter]
41
+ #
42
+ # @return [String]
43
+ def manifest_cache_key(presenter:)
44
+ "#{KEY_PREFIX}_#{presenter.id}/#{version_for(presenter)}"
45
+ end
46
+
47
+ ##
48
+ # @return [String]
49
+ def version_for(presenter)
50
+ presenter.version
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module Identifier
4
+ ##
5
+ # Builds an identifier string.
6
+ #
7
+ # Implementations must accept a `prefix:` to `#initialize`, and a `hint:` to
8
+ # `#build`. Either or both may be used at the preference of the specific
9
+ # implementer or ignored entirely when `#build` is called.
10
+ #
11
+ # @example
12
+ # builder = Hyrax::Identifier::Builder.new(prefix: 'moomin')
13
+ # builder.build(hint: '1') # => "moomin/1"
14
+ class Builder
15
+ ##
16
+ # @!attribute prefix [rw]
17
+ # @return [String] the prefix to use when building identifiers
18
+ attr_accessor :prefix
19
+
20
+ ##
21
+ # @param prefix [String] the prefix to use when building identifiers
22
+ def initialize(prefix: 'pfx')
23
+ @prefix = prefix
24
+ end
25
+
26
+ ##
27
+ # @note this default builder requires a `hint` which it appends to the
28
+ # prefix to generate the identifier string.
29
+ #
30
+ # @param hint [#to_s] a string-able object which may be used by the builder
31
+ # to generate an identifier. Hints may be required by some builders, while
32
+ # others may ignore them to generate an identifier by other means.
33
+ #
34
+ # @return [String]
35
+ # @raise [ArgumentError] if an identifer can't be built from the provided
36
+ # hint.
37
+ def build(hint: nil)
38
+ raise(ArgumentError, "No hint provided to #{self.class}#build") if
39
+ hint.nil?
40
+
41
+ "#{prefix}/#{hint}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module Identifier
4
+ class Dispatcher
5
+ ##
6
+ # @!attribute [rw] registrar
7
+ # @return [Hyrax::Identifier::Registrar]
8
+ attr_accessor :registrar
9
+
10
+ ##
11
+ # @param registrar [Hyrax::Identifier::Registrar]
12
+ def initialize(registrar:)
13
+ @registrar = registrar
14
+ end
15
+
16
+ class << self
17
+ ##
18
+ # @param type [Symbol]
19
+ # @param registrar_opts [Hash]
20
+ # @option registrar_opts [Hyrax::Identifier::Builder] :builder
21
+ #
22
+ # @return [Hyrax::Identifier::Dispatcher] a dispatcher with an registrar for the
23
+ # given type
24
+ # @see IdentifierRegistrar.for
25
+ def for(type, **registrar_opts)
26
+ new(registrar: Hyrax::Identifier::Registrar.for(type, **registrar_opts))
27
+ end
28
+ end
29
+
30
+ ##
31
+ # Assigns an identifier to the object.
32
+ #
33
+ # This involves two steps:
34
+ # - Registering the identifier with the registrar service via `registrar`.
35
+ # - Storing the new identifier on the object, in the provided `attribute`.
36
+ #
37
+ # @note the attribute for identifier storage must be multi-valued, and will
38
+ # be overwritten during assignment.
39
+ #
40
+ # @param attribute [Symbol] the attribute in which to store the identifier.
41
+ # This attribute will be overwritten during assignment.
42
+ # @param object [ActiveFedora::Base, Hyrax::Resource] the object to assign an identifier.
43
+ #
44
+ # @return [ActiveFedora::Base, Hyrax::Resource] object
45
+ def assign_for(object:, attribute: :identifier)
46
+ record = registrar.register!(object: object)
47
+ object.public_send("#{attribute}=".to_sym, [record.identifier])
48
+ object
49
+ end
50
+
51
+ ##
52
+ # Assigns an identifier and saves the object.
53
+ #
54
+ # @see #assign_for
55
+ def assign_for!(object:, attribute: :identifier)
56
+ assign_for(object: object, attribute: attribute).save!
57
+ object
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module Identifier
4
+ class Registrar
5
+ class << self
6
+ ##
7
+ # @param type [Symbol]
8
+ # @param opts [Hash]
9
+ # @option opts [Hyrax::Identifier::Builder] :builder
10
+ #
11
+ # @return [Hyrax::Identifier::Registrar] a registrar for the given type
12
+ def for(type, **opts)
13
+ return Hyrax.config.identifier_registrars[type].new(**opts) if Hyrax.config.identifier_registrars.include?(type)
14
+ raise ArgumentError, "Hyrax::Identifier::Registrar not found to handle #{type}"
15
+ end
16
+ end
17
+
18
+ ##
19
+ # @!attribute builder [rw]
20
+ # @return [Hyrax::Identifier::Builder]
21
+ attr_accessor :builder
22
+
23
+ ##
24
+ # @param builder [Hyrax::Identifier::Builder]
25
+ def initialize(builder:)
26
+ @builder = builder
27
+ end
28
+
29
+ ##
30
+ # @abstract
31
+ #
32
+ # @param object [#id]
33
+ #
34
+ # @return [#identifier]
35
+ # @raise [NotImplementedError] when the method is abstract
36
+ def register!(*)
37
+ raise NotImplementedError
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyrax
4
+ ##
5
+ # A class responsible for converting a Hyrax::Work like thing into a IIIF
6
+ # manifest.
7
+ #
8
+ # @see !{.as_json}
9
+ class ManifestBuilderService
10
+ ##
11
+ # @api public
12
+ #
13
+ # @param presenter [Hyrax::WorkShowPresenter] the work presenter from which
14
+ # we'll build a manifest.
15
+ # @param iiif_manifest_factory [Class] a class that initializes with presenter
16
+ # object and returns an object that responds to `#to_h`
17
+ #
18
+ # @note While the :presenter may be a Hyrax::WorkShowPresenter it is likely
19
+ # defined by Hyrax::WorksControllerBehavior.show_presenter
20
+ #
21
+ # @return [Hash] a Ruby hash representation of a IIIF manifest document
22
+ #
23
+ # @see Hyrax::WorksControllerBehavior
24
+ def self.manifest_for(presenter:, iiif_manifest_factory: ::IIIFManifest::ManifestFactory)
25
+ new(iiif_manifest_factory: iiif_manifest_factory)
26
+ .manifest_for(presenter: presenter)
27
+ end
28
+
29
+ ##
30
+ # @!attribute [r] manifest_factory
31
+ # @return [#to_h]
32
+ attr_reader :manifest_factory
33
+
34
+ ##
35
+ # @api public
36
+ #
37
+ # @param iiif_manifest_factory [Class] a class that initializes with presenter
38
+ # object and returns an object that responds to `#to_h`
39
+ def initialize(iiif_manifest_factory: ::IIIFManifest::ManifestFactory)
40
+ @manifest_factory = iiif_manifest_factory
41
+ end
42
+
43
+ ##
44
+ # @api public
45
+ #
46
+ # @param presenter [Hyrax::WorkShowPresenter]
47
+ #
48
+ # @return [Hash] a Ruby hash representation of a IIIF manifest document
49
+ def manifest_for(presenter:)
50
+ sanitized_manifest(presenter: presenter)
51
+ end
52
+
53
+ private
54
+
55
+ ##
56
+ # @api private
57
+ # @param presenter [Hyrax::WorkShowPresenter]
58
+ def sanitized_manifest(presenter:)
59
+ # ::IIIFManifest::ManifestBuilder#to_h returns a
60
+ # IIIFManifest::ManifestBuilder::IIIFManifest, not a Hash.
61
+ # to get a Hash, we have to call its #to_json, then parse.
62
+ #
63
+ # wild times. maybe there's a better way to do this with the
64
+ # ManifestFactory interface?
65
+ manifest = manifest_factory.new(presenter).to_h
66
+ hash = JSON.parse(manifest.to_json)
67
+
68
+ hash['label'] = sanitize_value(hash['label']) if hash.key?('label')
69
+ hash['description'] = Array(hash['description'])&.collect { |elem| sanitize_value(elem) } if hash.key?('description')
70
+
71
+ hash['sequences']&.each do |sequence|
72
+ sequence['canvases']&.each do |canvas|
73
+ canvas['label'] = sanitize_value(canvas['label'])
74
+ end
75
+ end
76
+
77
+ hash
78
+ end
79
+
80
+ ##
81
+ # @api private
82
+ # @param [#to_s] text
83
+ # @return [String] a sanitized verison of `text`
84
+ def sanitize_value(text)
85
+ Loofah.fragment(text.to_s).scrub!(:prune).to_s
86
+ end
87
+ end
88
+ end
@@ -22,5 +22,14 @@ module Hyrax
22
22
  return if version.nil?
23
23
  VersionCommitter.create(version_id: version.uri, committer_login: user_key)
24
24
  end
25
+
26
+ # @param [ActiveFedora::File | Hyrax::FileMetadata] content
27
+ def self.versioned_file_id(file)
28
+ versions = file.versions.all
29
+
30
+ return ActiveFedora::Base.uri_to_id(versions.last.uri) if versions.present?
31
+
32
+ file.id
33
+ end
25
34
  end
26
35
  end
@@ -19,7 +19,7 @@
19
19
  <p class="switch-upload-type">To create a separate work for each of the files, go to <%= link_to "Batch upload", hyrax.new_batch_upload_path %></p>
20
20
  <% end %>
21
21
  <% end %>
22
- <%= render 'hyrax/base/guts4form', f: f %>
22
+ <%= render 'hyrax/base/guts4form', f: f, tabs: form_tabs_for(form: f.object) %>
23
23
  <% end %>
24
24
 
25
25
  <script type="text/javascript">
@@ -26,6 +26,10 @@
26
26
  <%= f.input :on_behalf_of, collection: current_user.can_make_deposits_for.map(&:user_key), prompt: "Yourself" %>
27
27
  </div>
28
28
  <% end %>
29
+
30
+ <% form_progress_sections_for(form: f.object).each do |section| %>
31
+ <%= render "form_progress_#{section}", f: f %>
32
+ <% end %>
29
33
  </div>
30
34
  <div class="panel-footer text-center">
31
35
  <% if ::Flipflop.show_deposit_agreement? %>
@@ -1,5 +1,11 @@
1
1
  <% # we will yield to content_for for each tab, e.g. :files_tab %>
2
- <% tabs ||= %w[metadata files relationships] # default tab order %>
2
+ <%# Not passing tabs local param to this partial is deprecated and the tabs param will be required in Hyrax 3.0 %>
3
+ <% unless defined?(tabs) %>
4
+ <% Deprecation.warn(self, "Passing the tabs local param to the _guts4form partial will be required in Hyrax 3.0. " \
5
+ "Consider removing overriding view partials and customizing the tab list " \
6
+ "by overriding the new form_tabs_for(form:) helper.") %>
7
+ <% tabs = form_tabs_for(form: f.object) # default tab order %>
8
+ <% end %>
3
9
  <div class="row">
4
10
  <div class="col-xs-12 col-sm-8">
5
11
  <div class="panel panel-default tabs" role="main">
@@ -10,7 +10,7 @@
10
10
  <%= link_to t("hyrax.batch_uploads.files.button_label"), [main_app, :new, Hyrax.primary_work_type.model_name.singular_route_key] %>
11
11
  </p>
12
12
  <% end %>
13
- <%= render 'hyrax/base/guts4form', f: f, tabs: %w[files metadata relationships] %>
13
+ <%= render 'hyrax/base/guts4form', f: f, tabs: form_tabs_for(form: f.object) %>
14
14
  <%= f.hidden_field :payload_concern, value: @form.payload_concern %>
15
15
  <% end %>
16
16
 
@@ -1,6 +1,6 @@
1
1
  <% menu = Hyrax::MenuPresenter.new(self) %>
2
2
  <div class="sidebar-toggle"><span class="fa fa-chevron-circle-right"></span></div>
3
- <nav>
3
+ <nav aria-label="sidebar-nav">
4
4
  <ul class="nav nav-pills nav-stacked">
5
5
  <li>
6
6
  <div class="profile">
@@ -42,4 +42,8 @@ Flipflop.configure do
42
42
  feature :hide_users_list,
43
43
  default: true,
44
44
  description: "Do not show users list unless user has authenticated."
45
+
46
+ feature :cache_work_iiif_manifest,
47
+ default: false,
48
+ description: "Use Rails.cache to cache the JSON document for IIIF manifests"
45
49
  end
@@ -39,6 +39,7 @@ SUMMARY
39
39
  spec.add_dependency 'browse-everything', '>= 0.16'
40
40
  spec.add_dependency 'carrierwave', '~> 1.0'
41
41
  spec.add_dependency 'clipboard-rails', '~> 1.5'
42
+ spec.add_dependency 'draper', '~> 4.0'
42
43
  spec.add_dependency 'dry-equalizer', '~> 0.2'
43
44
  spec.add_dependency 'dry-struct', '>= 0.1', '< 2.0'
44
45
  spec.add_dependency 'dry-transaction', '~> 0.11'
@@ -266,6 +266,11 @@ Hyrax.config do |config|
266
266
  # mount point.
267
267
  #
268
268
  # config.whitelisted_ingest_dirs = []
269
+
270
+ ## Remote identifiers configuration
271
+ # Add registrar implementations by uncommenting and adding to the hash below.
272
+ # See app/services/hyrax/identifier/registrar.rb for the registrar interface
273
+ # config.identifier_registrars = {}
269
274
  end
270
275
 
271
276
  Date::DATE_FORMATS[:standard] = "%m/%d/%Y"
@@ -8,6 +8,7 @@ require 'font-awesome-rails'
8
8
  require 'tinymce-rails'
9
9
  require 'blacklight'
10
10
  require 'blacklight/gallery'
11
+ require 'iiif_manifest'
11
12
  require 'noid-rails'
12
13
  require 'hydra/head'
13
14
  require 'hydra-editor'
@@ -431,6 +431,16 @@ module Hyrax
431
431
  end
432
432
  attr_writer :iiif_metadata_fields
433
433
 
434
+ # Duration in which we should cache the generated IIIF manifest.
435
+ # Default is 30 days (in seconds).
436
+ #
437
+ # @return [Integer] number of seconds
438
+ # @see https://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html#method-i-fetch
439
+ def iiif_manifest_cache_duration
440
+ @iiif_manifest_cache_duration ||= 30.days.to_i
441
+ end
442
+ attr_writer :iiif_manifest_cache_duration
443
+
434
444
  # Should a button with "Share my work" show on the front page to users who are not logged in?
435
445
  attr_writer :display_share_button_when_not_logged_in
436
446
  def display_share_button_when_not_logged_in?
@@ -514,6 +524,11 @@ module Hyrax
514
524
  ->(id:, extent:) { Samvera::NestingIndexer.reindex_relationships(id: id, extent: extent) }
515
525
  end
516
526
 
527
+ attr_writer :identifier_registrars
528
+ def identifier_registrars
529
+ @identifier_registrars ||= {}
530
+ end
531
+
517
532
  private
518
533
 
519
534
  # @param [Symbol, #to_s] model_name - symbol representing the model
@@ -5,6 +5,7 @@ module Hyrax
5
5
  # These gems must be required outside of an initializer or they don't get loaded.
6
6
  require 'awesome_nested_set'
7
7
  require 'breadcrumbs_on_rails'
8
+ require 'draper'
8
9
  require 'jquery-ui-rails'
9
10
  require 'flot-rails'
10
11
  require 'almond-rails'
@@ -1,2 +1,3 @@
1
1
  require 'hyrax/specs/shared_specs/derivative_service'
2
+ require 'hyrax/specs/shared_specs/identifiers'
2
3
  require 'hyrax/specs/shared_specs/workflow_method'
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples 'a Hyrax::Identifier::Builder' do
4
+ subject(:builder) { described_class.new }
5
+
6
+ describe '#build' do
7
+ it 'returns an identifier string' do
8
+ expect(builder.build(hint: 'moomin'))
9
+ .to respond_to :to_str
10
+ end
11
+ end
12
+ end
13
+
14
+ RSpec.shared_examples 'a Hyrax::Identifier::Registrar' do
15
+ subject(:registrar) { described_class.new(builder: builder) }
16
+ let(:builder) { instance_double(Hyrax::Identifier::Builder, build: 'moomin') }
17
+ let(:object) { instance_double(GenericWork, id: 'moomin_id') }
18
+
19
+ it { is_expected.to have_attributes(builder: builder) }
20
+
21
+ describe '#register!' do
22
+ it 'creates an identifier record' do
23
+ expect(registrar.register!(object: object).identifier)
24
+ .to respond_to :to_str
25
+ end
26
+ end
27
+ end
@@ -1,3 +1,3 @@
1
1
  module Hyrax
2
- VERSION = '2.8.0'.freeze
2
+ VERSION = '2.9.0'.freeze
3
3
  end
@@ -1,6 +1,6 @@
1
1
  # Hack for https://github.com/rails/rails/issues/35153
2
2
  gsub_file 'Gemfile', /^gem ["']sqlite3["']$/, 'gem "sqlite3", "~> 1.3.0"'
3
- gem 'hyrax', '2.6.0'
3
+ gem 'hyrax', '2.9.0'
4
4
  run 'bundle install'
5
5
  generate 'hyrax:install', '-f'
6
6
  rails_command 'db:migrate'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hyrax
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.0
4
+ version: 2.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
@@ -14,7 +14,7 @@ authors:
14
14
  autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
- date: 2020-06-26 00:00:00.000000000 Z
17
+ date: 2020-07-28 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: rails
@@ -162,6 +162,20 @@ dependencies:
162
162
  - - "~>"
163
163
  - !ruby/object:Gem::Version
164
164
  version: '1.5'
165
+ - !ruby/object:Gem::Dependency
166
+ name: draper
167
+ requirement: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - "~>"
170
+ - !ruby/object:Gem::Version
171
+ version: '4.0'
172
+ type: :runtime
173
+ prerelease: false
174
+ version_requirements: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - "~>"
177
+ - !ruby/object:Gem::Version
178
+ version: '4.0'
165
179
  - !ruby/object:Gem::Dependency
166
180
  name: dry-equalizer
167
181
  requirement: !ruby/object:Gem::Requirement
@@ -1460,6 +1474,7 @@ files:
1460
1474
  - app/helpers/hyrax/title_helper.rb
1461
1475
  - app/helpers/hyrax/trophy_helper.rb
1462
1476
  - app/helpers/hyrax/url_helper.rb
1477
+ - app/helpers/hyrax/work_form_helper.rb
1463
1478
  - app/indexers/concerns/hyrax/indexes_basic_metadata.rb
1464
1479
  - app/indexers/concerns/hyrax/indexes_linked_metadata.rb
1465
1480
  - app/indexers/hyrax/admin_set_indexer.rb
@@ -1497,6 +1512,7 @@ files:
1497
1512
  - app/jobs/hyrax/grant_read_to_members_job.rb
1498
1513
  - app/jobs/hyrax/revoke_edit_from_members_job.rb
1499
1514
  - app/jobs/hyrax/revoke_edit_job.rb
1515
+ - app/jobs/iiif_manifest_cache_prewarm_job.rb
1500
1516
  - app/jobs/import_export_job.rb
1501
1517
  - app/jobs/import_url_job.rb
1502
1518
  - app/jobs/ingest_job.rb
@@ -1541,6 +1557,7 @@ files:
1541
1557
  - app/models/concerns/hyrax/solr_document/characterization.rb
1542
1558
  - app/models/concerns/hyrax/solr_document/export.rb
1543
1559
  - app/models/concerns/hyrax/solr_document/metadata.rb
1560
+ - app/models/concerns/hyrax/solr_document/ordered_members.rb
1544
1561
  - app/models/concerns/hyrax/solr_document_behavior.rb
1545
1562
  - app/models/concerns/hyrax/suppressible.rb
1546
1563
  - app/models/concerns/hyrax/user.rb
@@ -1619,6 +1636,7 @@ files:
1619
1636
  - app/presenters/hyrax/file_usage.rb
1620
1637
  - app/presenters/hyrax/fixity_status_presenter.rb
1621
1638
  - app/presenters/hyrax/homepage_presenter.rb
1639
+ - app/presenters/hyrax/iiif_manifest_presenter.rb
1622
1640
  - app/presenters/hyrax/inspect_work_presenter.rb
1623
1641
  - app/presenters/hyrax/lease_presenter.rb
1624
1642
  - app/presenters/hyrax/member_presenter_factory.rb
@@ -1705,6 +1723,7 @@ files:
1705
1723
  - app/services/hyrax/analytics.rb
1706
1724
  - app/services/hyrax/batch_create_failure_service.rb
1707
1725
  - app/services/hyrax/batch_create_success_service.rb
1726
+ - app/services/hyrax/caching_iiif_manifest_builder.rb
1708
1727
  - app/services/hyrax/change_content_depositor_service.rb
1709
1728
  - app/services/hyrax/collection_member_service.rb
1710
1729
  - app/services/hyrax/collection_size_service.rb
@@ -1733,6 +1752,9 @@ files:
1733
1752
  - app/services/hyrax/fixity_check_failure_service.rb
1734
1753
  - app/services/hyrax/form_metadata_service.rb
1735
1754
  - app/services/hyrax/graph_exporter.rb
1755
+ - app/services/hyrax/identifier/builder.rb
1756
+ - app/services/hyrax/identifier/dispatcher.rb
1757
+ - app/services/hyrax/identifier/registrar.rb
1736
1758
  - app/services/hyrax/iiif_authorization_service.rb
1737
1759
  - app/services/hyrax/import_url_failure_service.rb
1738
1760
  - app/services/hyrax/indexes_thumbnails.rb
@@ -1743,6 +1765,7 @@ files:
1743
1765
  - app/services/hyrax/local_file_service.rb
1744
1766
  - app/services/hyrax/lock_manager.rb
1745
1767
  - app/services/hyrax/lockable.rb
1768
+ - app/services/hyrax/manifest_builder_service.rb
1746
1769
  - app/services/hyrax/messenger_service.rb
1747
1770
  - app/services/hyrax/microdata.rb
1748
1771
  - app/services/hyrax/multiple_membership_checker.rb
@@ -2371,6 +2394,7 @@ files:
2371
2394
  - lib/hyrax/search_state.rb
2372
2395
  - lib/hyrax/specs/shared_specs.rb
2373
2396
  - lib/hyrax/specs/shared_specs/derivative_service.rb
2397
+ - lib/hyrax/specs/shared_specs/identifiers.rb
2374
2398
  - lib/hyrax/specs/shared_specs/workflow_method.rb
2375
2399
  - lib/hyrax/transactions.rb
2376
2400
  - lib/hyrax/transactions/container.rb