blacklight-spotlight 1.4.1 → 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +5 -1
- data/app/assets/javascripts/spotlight/blocks/resources_block.js +3 -2
- data/app/assets/javascripts/spotlight/crop.es6 +2 -0
- data/app/assets/javascripts/spotlight/multi_image_selector.js +5 -4
- data/app/assets/javascripts/spotlight/reindex_monitor.js +16 -3
- data/app/controllers/concerns/spotlight/controller.rb +6 -1
- data/app/controllers/spotlight/browse_controller.rb +27 -0
- data/app/controllers/spotlight/catalog_controller.rb +2 -2
- data/app/helpers/spotlight/browse_helper.rb +0 -29
- data/app/helpers/spotlight/title_helper.rb +2 -2
- data/app/models/concerns/spotlight/custom_translation_extension.rb +23 -0
- data/app/models/concerns/spotlight/solr_document/atomic_updates.rb +4 -2
- data/app/models/spotlight/blacklight_configuration.rb +2 -2
- data/app/models/spotlight/exhibit.rb +1 -1
- data/app/views/layouts/spotlight/spotlight.html.erb +1 -0
- data/app/views/spotlight/dashboards/_analytics.html.erb +4 -4
- data/app/views/spotlight/search_configurations/_search_fields.html.erb +1 -1
- data/db/migrate/20180306142612_create_translations.rb +18 -0
- data/lib/generators/spotlight/install_generator.rb +4 -0
- data/lib/generators/spotlight/templates/config/initializers/translation.rb +17 -0
- data/lib/spotlight/engine.rb +1 -0
- data/lib/spotlight/version.rb +1 -1
- data/spec/controllers/spotlight/browse_controller_spec.rb +32 -0
- data/spec/controllers/spotlight/confirmations_controller_spec.rb +1 -1
- data/spec/controllers/spotlight/filters_controller_spec.rb +0 -1
- data/spec/controllers/spotlight/roles_controller_spec.rb +17 -0
- data/spec/controllers/spotlight/tags_controller_spec.rb +6 -1
- data/spec/examples.txt +1280 -1167
- data/spec/factories/translation.rb +6 -0
- data/spec/features/add_iiif_manifest_spec.rb +2 -0
- data/spec/features/browse_category_spec.rb +28 -0
- data/spec/features/edit_search_fields_spec.rb +1 -2
- data/spec/features/exhibit_masthead_spec.rb +2 -2
- data/spec/features/exhibit_themes_spec.rb +1 -1
- data/spec/features/exhibits/add_tags_spec.rb +1 -1
- data/spec/features/item_admin_spec.rb +1 -1
- data/spec/features/javascript/blocks/solr_documents_block_spec.rb +41 -28
- data/spec/features/javascript/multi_image_select_spec.rb +1 -1
- data/spec/features/javascript/reindex_monitor_spec.rb +1 -1
- data/spec/features/javascript/search_config_admin_spec.rb +16 -29
- data/spec/features/site_masthead_spec.rb +1 -1
- data/spec/features/slideshow_spec.rb +1 -2
- data/spec/features/translation_scope_spec.rb +24 -0
- data/spec/models/spotlight/ability_spec.rb +7 -10
- data/spec/models/spotlight/contact_email_spec.rb +1 -1
- data/spec/models/spotlight/contact_form_spec.rb +1 -1
- data/spec/models/spotlight/resources/iiif_harvester_spec.rb +3 -0
- data/spec/models/spotlight/solr_document/atomic_updates_spec.rb +2 -2
- data/spec/spec_helper.rb +12 -4
- data/spec/support/disable_friendly_id_deprecation_warnings.rb +8 -0
- data/spec/support/features/capybara_default_max_wait_metadata_helper.rb +16 -0
- data/spec/support/features/test_features_helpers.rb +12 -2
- data/spec/views/spotlight/browse/show.html.erb_spec.rb +0 -7
- data/spec/views/spotlight/roles/index.html.erb_spec.rb +2 -0
- data/spec/views/spotlight/tags/index.html.erb_spec.rb +1 -0
- data/vendor/assets/javascripts/Leaflet.Editable.js +22 -11
- data/vendor/assets/javascripts/leaflet-iiif.js +91 -23
- metadata +246 -227
- data/spec/features/curator_items.rb +0 -10
- data/spec/features/tags_admin_spec.rb +0 -27
- data/spec/features/user_admin_spec.rb +0 -29
@@ -0,0 +1,24 @@
|
|
1
|
+
describe 'Translations scope setting', type: :feature do
|
2
|
+
let(:exhibit) { FactoryBot.create(:exhibit) }
|
3
|
+
let(:other_exhibit) { FactoryBot.create(:exhibit) }
|
4
|
+
let(:exhibit_curator) { FactoryBot.create(:exhibit_curator, exhibit: exhibit) }
|
5
|
+
|
6
|
+
describe 'exhibit route set' do
|
7
|
+
before do
|
8
|
+
login_as exhibit_curator
|
9
|
+
FactoryBot.create(:translation, exhibit: exhibit)
|
10
|
+
FactoryBot.create(:translation, exhibit: other_exhibit)
|
11
|
+
end
|
12
|
+
it 'default scope of Translation should be limited to current exhibit' do
|
13
|
+
visit spotlight.exhibit_path(exhibit)
|
14
|
+
expect(Translation.all.count).to eq 1
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'without the context of an exhibit' do
|
19
|
+
it 'renders page ok' do
|
20
|
+
visit root_path
|
21
|
+
expect(page).to have_css '.site-title', text: 'Blacklight'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -4,10 +4,10 @@ describe Spotlight::Ability, type: :model do
|
|
4
4
|
before do
|
5
5
|
allow_any_instance_of(Spotlight::Search).to receive(:set_default_featured_image)
|
6
6
|
end
|
7
|
-
let(:exhibit) { FactoryBot.
|
8
|
-
let(:search) { FactoryBot.
|
9
|
-
let(:unpublished_search) { FactoryBot.
|
10
|
-
let(:page) { FactoryBot.
|
7
|
+
let(:exhibit) { FactoryBot.build_stubbed(:exhibit) }
|
8
|
+
let(:search) { FactoryBot.build_stubbed(:published_search, exhibit: exhibit) }
|
9
|
+
let(:unpublished_search) { FactoryBot.build_stubbed(:search, exhibit: exhibit) }
|
10
|
+
let(:page) { FactoryBot.build_stubbed(:feature_page, exhibit: exhibit) }
|
11
11
|
subject { Ability.new(user) }
|
12
12
|
|
13
13
|
describe 'a user with no roles' do
|
@@ -30,6 +30,7 @@ describe Spotlight::Ability, type: :model do
|
|
30
30
|
describe 'a user with admin role' do
|
31
31
|
let(:user) { FactoryBot.create(:exhibit_admin, exhibit: exhibit) }
|
32
32
|
let(:role) { FactoryBot.create(:role, resource: exhibit) }
|
33
|
+
let(:blacklight_config) { exhibit.blacklight_configuration }
|
33
34
|
|
34
35
|
it { is_expected.to be_able_to(:update, exhibit) }
|
35
36
|
|
@@ -41,12 +42,12 @@ describe Spotlight::Ability, type: :model do
|
|
41
42
|
it { is_expected.to be_able_to(:import, exhibit) }
|
42
43
|
it { is_expected.to be_able_to(:process_import, exhibit) }
|
43
44
|
it { is_expected.to be_able_to(:destroy, exhibit) }
|
44
|
-
|
45
|
-
let(:blacklight_config) { exhibit.blacklight_configuration }
|
46
45
|
end
|
47
46
|
|
48
47
|
describe 'a user with curate role' do
|
49
48
|
let(:user) { FactoryBot.create(:exhibit_curator, exhibit: exhibit) }
|
49
|
+
let(:contact) { FactoryBot.build_stubbed(:contact, exhibit: exhibit) }
|
50
|
+
let(:blacklight_config) { exhibit.blacklight_configuration }
|
50
51
|
|
51
52
|
it { is_expected.not_to be_able_to(:update, exhibit) }
|
52
53
|
it { is_expected.to be_able_to(:curate, exhibit) }
|
@@ -64,13 +65,9 @@ describe Spotlight::Ability, type: :model do
|
|
64
65
|
|
65
66
|
it { is_expected.to be_able_to(:tag, exhibit) }
|
66
67
|
|
67
|
-
let(:contact) { FactoryBot.create(:contact, exhibit: exhibit) }
|
68
|
-
|
69
68
|
it { is_expected.to be_able_to(:edit, contact) }
|
70
69
|
it { is_expected.to be_able_to(:new, contact) }
|
71
70
|
it { is_expected.to be_able_to(:create, contact) }
|
72
71
|
it { is_expected.to be_able_to(:destroy, contact) }
|
73
|
-
|
74
|
-
let(:blacklight_config) { exhibit.blacklight_configuration }
|
75
72
|
end
|
76
73
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
describe Spotlight::ContactForm do
|
2
2
|
subject { described_class.new(name: 'Root', email: 'user@example.com').tap { |c| c.current_exhibit = exhibit } }
|
3
|
-
let(:exhibit) { FactoryBot.
|
3
|
+
let(:exhibit) { FactoryBot.build_stubbed(:exhibit) }
|
4
4
|
let(:honeypot_field_name) { Spotlight::Engine.config.spambot_honeypot_email_field }
|
5
5
|
|
6
6
|
context 'with a site-wide contact email' do
|
@@ -7,6 +7,9 @@ describe Spotlight::Resources::IiifHarvester do
|
|
7
7
|
describe 'Validation' do
|
8
8
|
subject { harvester }
|
9
9
|
context 'when given an invalid URL' do
|
10
|
+
before do
|
11
|
+
stub_request(:head, 'http://example.com').to_return(status: 200, headers: { 'Content-Type' => 'text/html' })
|
12
|
+
end
|
10
13
|
let(:url) { 'http://example.com' }
|
11
14
|
|
12
15
|
it 'errors when the URL is not a IIIF URL' do
|
@@ -28,7 +28,7 @@ describe Spotlight::SolrDocument::AtomicUpdates, type: :model do
|
|
28
28
|
it 'sends an atomic update request' do
|
29
29
|
expected = {
|
30
30
|
params: { commitWithin: 500 },
|
31
|
-
data: [{ id: 'doc_id', a: { set: 1 }, b: { set: 2 } }].to_json,
|
31
|
+
data: [{ id: 'doc_id', a: { set: 1 }, b: { set: 2 }, timestamp: { set: nil } }].to_json,
|
32
32
|
headers: { 'Content-Type' => 'application/json' }
|
33
33
|
}
|
34
34
|
expect(blacklight_solr).to receive(:update).with(expected)
|
@@ -36,7 +36,7 @@ describe Spotlight::SolrDocument::AtomicUpdates, type: :model do
|
|
36
36
|
end
|
37
37
|
|
38
38
|
it 'cowardlies refuse to index a document if the only value is an id' do
|
39
|
-
allow(subject).to receive_messages(to_solr: { id: 'doc_id' })
|
39
|
+
allow(subject).to receive_messages(to_solr: { id: 'doc_id' }, timestamp: { set: nil })
|
40
40
|
expect(blacklight_solr).not_to receive(:update)
|
41
41
|
subject.reindex
|
42
42
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -7,7 +7,7 @@ EngineCart.load_application!
|
|
7
7
|
|
8
8
|
Internal::Application.config.active_job.queue_adapter = :inline
|
9
9
|
|
10
|
-
require 'rails-controller-testing'
|
10
|
+
require 'rails-controller-testing'
|
11
11
|
require 'rspec/collection_matchers'
|
12
12
|
require 'rspec/its'
|
13
13
|
require 'rspec/rails'
|
@@ -15,19 +15,24 @@ require 'rspec/active_model/mocks'
|
|
15
15
|
require 'paper_trail/frameworks/rspec'
|
16
16
|
|
17
17
|
require 'selenium-webdriver'
|
18
|
+
require 'webmock/rspec'
|
18
19
|
|
19
20
|
Capybara.javascript_driver = :headless_chrome
|
20
21
|
|
22
|
+
# @note In January 2018, TravisCI disabled Chrome sandboxing in its Linux
|
23
|
+
# container build environments to mitigate Meltdown/Spectre
|
24
|
+
# vulnerabilities, at which point Spotlight needs to use the --no-sandbox
|
25
|
+
# flag. https://github.com/travis-ci/docs-travis-ci-com/blob/c1da4af0b7ee5de35fa4490fa8e0fc4b44881089/user/chrome.md
|
26
|
+
# h/t @mjgiarlo
|
21
27
|
Capybara.register_driver :headless_chrome do |app|
|
22
28
|
capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
|
23
|
-
chromeOptions: { args: %w[headless disable-gpu] }
|
29
|
+
chromeOptions: { args: %w[headless disable-gpu no-sandbox window-size=1280,1696] }
|
24
30
|
)
|
25
31
|
|
26
32
|
Capybara::Selenium::Driver.new(app,
|
27
33
|
browser: :chrome,
|
28
34
|
desired_capabilities: capabilities)
|
29
35
|
end
|
30
|
-
Capybara.default_max_wait_time = 10
|
31
36
|
|
32
37
|
if ENV['COVERAGE'] || ENV['CI']
|
33
38
|
require 'simplecov'
|
@@ -56,7 +61,9 @@ RSpec.configure do |config|
|
|
56
61
|
config.filter_rails_from_backtrace!
|
57
62
|
|
58
63
|
config.use_transactional_fixtures = false
|
59
|
-
|
64
|
+
config.before :all do
|
65
|
+
WebMock.disable_net_connect!(allow_localhost: true)
|
66
|
+
end
|
60
67
|
config.before :each do
|
61
68
|
DatabaseCleaner.strategy = if Capybara.current_driver == :rack_test
|
62
69
|
:transaction
|
@@ -96,6 +103,7 @@ RSpec.configure do |config|
|
|
96
103
|
config.include ::Rails.application.routes.url_helpers
|
97
104
|
config.include ::Rails.application.routes.mounted_helpers
|
98
105
|
config.include Spotlight::TestFeaturesHelpers, type: :feature
|
106
|
+
config.include CapybaraDefaultMaxWaitMetadataHelper, type: :feature
|
99
107
|
|
100
108
|
config.expect_with :rspec do |expectations|
|
101
109
|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
@@ -0,0 +1,8 @@
|
|
1
|
+
default_deprecation_behaviours = ActiveSupport::Deprecation.behavior
|
2
|
+
ActiveSupport::Deprecation.behavior = lambda do |message, callstack|
|
3
|
+
raise 'Remove friendly_id deprecation silencing patch!' if Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR > 1
|
4
|
+
unless callstack.find { |l| l.path =~ %r{gems/friendly_id} } &&
|
5
|
+
message =~ /The behavior of .* inside of after callbacks will be changing/
|
6
|
+
default_deprecation_behaviours.each { |b| b.call(message, callstack) }
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CapybaraDefaultMaxWaitMetadataHelper
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
before do |example|
|
6
|
+
next unless example.metadata[:default_max_wait_time]
|
7
|
+
@previous_wait_time = Capybara.default_max_wait_time
|
8
|
+
Capybara.default_max_wait_time = example.metadata[:default_max_wait_time]
|
9
|
+
end
|
10
|
+
|
11
|
+
after do |example|
|
12
|
+
next unless example.metadata[:default_max_wait_time]
|
13
|
+
Capybara.default_max_wait_time = @previous_wait_time
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -13,6 +13,13 @@ module Spotlight
|
|
13
13
|
find('.tt-suggestion', text: opts[:with], match: :first).click
|
14
14
|
end
|
15
15
|
|
16
|
+
# just like #fill_in_typeahead_field, but wait for the
|
17
|
+
# form fields to show up on the page too
|
18
|
+
def fill_in_solr_document_block_typeahead_field(opts)
|
19
|
+
fill_in_typeahead_field(opts)
|
20
|
+
expect(page).to have_css('li[data-resource-id="' + opts[:with] + '"]')
|
21
|
+
end
|
22
|
+
|
16
23
|
def add_widget(type)
|
17
24
|
click_add_widget
|
18
25
|
|
@@ -31,10 +38,13 @@ module Spotlight
|
|
31
38
|
end
|
32
39
|
|
33
40
|
def save_page
|
34
|
-
|
41
|
+
page.execute_script <<-EOF
|
42
|
+
SirTrevor.getInstance().onFormSubmit();
|
43
|
+
EOF
|
35
44
|
click_button('Save changes')
|
36
45
|
# verify that the page was created
|
37
|
-
expect(page).
|
46
|
+
expect(page).to_not have_selector('.alert-danger')
|
47
|
+
expect(page).to have_selector('.alert-info', text: 'page was successfully updated')
|
38
48
|
end
|
39
49
|
|
40
50
|
RSpec::Matchers.define :have_breadcrumbs do |*expected|
|
@@ -6,7 +6,6 @@ describe 'spotlight/browse/show', type: :view do
|
|
6
6
|
before do
|
7
7
|
allow(view).to receive_messages(resource_masthead?: false)
|
8
8
|
allow(view).to receive_messages(blacklight_config: Blacklight::Configuration.new)
|
9
|
-
view.blacklight_config.view.gallery = true
|
10
9
|
allow(search).to receive_messages(documents: double(size: 15))
|
11
10
|
allow(view).to receive_messages(render_document_index_with_view: '')
|
12
11
|
stub_template('_results_pagination.html.erb' => '')
|
@@ -62,10 +61,4 @@ describe 'spotlight/browse/show', type: :view do
|
|
62
61
|
render
|
63
62
|
expect(response).to have_content 'Sort and Per Page actions'
|
64
63
|
end
|
65
|
-
|
66
|
-
it 'displays the search results' do
|
67
|
-
expect(view).to receive(:render_document_index_with_view).with(:gallery, anything, anything).and_return 'Gallery View'
|
68
|
-
render
|
69
|
-
expect(response).to include 'Gallery View'
|
70
|
-
end
|
71
64
|
end
|
@@ -17,6 +17,8 @@ describe 'spotlight/roles/index', type: :view do
|
|
17
17
|
assert_select "form[action='#{action}'][method='post']" do
|
18
18
|
assert_select "tr[data-show-for='#{admin_role.id}']"
|
19
19
|
assert_select "tr[data-edit-for='#{admin_role.id}']"
|
20
|
+
assert_select 'td', /jane@example.com/
|
21
|
+
assert_select 'td', 'Admin'
|
20
22
|
assert_select "input[type='submit'][data-behavior='destroy-user'][data-target='#{admin_role.id}']"
|
21
23
|
assert_select "input[type='hidden'][data-destroy-for='#{admin_role.id}']"
|
22
24
|
assert_select "a[data-behavior='cancel-edit']"
|
@@ -716,6 +716,7 @@
|
|
716
716
|
this.editor.refresh();
|
717
717
|
var icon = this._icon;
|
718
718
|
var marker = this.editor.addVertexMarker(e.latlng, this.latlngs);
|
719
|
+
this.editor.onNewVertex(marker);
|
719
720
|
/* Hack to workaround browser not firing touchend when element is no more on DOM */
|
720
721
|
var parent = marker._icon.parentNode;
|
721
722
|
parent.removeChild(marker._icon);
|
@@ -1018,7 +1019,7 @@
|
|
1018
1019
|
initVertexMarkers: function (latlngs) {
|
1019
1020
|
if (!this.enabled()) return;
|
1020
1021
|
latlngs = latlngs || this.getLatLngs();
|
1021
|
-
if (
|
1022
|
+
if (isFlat(latlngs)) this.addVertexMarkers(latlngs);
|
1022
1023
|
else for (var i = 0; i < latlngs.length; i++) this.initVertexMarkers(latlngs[i]);
|
1023
1024
|
},
|
1024
1025
|
|
@@ -1037,6 +1038,14 @@
|
|
1037
1038
|
return new this.tools.options.vertexMarkerClass(latlng, latlngs, this);
|
1038
1039
|
},
|
1039
1040
|
|
1041
|
+
onNewVertex: function (vertex) {
|
1042
|
+
// 🍂namespace Editable
|
1043
|
+
// 🍂section Vertex events
|
1044
|
+
// 🍂event editable:vertex:new: VertexEvent
|
1045
|
+
// Fired when a new vertex is created.
|
1046
|
+
this.fireAndForward('editable:vertex:new', {latlng: vertex.latlng, vertex: vertex});
|
1047
|
+
},
|
1048
|
+
|
1040
1049
|
addVertexMarkers: function (latlngs) {
|
1041
1050
|
for (var i = 0; i < latlngs.length; i++) {
|
1042
1051
|
this.addVertexMarker(latlngs[i], latlngs);
|
@@ -1219,7 +1228,8 @@
|
|
1219
1228
|
if (this._drawing === L.Editable.FORWARD) this._drawnLatLngs.push(latlng);
|
1220
1229
|
else this._drawnLatLngs.unshift(latlng);
|
1221
1230
|
this.feature._bounds.extend(latlng);
|
1222
|
-
this.addVertexMarker(latlng, this._drawnLatLngs);
|
1231
|
+
var vertex = this.addVertexMarker(latlng, this._drawnLatLngs);
|
1232
|
+
this.onNewVertex(vertex);
|
1223
1233
|
this.refresh();
|
1224
1234
|
},
|
1225
1235
|
|
@@ -1431,7 +1441,7 @@
|
|
1431
1441
|
},
|
1432
1442
|
|
1433
1443
|
ensureMulti: function () {
|
1434
|
-
if (this.feature._latlngs.length &&
|
1444
|
+
if (this.feature._latlngs.length && isFlat(this.feature._latlngs)) {
|
1435
1445
|
this.feature._latlngs = [this.feature._latlngs];
|
1436
1446
|
}
|
1437
1447
|
},
|
@@ -1447,7 +1457,7 @@
|
|
1447
1457
|
},
|
1448
1458
|
|
1449
1459
|
formatShape: function (shape) {
|
1450
|
-
if (
|
1460
|
+
if (isFlat(shape)) return shape;
|
1451
1461
|
else if (shape[0]) return this.formatShape(shape[0]);
|
1452
1462
|
},
|
1453
1463
|
|
@@ -1512,13 +1522,13 @@
|
|
1512
1522
|
},
|
1513
1523
|
|
1514
1524
|
ensureMulti: function () {
|
1515
|
-
if (this.feature._latlngs.length &&
|
1525
|
+
if (this.feature._latlngs.length && isFlat(this.feature._latlngs[0])) {
|
1516
1526
|
this.feature._latlngs = [this.feature._latlngs];
|
1517
1527
|
}
|
1518
1528
|
},
|
1519
1529
|
|
1520
1530
|
ensureNotFlat: function () {
|
1521
|
-
if (!this.feature._latlngs.length ||
|
1531
|
+
if (!this.feature._latlngs.length || isFlat(this.feature._latlngs)) this.feature._latlngs = [this.feature._latlngs];
|
1522
1532
|
},
|
1523
1533
|
|
1524
1534
|
vertexCanBeDeleted: function (vertex) {
|
@@ -1537,7 +1547,7 @@
|
|
1537
1547
|
// [[1, 2], [3, 4]] => must be nested
|
1538
1548
|
// [] => must be nested
|
1539
1549
|
// [[]] => is already nested
|
1540
|
-
if (
|
1550
|
+
if (isFlat(shape) && (!shape[0] || shape[0].length !== 0)) return [shape];
|
1541
1551
|
else return shape;
|
1542
1552
|
}
|
1543
1553
|
|
@@ -1706,7 +1716,7 @@
|
|
1706
1716
|
|
1707
1717
|
// 🍂namespace Editable; 🍂class EditableMixin
|
1708
1718
|
// `EditableMixin` is included to `L.Polyline`, `L.Polygon`, `L.Rectangle`, `L.Circle`
|
1709
|
-
//
|
1719
|
+
// and `L.Marker`. It adds some methods to them.
|
1710
1720
|
// *When editing is enabled, the editor is accessible on the instance with the
|
1711
1721
|
// `editor` property.*
|
1712
1722
|
var EditableMixin = {
|
@@ -1768,7 +1778,7 @@
|
|
1768
1778
|
var shape = null;
|
1769
1779
|
latlngs = latlngs || this._latlngs;
|
1770
1780
|
if (!latlngs.length) return shape;
|
1771
|
-
else if (
|
1781
|
+
else if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs;
|
1772
1782
|
else for (var i = 0; i < latlngs.length; i++) if (this.isInLatLngs(latlng, latlngs[i])) return latlngs[i];
|
1773
1783
|
return shape;
|
1774
1784
|
},
|
@@ -1807,8 +1817,8 @@
|
|
1807
1817
|
var shape = null;
|
1808
1818
|
latlngs = latlngs || this._latlngs;
|
1809
1819
|
if (!latlngs.length) return shape;
|
1810
|
-
else if (
|
1811
|
-
else if (
|
1820
|
+
else if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs;
|
1821
|
+
else if (isFlat(latlngs[0]) && this.isInLatLngs(latlng, latlngs[0])) shape = latlngs;
|
1812
1822
|
else for (var i = 0; i < latlngs.length; i++) if (this.isInLatLngs(latlng, latlngs[i][0])) return latlngs[i];
|
1813
1823
|
return shape;
|
1814
1824
|
},
|
@@ -1872,6 +1882,7 @@
|
|
1872
1882
|
this.on('add', this._onEditableAdd);
|
1873
1883
|
};
|
1874
1884
|
|
1885
|
+
var isFlat = L.LineUtil.isFlat || L.LineUtil._flat || L.Polyline._flat; // <=> 1.1 compat.
|
1875
1886
|
|
1876
1887
|
|
1877
1888
|
if (L.Polyline) {
|
@@ -1,5 +1,5 @@
|
|
1
1
|
/*
|
2
|
-
* Leaflet-IIIF
|
2
|
+
* Leaflet-IIIF 2.0.0
|
3
3
|
* IIIF Viewer for Leaflet
|
4
4
|
* by Jack Reed, @mejackreed
|
5
5
|
*/
|
@@ -10,7 +10,8 @@ L.TileLayer.Iiif = L.TileLayer.extend({
|
|
10
10
|
tileSize: 256,
|
11
11
|
updateWhenIdle: true,
|
12
12
|
tileFormat: 'jpg',
|
13
|
-
fitBounds: true
|
13
|
+
fitBounds: true,
|
14
|
+
setMaxBounds: false
|
14
15
|
},
|
15
16
|
|
16
17
|
initialize: function(url, options) {
|
@@ -25,6 +26,11 @@ L.TileLayer.Iiif = L.TileLayer.extend({
|
|
25
26
|
this._explicitTileSize = true;
|
26
27
|
}
|
27
28
|
|
29
|
+
// Check for an explicit quality
|
30
|
+
if (options.quality) {
|
31
|
+
this._explicitQuality = true;
|
32
|
+
}
|
33
|
+
|
28
34
|
options = L.setOptions(this, options);
|
29
35
|
this._infoDeferred = new $.Deferred();
|
30
36
|
this._infoUrl = url;
|
@@ -42,7 +48,7 @@ L.TileLayer.Iiif = L.TileLayer.extend({
|
|
42
48
|
miny = (y * tileBaseSize),
|
43
49
|
maxx = Math.min(minx + tileBaseSize, _this.x),
|
44
50
|
maxy = Math.min(miny + tileBaseSize, _this.y);
|
45
|
-
|
51
|
+
|
46
52
|
var xDiff = (maxx - minx);
|
47
53
|
var yDiff = (maxy - miny);
|
48
54
|
|
@@ -63,6 +69,25 @@ L.TileLayer.Iiif = L.TileLayer.extend({
|
|
63
69
|
// Set maxZoom for map
|
64
70
|
map._layersMaxZoom = _this.maxZoom;
|
65
71
|
|
72
|
+
// Set minZoom and minNativeZoom based on how the imageSizes match up
|
73
|
+
var smallestImage = _this._imageSizes[0];
|
74
|
+
var mapSize = _this._map.getSize();
|
75
|
+
var newMinZoom = 0;
|
76
|
+
// Loop back through 5 times to see if a better fit can be found.
|
77
|
+
for (var i = 1; i <= 5; i++) {
|
78
|
+
if (smallestImage.x > mapSize.x || smallestImage.y > mapSize.y) {
|
79
|
+
smallestImage = smallestImage.divideBy(2);
|
80
|
+
_this._imageSizes.unshift(smallestImage);
|
81
|
+
newMinZoom = -i;
|
82
|
+
} else {
|
83
|
+
break;
|
84
|
+
}
|
85
|
+
}
|
86
|
+
_this.options.minZoom = newMinZoom;
|
87
|
+
_this.options.minNativeZoom = newMinZoom;
|
88
|
+
_this._prev_map_layersMinZoom = _this._map._layersMinZoom;
|
89
|
+
_this._map._layersMinZoom = newMinZoom;
|
90
|
+
|
66
91
|
// Call add TileLayer
|
67
92
|
L.TileLayer.prototype.onAdd.call(_this, map);
|
68
93
|
|
@@ -70,6 +95,10 @@ L.TileLayer.Iiif = L.TileLayer.extend({
|
|
70
95
|
_this._fitBounds();
|
71
96
|
}
|
72
97
|
|
98
|
+
if(_this.options.setMaxBounds) {
|
99
|
+
_this._setMaxBounds();
|
100
|
+
}
|
101
|
+
|
73
102
|
// Reset tile sizes to handle non 256x256 IIIF tiles
|
74
103
|
_this.on('tileload', function(tile, url) {
|
75
104
|
|
@@ -85,18 +114,45 @@ L.TileLayer.Iiif = L.TileLayer.extend({
|
|
85
114
|
});
|
86
115
|
});
|
87
116
|
},
|
117
|
+
onRemove: function(map) {
|
118
|
+
var _this = this;
|
119
|
+
|
120
|
+
map._layersMinZoom = _this._prev_map_layersMinZoom;
|
121
|
+
|
122
|
+
// Remove maxBounds set for this image
|
123
|
+
if(_this.options.setMaxBounds) {
|
124
|
+
map.setMaxBounds(null);
|
125
|
+
}
|
126
|
+
|
127
|
+
// Call remove TileLayer
|
128
|
+
L.TileLayer.prototype.onRemove.call(_this, map);
|
129
|
+
|
130
|
+
},
|
88
131
|
_fitBounds: function() {
|
89
132
|
var _this = this;
|
90
133
|
|
91
134
|
// Find best zoom level and center map
|
92
135
|
var initialZoom = _this._getInitialZoom(_this._map.getSize());
|
93
|
-
var
|
136
|
+
var offset = _this._imageSizes.length - 1 - _this.options.maxNativeZoom;
|
137
|
+
var imageSize = _this._imageSizes[initialZoom + offset];
|
94
138
|
var sw = _this._map.options.crs.pointToLatLng(L.point(0, imageSize.y), initialZoom);
|
95
139
|
var ne = _this._map.options.crs.pointToLatLng(L.point(imageSize.x, 0), initialZoom);
|
96
140
|
var bounds = L.latLngBounds(sw, ne);
|
97
141
|
|
98
142
|
_this._map.fitBounds(bounds, true);
|
99
143
|
},
|
144
|
+
_setMaxBounds: function() {
|
145
|
+
var _this = this;
|
146
|
+
|
147
|
+
// Find best zoom level, center map, and constrain viewer
|
148
|
+
var initialZoom = _this._getInitialZoom(_this._map.getSize());
|
149
|
+
var imageSize = _this._imageSizes[initialZoom];
|
150
|
+
var sw = _this._map.options.crs.pointToLatLng(L.point(0, imageSize.y), initialZoom);
|
151
|
+
var ne = _this._map.options.crs.pointToLatLng(L.point(imageSize.x, 0), initialZoom);
|
152
|
+
var bounds = L.latLngBounds(sw, ne);
|
153
|
+
|
154
|
+
_this._map.setMaxBounds(bounds, true);
|
155
|
+
},
|
100
156
|
_getInfo: function() {
|
101
157
|
var _this = this;
|
102
158
|
|
@@ -136,14 +192,15 @@ L.TileLayer.Iiif = L.TileLayer.extend({
|
|
136
192
|
}
|
137
193
|
}
|
138
194
|
|
139
|
-
ceilLog2
|
195
|
+
function ceilLog2(x) {
|
140
196
|
return Math.ceil(Math.log(x) / Math.LN2);
|
141
197
|
};
|
142
198
|
|
143
199
|
// Calculates maximum native zoom for the layer
|
144
200
|
_this.maxNativeZoom = Math.max(ceilLog2(_this.x / _this.options.tileSize),
|
145
201
|
ceilLog2(_this.y / _this.options.tileSize));
|
146
|
-
|
202
|
+
_this.options.maxNativeZoom = _this.maxNativeZoom;
|
203
|
+
|
147
204
|
// Enable zooming further than native if maxZoom option supplied
|
148
205
|
if (_this._customMaxZoom && _this.options.maxZoom > _this.maxNativeZoom) {
|
149
206
|
_this.maxZoom = _this.options.maxZoom;
|
@@ -151,7 +208,7 @@ L.TileLayer.Iiif = L.TileLayer.extend({
|
|
151
208
|
else {
|
152
209
|
_this.maxZoom = _this.maxNativeZoom;
|
153
210
|
}
|
154
|
-
|
211
|
+
|
155
212
|
for (var i = 0; i <= _this.maxZoom; i++) {
|
156
213
|
scale = Math.pow(2, _this.maxNativeZoom - i);
|
157
214
|
width_ = Math.ceil(_this.x / scale);
|
@@ -172,18 +229,24 @@ L.TileLayer.Iiif = L.TileLayer.extend({
|
|
172
229
|
|
173
230
|
_setQuality: function() {
|
174
231
|
var _this = this;
|
232
|
+
var profileToCheck = _this.profile;
|
175
233
|
|
176
|
-
|
177
|
-
if (_this.options.quality) {
|
234
|
+
if (_this._explicitQuality) {
|
178
235
|
return;
|
179
236
|
}
|
180
237
|
|
238
|
+
// If profile is an object
|
239
|
+
if (typeof(profileToCheck) === 'object') {
|
240
|
+
profileToCheck = profileToCheck['@id'];
|
241
|
+
}
|
242
|
+
|
181
243
|
// Set the quality based on the IIIF compliance level
|
182
244
|
switch (true) {
|
183
|
-
case /^http:\/\/library.stanford.edu\/iiif\/image-api\/1.1\/compliance.html.*$/.test(
|
245
|
+
case /^http:\/\/library.stanford.edu\/iiif\/image-api\/1.1\/compliance.html.*$/.test(profileToCheck):
|
184
246
|
_this.options.quality = 'native';
|
185
247
|
break;
|
186
|
-
|
248
|
+
// Assume later profiles and set to default
|
249
|
+
default:
|
187
250
|
_this.options.quality = 'default';
|
188
251
|
break;
|
189
252
|
}
|
@@ -196,11 +259,15 @@ L.TileLayer.Iiif = L.TileLayer.extend({
|
|
196
259
|
return this._infoToBaseUrl() + '{region}/{size}/{rotation}/{quality}.{format}';
|
197
260
|
},
|
198
261
|
_isValidTile: function(coords) {
|
199
|
-
var
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
262
|
+
var tileBounds = this._tileCoordsToBounds(coords);
|
263
|
+
var _this = this;
|
264
|
+
var zoom = _this._getZoomForUrl();
|
265
|
+
var sizes = _this._tierSizes[zoom];
|
266
|
+
var x = coords.x;
|
267
|
+
var y = coords.y;
|
268
|
+
if (zoom < 0 && x >= 0 && y >= 0) {
|
269
|
+
return true;
|
270
|
+
}
|
204
271
|
|
205
272
|
if (!sizes) return false;
|
206
273
|
if (x < 0 || sizes[0] <= x || y < 0 || sizes[1] <= y) {
|
@@ -210,14 +277,15 @@ L.TileLayer.Iiif = L.TileLayer.extend({
|
|
210
277
|
}
|
211
278
|
},
|
212
279
|
_getInitialZoom: function (mapSize) {
|
213
|
-
var _this = this
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
280
|
+
var _this = this;
|
281
|
+
var tolerance = 0.8;
|
282
|
+
var imageSize;
|
283
|
+
// Calculate an offset between the zoom levels and the array accessors
|
284
|
+
var offset = _this._imageSizes.length - 1 - _this.options.maxNativeZoom;
|
285
|
+
for (var i = _this._imageSizes.length - 1; i >= 0; i--) {
|
286
|
+
imageSize = _this._imageSizes[i];
|
219
287
|
if (imageSize.x * tolerance < mapSize.x && imageSize.y * tolerance < mapSize.y) {
|
220
|
-
return i;
|
288
|
+
return i - offset;
|
221
289
|
}
|
222
290
|
}
|
223
291
|
// return a default zoom
|