blacklight-spotlight 5.2.4 → 5.3.1
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.
- checksums.yaml +4 -4
- data/README.md +25 -5
- data/app/assets/javascripts/spotlight/spotlight.esm.js +1679 -1228
- data/app/assets/javascripts/spotlight/spotlight.esm.js.map +1 -1
- data/app/assets/javascripts/spotlight/spotlight.js +1682 -1231
- data/app/assets/javascripts/spotlight/spotlight.js.map +1 -1
- data/app/assets/stylesheets/spotlight/_spotlight.scss +0 -2
- data/app/components/spotlight/analytics/aggregation_component.html.erb +1 -1
- data/app/components/spotlight/document_admin_table_component.html.erb +2 -2
- data/app/components/spotlight/edit_view_links_component.html.erb +1 -1
- data/app/components/spotlight/header_component.html.erb +1 -1
- data/app/components/spotlight/tag_list_form_component.html.erb +1 -1
- data/app/components/spotlight/tag_selector_component.html.erb +2 -2
- data/app/components/spotlight/translations/subheading_component.html.erb +1 -1
- data/app/controllers/spotlight/bulk_actions_controller.rb +2 -2
- data/app/controllers/spotlight/bulk_updates_controller.rb +2 -2
- data/app/controllers/spotlight/catalog_controller.rb +2 -2
- data/app/controllers/spotlight/contact_forms_controller.rb +1 -1
- data/app/controllers/spotlight/exhibits_controller.rb +1 -1
- data/app/controllers/spotlight/featured_images_controller.rb +1 -1
- data/app/controllers/spotlight/pages_controller.rb +1 -1
- data/app/controllers/spotlight/resources_controller.rb +1 -1
- data/app/controllers/spotlight/searches_controller.rb +3 -4
- data/app/controllers/spotlight/solr_controller.rb +1 -1
- data/app/controllers/spotlight/tags_controller.rb +1 -1
- data/app/controllers/spotlight/versions_controller.rb +1 -1
- data/app/javascript/spotlight/admin/add_another.js +22 -15
- data/app/javascript/spotlight/admin/add_image_selector.js +27 -25
- data/app/javascript/spotlight/admin/add_new_button.js +47 -43
- data/app/javascript/spotlight/admin/blacklight_configuration.js +52 -38
- data/app/javascript/spotlight/admin/block_mixins/autocompleteable.js +68 -60
- data/app/javascript/spotlight/admin/block_mixins/formable.js +55 -47
- data/app/javascript/spotlight/admin/block_mixins/plustextable.js +19 -20
- data/app/javascript/spotlight/admin/blocks/block.js +20 -16
- data/app/javascript/spotlight/admin/blocks/browse_block.js +42 -37
- data/app/javascript/spotlight/admin/blocks/browse_group_categories_block.js +33 -31
- data/app/javascript/spotlight/admin/blocks/iframe_block.js +14 -11
- data/app/javascript/spotlight/admin/blocks/link_to_search_block.js +4 -7
- data/app/javascript/spotlight/admin/blocks/oembed_block.js +13 -10
- data/app/javascript/spotlight/admin/blocks/pages_block.js +20 -13
- data/app/javascript/spotlight/admin/blocks/resources_block.js +150 -117
- data/app/javascript/spotlight/admin/blocks/rule_block.js +11 -10
- data/app/javascript/spotlight/admin/blocks/search_result_block.js +16 -17
- data/app/javascript/spotlight/admin/blocks/solr_documents_base_block.js +123 -64
- data/app/javascript/spotlight/admin/blocks/solr_documents_block.js +8 -8
- data/app/javascript/spotlight/admin/blocks/solr_documents_carousel_block.js +63 -43
- data/app/javascript/spotlight/admin/blocks/solr_documents_embed_block.js +9 -9
- data/app/javascript/spotlight/admin/blocks/solr_documents_features_block.js +17 -16
- data/app/javascript/spotlight/admin/blocks/solr_documents_grid_block.js +6 -7
- data/app/javascript/spotlight/admin/blocks/uploaded_items_block.js +84 -64
- data/app/javascript/spotlight/admin/crop.js +203 -156
- data/app/javascript/spotlight/admin/croppable.js +4 -4
- data/app/javascript/spotlight/admin/croppable_modal.js +59 -37
- data/app/javascript/spotlight/admin/edit_in_place.js +50 -47
- data/app/javascript/spotlight/admin/exhibits.js +52 -41
- data/app/javascript/spotlight/admin/form_observer.js +59 -53
- data/app/javascript/spotlight/admin/iiif.js +26 -25
- data/app/javascript/spotlight/admin/index.js +44 -47
- data/app/javascript/spotlight/admin/locks.js +14 -14
- data/app/javascript/spotlight/admin/multi_image_selector.js +127 -111
- data/app/javascript/spotlight/admin/pages.js +21 -21
- data/app/javascript/spotlight/admin/progress_monitor.js +67 -65
- data/app/javascript/spotlight/admin/readonly_checkbox.js +3 -3
- data/app/javascript/spotlight/admin/search_typeahead.js +55 -43
- data/app/javascript/spotlight/admin/select_related_input.js +17 -17
- data/app/javascript/spotlight/admin/sir-trevor/block_controls.js +100 -84
- data/app/javascript/spotlight/admin/sir-trevor/block_limits.js +36 -31
- data/app/javascript/spotlight/admin/sir-trevor/locales.js +52 -39
- data/app/javascript/spotlight/admin/spotlight_nestable.js +112 -90
- data/app/javascript/spotlight/admin/tabs.js +16 -3
- data/app/javascript/spotlight/admin/translation_progress.js +13 -7
- data/app/javascript/spotlight/admin/users.js +41 -41
- data/app/javascript/spotlight/admin/visibility_toggle.js +4 -4
- data/app/javascript/spotlight/controllers/clipboard_controller.js +14 -0
- data/app/javascript/spotlight/controllers/index.js +4 -2
- data/app/javascript/spotlight/controllers/tag_selector_controller.js +116 -79
- data/app/javascript/spotlight/core.js +18 -15
- data/app/javascript/spotlight/index.js +4 -4
- data/app/javascript/spotlight/user/browse_group_categories.js +24 -22
- data/app/javascript/spotlight/user/carousel.js +23 -19
- data/app/javascript/spotlight/user/clear_form_button.js +15 -15
- data/app/javascript/spotlight/user/index.js +4 -4
- data/app/javascript/spotlight/user/zpr_links.js +18 -18
- data/app/models/spotlight/analytics/ga.rb +0 -1
- data/app/models/spotlight/custom_field.rb +1 -1
- data/app/models/spotlight/resources/iiif_manifest_v3.rb +2 -2
- data/app/services/spotlight/exhibit_import_export_service.rb +0 -2
- data/app/views/blacklight/nav/_bookmark.html.erb +1 -1
- data/app/views/catalog/_remove_tags.html.erb +1 -1
- data/app/views/layouts/spotlight/base.html.erb +1 -1
- data/app/views/shared/_header_navbar.html.erb +1 -1
- data/app/views/shared/_masthead.html.erb +2 -2
- data/app/views/shared/_site_sidebar.html.erb +1 -1
- data/app/views/shared/_user_util_links.html.erb +2 -2
- data/app/views/spotlight/about_pages/_contacts.html.erb +1 -1
- data/app/views/spotlight/accessibility/alt_text.html.erb +1 -1
- data/app/views/spotlight/admin_users/index.html.erb +11 -9
- data/app/views/spotlight/appearances/edit.html.erb +1 -1
- data/app/views/spotlight/browse/_search_box.html.erb +1 -1
- data/app/views/spotlight/bulk_updates/_download.html.erb +1 -1
- data/app/views/spotlight/bulk_updates/_progress_panel.html.erb +6 -6
- data/app/views/spotlight/bulk_updates/edit.html.erb +1 -1
- data/app/views/spotlight/catalog/_admin_header.html.erb +1 -1
- data/app/views/spotlight/catalog/_admin_index_header_default.html.erb +1 -1
- data/app/views/spotlight/catalog/_curation_mode_toggle_default.html.erb +1 -1
- data/app/views/spotlight/catalog/_document.html.erb +2 -2
- data/app/views/spotlight/catalog/_document_admin_table.html.erb +1 -1
- data/app/views/spotlight/catalog/_edit_sidecar.html.erb +1 -1
- data/app/views/spotlight/catalog/_index_compact_default.html.erb +1 -1
- data/app/views/spotlight/catalog/_reindex_progress_panel.html.erb +6 -6
- data/app/views/spotlight/catalog/select_image.html.erb +1 -1
- data/app/views/spotlight/contact_mailer/report_problem.html.erb +2 -2
- data/app/views/spotlight/exhibits/_contact.html.erb +2 -2
- data/app/views/spotlight/exhibits/_delete.html.erb +2 -2
- data/app/views/spotlight/exhibits/_exhibit_card.html.erb +1 -1
- data/app/views/spotlight/exhibits/_export.html.erb +1 -1
- data/app/views/spotlight/exhibits/_form.html.erb +2 -2
- data/app/views/spotlight/exhibits/_languages.html.erb +7 -7
- data/app/views/spotlight/exhibits/_missing_exhibits.html.erb +1 -1
- data/app/views/spotlight/locks/_lock.html.erb +2 -2
- data/app/views/spotlight/metadata_configurations/edit.html.erb +1 -1
- data/app/views/spotlight/pages/_order_pages.html.erb +3 -3
- data/app/views/spotlight/pages/preview.html.erb +1 -1
- data/app/views/spotlight/pages/show.html.erb +2 -2
- data/app/views/spotlight/resources/_form.html.erb +1 -1
- data/app/views/spotlight/resources/csv_upload/_form.html.erb +1 -1
- data/app/views/spotlight/resources/upload/_form.html.erb +1 -1
- data/app/views/spotlight/roles/index.html.erb +5 -5
- data/app/views/spotlight/search_configurations/_search_fields.html.erb +1 -1
- data/app/views/spotlight/search_configurations/edit.html.erb +1 -1
- data/app/views/spotlight/searches/index.html.erb +2 -2
- data/app/views/spotlight/shared/_configuration_sidebar.html.erb +1 -1
- data/app/views/spotlight/shared/_curation_sidebar.html.erb +1 -1
- data/app/views/spotlight/shared/_dd3_item.html.erb +1 -1
- data/app/views/spotlight/sir_trevor/_sir_trevor_block_array.html.erb +1 -1
- data/app/views/spotlight/sir_trevor/blocks/_browse_block.html.erb +1 -1
- data/app/views/spotlight/sir_trevor/blocks/_browse_group_categories_block.html.erb +1 -1
- data/app/views/spotlight/sir_trevor/blocks/_featured_pages_block.html.erb +2 -2
- data/app/views/spotlight/sir_trevor/blocks/_link_to_search_block.html.erb +1 -1
- data/app/views/spotlight/sir_trevor/blocks/_rule_block.html.erb +1 -1
- data/app/views/spotlight/sir_trevor/blocks/_search_results_block.html.erb +1 -1
- data/app/views/spotlight/sir_trevor/blocks/_solr_documents_block.html.erb +1 -1
- data/app/views/spotlight/sir_trevor/blocks/_solr_documents_carousel_block.html.erb +1 -1
- data/app/views/spotlight/sir_trevor/blocks/_solr_documents_features_block.html.erb +1 -1
- data/app/views/spotlight/sir_trevor/blocks/_solr_documents_grid_block.html.erb +1 -1
- data/app/views/spotlight/sites/edit_exhibits.html.erb +3 -3
- data/app/views/spotlight/translations/_browse_categories.html.erb +2 -2
- data/app/views/spotlight/translations/_general.html.erb +21 -21
- data/app/views/spotlight/translations/_groups.html.erb +1 -1
- data/app/views/spotlight/translations/_metadata.html.erb +8 -8
- data/app/views/spotlight/translations/_pages_table.html.erb +2 -2
- data/app/views/spotlight/translations/_search_fields.html.erb +13 -13
- data/app/views/spotlight/translations/edit.html.erb +5 -5
- data/config/importmap.rb +0 -1
- data/lib/generators/spotlight/assets/propshaft_generator.rb +0 -1
- data/lib/generators/spotlight/templates/solr/conf/admin-extra.html +31 -31
- data/lib/spotlight/version.rb +1 -1
- data/spec/factories/contact_images.rb +1 -1
- data/spec/factories/exhibit_thumbnails.rb +1 -1
- data/spec/factories/featured_images.rb +1 -1
- data/spec/support/features/test_features_helpers.rb +12 -3
- metadata +12 -64
- data/app/assets/stylesheets/spotlight/typeahead.scss +0 -77
- data/app/javascript/spotlight/admin/copy_email_addresses.js +0 -7
- data/vendor/assets/javascripts/jquery.waitforimages.min.js +0 -2
|
@@ -1,314 +1,355 @@
|
|
|
1
|
-
import { addImageSelector } from
|
|
2
|
-
import Core from
|
|
1
|
+
import { addImageSelector } from "spotlight/admin/add_image_selector"
|
|
2
|
+
import Core from "spotlight/core"
|
|
3
3
|
|
|
4
4
|
export default class Crop {
|
|
5
5
|
constructor(cropArea, preserveAspectRatio = true) {
|
|
6
|
-
this.cropArea = cropArea
|
|
7
|
-
this.cropArea.data(
|
|
6
|
+
this.cropArea = cropArea
|
|
7
|
+
this.cropArea.data("iiifCropper", this)
|
|
8
8
|
// This element will also have the IIIF input elements contained
|
|
9
9
|
// There may be multiple elements with data-cropper attributes, but
|
|
10
10
|
// there should only one element with this data-cropper attribute value.
|
|
11
|
-
this.cropSelector = '[data-cropper="' + cropArea.data(
|
|
12
|
-
this.cropTool = $(this.cropSelector)
|
|
11
|
+
this.cropSelector = '[data-cropper="' + cropArea.data("cropperKey") + '"]'
|
|
12
|
+
this.cropTool = $(this.cropSelector)
|
|
13
13
|
// Exhibit and masthead cropping requires the ratio between image width and height
|
|
14
|
-
// to be consistent, whereas item widget cropping allows any combination of
|
|
14
|
+
// to be consistent, whereas item widget cropping allows any combination of
|
|
15
15
|
// image width and height.
|
|
16
|
-
this.preserveAspectRatio = preserveAspectRatio
|
|
16
|
+
this.preserveAspectRatio = preserveAspectRatio
|
|
17
17
|
// Get the IIIF input elements used to store/reference IIIF information
|
|
18
|
-
this.inputPrefix = this.cropTool.data(
|
|
19
|
-
this.iiifUrlField = this.iiifInputElement(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
this.inputPrefix = this.cropTool.data("input-prefix")
|
|
19
|
+
this.iiifUrlField = this.iiifInputElement(
|
|
20
|
+
this.inputPrefix,
|
|
21
|
+
"iiif_tilesource",
|
|
22
|
+
this.cropTool,
|
|
23
|
+
)
|
|
24
|
+
this.iiifRegionField = this.iiifInputElement(
|
|
25
|
+
this.inputPrefix,
|
|
26
|
+
"iiif_region",
|
|
27
|
+
this.cropTool,
|
|
28
|
+
)
|
|
29
|
+
this.iiifManifestField = this.iiifInputElement(
|
|
30
|
+
this.inputPrefix,
|
|
31
|
+
"iiif_manifest_url",
|
|
32
|
+
this.cropTool,
|
|
33
|
+
)
|
|
34
|
+
this.iiifCanvasField = this.iiifInputElement(
|
|
35
|
+
this.inputPrefix,
|
|
36
|
+
"iiif_canvas_id",
|
|
37
|
+
this.cropTool,
|
|
38
|
+
)
|
|
39
|
+
this.iiifImageField = this.iiifInputElement(
|
|
40
|
+
this.inputPrefix,
|
|
41
|
+
"iiif_image_id",
|
|
42
|
+
this.cropTool,
|
|
43
|
+
)
|
|
24
44
|
// Get the closest form element
|
|
25
|
-
this.form = cropArea.closest(
|
|
26
|
-
this.tileSource = null
|
|
45
|
+
this.form = cropArea.closest("form")
|
|
46
|
+
this.tileSource = null
|
|
27
47
|
}
|
|
28
48
|
|
|
29
49
|
// Return the iiif input element based on the fieldname.
|
|
30
|
-
// Multiple input fields with the same name on the page may be related
|
|
31
|
-
// to a cropper. We thus need to pass in a parent element.
|
|
50
|
+
// Multiple input fields with the same name on the page may be related
|
|
51
|
+
// to a cropper. We thus need to pass in a parent element.
|
|
32
52
|
iiifInputElement(inputPrefix, fieldName, inputParentElement) {
|
|
33
|
-
return $(
|
|
53
|
+
return $(
|
|
54
|
+
'input[name="' + inputPrefix + "[" + fieldName + ']"]',
|
|
55
|
+
inputParentElement,
|
|
56
|
+
)
|
|
34
57
|
}
|
|
35
58
|
|
|
36
59
|
// Render the cropper environment and add hooks into the autocomplete and upload forms
|
|
37
60
|
render() {
|
|
38
|
-
this.setupAutoCompletes()
|
|
39
|
-
this.setupAjaxFileUpload()
|
|
40
|
-
this.setupExistingIiifCropper()
|
|
61
|
+
this.setupAutoCompletes()
|
|
62
|
+
this.setupAjaxFileUpload()
|
|
63
|
+
this.setupExistingIiifCropper()
|
|
41
64
|
}
|
|
42
65
|
|
|
43
66
|
// Setup the cropper on page load if the field
|
|
44
67
|
// that holds the IIIF url is populated
|
|
45
68
|
setupExistingIiifCropper() {
|
|
46
|
-
if(this.iiifUrlField.val() ===
|
|
47
|
-
return
|
|
69
|
+
if (this.iiifUrlField.val() === "") {
|
|
70
|
+
return
|
|
48
71
|
}
|
|
49
72
|
|
|
50
|
-
this.addImageSelectorToExistingCropTool()
|
|
51
|
-
this.setTileSource(this.iiifUrlField.val())
|
|
73
|
+
this.addImageSelectorToExistingCropTool()
|
|
74
|
+
this.setTileSource(this.iiifUrlField.val())
|
|
52
75
|
}
|
|
53
76
|
|
|
54
77
|
// Display the IIIF Cropper map with the current IIIF Layer (and cropbox, once the layer is available)
|
|
55
78
|
setupIiifCropper() {
|
|
56
|
-
this.loaded = false
|
|
79
|
+
this.loaded = false
|
|
57
80
|
|
|
58
|
-
this.renderCropperMap()
|
|
81
|
+
this.renderCropperMap()
|
|
59
82
|
|
|
60
83
|
if (this.imageLayer) {
|
|
61
84
|
// Force a broken layer's container to be an element before removing.
|
|
62
85
|
// Code in leaflet-iiif land calls delete on the image layer's container when removing,
|
|
63
86
|
// which errors if there is an issue fetching the info.json and stops further necessary steps to execute.
|
|
64
|
-
if(!this.imageLayer._container) {
|
|
65
|
-
this.imageLayer._container = $(
|
|
87
|
+
if (!this.imageLayer._container) {
|
|
88
|
+
this.imageLayer._container = $("<div></div>")
|
|
66
89
|
}
|
|
67
|
-
this.cropperMap.removeLayer(this.imageLayer)
|
|
90
|
+
this.cropperMap.removeLayer(this.imageLayer)
|
|
68
91
|
}
|
|
69
92
|
|
|
70
|
-
this.imageLayer = L.tileLayer.iiif(this.tileSource).addTo(this.cropperMap)
|
|
93
|
+
this.imageLayer = L.tileLayer.iiif(this.tileSource).addTo(this.cropperMap)
|
|
71
94
|
|
|
72
|
-
var self = this
|
|
73
|
-
this.imageLayer.on(
|
|
95
|
+
var self = this
|
|
96
|
+
this.imageLayer.on("load", function () {
|
|
74
97
|
if (!self.loaded) {
|
|
75
|
-
var region = self.getCropRegion()
|
|
76
|
-
self.positionIiifCropBox(region)
|
|
77
|
-
self.loaded = true
|
|
98
|
+
var region = self.getCropRegion()
|
|
99
|
+
self.positionIiifCropBox(region)
|
|
100
|
+
self.loaded = true
|
|
78
101
|
}
|
|
79
|
-
})
|
|
102
|
+
})
|
|
80
103
|
|
|
81
|
-
this.cropArea.data(
|
|
104
|
+
this.cropArea.data("initiallyVisible", this.cropArea.is(":visible"))
|
|
82
105
|
}
|
|
83
106
|
|
|
84
107
|
// Get (or initialize) the current crop region from the form data
|
|
85
108
|
getCropRegion() {
|
|
86
|
-
var regionFieldValue = this.iiifRegionField.val()
|
|
87
|
-
if(!regionFieldValue || regionFieldValue ===
|
|
88
|
-
var region = this.defaultCropRegion()
|
|
89
|
-
this.iiifRegionField.val(region)
|
|
90
|
-
return region
|
|
109
|
+
var regionFieldValue = this.iiifRegionField.val()
|
|
110
|
+
if (!regionFieldValue || regionFieldValue === "") {
|
|
111
|
+
var region = this.defaultCropRegion()
|
|
112
|
+
this.iiifRegionField.val(region)
|
|
113
|
+
return region
|
|
91
114
|
} else {
|
|
92
|
-
return regionFieldValue.split(
|
|
115
|
+
return regionFieldValue.split(",")
|
|
93
116
|
}
|
|
94
117
|
}
|
|
95
118
|
|
|
96
119
|
// Calculate a default crop region in the center of the image using the correct aspect ratio
|
|
97
120
|
defaultCropRegion() {
|
|
98
|
-
var imageWidth = this.imageLayer.x
|
|
99
|
-
var imageHeight = this.imageLayer.y
|
|
121
|
+
var imageWidth = this.imageLayer.x
|
|
122
|
+
var imageHeight = this.imageLayer.y
|
|
100
123
|
|
|
101
|
-
var boxWidth = Math.floor(imageWidth / 2)
|
|
102
|
-
var boxHeight = Math.floor(boxWidth / this.aspectRatio())
|
|
124
|
+
var boxWidth = Math.floor(imageWidth / 2)
|
|
125
|
+
var boxHeight = Math.floor(boxWidth / this.aspectRatio())
|
|
103
126
|
|
|
104
127
|
return [
|
|
105
128
|
Math.floor((imageWidth - boxWidth) / 2),
|
|
106
129
|
Math.floor((imageHeight - boxHeight) / 2),
|
|
107
130
|
boxWidth,
|
|
108
|
-
boxHeight
|
|
109
|
-
]
|
|
131
|
+
boxHeight,
|
|
132
|
+
]
|
|
110
133
|
}
|
|
111
134
|
|
|
112
135
|
// Calculate the required aspect ratio for the crop area
|
|
113
136
|
aspectRatio() {
|
|
114
|
-
var cropWidth = parseInt(this.cropArea.data(
|
|
115
|
-
var cropHeight = parseInt(this.cropArea.data(
|
|
116
|
-
return cropWidth / cropHeight
|
|
137
|
+
var cropWidth = parseInt(this.cropArea.data("crop-width"))
|
|
138
|
+
var cropHeight = parseInt(this.cropArea.data("crop-height"))
|
|
139
|
+
return cropWidth / cropHeight
|
|
117
140
|
}
|
|
118
141
|
|
|
119
142
|
// Position the IIIF Crop Box at the given IIIF region
|
|
120
143
|
positionIiifCropBox(region) {
|
|
121
|
-
var bounds = this.unprojectIIIFRegionToBounds(region)
|
|
144
|
+
var bounds = this.unprojectIIIFRegionToBounds(region)
|
|
122
145
|
|
|
123
146
|
if (!this.cropBox) {
|
|
124
|
-
this.renderCropBox(bounds)
|
|
147
|
+
this.renderCropBox(bounds)
|
|
125
148
|
}
|
|
126
149
|
|
|
127
|
-
this.cropBox.setBounds(bounds)
|
|
128
|
-
this.cropperMap.invalidateSize()
|
|
129
|
-
this.cropperMap.fitBounds(bounds)
|
|
150
|
+
this.cropBox.setBounds(bounds)
|
|
151
|
+
this.cropperMap.invalidateSize()
|
|
152
|
+
this.cropperMap.fitBounds(bounds)
|
|
130
153
|
|
|
131
|
-
this.cropBox.editor.editLayer.clearLayers()
|
|
132
|
-
this.cropBox.editor.refresh()
|
|
133
|
-
this.cropBox.editor.initVertexMarkers()
|
|
154
|
+
this.cropBox.editor.editLayer.clearLayers()
|
|
155
|
+
this.cropBox.editor.refresh()
|
|
156
|
+
this.cropBox.editor.initVertexMarkers()
|
|
134
157
|
}
|
|
135
158
|
|
|
136
159
|
// Set all of the various input fields to
|
|
137
160
|
// the appropriate IIIF URL or identifier
|
|
138
161
|
setIiifFields(iiifObject) {
|
|
139
|
-
this.setTileSource(iiifObject.tilesource)
|
|
140
|
-
this.iiifManifestField.val(iiifObject.manifest)
|
|
141
|
-
this.iiifCanvasField.val(iiifObject.canvasId)
|
|
142
|
-
this.iiifImageField.val(iiifObject.imageId)
|
|
162
|
+
this.setTileSource(iiifObject.tilesource)
|
|
163
|
+
this.iiifManifestField.val(iiifObject.manifest)
|
|
164
|
+
this.iiifCanvasField.val(iiifObject.canvasId)
|
|
165
|
+
this.iiifImageField.val(iiifObject.imageId)
|
|
143
166
|
}
|
|
144
167
|
|
|
145
168
|
// Set the Crop tileSource and setup the cropper
|
|
146
169
|
setTileSource(source) {
|
|
147
170
|
if (source == this.tileSource) {
|
|
148
|
-
return
|
|
171
|
+
return
|
|
149
172
|
}
|
|
150
173
|
|
|
151
174
|
if (source === null || source === undefined) {
|
|
152
|
-
console.error(
|
|
153
|
-
return
|
|
175
|
+
console.error("No tilesource provided when setting up IIIF Cropper")
|
|
176
|
+
return
|
|
154
177
|
}
|
|
155
178
|
|
|
156
179
|
if (this.cropBox) {
|
|
157
|
-
this.iiifRegionField.val("")
|
|
180
|
+
this.iiifRegionField.val("")
|
|
158
181
|
}
|
|
159
182
|
|
|
160
|
-
this.tileSource = source
|
|
161
|
-
this.iiifUrlField.val(source)
|
|
162
|
-
this.setupIiifCropper()
|
|
183
|
+
this.tileSource = source
|
|
184
|
+
this.iiifUrlField.val(source)
|
|
185
|
+
this.setupIiifCropper()
|
|
163
186
|
}
|
|
164
187
|
|
|
165
188
|
// Render the Leaflet Map into the crop area
|
|
166
189
|
renderCropperMap() {
|
|
167
190
|
if (this.cropperMap) {
|
|
168
|
-
return
|
|
191
|
+
return
|
|
169
192
|
}
|
|
170
193
|
|
|
171
194
|
var cropperOptions = {
|
|
172
195
|
editable: true,
|
|
173
196
|
center: [0, 0],
|
|
174
197
|
crs: L.CRS.Simple,
|
|
175
|
-
zoom: 0
|
|
198
|
+
zoom: 0,
|
|
176
199
|
}
|
|
177
200
|
|
|
178
|
-
if(this.preserveAspectRatio) {
|
|
179
|
-
cropperOptions[
|
|
180
|
-
rectangleEditorClass: this.aspectRatioPreservingRectangleEditor(
|
|
181
|
-
|
|
201
|
+
if (this.preserveAspectRatio) {
|
|
202
|
+
cropperOptions["editOptions"] = {
|
|
203
|
+
rectangleEditorClass: this.aspectRatioPreservingRectangleEditor(
|
|
204
|
+
this.aspectRatio(),
|
|
205
|
+
),
|
|
206
|
+
}
|
|
182
207
|
}
|
|
183
208
|
|
|
184
|
-
this.cropperMap = L.map(this.cropArea.attr(
|
|
185
|
-
this.invalidateMapSizeOnTabToggle()
|
|
209
|
+
this.cropperMap = L.map(this.cropArea.attr("id"), cropperOptions)
|
|
210
|
+
this.invalidateMapSizeOnTabToggle()
|
|
186
211
|
}
|
|
187
212
|
|
|
188
213
|
// Render the crop box (a Leaflet editable rectangle) onto the canvas
|
|
189
214
|
renderCropBox(initialBounds) {
|
|
190
|
-
this.cropBox = L.rectangle(initialBounds)
|
|
191
|
-
this.cropBox.addTo(this.cropperMap)
|
|
192
|
-
this.cropBox.enableEdit()
|
|
193
|
-
this.cropBox
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
215
|
+
this.cropBox = L.rectangle(initialBounds)
|
|
216
|
+
this.cropBox.addTo(this.cropperMap)
|
|
217
|
+
this.cropBox.enableEdit()
|
|
218
|
+
this.cropBox
|
|
219
|
+
.on("dblclick", L.DomEvent.stop)
|
|
220
|
+
.on("dblclick", this.cropBox.toggleEdit)
|
|
221
|
+
|
|
222
|
+
var self = this
|
|
223
|
+
this.cropperMap.on(
|
|
224
|
+
"editable:dragend editable:vertex:dragend",
|
|
225
|
+
function (e) {
|
|
226
|
+
var bounds = e.layer.getBounds()
|
|
227
|
+
var region = self.projectBoundsToIIIFRegion(bounds)
|
|
228
|
+
|
|
229
|
+
self.iiifRegionField.val(region.join(","))
|
|
230
|
+
},
|
|
231
|
+
)
|
|
202
232
|
}
|
|
203
233
|
|
|
204
234
|
// Get the maximum zoom level for the IIIF Layer (always 1:1 image pixel to canvas?)
|
|
205
235
|
maxZoom() {
|
|
206
|
-
if(this.imageLayer) {
|
|
207
|
-
return this.imageLayer.maxZoom
|
|
236
|
+
if (this.imageLayer) {
|
|
237
|
+
return this.imageLayer.maxZoom
|
|
208
238
|
}
|
|
209
239
|
}
|
|
210
240
|
|
|
211
241
|
// Take a Leaflet LatLngBounds object and transform it into a IIIF [x, y, w, h] region
|
|
212
242
|
projectBoundsToIIIFRegion(bounds) {
|
|
213
|
-
var min = this.cropperMap.project(bounds.getNorthWest(), this.maxZoom())
|
|
214
|
-
var max = this.cropperMap.project(bounds.getSouthEast(), this.maxZoom())
|
|
243
|
+
var min = this.cropperMap.project(bounds.getNorthWest(), this.maxZoom())
|
|
244
|
+
var max = this.cropperMap.project(bounds.getSouthEast(), this.maxZoom())
|
|
215
245
|
return [
|
|
216
246
|
Math.max(Math.floor(min.x), 0),
|
|
217
247
|
Math.max(Math.floor(min.y), 0),
|
|
218
248
|
Math.floor(max.x - min.x),
|
|
219
|
-
Math.floor(max.y - min.y)
|
|
220
|
-
]
|
|
249
|
+
Math.floor(max.y - min.y),
|
|
250
|
+
]
|
|
221
251
|
}
|
|
222
252
|
|
|
223
253
|
// Take a IIIF [x, y, w, h] region and transform it into a Leaflet LatLngBounds
|
|
224
254
|
unprojectIIIFRegionToBounds(region) {
|
|
225
|
-
var minPoint = L.point(parseInt(region[0]), parseInt(region[1]))
|
|
226
|
-
var maxPoint = L.point(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
255
|
+
var minPoint = L.point(parseInt(region[0]), parseInt(region[1]))
|
|
256
|
+
var maxPoint = L.point(
|
|
257
|
+
parseInt(region[0]) + parseInt(region[2]),
|
|
258
|
+
parseInt(region[1]) + parseInt(region[3]),
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
var min = this.cropperMap.unproject(minPoint, this.maxZoom())
|
|
262
|
+
var max = this.cropperMap.unproject(maxPoint, this.maxZoom())
|
|
263
|
+
return L.latLngBounds(min, max)
|
|
231
264
|
}
|
|
232
265
|
|
|
233
266
|
// TODO: Add accessors to update hidden inputs with IIIF uri/ids?
|
|
234
267
|
|
|
235
268
|
// Setup autocomplete inputs to have the iiif_cropper context
|
|
236
269
|
setupAutoCompletes() {
|
|
237
|
-
var input = $('[data-behavior="autocomplete"]', this.cropTool)
|
|
238
|
-
input.data(
|
|
270
|
+
var input = $('[data-behavior="autocomplete"]', this.cropTool)
|
|
271
|
+
input.data("iiifCropper", this)
|
|
239
272
|
}
|
|
240
273
|
|
|
241
274
|
setupAjaxFileUpload() {
|
|
242
|
-
this.fileInput = $('input[type="file"]', this.cropTool)
|
|
243
|
-
this.fileInput.change(() => this.uploadFile())
|
|
275
|
+
this.fileInput = $('input[type="file"]', this.cropTool)
|
|
276
|
+
this.fileInput.change(() => this.uploadFile())
|
|
244
277
|
}
|
|
245
278
|
|
|
246
279
|
addImageSelectorToExistingCropTool() {
|
|
247
|
-
if(this.iiifManifestField.val() ===
|
|
248
|
-
return
|
|
280
|
+
if (this.iiifManifestField.val() === "") {
|
|
281
|
+
return
|
|
249
282
|
}
|
|
250
283
|
|
|
251
|
-
var input = $('[data-behavior="autocomplete"]', this.cropTool)
|
|
252
|
-
|
|
284
|
+
var input = $('[data-behavior="autocomplete"]', this.cropTool)
|
|
285
|
+
|
|
253
286
|
// Not every page which uses this module has autocomplete linked directly to the cropping tool
|
|
254
|
-
if(input.length) {
|
|
255
|
-
var panel = $(input.data(
|
|
256
|
-
addImageSelector(
|
|
287
|
+
if (input.length) {
|
|
288
|
+
var panel = $(input.data("target-panel"))
|
|
289
|
+
addImageSelector(
|
|
290
|
+
input,
|
|
291
|
+
panel,
|
|
292
|
+
this.iiifManifestField.val(),
|
|
293
|
+
!this.iiifImageField.val(),
|
|
294
|
+
)
|
|
257
295
|
}
|
|
258
296
|
}
|
|
259
297
|
|
|
260
298
|
invalidateMapSizeOnTabToggle() {
|
|
261
|
-
var tabs = $('[role="tablist"]', this.form)
|
|
262
|
-
var self = this
|
|
263
|
-
tabs.on(
|
|
264
|
-
if
|
|
265
|
-
self.
|
|
299
|
+
var tabs = $('[role="tablist"]', this.form)
|
|
300
|
+
var self = this
|
|
301
|
+
tabs.on("shown.bs.tab", function () {
|
|
302
|
+
if (
|
|
303
|
+
self.cropArea.data("initiallyVisible") === false &&
|
|
304
|
+
self.cropArea.is(":visible")
|
|
305
|
+
) {
|
|
306
|
+
self.cropperMap.invalidateSize()
|
|
266
307
|
// Because the map size is 0,0 when image is loading (not visible) we need to refit the bounds of the layer
|
|
267
|
-
self.imageLayer._fitBounds()
|
|
268
|
-
self.cropArea.data(
|
|
308
|
+
self.imageLayer._fitBounds()
|
|
309
|
+
self.cropArea.data("initiallyVisible", null)
|
|
269
310
|
}
|
|
270
|
-
})
|
|
311
|
+
})
|
|
271
312
|
}
|
|
272
313
|
|
|
273
314
|
// Get all the form data with the exception of the _method field.
|
|
274
315
|
getData() {
|
|
275
|
-
var data = new FormData(this.form[0])
|
|
276
|
-
data.append(
|
|
277
|
-
return data
|
|
316
|
+
var data = new FormData(this.form[0])
|
|
317
|
+
data.append("_method", null)
|
|
318
|
+
return data
|
|
278
319
|
}
|
|
279
320
|
|
|
280
321
|
uploadFile() {
|
|
281
|
-
var url = this.fileInput.data(
|
|
322
|
+
var url = this.fileInput.data("endpoint")
|
|
282
323
|
// Every post creates a new image/masthead.
|
|
283
324
|
// Because they create IIIF urls which are heavily cached.
|
|
284
325
|
$.ajax({
|
|
285
|
-
url: url,
|
|
286
|
-
type:
|
|
326
|
+
url: url, //Server script to process data
|
|
327
|
+
type: "POST",
|
|
287
328
|
success: (data, stat, xhr) => this.successHandler(data, stat, xhr),
|
|
288
329
|
error: (xhr, stat, error) => this.errorHandler(xhr, stat, error),
|
|
289
330
|
// Form data
|
|
290
331
|
data: this.getData(),
|
|
291
332
|
headers: {
|
|
292
|
-
|
|
333
|
+
"X-CSRF-Token": Core.csrfToken() || "",
|
|
293
334
|
},
|
|
294
335
|
//Options to tell jQuery not to process data or worry about content-type.
|
|
295
336
|
cache: false,
|
|
296
337
|
contentType: false,
|
|
297
|
-
processData: false
|
|
298
|
-
})
|
|
338
|
+
processData: false,
|
|
339
|
+
})
|
|
299
340
|
}
|
|
300
341
|
|
|
301
|
-
successHandler(data,
|
|
302
|
-
this.setIiifFields({ tilesource: data.tilesource })
|
|
303
|
-
this.setUploadId(data.id)
|
|
342
|
+
successHandler(data, _stat, _xhr) {
|
|
343
|
+
this.setIiifFields({ tilesource: data.tilesource })
|
|
344
|
+
this.setUploadId(data.id)
|
|
304
345
|
this.clearUploadErrors()
|
|
305
346
|
}
|
|
306
347
|
|
|
307
|
-
errorHandler(xhr,
|
|
348
|
+
errorHandler(xhr, _stat, _error) {
|
|
308
349
|
let errorMessage = "Upload failed"
|
|
309
350
|
if (xhr.responseJSON) {
|
|
310
351
|
if (xhr.responseJSON.errors) {
|
|
311
|
-
errorMessage = xhr.responseJSON.errors.join(
|
|
352
|
+
errorMessage = xhr.responseJSON.errors.join(", ")
|
|
312
353
|
} else if (xhr.responseJSON.error) {
|
|
313
354
|
errorMessage = xhr.responseJSON.error
|
|
314
355
|
}
|
|
@@ -325,7 +366,7 @@ export default class Crop {
|
|
|
325
366
|
if (errorsElement) {
|
|
326
367
|
errorsElement.text(errorMessage).show()
|
|
327
368
|
} else {
|
|
328
|
-
console.error("uploadFile",
|
|
369
|
+
console.error("uploadFile", errorMessage)
|
|
329
370
|
}
|
|
330
371
|
}
|
|
331
372
|
|
|
@@ -339,34 +380,40 @@ export default class Crop {
|
|
|
339
380
|
setUploadId(id) {
|
|
340
381
|
// This input is currently used for exhibit masthead or thumbnail image upload.
|
|
341
382
|
// The name should be sufficient in this case, as we don't use this part of the
|
|
342
|
-
// code for solr document widgets where we enable cropping.
|
|
343
|
-
// If we require more specificity, we can scope this to this.cropTool.
|
|
344
|
-
$('input[name="' + this.inputPrefix + '[upload_id]"]').val(id)
|
|
383
|
+
// code for solr document widgets where we enable cropping.
|
|
384
|
+
// If we require more specificity, we can scope this to this.cropTool.
|
|
385
|
+
$('input[name="' + this.inputPrefix + '[upload_id]"]').val(id)
|
|
345
386
|
}
|
|
346
387
|
|
|
347
388
|
aspectRatioPreservingRectangleEditor(aspect) {
|
|
348
389
|
return L.Editable.RectangleEditor.extend({
|
|
349
390
|
extendBounds: function (e) {
|
|
350
391
|
var index = e.vertex.getIndex(),
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
392
|
+
next = e.vertex.getNext(),
|
|
393
|
+
previous = e.vertex.getPrevious(),
|
|
394
|
+
oppositeIndex = (index + 2) % 4,
|
|
395
|
+
opposite = e.vertex.latlngs[oppositeIndex]
|
|
355
396
|
|
|
356
|
-
if (
|
|
397
|
+
if (index % 2 == 1) {
|
|
357
398
|
// calculate horiz. displacement
|
|
358
|
-
e.latlng.update([
|
|
399
|
+
e.latlng.update([
|
|
400
|
+
opposite.lat + (1 / aspect) * (opposite.lng - e.latlng.lng),
|
|
401
|
+
e.latlng.lng,
|
|
402
|
+
])
|
|
359
403
|
} else {
|
|
360
404
|
// calculate vert. displacement
|
|
361
|
-
e.latlng.update([
|
|
405
|
+
e.latlng.update([
|
|
406
|
+
e.latlng.lat,
|
|
407
|
+
opposite.lng - aspect * (opposite.lat - e.latlng.lat),
|
|
408
|
+
])
|
|
362
409
|
}
|
|
363
|
-
var bounds = new L.LatLngBounds(e.latlng, opposite)
|
|
410
|
+
var bounds = new L.LatLngBounds(e.latlng, opposite)
|
|
364
411
|
// Update latlngs by hand to preserve order.
|
|
365
|
-
previous.latlng.update([e.latlng.lat, opposite.lng])
|
|
366
|
-
next.latlng.update([opposite.lat, e.latlng.lng])
|
|
367
|
-
this.updateBounds(bounds)
|
|
368
|
-
this.refreshVertexMarkers()
|
|
369
|
-
}
|
|
370
|
-
})
|
|
412
|
+
previous.latlng.update([e.latlng.lat, opposite.lng])
|
|
413
|
+
next.latlng.update([opposite.lat, e.latlng.lng])
|
|
414
|
+
this.updateBounds(bounds)
|
|
415
|
+
this.refreshVertexMarkers()
|
|
416
|
+
},
|
|
417
|
+
})
|
|
371
418
|
}
|
|
372
419
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import Crop from
|
|
2
|
-
import CroppableModal from
|
|
1
|
+
import Crop from "spotlight/admin/crop"
|
|
2
|
+
import CroppableModal from "spotlight/admin/croppable_modal"
|
|
3
3
|
|
|
4
4
|
export default class Croppable {
|
|
5
5
|
connect() {
|
|
6
6
|
// For exhibit masthead or thumbnail pages, where
|
|
7
7
|
// the div exists on page load
|
|
8
|
-
$('[data-behavior="iiif-cropper"]').each(function() {
|
|
8
|
+
$('[data-behavior="iiif-cropper"]').each(function () {
|
|
9
9
|
var cropElement = $(this)
|
|
10
10
|
new Crop(cropElement).render()
|
|
11
11
|
})
|
|
12
12
|
|
|
13
13
|
// In the case of individual document thumbnails, selection
|
|
14
14
|
// of the image is through a modal. Here we attach the event
|
|
15
|
-
new CroppableModal().attachModalHandlers()
|
|
15
|
+
new CroppableModal().attachModalHandlers()
|
|
16
16
|
}
|
|
17
17
|
}
|