blacklight-spotlight 0.33.3 → 0.34.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,7 +10,9 @@
10
10
  <h1><%= render 'search_title', search: @search %></h1>
11
11
  <% end %>
12
12
  <% if @search.long_description.present? %>
13
- <p class="long-description-text"><%= render_markdown @search.long_description %></p>
13
+ <div class="long-description-text <%= 'very-long-description-text' if @search.long_description.length > 600 %>">
14
+ <%= render_markdown @search.long_description %>
15
+ </div>
14
16
  <% end %>
15
17
  </div>
16
18
 
@@ -1,5 +1,7 @@
1
1
  <div role="tabpanel" class="tab-pane" id="filter">
2
2
  <%= bootstrap_form_for [@exhibit, @exhibit.filters.first_or_initialize], layout: :horizontal, label_col: 'col-md-2', control_col: 'col-md-10', html: {class: "row"} do |f| %>
3
+ <p class="instructions"><%= t :'spotlight.exhibits.filter.instructions' %></p>
4
+
3
5
  <%= f.text_field :field %>
4
6
  <%= f.text_field :value %>
5
7
 
@@ -10,4 +12,3 @@
10
12
  </div>
11
13
  <% end %>
12
14
  </div>
13
-
@@ -409,6 +409,9 @@ en:
409
409
  header: Create a new exhibit
410
410
  filter:
411
411
  heading: Filter items
412
+ instructions: >
413
+ You can limit the items included in this exhibit by applying a filter to your Solr index. When you apply a filter, only items with values that match the filter will be included in your exhibit.
414
+ To apply a filter, enter both a field and a value that match the names used in your Solr index.
412
415
  import:
413
416
  heading: Import data
414
417
  instructions: You can import an exhibit JSON file exported from this application to use that data file to define this exhibit.
@@ -6,5 +6,4 @@
6
6
  @import 'bootstrap';
7
7
  @import 'sir-trevor/main';
8
8
  @import 'leaflet';
9
- @import 'leaflet-areaselect';
10
9
  @import 'spotlight/spotlight';
@@ -43,19 +43,22 @@ module Migration
43
43
  def copy_exhibit_thumbnail_from_featured_image(image)
44
44
  return unless Spotlight::Exhibit.where(thumbnail_id: image.id).any?
45
45
  filename = image.read_attribute_before_type_cast('image')
46
- old_file = "public/#{image.image.store_dir}/#{filename}"
46
+ filepath = "public/#{image.image.store_dir}/#{filename}"
47
47
  image.becomes!(Spotlight::ExhibitThumbnail)
48
48
  image.save
49
+ return unless filename.present? && File.exist?(filepath)
50
+ old_file = File.new(filepath)
49
51
  # AR + STI seems to require that we re-query for this
50
52
  # otherwise we get an association miss-match
51
53
  reloaded_image = Spotlight::ExhibitThumbnail.find(image.id)
52
- reloaded_image.image.store!(File.new(old_file))
54
+ reloaded_image.image.store!(old_file)
53
55
  end
54
56
 
55
57
  # Looks for a file at the old uploader location and copies it to a FeaturedImage
56
58
  def copy_contact_image_to_avatar(contact)
57
59
  filename = contact.read_attribute_before_type_cast('avatar')
58
60
  filepath = "public/uploads/spotlight/contact/avatar/#{contact.id}/#{filename}"
61
+ return unless filename.present? && File.exist?(filepath)
59
62
  old_file = File.new(filepath)
60
63
  image = contact.create_avatar { |i| i.image.store!(old_file) }
61
64
  iiif_tilesource = riiif.info_path(image.id)
@@ -1,3 +1,3 @@
1
1
  module Spotlight
2
- VERSION = '0.33.3'.freeze
2
+ VERSION = '0.34.0'.freeze
3
3
  end
@@ -475,12 +475,12 @@ example_id
475
475
  ./spec/jobs/spotlight/reindex_job_spec.rb[1:2:1] | passed | 0.08176 seconds |
476
476
  ./spec/jobs/spotlight/rename_sidecar_field_job_spec.rb[1:1] | passed | 0.09844 seconds |
477
477
  ./spec/jobs/spotlight/rename_sidecar_field_job_spec.rb[1:2] | passed | 0.08178 seconds |
478
- ./spec/lib/migration/iiif_spec.rb[1:1:1:1] | passed | 0.09597 seconds |
479
- ./spec/lib/migration/iiif_spec.rb[1:1:1:2] | passed | 0.0943 seconds |
480
- ./spec/lib/migration/iiif_spec.rb[1:1:2:1] | passed | 0.09824 seconds |
481
- ./spec/lib/migration/iiif_spec.rb[1:1:2:2] | passed | 0.10211 seconds |
482
- ./spec/lib/migration/iiif_spec.rb[1:1:2:3] | passed | 0.10103 seconds |
483
- ./spec/lib/migration/iiif_spec.rb[1:2:1] | passed | 0.04016 seconds |
478
+ ./spec/lib/migration/iiif_spec.rb[1:1:1:1] | passed | 0.45193 seconds |
479
+ ./spec/lib/migration/iiif_spec.rb[1:1:1:2] | passed | 0.08761 seconds |
480
+ ./spec/lib/migration/iiif_spec.rb[1:1:2:1] | passed | 0.09474 seconds |
481
+ ./spec/lib/migration/iiif_spec.rb[1:1:2:2] | passed | 0.09019 seconds |
482
+ ./spec/lib/migration/iiif_spec.rb[1:1:2:3] | passed | 0.09021 seconds |
483
+ ./spec/lib/migration/iiif_spec.rb[1:2:1] | failed | 0.0757 seconds |
484
484
  ./spec/lib/spotlight/controller_spec.rb[1:1:1] | passed | 0.01355 seconds |
485
485
  ./spec/lib/spotlight/controller_spec.rb[1:2:1] | passed | 0.01482 seconds |
486
486
  ./spec/lib/spotlight/controller_spec.rb[1:2:2] | passed | 0.10532 seconds |
@@ -1010,13 +1010,14 @@ example_id
1010
1010
  ./spec/views/spotlight/browse/_sort_and_per_page.html.erb_spec.rb[1:1] | passed | 0.07568 seconds |
1011
1011
  ./spec/views/spotlight/browse/index.html.erb_spec.rb[1:1] | passed | 0.09949 seconds |
1012
1012
  ./spec/views/spotlight/browse/index.html.erb_spec.rb[1:2] | passed | 0.17661 seconds |
1013
- ./spec/views/spotlight/browse/show.html.erb_spec.rb[1:1] | passed | 0.53818 seconds |
1014
- ./spec/views/spotlight/browse/show.html.erb_spec.rb[1:2] | passed | 0.17832 seconds |
1015
- ./spec/views/spotlight/browse/show.html.erb_spec.rb[1:3] | passed | 0.17218 seconds |
1016
- ./spec/views/spotlight/browse/show.html.erb_spec.rb[1:4] | passed | 0.18409 seconds |
1017
- ./spec/views/spotlight/browse/show.html.erb_spec.rb[1:5] | passed | 0.18048 seconds |
1018
- ./spec/views/spotlight/browse/show.html.erb_spec.rb[1:6] | passed | 0.19157 seconds |
1019
- ./spec/views/spotlight/browse/show.html.erb_spec.rb[1:7] | passed | 0.2647 seconds |
1013
+ ./spec/views/spotlight/browse/show.html.erb_spec.rb[1:1] | passed | 0.86334 seconds |
1014
+ ./spec/views/spotlight/browse/show.html.erb_spec.rb[1:2] | passed | 0.15275 seconds |
1015
+ ./spec/views/spotlight/browse/show.html.erb_spec.rb[1:3] | passed | 0.14718 seconds |
1016
+ ./spec/views/spotlight/browse/show.html.erb_spec.rb[1:4] | passed | 0.1571 seconds |
1017
+ ./spec/views/spotlight/browse/show.html.erb_spec.rb[1:5] | passed | 0.13794 seconds |
1018
+ ./spec/views/spotlight/browse/show.html.erb_spec.rb[1:6] | passed | 0.16692 seconds |
1019
+ ./spec/views/spotlight/browse/show.html.erb_spec.rb[1:7] | passed | 0.15858 seconds |
1020
+ ./spec/views/spotlight/browse/show.html.erb_spec.rb[1:8] | passed | 0.1863 seconds |
1020
1021
  ./spec/views/spotlight/catalog/_edit_default.html.erb_spec.rb[1:1] | passed | 0.03954 seconds |
1021
1022
  ./spec/views/spotlight/catalog/_edit_default.html.erb_spec.rb[1:2] | passed | 0.03291 seconds |
1022
1023
  ./spec/views/spotlight/catalog/_edit_default.html.erb_spec.rb[1:3] | passed | 0.04079 seconds |
@@ -26,11 +26,12 @@ describe Spotlight::AddUploadsFromCSV do
26
26
  end
27
27
 
28
28
  it 'creates uploaded resources for each row of data' do
29
- expect(Spotlight::Resources::Upload).to receive(:new).with(hash_including(remote_url_url: 'x')).and_return(resource_x)
30
- expect(Spotlight::Resources::Upload).to receive(:new).with(hash_including(remote_url_url: 'y')).and_return(resource_y)
29
+ upload = FactoryGirl.create(:uploaded_resource)
30
+ expect(Spotlight::Resources::Upload).to receive(:new).at_least(:once).and_return(upload)
31
31
 
32
- expect(resource_x).to receive(:save_and_index)
33
- expect(resource_y).to receive(:save_and_index)
32
+ expect(upload).to receive(:build_upload).with(remote_image_url: 'x').and_call_original
33
+ expect(upload).to receive(:build_upload).with(remote_image_url: 'y').and_call_original
34
+ expect(upload).to receive(:save_and_index).at_least(:once)
34
35
 
35
36
  job.perform_now
36
37
  end
@@ -3,10 +3,6 @@ require 'migration/iiif'
3
3
  RSpec.describe Migration::IIIF do
4
4
  let(:instance) { described_class.new('http://test.host') }
5
5
 
6
- before do
7
- expect(File).to receive(:new).and_return(double)
8
- end
9
-
10
6
  context '#migrate_featured_images' do
11
7
  let!(:old_exhibit_thumbnail) { FactoryGirl.create(:featured_image, type: nil, iiif_tilesource: nil) }
12
8
  let!(:exhibit) { FactoryGirl.create(:exhibit, thumbnail_id: old_exhibit_thumbnail.id) }
@@ -51,19 +47,20 @@ RSpec.describe Migration::IIIF do
51
47
 
52
48
  describe '#migrate_contact_avatars' do
53
49
  let(:file) { double }
54
- let(:contact1) { Spotlight::Contact.create }
55
- let(:contact2) { Spotlight::Contact.create }
50
+ let!(:contact1) { Spotlight::Contact.create }
51
+
56
52
  before do
53
+ allow(File).to receive(:exist?).and_return(true)
57
54
  allow(File).to receive(:new).and_return(file)
58
- allow(contact1).to receive('read_attribute_before_type_cast').and_call_original
59
- allow(contact2).to receive('read_attribute_before_type_cast').and_call_original
60
- allow(contact1).to receive('read_attribute_before_type_cast').with('avatar').and_return('file1.jpg')
61
- allow(contact2).to receive('read_attribute_before_type_cast').with('avatar').and_return('file2.jpg')
55
+ expect_any_instance_of(Spotlight::Contact).to receive('read_attribute_before_type_cast').with('avatar').and_return('file1.jpg')
56
+ # allow other calls (from rails 4)
57
+ allow_any_instance_of(Spotlight::Contact).to receive('read_attribute_before_type_cast').with(anything).and_call_original
62
58
  end
59
+
63
60
  it 'migrates' do
64
61
  expect do
65
62
  instance.send :migrate_contact_avatars
66
- end.to change { Spotlight::FeaturedImage.count }.by(2)
63
+ end.to change { Spotlight::FeaturedImage.count }.by(1)
67
64
  expect(Spotlight::Contact.all.pluck(:avatar_id)).to eq Spotlight::FeaturedImage.all.pluck(:id)
68
65
  end
69
66
  end
@@ -42,7 +42,14 @@ describe 'spotlight/browse/show', type: :view do
42
42
  it 'displays the long description' do
43
43
  allow(search).to receive_messages(long_description: 'Long description')
44
44
  render
45
- expect(response).to have_selector 'p', text: search.long_description
45
+ expect(response).to have_selector '.long-description-text p', text: search.long_description
46
+ expect(response).not_to have_selector '.very-long-description-text'
47
+ end
48
+
49
+ it 'adds markup for very long descriptions' do
50
+ allow(search).to receive_messages(long_description: 'A' * 601)
51
+ render
52
+ expect(response).to have_selector '.very-long-description-text p'
46
53
  end
47
54
 
48
55
  it 'renders the long description as markdown' do
@@ -0,0 +1,1906 @@
1
+ 'use strict';
2
+ (function (factory, window) {
3
+ /*globals define, module, require*/
4
+
5
+ // define an AMD module that relies on 'leaflet'
6
+ if (typeof define === 'function' && define.amd) {
7
+ define(['leaflet'], factory);
8
+
9
+
10
+ // define a Common JS module that relies on 'leaflet'
11
+ } else if (typeof exports === 'object') {
12
+ module.exports = factory(require('leaflet'));
13
+ }
14
+
15
+ // attach your plugin to the global 'L' variable
16
+ if(typeof window !== 'undefined' && window.L){
17
+ factory(window.L);
18
+ }
19
+
20
+ }(function (L) {
21
+ // 🍂miniclass CancelableEvent (Event objects)
22
+ // 🍂method cancel()
23
+ // Cancel any subsequent action.
24
+
25
+ // 🍂miniclass VertexEvent (Event objects)
26
+ // 🍂property vertex: VertexMarker
27
+ // The vertex that fires the event.
28
+
29
+ // 🍂miniclass ShapeEvent (Event objects)
30
+ // 🍂property shape: Array
31
+ // The shape (LatLngs array) subject of the action.
32
+
33
+ // 🍂miniclass CancelableVertexEvent (Event objects)
34
+ // 🍂inherits VertexEvent
35
+ // 🍂inherits CancelableEvent
36
+
37
+ // 🍂miniclass CancelableShapeEvent (Event objects)
38
+ // 🍂inherits ShapeEvent
39
+ // 🍂inherits CancelableEvent
40
+
41
+ // 🍂miniclass LayerEvent (Event objects)
42
+ // 🍂property layer: object
43
+ // The Layer (Marker, Polyline…) subject of the action.
44
+
45
+ // 🍂namespace Editable; 🍂class Editable; 🍂aka L.Editable
46
+ // Main edition handler. By default, it is attached to the map
47
+ // as `map.editTools` property.
48
+ // Leaflet.Editable is made to be fully extendable. You have three ways to customize
49
+ // the behaviour: using options, listening to events, or extending.
50
+ L.Editable = L.Evented.extend({
51
+
52
+ statics: {
53
+ FORWARD: 1,
54
+ BACKWARD: -1
55
+ },
56
+
57
+ options: {
58
+
59
+ // You can pass them when creating a map using the `editOptions` key.
60
+ // 🍂option zIndex: int = 1000
61
+ // The default zIndex of the editing tools.
62
+ zIndex: 1000,
63
+
64
+ // 🍂option polygonClass: class = L.Polygon
65
+ // Class to be used when creating a new Polygon.
66
+ polygonClass: L.Polygon,
67
+
68
+ // 🍂option polylineClass: class = L.Polyline
69
+ // Class to be used when creating a new Polyline.
70
+ polylineClass: L.Polyline,
71
+
72
+ // 🍂option markerClass: class = L.Marker
73
+ // Class to be used when creating a new Marker.
74
+ markerClass: L.Marker,
75
+
76
+ // 🍂option rectangleClass: class = L.Rectangle
77
+ // Class to be used when creating a new Rectangle.
78
+ rectangleClass: L.Rectangle,
79
+
80
+ // 🍂option circleClass: class = L.Circle
81
+ // Class to be used when creating a new Circle.
82
+ circleClass: L.Circle,
83
+
84
+ // 🍂option drawingCSSClass: string = 'leaflet-editable-drawing'
85
+ // CSS class to be added to the map container while drawing.
86
+ drawingCSSClass: 'leaflet-editable-drawing',
87
+
88
+ // 🍂option drawingCursor: const = 'crosshair'
89
+ // Cursor mode set to the map while drawing.
90
+ drawingCursor: 'crosshair',
91
+
92
+ // 🍂option editLayer: Layer = new L.LayerGroup()
93
+ // Layer used to store edit tools (vertex, line guide…).
94
+ editLayer: undefined,
95
+
96
+ // 🍂option featuresLayer: Layer = new L.LayerGroup()
97
+ // Default layer used to store drawn features (Marker, Polyline…).
98
+ featuresLayer: undefined,
99
+
100
+ // 🍂option polylineEditorClass: class = PolylineEditor
101
+ // Class to be used as Polyline editor.
102
+ polylineEditorClass: undefined,
103
+
104
+ // 🍂option polygonEditorClass: class = PolygonEditor
105
+ // Class to be used as Polygon editor.
106
+ polygonEditorClass: undefined,
107
+
108
+ // 🍂option markerEditorClass: class = MarkerEditor
109
+ // Class to be used as Marker editor.
110
+ markerEditorClass: undefined,
111
+
112
+ // 🍂option rectangleEditorClass: class = RectangleEditor
113
+ // Class to be used as Rectangle editor.
114
+ rectangleEditorClass: undefined,
115
+
116
+ // 🍂option circleEditorClass: class = CircleEditor
117
+ // Class to be used as Circle editor.
118
+ circleEditorClass: undefined,
119
+
120
+ // 🍂option lineGuideOptions: hash = {}
121
+ // Options to be passed to the line guides.
122
+ lineGuideOptions: {},
123
+
124
+ // 🍂option skipMiddleMarkers: boolean = false
125
+ // Set this to true if you don't want middle markers.
126
+ skipMiddleMarkers: false
127
+
128
+ },
129
+
130
+ initialize: function (map, options) {
131
+ L.setOptions(this, options);
132
+ this._lastZIndex = this.options.zIndex;
133
+ this.map = map;
134
+ this.editLayer = this.createEditLayer();
135
+ this.featuresLayer = this.createFeaturesLayer();
136
+ this.forwardLineGuide = this.createLineGuide();
137
+ this.backwardLineGuide = this.createLineGuide();
138
+ },
139
+
140
+ fireAndForward: function (type, e) {
141
+ e = e || {};
142
+ e.editTools = this;
143
+ this.fire(type, e);
144
+ this.map.fire(type, e);
145
+ },
146
+
147
+ createLineGuide: function () {
148
+ var options = L.extend({dashArray: '5,10', weight: 1, interactive: false}, this.options.lineGuideOptions);
149
+ return L.polyline([], options);
150
+ },
151
+
152
+ createVertexIcon: function (options) {
153
+ return L.Browser.touch ? new L.Editable.TouchVertexIcon(options) : new L.Editable.VertexIcon(options);
154
+ },
155
+
156
+ createEditLayer: function () {
157
+ return this.options.editLayer || new L.LayerGroup().addTo(this.map);
158
+ },
159
+
160
+ createFeaturesLayer: function () {
161
+ return this.options.featuresLayer || new L.LayerGroup().addTo(this.map);
162
+ },
163
+
164
+ moveForwardLineGuide: function (latlng) {
165
+ if (this.forwardLineGuide._latlngs.length) {
166
+ this.forwardLineGuide._latlngs[1] = latlng;
167
+ this.forwardLineGuide._bounds.extend(latlng);
168
+ this.forwardLineGuide.redraw();
169
+ }
170
+ },
171
+
172
+ moveBackwardLineGuide: function (latlng) {
173
+ if (this.backwardLineGuide._latlngs.length) {
174
+ this.backwardLineGuide._latlngs[1] = latlng;
175
+ this.backwardLineGuide._bounds.extend(latlng);
176
+ this.backwardLineGuide.redraw();
177
+ }
178
+ },
179
+
180
+ anchorForwardLineGuide: function (latlng) {
181
+ this.forwardLineGuide._latlngs[0] = latlng;
182
+ this.forwardLineGuide._bounds.extend(latlng);
183
+ this.forwardLineGuide.redraw();
184
+ },
185
+
186
+ anchorBackwardLineGuide: function (latlng) {
187
+ this.backwardLineGuide._latlngs[0] = latlng;
188
+ this.backwardLineGuide._bounds.extend(latlng);
189
+ this.backwardLineGuide.redraw();
190
+ },
191
+
192
+ attachForwardLineGuide: function () {
193
+ this.editLayer.addLayer(this.forwardLineGuide);
194
+ },
195
+
196
+ attachBackwardLineGuide: function () {
197
+ this.editLayer.addLayer(this.backwardLineGuide);
198
+ },
199
+
200
+ detachForwardLineGuide: function () {
201
+ this.forwardLineGuide.setLatLngs([]);
202
+ this.editLayer.removeLayer(this.forwardLineGuide);
203
+ },
204
+
205
+ detachBackwardLineGuide: function () {
206
+ this.backwardLineGuide.setLatLngs([]);
207
+ this.editLayer.removeLayer(this.backwardLineGuide);
208
+ },
209
+
210
+ blockEvents: function () {
211
+ // Hack: force map not to listen to other layers events while drawing.
212
+ if (!this._oldTargets) {
213
+ this._oldTargets = this.map._targets;
214
+ this.map._targets = {};
215
+ }
216
+ },
217
+
218
+ unblockEvents: function () {
219
+ if (this._oldTargets) {
220
+ // Reset, but keep targets created while drawing.
221
+ this.map._targets = L.extend(this.map._targets, this._oldTargets);
222
+ delete this._oldTargets;
223
+ }
224
+ },
225
+
226
+ registerForDrawing: function (editor) {
227
+ if (this._drawingEditor) this.unregisterForDrawing(this._drawingEditor);
228
+ this.blockEvents();
229
+ editor.reset(); // Make sure editor tools still receive events.
230
+ this._drawingEditor = editor;
231
+ this.map.on('mousemove touchmove', editor.onDrawingMouseMove, editor);
232
+ this.map.on('mousedown', this.onMousedown, this);
233
+ this.map.on('mouseup', this.onMouseup, this);
234
+ L.DomUtil.addClass(this.map._container, this.options.drawingCSSClass);
235
+ this.defaultMapCursor = this.map._container.style.cursor;
236
+ this.map._container.style.cursor = this.options.drawingCursor;
237
+ },
238
+
239
+ unregisterForDrawing: function (editor) {
240
+ this.unblockEvents();
241
+ L.DomUtil.removeClass(this.map._container, this.options.drawingCSSClass);
242
+ this.map._container.style.cursor = this.defaultMapCursor;
243
+ editor = editor || this._drawingEditor;
244
+ if (!editor) return;
245
+ this.map.off('mousemove touchmove', editor.onDrawingMouseMove, editor);
246
+ this.map.off('mousedown', this.onMousedown, this);
247
+ this.map.off('mouseup', this.onMouseup, this);
248
+ if (editor !== this._drawingEditor) return;
249
+ delete this._drawingEditor;
250
+ if (editor._drawing) editor.cancelDrawing();
251
+ },
252
+
253
+ onMousedown: function (e) {
254
+ this._mouseDown = e;
255
+ this._drawingEditor.onDrawingMouseDown(e);
256
+ },
257
+
258
+ onMouseup: function (e) {
259
+ if (this._mouseDown) {
260
+ var editor = this._drawingEditor,
261
+ mouseDown = this._mouseDown;
262
+ this._mouseDown = null;
263
+ editor.onDrawingMouseUp(e);
264
+ if (this._drawingEditor !== editor) return; // onDrawingMouseUp may call unregisterFromDrawing.
265
+ var origin = L.point(mouseDown.originalEvent.clientX, mouseDown.originalEvent.clientY);
266
+ var distance = L.point(e.originalEvent.clientX, e.originalEvent.clientY).distanceTo(origin);
267
+ if (Math.abs(distance) < 9 * (window.devicePixelRatio || 1)) this._drawingEditor.onDrawingClick(e);
268
+ }
269
+ },
270
+
271
+ // 🍂section Public methods
272
+ // You will generally access them by the `map.editTools`
273
+ // instance:
274
+ //
275
+ // `map.editTools.startPolyline();`
276
+
277
+ // 🍂method drawing(): boolean
278
+ // Return true if any drawing action is ongoing.
279
+ drawing: function () {
280
+ return this._drawingEditor && this._drawingEditor.drawing();
281
+ },
282
+
283
+ // 🍂method stopDrawing()
284
+ // When you need to stop any ongoing drawing, without needing to know which editor is active.
285
+ stopDrawing: function () {
286
+ this.unregisterForDrawing();
287
+ },
288
+
289
+ // 🍂method commitDrawing()
290
+ // When you need to commit any ongoing drawing, without needing to know which editor is active.
291
+ commitDrawing: function (e) {
292
+ if (!this._drawingEditor) return;
293
+ this._drawingEditor.commitDrawing(e);
294
+ },
295
+
296
+ connectCreatedToMap: function (layer) {
297
+ return this.featuresLayer.addLayer(layer);
298
+ },
299
+
300
+ // 🍂method startPolyline(latlng: L.LatLng, options: hash): L.Polyline
301
+ // Start drawing a Polyline. If `latlng` is given, a first point will be added. In any case, continuing on user click.
302
+ // If `options` is given, it will be passed to the Polyline class constructor.
303
+ startPolyline: function (latlng, options) {
304
+ var line = this.createPolyline([], options);
305
+ line.enableEdit(this.map).newShape(latlng);
306
+ return line;
307
+ },
308
+
309
+ // 🍂method startPolygon(latlng: L.LatLng, options: hash): L.Polygon
310
+ // Start drawing a Polygon. If `latlng` is given, a first point will be added. In any case, continuing on user click.
311
+ // If `options` is given, it will be passed to the Polygon class constructor.
312
+ startPolygon: function (latlng, options) {
313
+ var polygon = this.createPolygon([], options);
314
+ polygon.enableEdit(this.map).newShape(latlng);
315
+ return polygon;
316
+ },
317
+
318
+ // 🍂method startMarker(latlng: L.LatLng, options: hash): L.Marker
319
+ // Start adding a Marker. If `latlng` is given, the Marker will be shown first at this point.
320
+ // In any case, it will follow the user mouse, and will have a final `latlng` on next click (or touch).
321
+ // If `options` is given, it will be passed to the Marker class constructor.
322
+ startMarker: function (latlng, options) {
323
+ latlng = latlng || this.map.getCenter().clone();
324
+ var marker = this.createMarker(latlng, options);
325
+ marker.enableEdit(this.map).startDrawing();
326
+ return marker;
327
+ },
328
+
329
+ // 🍂method startRectangle(latlng: L.LatLng, options: hash): L.Rectangle
330
+ // Start drawing a Rectangle. If `latlng` is given, the Rectangle anchor will be added. In any case, continuing on user drag.
331
+ // If `options` is given, it will be passed to the Rectangle class constructor.
332
+ startRectangle: function(latlng, options) {
333
+ var corner = latlng || L.latLng([0, 0]);
334
+ var bounds = new L.LatLngBounds(corner, corner);
335
+ var rectangle = this.createRectangle(bounds, options);
336
+ rectangle.enableEdit(this.map).startDrawing();
337
+ return rectangle;
338
+ },
339
+
340
+ // 🍂method startCircle(latlng: L.LatLng, options: hash): L.Circle
341
+ // Start drawing a Circle. If `latlng` is given, the Circle anchor will be added. In any case, continuing on user drag.
342
+ // If `options` is given, it will be passed to the Circle class constructor.
343
+ startCircle: function (latlng, options) {
344
+ latlng = latlng || this.map.getCenter().clone();
345
+ var circle = this.createCircle(latlng, options);
346
+ circle.enableEdit(this.map).startDrawing();
347
+ return circle;
348
+ },
349
+
350
+ startHole: function (editor, latlng) {
351
+ editor.newHole(latlng);
352
+ },
353
+
354
+ createLayer: function (klass, latlngs, options) {
355
+ options = L.Util.extend({editOptions: {editTools: this}}, options);
356
+ var layer = new klass(latlngs, options);
357
+ // 🍂namespace Editable
358
+ // 🍂event editable:created: LayerEvent
359
+ // Fired when a new feature (Marker, Polyline…) is created.
360
+ this.fireAndForward('editable:created', {layer: layer});
361
+ return layer;
362
+ },
363
+
364
+ createPolyline: function (latlngs, options) {
365
+ return this.createLayer(options && options.polylineClass || this.options.polylineClass, latlngs, options);
366
+ },
367
+
368
+ createPolygon: function (latlngs, options) {
369
+ return this.createLayer(options && options.polygonClass || this.options.polygonClass, latlngs, options);
370
+ },
371
+
372
+ createMarker: function (latlng, options) {
373
+ return this.createLayer(options && options.markerClass || this.options.markerClass, latlng, options);
374
+ },
375
+
376
+ createRectangle: function (bounds, options) {
377
+ return this.createLayer(options && options.rectangleClass || this.options.rectangleClass, bounds, options);
378
+ },
379
+
380
+ createCircle: function (latlng, options) {
381
+ return this.createLayer(options && options.circleClass || this.options.circleClass, latlng, options);
382
+ }
383
+
384
+ });
385
+
386
+ L.extend(L.Editable, {
387
+
388
+ makeCancellable: function (e) {
389
+ e.cancel = function () {
390
+ e._cancelled = true;
391
+ };
392
+ }
393
+
394
+ });
395
+
396
+ // 🍂namespace Map; 🍂class Map
397
+ // Leaflet.Editable add options and events to the `L.Map` object.
398
+ // See `Editable` events for the list of events fired on the Map.
399
+ // 🍂example
400
+ //
401
+ // ```js
402
+ // var map = L.map('map', {
403
+ // editable: true,
404
+ // editOptions: {
405
+ // …
406
+ // }
407
+ // });
408
+ // ```
409
+ // 🍂section Editable Map Options
410
+ L.Map.mergeOptions({
411
+
412
+ // 🍂namespace Map
413
+ // 🍂section Map Options
414
+ // 🍂option editToolsClass: class = L.Editable
415
+ // Class to be used as vertex, for path editing.
416
+ editToolsClass: L.Editable,
417
+
418
+ // 🍂option editable: boolean = false
419
+ // Whether to create a L.Editable instance at map init.
420
+ editable: false,
421
+
422
+ // 🍂option editOptions: hash = {}
423
+ // Options to pass to L.Editable when instanciating.
424
+ editOptions: {}
425
+
426
+ });
427
+
428
+ L.Map.addInitHook(function () {
429
+
430
+ this.whenReady(function () {
431
+ if (this.options.editable) {
432
+ this.editTools = new this.options.editToolsClass(this, this.options.editOptions);
433
+ }
434
+ });
435
+
436
+ });
437
+
438
+ L.Editable.VertexIcon = L.DivIcon.extend({
439
+
440
+ options: {
441
+ iconSize: new L.Point(8, 8)
442
+ }
443
+
444
+ });
445
+
446
+ L.Editable.TouchVertexIcon = L.Editable.VertexIcon.extend({
447
+
448
+ options: {
449
+ iconSize: new L.Point(20, 20)
450
+ }
451
+
452
+ });
453
+
454
+
455
+ // 🍂namespace Editable; 🍂class VertexMarker; Handler for dragging path vertices.
456
+ L.Editable.VertexMarker = L.Marker.extend({
457
+
458
+ options: {
459
+ draggable: true,
460
+ className: 'leaflet-div-icon leaflet-vertex-icon'
461
+ },
462
+
463
+
464
+ // 🍂section Public methods
465
+ // The marker used to handle path vertex. You will usually interact with a `VertexMarker`
466
+ // instance when listening for events like `editable:vertex:ctrlclick`.
467
+
468
+ initialize: function (latlng, latlngs, editor, options) {
469
+ // We don't use this._latlng, because on drag Leaflet replace it while
470
+ // we want to keep reference.
471
+ this.latlng = latlng;
472
+ this.latlngs = latlngs;
473
+ this.editor = editor;
474
+ L.Marker.prototype.initialize.call(this, latlng, options);
475
+ this.options.icon = this.editor.tools.createVertexIcon({className: this.options.className});
476
+ this.latlng.__vertex = this;
477
+ this.editor.editLayer.addLayer(this);
478
+ this.setZIndexOffset(editor.tools._lastZIndex + 1);
479
+ },
480
+
481
+ onAdd: function (map) {
482
+ L.Marker.prototype.onAdd.call(this, map);
483
+ this.on('drag', this.onDrag);
484
+ this.on('dragstart', this.onDragStart);
485
+ this.on('dragend', this.onDragEnd);
486
+ this.on('mouseup', this.onMouseup);
487
+ this.on('click', this.onClick);
488
+ this.on('contextmenu', this.onContextMenu);
489
+ this.on('mousedown touchstart', this.onMouseDown);
490
+ this.addMiddleMarkers();
491
+ },
492
+
493
+ onRemove: function (map) {
494
+ if (this.middleMarker) this.middleMarker.delete();
495
+ delete this.latlng.__vertex;
496
+ this.off('drag', this.onDrag);
497
+ this.off('dragstart', this.onDragStart);
498
+ this.off('dragend', this.onDragEnd);
499
+ this.off('mouseup', this.onMouseup);
500
+ this.off('click', this.onClick);
501
+ this.off('contextmenu', this.onContextMenu);
502
+ this.off('mousedown touchstart', this.onMouseDown);
503
+ L.Marker.prototype.onRemove.call(this, map);
504
+ },
505
+
506
+ onDrag: function (e) {
507
+ e.vertex = this;
508
+ this.editor.onVertexMarkerDrag(e);
509
+ var iconPos = L.DomUtil.getPosition(this._icon),
510
+ latlng = this._map.layerPointToLatLng(iconPos);
511
+ this.latlng.update(latlng);
512
+ this._latlng = this.latlng; // Push back to Leaflet our reference.
513
+ this.editor.refresh();
514
+ if (this.middleMarker) this.middleMarker.updateLatLng();
515
+ var next = this.getNext();
516
+ if (next && next.middleMarker) next.middleMarker.updateLatLng();
517
+ },
518
+
519
+ onDragStart: function (e) {
520
+ e.vertex = this;
521
+ this.editor.onVertexMarkerDragStart(e);
522
+ },
523
+
524
+ onDragEnd: function (e) {
525
+ e.vertex = this;
526
+ this.editor.onVertexMarkerDragEnd(e);
527
+ },
528
+
529
+ onClick: function (e) {
530
+ e.vertex = this;
531
+ this.editor.onVertexMarkerClick(e);
532
+ },
533
+
534
+ onMouseup: function (e) {
535
+ L.DomEvent.stop(e);
536
+ e.vertex = this;
537
+ this.editor.map.fire('mouseup', e);
538
+ },
539
+
540
+ onContextMenu: function (e) {
541
+ e.vertex = this;
542
+ this.editor.onVertexMarkerContextMenu(e);
543
+ },
544
+
545
+ onMouseDown: function (e) {
546
+ e.vertex = this;
547
+ this.editor.onVertexMarkerMouseDown(e);
548
+ },
549
+
550
+ // 🍂method delete()
551
+ // Delete a vertex and the related LatLng.
552
+ delete: function () {
553
+ var next = this.getNext(); // Compute before changing latlng
554
+ this.latlngs.splice(this.getIndex(), 1);
555
+ this.editor.editLayer.removeLayer(this);
556
+ this.editor.onVertexDeleted({latlng: this.latlng, vertex: this});
557
+ if (!this.latlngs.length) this.editor.deleteShape(this.latlngs);
558
+ if (next) next.resetMiddleMarker();
559
+ this.editor.refresh();
560
+ },
561
+
562
+ // 🍂method getIndex(): int
563
+ // Get the index of the current vertex among others of the same LatLngs group.
564
+ getIndex: function () {
565
+ return this.latlngs.indexOf(this.latlng);
566
+ },
567
+
568
+ // 🍂method getLastIndex(): int
569
+ // Get last vertex index of the LatLngs group of the current vertex.
570
+ getLastIndex: function () {
571
+ return this.latlngs.length - 1;
572
+ },
573
+
574
+ // 🍂method getPrevious(): VertexMarker
575
+ // Get the previous VertexMarker in the same LatLngs group.
576
+ getPrevious: function () {
577
+ if (this.latlngs.length < 2) return;
578
+ var index = this.getIndex(),
579
+ previousIndex = index - 1;
580
+ if (index === 0 && this.editor.CLOSED) previousIndex = this.getLastIndex();
581
+ var previous = this.latlngs[previousIndex];
582
+ if (previous) return previous.__vertex;
583
+ },
584
+
585
+ // 🍂method getNext(): VertexMarker
586
+ // Get the next VertexMarker in the same LatLngs group.
587
+ getNext: function () {
588
+ if (this.latlngs.length < 2) return;
589
+ var index = this.getIndex(),
590
+ nextIndex = index + 1;
591
+ if (index === this.getLastIndex() && this.editor.CLOSED) nextIndex = 0;
592
+ var next = this.latlngs[nextIndex];
593
+ if (next) return next.__vertex;
594
+ },
595
+
596
+ addMiddleMarker: function (previous) {
597
+ if (!this.editor.hasMiddleMarkers()) return;
598
+ previous = previous || this.getPrevious();
599
+ if (previous && !this.middleMarker) this.middleMarker = this.editor.addMiddleMarker(previous, this, this.latlngs, this.editor);
600
+ },
601
+
602
+ addMiddleMarkers: function () {
603
+ if (!this.editor.hasMiddleMarkers()) return;
604
+ var previous = this.getPrevious();
605
+ if (previous) this.addMiddleMarker(previous);
606
+ var next = this.getNext();
607
+ if (next) next.resetMiddleMarker();
608
+ },
609
+
610
+ resetMiddleMarker: function () {
611
+ if (this.middleMarker) this.middleMarker.delete();
612
+ this.addMiddleMarker();
613
+ },
614
+
615
+ // 🍂method split()
616
+ // Split the vertex LatLngs group at its index, if possible.
617
+ split: function () {
618
+ if (!this.editor.splitShape) return; // Only for PolylineEditor
619
+ this.editor.splitShape(this.latlngs, this.getIndex());
620
+ },
621
+
622
+ // 🍂method continue()
623
+ // Continue the vertex LatLngs from this vertex. Only active for first and last vertices of a Polyline.
624
+ continue: function () {
625
+ if (!this.editor.continueBackward) return; // Only for PolylineEditor
626
+ var index = this.getIndex();
627
+ if (index === 0) this.editor.continueBackward(this.latlngs);
628
+ else if (index === this.getLastIndex()) this.editor.continueForward(this.latlngs);
629
+ }
630
+
631
+ });
632
+
633
+ L.Editable.mergeOptions({
634
+
635
+ // 🍂namespace Editable
636
+ // 🍂option vertexMarkerClass: class = VertexMarker
637
+ // Class to be used as vertex, for path editing.
638
+ vertexMarkerClass: L.Editable.VertexMarker
639
+
640
+ });
641
+
642
+ L.Editable.MiddleMarker = L.Marker.extend({
643
+
644
+ options: {
645
+ opacity: 0.5,
646
+ className: 'leaflet-div-icon leaflet-middle-icon',
647
+ draggable: true
648
+ },
649
+
650
+ initialize: function (left, right, latlngs, editor, options) {
651
+ this.left = left;
652
+ this.right = right;
653
+ this.editor = editor;
654
+ this.latlngs = latlngs;
655
+ L.Marker.prototype.initialize.call(this, this.computeLatLng(), options);
656
+ this._opacity = this.options.opacity;
657
+ this.options.icon = this.editor.tools.createVertexIcon({className: this.options.className});
658
+ this.editor.editLayer.addLayer(this);
659
+ this.setVisibility();
660
+ },
661
+
662
+ setVisibility: function () {
663
+ var leftPoint = this._map.latLngToContainerPoint(this.left.latlng),
664
+ rightPoint = this._map.latLngToContainerPoint(this.right.latlng),
665
+ size = L.point(this.options.icon.options.iconSize);
666
+ if (leftPoint.distanceTo(rightPoint) < size.x * 3) this.hide();
667
+ else this.show();
668
+ },
669
+
670
+ show: function () {
671
+ this.setOpacity(this._opacity);
672
+ },
673
+
674
+ hide: function () {
675
+ this.setOpacity(0);
676
+ },
677
+
678
+ updateLatLng: function () {
679
+ this.setLatLng(this.computeLatLng());
680
+ this.setVisibility();
681
+ },
682
+
683
+ computeLatLng: function () {
684
+ var leftPoint = this.editor.map.latLngToContainerPoint(this.left.latlng),
685
+ rightPoint = this.editor.map.latLngToContainerPoint(this.right.latlng),
686
+ y = (leftPoint.y + rightPoint.y) / 2,
687
+ x = (leftPoint.x + rightPoint.x) / 2;
688
+ return this.editor.map.containerPointToLatLng([x, y]);
689
+ },
690
+
691
+ onAdd: function (map) {
692
+ L.Marker.prototype.onAdd.call(this, map);
693
+ L.DomEvent.on(this._icon, 'mousedown touchstart', this.onMouseDown, this);
694
+ map.on('zoomend', this.setVisibility, this);
695
+ },
696
+
697
+ onRemove: function (map) {
698
+ delete this.right.middleMarker;
699
+ L.DomEvent.off(this._icon, 'mousedown touchstart', this.onMouseDown, this);
700
+ map.off('zoomend', this.setVisibility, this);
701
+ L.Marker.prototype.onRemove.call(this, map);
702
+ },
703
+
704
+ onMouseDown: function (e) {
705
+ var iconPos = L.DomUtil.getPosition(this._icon),
706
+ latlng = this.editor.map.layerPointToLatLng(iconPos);
707
+ e = {
708
+ originalEvent: e,
709
+ latlng: latlng
710
+ };
711
+ if (this.options.opacity === 0) return;
712
+ L.Editable.makeCancellable(e);
713
+ this.editor.onMiddleMarkerMouseDown(e);
714
+ if (e._cancelled) return;
715
+ this.latlngs.splice(this.index(), 0, e.latlng);
716
+ this.editor.refresh();
717
+ var icon = this._icon;
718
+ var marker = this.editor.addVertexMarker(e.latlng, this.latlngs);
719
+ /* Hack to workaround browser not firing touchend when element is no more on DOM */
720
+ var parent = marker._icon.parentNode;
721
+ parent.removeChild(marker._icon);
722
+ marker._icon = icon;
723
+ parent.appendChild(marker._icon);
724
+ marker._initIcon();
725
+ marker._initInteraction();
726
+ marker.setOpacity(1);
727
+ /* End hack */
728
+ // Transfer ongoing dragging to real marker
729
+ L.Draggable._dragging = false;
730
+ marker.dragging._draggable._onDown(e.originalEvent);
731
+ this.delete();
732
+ },
733
+
734
+ delete: function () {
735
+ this.editor.editLayer.removeLayer(this);
736
+ },
737
+
738
+ index: function () {
739
+ return this.latlngs.indexOf(this.right.latlng);
740
+ }
741
+
742
+ });
743
+
744
+ L.Editable.mergeOptions({
745
+
746
+ // 🍂namespace Editable
747
+ // 🍂option middleMarkerClass: class = VertexMarker
748
+ // Class to be used as middle vertex, pulled by the user to create a new point in the middle of a path.
749
+ middleMarkerClass: L.Editable.MiddleMarker
750
+
751
+ });
752
+
753
+ // 🍂namespace Editable; 🍂class BaseEditor; 🍂aka L.Editable.BaseEditor
754
+ // When editing a feature (Marker, Polyline…), an editor is attached to it. This
755
+ // editor basically knows how to handle the edition.
756
+ L.Editable.BaseEditor = L.Handler.extend({
757
+
758
+ initialize: function (map, feature, options) {
759
+ L.setOptions(this, options);
760
+ this.map = map;
761
+ this.feature = feature;
762
+ this.feature.editor = this;
763
+ this.editLayer = new L.LayerGroup();
764
+ this.tools = this.options.editTools || map.editTools;
765
+ },
766
+
767
+ // 🍂method enable(): this
768
+ // Set up the drawing tools for the feature to be editable.
769
+ addHooks: function () {
770
+ if (this.isConnected()) this.onFeatureAdd();
771
+ else this.feature.once('add', this.onFeatureAdd, this);
772
+ this.onEnable();
773
+ this.feature.on(this._getEvents(), this);
774
+ return;
775
+ },
776
+
777
+ // 🍂method disable(): this
778
+ // Remove the drawing tools for the feature.
779
+ removeHooks: function () {
780
+ this.feature.off(this._getEvents(), this);
781
+ if (this.feature.dragging) this.feature.dragging.disable();
782
+ this.editLayer.clearLayers();
783
+ this.tools.editLayer.removeLayer(this.editLayer);
784
+ this.onDisable();
785
+ if (this._drawing) this.cancelDrawing();
786
+ return;
787
+ },
788
+
789
+ // 🍂method drawing(): boolean
790
+ // Return true if any drawing action is ongoing with this editor.
791
+ drawing: function () {
792
+ return !!this._drawing;
793
+ },
794
+
795
+ reset: function () {},
796
+
797
+ onFeatureAdd: function () {
798
+ this.tools.editLayer.addLayer(this.editLayer);
799
+ if (this.feature.dragging) this.feature.dragging.enable();
800
+ },
801
+
802
+ hasMiddleMarkers: function () {
803
+ return !this.options.skipMiddleMarkers && !this.tools.options.skipMiddleMarkers;
804
+ },
805
+
806
+ fireAndForward: function (type, e) {
807
+ e = e || {};
808
+ e.layer = this.feature;
809
+ this.feature.fire(type, e);
810
+ this.tools.fireAndForward(type, e);
811
+ },
812
+
813
+ onEnable: function () {
814
+ // 🍂namespace Editable
815
+ // 🍂event editable:enable: Event
816
+ // Fired when an existing feature is ready to be edited.
817
+ this.fireAndForward('editable:enable');
818
+ },
819
+
820
+ onDisable: function () {
821
+ // 🍂namespace Editable
822
+ // 🍂event editable:disable: Event
823
+ // Fired when an existing feature is not ready anymore to be edited.
824
+ this.fireAndForward('editable:disable');
825
+ },
826
+
827
+ onEditing: function () {
828
+ // 🍂namespace Editable
829
+ // 🍂event editable:editing: Event
830
+ // Fired as soon as any change is made to the feature geometry.
831
+ this.fireAndForward('editable:editing');
832
+ },
833
+
834
+ onStartDrawing: function () {
835
+ // 🍂namespace Editable
836
+ // 🍂section Drawing events
837
+ // 🍂event editable:drawing:start: Event
838
+ // Fired when a feature is to be drawn.
839
+ this.fireAndForward('editable:drawing:start');
840
+ },
841
+
842
+ onEndDrawing: function () {
843
+ // 🍂namespace Editable
844
+ // 🍂section Drawing events
845
+ // 🍂event editable:drawing:end: Event
846
+ // Fired when a feature is not drawn anymore.
847
+ this.fireAndForward('editable:drawing:end');
848
+ },
849
+
850
+ onCancelDrawing: function () {
851
+ // 🍂namespace Editable
852
+ // 🍂section Drawing events
853
+ // 🍂event editable:drawing:cancel: Event
854
+ // Fired when user cancel drawing while a feature is being drawn.
855
+ this.fireAndForward('editable:drawing:cancel');
856
+ },
857
+
858
+ onCommitDrawing: function (e) {
859
+ // 🍂namespace Editable
860
+ // 🍂section Drawing events
861
+ // 🍂event editable:drawing:commit: Event
862
+ // Fired when user finish drawing a feature.
863
+ this.fireAndForward('editable:drawing:commit', e);
864
+ },
865
+
866
+ onDrawingMouseDown: function (e) {
867
+ // 🍂namespace Editable
868
+ // 🍂section Drawing events
869
+ // 🍂event editable:drawing:mousedown: Event
870
+ // Fired when user `mousedown` while drawing.
871
+ this.fireAndForward('editable:drawing:mousedown', e);
872
+ },
873
+
874
+ onDrawingMouseUp: function (e) {
875
+ // 🍂namespace Editable
876
+ // 🍂section Drawing events
877
+ // 🍂event editable:drawing:mouseup: Event
878
+ // Fired when user `mouseup` while drawing.
879
+ this.fireAndForward('editable:drawing:mouseup', e);
880
+ },
881
+
882
+ startDrawing: function () {
883
+ if (!this._drawing) this._drawing = L.Editable.FORWARD;
884
+ this.tools.registerForDrawing(this);
885
+ this.onStartDrawing();
886
+ },
887
+
888
+ commitDrawing: function (e) {
889
+ this.onCommitDrawing(e);
890
+ this.endDrawing();
891
+ },
892
+
893
+ cancelDrawing: function () {
894
+ // If called during a vertex drag, the vertex will be removed before
895
+ // the mouseup fires on it. This is a workaround. Maybe better fix is
896
+ // To have L.Draggable reset it's status on disable (Leaflet side).
897
+ L.Draggable._dragging = false;
898
+ this.onCancelDrawing();
899
+ this.endDrawing();
900
+ },
901
+
902
+ endDrawing: function () {
903
+ this._drawing = false;
904
+ this.tools.unregisterForDrawing(this);
905
+ this.onEndDrawing();
906
+ },
907
+
908
+ onDrawingClick: function (e) {
909
+ if (!this.drawing()) return;
910
+ L.Editable.makeCancellable(e);
911
+ // 🍂namespace Editable
912
+ // 🍂section Drawing events
913
+ // 🍂event editable:drawing:click: CancelableEvent
914
+ // Fired when user `click` while drawing, before any internal action is being processed.
915
+ this.fireAndForward('editable:drawing:click', e);
916
+ if (e._cancelled) return;
917
+ if (!this.isConnected()) this.connect(e);
918
+ this.processDrawingClick(e);
919
+ },
920
+
921
+ isConnected: function () {
922
+ return this.map.hasLayer(this.feature);
923
+ },
924
+
925
+ connect: function (e) {
926
+ this.tools.connectCreatedToMap(this.feature);
927
+ this.tools.editLayer.addLayer(this.editLayer);
928
+ },
929
+
930
+ onMove: function (e) {
931
+ // 🍂namespace Editable
932
+ // 🍂section Drawing events
933
+ // 🍂event editable:drawing:move: Event
934
+ // Fired when `move` mouse while drawing, while dragging a marker, and while dragging a vertex.
935
+ this.fireAndForward('editable:drawing:move', e);
936
+ },
937
+
938
+ onDrawingMouseMove: function (e) {
939
+ this.onMove(e);
940
+ },
941
+
942
+ _getEvents: function () {
943
+ return {
944
+ dragstart: this.onDragStart,
945
+ drag: this.onDrag,
946
+ dragend: this.onDragEnd,
947
+ remove: this.disable
948
+ };
949
+ },
950
+
951
+ onDragStart: function (e) {
952
+ this.onEditing();
953
+ // 🍂namespace Editable
954
+ // 🍂event editable:dragstart: Event
955
+ // Fired before a path feature is dragged.
956
+ this.fireAndForward('editable:dragstart', e);
957
+ },
958
+
959
+ onDrag: function (e) {
960
+ this.onMove(e);
961
+ // 🍂namespace Editable
962
+ // 🍂event editable:drag: Event
963
+ // Fired when a path feature is being dragged.
964
+ this.fireAndForward('editable:drag', e);
965
+ },
966
+
967
+ onDragEnd: function (e) {
968
+ // 🍂namespace Editable
969
+ // 🍂event editable:dragend: Event
970
+ // Fired after a path feature has been dragged.
971
+ this.fireAndForward('editable:dragend', e);
972
+ }
973
+
974
+ });
975
+
976
+ // 🍂namespace Editable; 🍂class MarkerEditor; 🍂aka L.Editable.MarkerEditor
977
+ // 🍂inherits BaseEditor
978
+ // Editor for Marker.
979
+ L.Editable.MarkerEditor = L.Editable.BaseEditor.extend({
980
+
981
+ onDrawingMouseMove: function (e) {
982
+ L.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e);
983
+ if (this._drawing) this.feature.setLatLng(e.latlng);
984
+ },
985
+
986
+ processDrawingClick: function (e) {
987
+ // 🍂namespace Editable
988
+ // 🍂section Drawing events
989
+ // 🍂event editable:drawing:clicked: Event
990
+ // Fired when user `click` while drawing, after all internal actions.
991
+ this.fireAndForward('editable:drawing:clicked', e);
992
+ this.commitDrawing(e);
993
+ },
994
+
995
+ connect: function (e) {
996
+ // On touch, the latlng has not been updated because there is
997
+ // no mousemove.
998
+ if (e) this.feature._latlng = e.latlng;
999
+ L.Editable.BaseEditor.prototype.connect.call(this, e);
1000
+ }
1001
+
1002
+ });
1003
+
1004
+ // 🍂namespace Editable; 🍂class PathEditor; 🍂aka L.Editable.PathEditor
1005
+ // 🍂inherits BaseEditor
1006
+ // Base class for all path editors.
1007
+ L.Editable.PathEditor = L.Editable.BaseEditor.extend({
1008
+
1009
+ CLOSED: false,
1010
+ MIN_VERTEX: 2,
1011
+
1012
+ addHooks: function () {
1013
+ L.Editable.BaseEditor.prototype.addHooks.call(this);
1014
+ if (this.feature) this.initVertexMarkers();
1015
+ return this;
1016
+ },
1017
+
1018
+ initVertexMarkers: function (latlngs) {
1019
+ if (!this.enabled()) return;
1020
+ latlngs = latlngs || this.getLatLngs();
1021
+ if (L.Polyline._flat(latlngs)) this.addVertexMarkers(latlngs);
1022
+ else for (var i = 0; i < latlngs.length; i++) this.initVertexMarkers(latlngs[i]);
1023
+ },
1024
+
1025
+ getLatLngs: function () {
1026
+ return this.feature.getLatLngs();
1027
+ },
1028
+
1029
+ // 🍂method reset()
1030
+ // Rebuild edit elements (Vertex, MiddleMarker, etc.).
1031
+ reset: function () {
1032
+ this.editLayer.clearLayers();
1033
+ this.initVertexMarkers();
1034
+ },
1035
+
1036
+ addVertexMarker: function (latlng, latlngs) {
1037
+ return new this.tools.options.vertexMarkerClass(latlng, latlngs, this);
1038
+ },
1039
+
1040
+ addVertexMarkers: function (latlngs) {
1041
+ for (var i = 0; i < latlngs.length; i++) {
1042
+ this.addVertexMarker(latlngs[i], latlngs);
1043
+ }
1044
+ },
1045
+
1046
+ refreshVertexMarkers: function (latlngs) {
1047
+ latlngs = latlngs || this.getDefaultLatLngs();
1048
+ for (var i = 0; i < latlngs.length; i++) {
1049
+ latlngs[i].__vertex.update();
1050
+ }
1051
+ },
1052
+
1053
+ addMiddleMarker: function (left, right, latlngs) {
1054
+ return new this.tools.options.middleMarkerClass(left, right, latlngs, this);
1055
+ },
1056
+
1057
+ onVertexMarkerClick: function (e) {
1058
+ L.Editable.makeCancellable(e);
1059
+ // 🍂namespace Editable
1060
+ // 🍂section Vertex events
1061
+ // 🍂event editable:vertex:click: CancelableVertexEvent
1062
+ // Fired when a `click` is issued on a vertex, before any internal action is being processed.
1063
+ this.fireAndForward('editable:vertex:click', e);
1064
+ if (e._cancelled) return;
1065
+ if (this.tools.drawing() && this.tools._drawingEditor !== this) return;
1066
+ var index = e.vertex.getIndex(), commit;
1067
+ if (e.originalEvent.ctrlKey) {
1068
+ this.onVertexMarkerCtrlClick(e);
1069
+ } else if (e.originalEvent.altKey) {
1070
+ this.onVertexMarkerAltClick(e);
1071
+ } else if (e.originalEvent.shiftKey) {
1072
+ this.onVertexMarkerShiftClick(e);
1073
+ } else if (e.originalEvent.metaKey) {
1074
+ this.onVertexMarkerMetaKeyClick(e);
1075
+ } else if (index === e.vertex.getLastIndex() && this._drawing === L.Editable.FORWARD) {
1076
+ if (index >= this.MIN_VERTEX - 1) commit = true;
1077
+ } else if (index === 0 && this._drawing === L.Editable.BACKWARD && this._drawnLatLngs.length >= this.MIN_VERTEX) {
1078
+ commit = true;
1079
+ } else if (index === 0 && this._drawing === L.Editable.FORWARD && this._drawnLatLngs.length >= this.MIN_VERTEX && this.CLOSED) {
1080
+ commit = true; // Allow to close on first point also for polygons
1081
+ } else {
1082
+ this.onVertexRawMarkerClick(e);
1083
+ }
1084
+ // 🍂namespace Editable
1085
+ // 🍂section Vertex events
1086
+ // 🍂event editable:vertex:clicked: VertexEvent
1087
+ // Fired when a `click` is issued on a vertex, after all internal actions.
1088
+ this.fireAndForward('editable:vertex:clicked', e);
1089
+ if (commit) this.commitDrawing(e);
1090
+ },
1091
+
1092
+ onVertexRawMarkerClick: function (e) {
1093
+ // 🍂namespace Editable
1094
+ // 🍂section Vertex events
1095
+ // 🍂event editable:vertex:rawclick: CancelableVertexEvent
1096
+ // Fired when a `click` is issued on a vertex without any special key and without being in drawing mode.
1097
+ this.fireAndForward('editable:vertex:rawclick', e);
1098
+ if (e._cancelled) return;
1099
+ if (!this.vertexCanBeDeleted(e.vertex)) return;
1100
+ e.vertex.delete();
1101
+ },
1102
+
1103
+ vertexCanBeDeleted: function (vertex) {
1104
+ return vertex.latlngs.length > this.MIN_VERTEX;
1105
+ },
1106
+
1107
+ onVertexDeleted: function (e) {
1108
+ // 🍂namespace Editable
1109
+ // 🍂section Vertex events
1110
+ // 🍂event editable:vertex:deleted: VertexEvent
1111
+ // Fired after a vertex has been deleted by user.
1112
+ this.fireAndForward('editable:vertex:deleted', e);
1113
+ },
1114
+
1115
+ onVertexMarkerCtrlClick: function (e) {
1116
+ // 🍂namespace Editable
1117
+ // 🍂section Vertex events
1118
+ // 🍂event editable:vertex:ctrlclick: VertexEvent
1119
+ // Fired when a `click` with `ctrlKey` is issued on a vertex.
1120
+ this.fireAndForward('editable:vertex:ctrlclick', e);
1121
+ },
1122
+
1123
+ onVertexMarkerShiftClick: function (e) {
1124
+ // 🍂namespace Editable
1125
+ // 🍂section Vertex events
1126
+ // 🍂event editable:vertex:shiftclick: VertexEvent
1127
+ // Fired when a `click` with `shiftKey` is issued on a vertex.
1128
+ this.fireAndForward('editable:vertex:shiftclick', e);
1129
+ },
1130
+
1131
+ onVertexMarkerMetaKeyClick: function (e) {
1132
+ // 🍂namespace Editable
1133
+ // 🍂section Vertex events
1134
+ // 🍂event editable:vertex:metakeyclick: VertexEvent
1135
+ // Fired when a `click` with `metaKey` is issued on a vertex.
1136
+ this.fireAndForward('editable:vertex:metakeyclick', e);
1137
+ },
1138
+
1139
+ onVertexMarkerAltClick: function (e) {
1140
+ // 🍂namespace Editable
1141
+ // 🍂section Vertex events
1142
+ // 🍂event editable:vertex:altclick: VertexEvent
1143
+ // Fired when a `click` with `altKey` is issued on a vertex.
1144
+ this.fireAndForward('editable:vertex:altclick', e);
1145
+ },
1146
+
1147
+ onVertexMarkerContextMenu: function (e) {
1148
+ // 🍂namespace Editable
1149
+ // 🍂section Vertex events
1150
+ // 🍂event editable:vertex:contextmenu: VertexEvent
1151
+ // Fired when a `contextmenu` is issued on a vertex.
1152
+ this.fireAndForward('editable:vertex:contextmenu', e);
1153
+ },
1154
+
1155
+ onVertexMarkerMouseDown: function (e) {
1156
+ // 🍂namespace Editable
1157
+ // 🍂section Vertex events
1158
+ // 🍂event editable:vertex:mousedown: VertexEvent
1159
+ // Fired when user `mousedown` a vertex.
1160
+ this.fireAndForward('editable:vertex:mousedown', e);
1161
+ },
1162
+
1163
+ onMiddleMarkerMouseDown: function (e) {
1164
+ // 🍂namespace Editable
1165
+ // 🍂section MiddleMarker events
1166
+ // 🍂event editable:middlemarker:mousedown: VertexEvent
1167
+ // Fired when user `mousedown` a middle marker.
1168
+ this.fireAndForward('editable:middlemarker:mousedown', e);
1169
+ },
1170
+
1171
+ onVertexMarkerDrag: function (e) {
1172
+ this.onMove(e);
1173
+ if (this.feature._bounds) this.extendBounds(e);
1174
+ // 🍂namespace Editable
1175
+ // 🍂section Vertex events
1176
+ // 🍂event editable:vertex:drag: VertexEvent
1177
+ // Fired when a vertex is dragged by user.
1178
+ this.fireAndForward('editable:vertex:drag', e);
1179
+ },
1180
+
1181
+ onVertexMarkerDragStart: function (e) {
1182
+ // 🍂namespace Editable
1183
+ // 🍂section Vertex events
1184
+ // 🍂event editable:vertex:dragstart: VertexEvent
1185
+ // Fired before a vertex is dragged by user.
1186
+ this.fireAndForward('editable:vertex:dragstart', e);
1187
+ },
1188
+
1189
+ onVertexMarkerDragEnd: function (e) {
1190
+ // 🍂namespace Editable
1191
+ // 🍂section Vertex events
1192
+ // 🍂event editable:vertex:dragend: VertexEvent
1193
+ // Fired after a vertex is dragged by user.
1194
+ this.fireAndForward('editable:vertex:dragend', e);
1195
+ },
1196
+
1197
+ setDrawnLatLngs: function (latlngs) {
1198
+ this._drawnLatLngs = latlngs || this.getDefaultLatLngs();
1199
+ },
1200
+
1201
+ startDrawing: function () {
1202
+ if (!this._drawnLatLngs) this.setDrawnLatLngs();
1203
+ L.Editable.BaseEditor.prototype.startDrawing.call(this);
1204
+ },
1205
+
1206
+ startDrawingForward: function () {
1207
+ this.startDrawing();
1208
+ },
1209
+
1210
+ endDrawing: function () {
1211
+ this.tools.detachForwardLineGuide();
1212
+ this.tools.detachBackwardLineGuide();
1213
+ if (this._drawnLatLngs && this._drawnLatLngs.length < this.MIN_VERTEX) this.deleteShape(this._drawnLatLngs);
1214
+ L.Editable.BaseEditor.prototype.endDrawing.call(this);
1215
+ delete this._drawnLatLngs;
1216
+ },
1217
+
1218
+ addLatLng: function (latlng) {
1219
+ if (this._drawing === L.Editable.FORWARD) this._drawnLatLngs.push(latlng);
1220
+ else this._drawnLatLngs.unshift(latlng);
1221
+ this.feature._bounds.extend(latlng);
1222
+ this.addVertexMarker(latlng, this._drawnLatLngs);
1223
+ this.refresh();
1224
+ },
1225
+
1226
+ newPointForward: function (latlng) {
1227
+ this.addLatLng(latlng);
1228
+ this.tools.attachForwardLineGuide();
1229
+ this.tools.anchorForwardLineGuide(latlng);
1230
+ },
1231
+
1232
+ newPointBackward: function (latlng) {
1233
+ this.addLatLng(latlng);
1234
+ this.tools.anchorBackwardLineGuide(latlng);
1235
+ },
1236
+
1237
+ // 🍂namespace PathEditor
1238
+ // 🍂method push()
1239
+ // Programmatically add a point while drawing.
1240
+ push: function (latlng) {
1241
+ if (!latlng) return console.error('L.Editable.PathEditor.push expect a vaild latlng as parameter');
1242
+ if (this._drawing === L.Editable.FORWARD) this.newPointForward(latlng);
1243
+ else this.newPointBackward(latlng);
1244
+ },
1245
+
1246
+ removeLatLng: function (latlng) {
1247
+ latlng.__vertex.delete();
1248
+ this.refresh();
1249
+ },
1250
+
1251
+ // 🍂method pop(): L.LatLng or null
1252
+ // Programmatically remove last point (if any) while drawing.
1253
+ pop: function () {
1254
+ if (this._drawnLatLngs.length <= 1) return;
1255
+ var latlng;
1256
+ if (this._drawing === L.Editable.FORWARD) latlng = this._drawnLatLngs[this._drawnLatLngs.length - 1];
1257
+ else latlng = this._drawnLatLngs[0];
1258
+ this.removeLatLng(latlng);
1259
+ if (this._drawing === L.Editable.FORWARD) this.tools.anchorForwardLineGuide(this._drawnLatLngs[this._drawnLatLngs.length - 1]);
1260
+ else this.tools.anchorForwardLineGuide(this._drawnLatLngs[0]);
1261
+ return latlng;
1262
+ },
1263
+
1264
+ processDrawingClick: function (e) {
1265
+ if (e.vertex && e.vertex.editor === this) return;
1266
+ if (this._drawing === L.Editable.FORWARD) this.newPointForward(e.latlng);
1267
+ else this.newPointBackward(e.latlng);
1268
+ this.fireAndForward('editable:drawing:clicked', e);
1269
+ },
1270
+
1271
+ onDrawingMouseMove: function (e) {
1272
+ L.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e);
1273
+ if (this._drawing) {
1274
+ this.tools.moveForwardLineGuide(e.latlng);
1275
+ this.tools.moveBackwardLineGuide(e.latlng);
1276
+ }
1277
+ },
1278
+
1279
+ refresh: function () {
1280
+ this.feature.redraw();
1281
+ this.onEditing();
1282
+ },
1283
+
1284
+ // 🍂namespace PathEditor
1285
+ // 🍂method newShape(latlng?: L.LatLng)
1286
+ // Add a new shape (Polyline, Polygon) in a multi, and setup up drawing tools to draw it;
1287
+ // if optional `latlng` is given, start a path at this point.
1288
+ newShape: function (latlng) {
1289
+ var shape = this.addNewEmptyShape();
1290
+ if (!shape) return;
1291
+ this.setDrawnLatLngs(shape[0] || shape); // Polygon or polyline
1292
+ this.startDrawingForward();
1293
+ // 🍂namespace Editable
1294
+ // 🍂section Shape events
1295
+ // 🍂event editable:shape:new: ShapeEvent
1296
+ // Fired when a new shape is created in a multi (Polygon or Polyline).
1297
+ this.fireAndForward('editable:shape:new', {shape: shape});
1298
+ if (latlng) this.newPointForward(latlng);
1299
+ },
1300
+
1301
+ deleteShape: function (shape, latlngs) {
1302
+ var e = {shape: shape};
1303
+ L.Editable.makeCancellable(e);
1304
+ // 🍂namespace Editable
1305
+ // 🍂section Shape events
1306
+ // 🍂event editable:shape:delete: CancelableShapeEvent
1307
+ // Fired before a new shape is deleted in a multi (Polygon or Polyline).
1308
+ this.fireAndForward('editable:shape:delete', e);
1309
+ if (e._cancelled) return;
1310
+ shape = this._deleteShape(shape, latlngs);
1311
+ if (this.ensureNotFlat) this.ensureNotFlat(); // Polygon.
1312
+ this.feature.setLatLngs(this.getLatLngs()); // Force bounds reset.
1313
+ this.refresh();
1314
+ this.reset();
1315
+ // 🍂namespace Editable
1316
+ // 🍂section Shape events
1317
+ // 🍂event editable:shape:deleted: ShapeEvent
1318
+ // Fired after a new shape is deleted in a multi (Polygon or Polyline).
1319
+ this.fireAndForward('editable:shape:deleted', {shape: shape});
1320
+ return shape;
1321
+ },
1322
+
1323
+ _deleteShape: function (shape, latlngs) {
1324
+ latlngs = latlngs || this.getLatLngs();
1325
+ if (!latlngs.length) return;
1326
+ var self = this,
1327
+ inplaceDelete = function (latlngs, shape) {
1328
+ // Called when deleting a flat latlngs
1329
+ shape = latlngs.splice(0, Number.MAX_VALUE);
1330
+ return shape;
1331
+ },
1332
+ spliceDelete = function (latlngs, shape) {
1333
+ // Called when removing a latlngs inside an array
1334
+ latlngs.splice(latlngs.indexOf(shape), 1);
1335
+ if (!latlngs.length) self._deleteShape(latlngs);
1336
+ return shape;
1337
+ };
1338
+ if (latlngs === shape) return inplaceDelete(latlngs, shape);
1339
+ for (var i = 0; i < latlngs.length; i++) {
1340
+ if (latlngs[i] === shape) return spliceDelete(latlngs, shape);
1341
+ else if (latlngs[i].indexOf(shape) !== -1) return spliceDelete(latlngs[i], shape);
1342
+ }
1343
+ },
1344
+
1345
+ // 🍂namespace PathEditor
1346
+ // 🍂method deleteShapeAt(latlng: L.LatLng): Array
1347
+ // Remove a path shape at the given `latlng`.
1348
+ deleteShapeAt: function (latlng) {
1349
+ var shape = this.feature.shapeAt(latlng);
1350
+ if (shape) return this.deleteShape(shape);
1351
+ },
1352
+
1353
+ // 🍂method appendShape(shape: Array)
1354
+ // Append a new shape to the Polygon or Polyline.
1355
+ appendShape: function (shape) {
1356
+ this.insertShape(shape);
1357
+ },
1358
+
1359
+ // 🍂method prependShape(shape: Array)
1360
+ // Prepend a new shape to the Polygon or Polyline.
1361
+ prependShape: function (shape) {
1362
+ this.insertShape(shape, 0);
1363
+ },
1364
+
1365
+ // 🍂method insertShape(shape: Array, index: int)
1366
+ // Insert a new shape to the Polygon or Polyline at given index (default is to append).
1367
+ insertShape: function (shape, index) {
1368
+ this.ensureMulti();
1369
+ shape = this.formatShape(shape);
1370
+ if (typeof index === 'undefined') index = this.feature._latlngs.length;
1371
+ this.feature._latlngs.splice(index, 0, shape);
1372
+ this.feature.redraw();
1373
+ if (this._enabled) this.reset();
1374
+ },
1375
+
1376
+ extendBounds: function (e) {
1377
+ this.feature._bounds.extend(e.vertex.latlng);
1378
+ },
1379
+
1380
+ onDragStart: function (e) {
1381
+ this.editLayer.clearLayers();
1382
+ L.Editable.BaseEditor.prototype.onDragStart.call(this, e);
1383
+ },
1384
+
1385
+ onDragEnd: function (e) {
1386
+ this.initVertexMarkers();
1387
+ L.Editable.BaseEditor.prototype.onDragEnd.call(this, e);
1388
+ }
1389
+
1390
+ });
1391
+
1392
+ // 🍂namespace Editable; 🍂class PolylineEditor; 🍂aka L.Editable.PolylineEditor
1393
+ // 🍂inherits PathEditor
1394
+ L.Editable.PolylineEditor = L.Editable.PathEditor.extend({
1395
+
1396
+ startDrawingBackward: function () {
1397
+ this._drawing = L.Editable.BACKWARD;
1398
+ this.startDrawing();
1399
+ },
1400
+
1401
+ // 🍂method continueBackward(latlngs?: Array)
1402
+ // Set up drawing tools to continue the line backward.
1403
+ continueBackward: function (latlngs) {
1404
+ if (this.drawing()) return;
1405
+ latlngs = latlngs || this.getDefaultLatLngs();
1406
+ this.setDrawnLatLngs(latlngs);
1407
+ if (latlngs.length > 0) {
1408
+ this.tools.attachBackwardLineGuide();
1409
+ this.tools.anchorBackwardLineGuide(latlngs[0]);
1410
+ }
1411
+ this.startDrawingBackward();
1412
+ },
1413
+
1414
+ // 🍂method continueForward(latlngs?: Array)
1415
+ // Set up drawing tools to continue the line forward.
1416
+ continueForward: function (latlngs) {
1417
+ if (this.drawing()) return;
1418
+ latlngs = latlngs || this.getDefaultLatLngs();
1419
+ this.setDrawnLatLngs(latlngs);
1420
+ if (latlngs.length > 0) {
1421
+ this.tools.attachForwardLineGuide();
1422
+ this.tools.anchorForwardLineGuide(latlngs[latlngs.length - 1]);
1423
+ }
1424
+ this.startDrawingForward();
1425
+ },
1426
+
1427
+ getDefaultLatLngs: function (latlngs) {
1428
+ latlngs = latlngs || this.feature._latlngs;
1429
+ if (!latlngs.length || latlngs[0] instanceof L.LatLng) return latlngs;
1430
+ else return this.getDefaultLatLngs(latlngs[0]);
1431
+ },
1432
+
1433
+ ensureMulti: function () {
1434
+ if (this.feature._latlngs.length && L.Polyline._flat(this.feature._latlngs)) {
1435
+ this.feature._latlngs = [this.feature._latlngs];
1436
+ }
1437
+ },
1438
+
1439
+ addNewEmptyShape: function () {
1440
+ if (this.feature._latlngs.length) {
1441
+ var shape = [];
1442
+ this.appendShape(shape);
1443
+ return shape;
1444
+ } else {
1445
+ return this.feature._latlngs;
1446
+ }
1447
+ },
1448
+
1449
+ formatShape: function (shape) {
1450
+ if (L.Polyline._flat(shape)) return shape;
1451
+ else if (shape[0]) return this.formatShape(shape[0]);
1452
+ },
1453
+
1454
+ // 🍂method splitShape(latlngs?: Array, index: int)
1455
+ // Split the given `latlngs` shape at index `index` and integrate new shape in instance `latlngs`.
1456
+ splitShape: function (shape, index) {
1457
+ if (!index || index >= shape.length - 1) return;
1458
+ this.ensureMulti();
1459
+ var shapeIndex = this.feature._latlngs.indexOf(shape);
1460
+ if (shapeIndex === -1) return;
1461
+ var first = shape.slice(0, index + 1),
1462
+ second = shape.slice(index);
1463
+ // We deal with reference, we don't want twice the same latlng around.
1464
+ second[0] = L.latLng(second[0].lat, second[0].lng, second[0].alt);
1465
+ this.feature._latlngs.splice(shapeIndex, 1, first, second);
1466
+ this.refresh();
1467
+ this.reset();
1468
+ }
1469
+
1470
+ });
1471
+
1472
+ // 🍂namespace Editable; 🍂class PolygonEditor; 🍂aka L.Editable.PolygonEditor
1473
+ // 🍂inherits PathEditor
1474
+ L.Editable.PolygonEditor = L.Editable.PathEditor.extend({
1475
+
1476
+ CLOSED: true,
1477
+ MIN_VERTEX: 3,
1478
+
1479
+ newPointForward: function (latlng) {
1480
+ L.Editable.PathEditor.prototype.newPointForward.call(this, latlng);
1481
+ if (!this.tools.backwardLineGuide._latlngs.length) this.tools.anchorBackwardLineGuide(latlng);
1482
+ if (this._drawnLatLngs.length === 2) this.tools.attachBackwardLineGuide();
1483
+ },
1484
+
1485
+ addNewEmptyHole: function (latlng) {
1486
+ this.ensureNotFlat();
1487
+ var latlngs = this.feature.shapeAt(latlng);
1488
+ if (!latlngs) return;
1489
+ var holes = [];
1490
+ latlngs.push(holes);
1491
+ return holes;
1492
+ },
1493
+
1494
+ // 🍂method newHole(latlng?: L.LatLng, index: int)
1495
+ // Set up drawing tools for creating a new hole on the Polygon. If the `latlng` param is given, a first point is created.
1496
+ newHole: function (latlng) {
1497
+ var holes = this.addNewEmptyHole(latlng);
1498
+ if (!holes) return;
1499
+ this.setDrawnLatLngs(holes);
1500
+ this.startDrawingForward();
1501
+ if (latlng) this.newPointForward(latlng);
1502
+ },
1503
+
1504
+ addNewEmptyShape: function () {
1505
+ if (this.feature._latlngs.length && this.feature._latlngs[0].length) {
1506
+ var shape = [];
1507
+ this.appendShape(shape);
1508
+ return shape;
1509
+ } else {
1510
+ return this.feature._latlngs;
1511
+ }
1512
+ },
1513
+
1514
+ ensureMulti: function () {
1515
+ if (this.feature._latlngs.length && L.Polyline._flat(this.feature._latlngs[0])) {
1516
+ this.feature._latlngs = [this.feature._latlngs];
1517
+ }
1518
+ },
1519
+
1520
+ ensureNotFlat: function () {
1521
+ if (!this.feature._latlngs.length || L.Polyline._flat(this.feature._latlngs)) this.feature._latlngs = [this.feature._latlngs];
1522
+ },
1523
+
1524
+ vertexCanBeDeleted: function (vertex) {
1525
+ var parent = this.feature.parentShape(vertex.latlngs),
1526
+ idx = L.Util.indexOf(parent, vertex.latlngs);
1527
+ if (idx > 0) return true; // Holes can be totally deleted without removing the layer itself.
1528
+ return L.Editable.PathEditor.prototype.vertexCanBeDeleted.call(this, vertex);
1529
+ },
1530
+
1531
+ getDefaultLatLngs: function () {
1532
+ if (!this.feature._latlngs.length) this.feature._latlngs.push([]);
1533
+ return this.feature._latlngs[0];
1534
+ },
1535
+
1536
+ formatShape: function (shape) {
1537
+ // [[1, 2], [3, 4]] => must be nested
1538
+ // [] => must be nested
1539
+ // [[]] => is already nested
1540
+ if (L.Polyline._flat(shape) && (!shape[0] || shape[0].length !== 0)) return [shape];
1541
+ else return shape;
1542
+ }
1543
+
1544
+ });
1545
+
1546
+ // 🍂namespace Editable; 🍂class RectangleEditor; 🍂aka L.Editable.RectangleEditor
1547
+ // 🍂inherits PathEditor
1548
+ L.Editable.RectangleEditor = L.Editable.PathEditor.extend({
1549
+
1550
+ CLOSED: true,
1551
+ MIN_VERTEX: 4,
1552
+
1553
+ options: {
1554
+ skipMiddleMarkers: true
1555
+ },
1556
+
1557
+ extendBounds: function (e) {
1558
+ var index = e.vertex.getIndex(),
1559
+ next = e.vertex.getNext(),
1560
+ previous = e.vertex.getPrevious(),
1561
+ oppositeIndex = (index + 2) % 4,
1562
+ opposite = e.vertex.latlngs[oppositeIndex],
1563
+ bounds = new L.LatLngBounds(e.latlng, opposite);
1564
+ // Update latlngs by hand to preserve order.
1565
+ previous.latlng.update([e.latlng.lat, opposite.lng]);
1566
+ next.latlng.update([opposite.lat, e.latlng.lng]);
1567
+ this.updateBounds(bounds);
1568
+ this.refreshVertexMarkers();
1569
+ },
1570
+
1571
+ onDrawingMouseDown: function (e) {
1572
+ L.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e);
1573
+ this.connect();
1574
+ var latlngs = this.getDefaultLatLngs();
1575
+ // L.Polygon._convertLatLngs removes last latlng if it equals first point,
1576
+ // which is the case here as all latlngs are [0, 0]
1577
+ if (latlngs.length === 3) latlngs.push(e.latlng);
1578
+ var bounds = new L.LatLngBounds(e.latlng, e.latlng);
1579
+ this.updateBounds(bounds);
1580
+ this.updateLatLngs(bounds);
1581
+ this.refresh();
1582
+ this.reset();
1583
+ // Stop dragging map.
1584
+ // L.Draggable has two workflows:
1585
+ // - mousedown => mousemove => mouseup
1586
+ // - touchstart => touchmove => touchend
1587
+ // Problem: L.Map.Tap does not allow us to listen to touchstart, so we only
1588
+ // can deal with mousedown, but then when in a touch device, we are dealing with
1589
+ // simulated events (actually simulated by L.Map.Tap), which are no more taken
1590
+ // into account by L.Draggable.
1591
+ // Ref.: https://github.com/Leaflet/Leaflet.Editable/issues/103
1592
+ e.originalEvent._simulated = false;
1593
+ this.map.dragging._draggable._onUp(e.originalEvent);
1594
+ // Now transfer ongoing drag action to the bottom right corner.
1595
+ // Should we refine which corne will handle the drag according to
1596
+ // drag direction?
1597
+ latlngs[3].__vertex.dragging._draggable._onDown(e.originalEvent);
1598
+ },
1599
+
1600
+ onDrawingMouseUp: function (e) {
1601
+ this.commitDrawing(e);
1602
+ e.originalEvent._simulated = false;
1603
+ L.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e);
1604
+ },
1605
+
1606
+ onDrawingMouseMove: function (e) {
1607
+ e.originalEvent._simulated = false;
1608
+ L.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e);
1609
+ },
1610
+
1611
+
1612
+ getDefaultLatLngs: function (latlngs) {
1613
+ return latlngs || this.feature._latlngs[0];
1614
+ },
1615
+
1616
+ updateBounds: function (bounds) {
1617
+ this.feature._bounds = bounds;
1618
+ },
1619
+
1620
+ updateLatLngs: function (bounds) {
1621
+ var latlngs = this.getDefaultLatLngs(),
1622
+ newLatlngs = this.feature._boundsToLatLngs(bounds);
1623
+ // Keep references.
1624
+ for (var i = 0; i < latlngs.length; i++) {
1625
+ latlngs[i].update(newLatlngs[i]);
1626
+ };
1627
+ }
1628
+
1629
+ });
1630
+
1631
+ // 🍂namespace Editable; 🍂class CircleEditor; 🍂aka L.Editable.CircleEditor
1632
+ // 🍂inherits PathEditor
1633
+ L.Editable.CircleEditor = L.Editable.PathEditor.extend({
1634
+
1635
+ MIN_VERTEX: 2,
1636
+
1637
+ options: {
1638
+ skipMiddleMarkers: true
1639
+ },
1640
+
1641
+ initialize: function (map, feature, options) {
1642
+ L.Editable.PathEditor.prototype.initialize.call(this, map, feature, options);
1643
+ this._resizeLatLng = this.computeResizeLatLng();
1644
+ },
1645
+
1646
+ computeResizeLatLng: function () {
1647
+ // While circle is not added to the map, _radius is not set.
1648
+ var delta = (this.feature._radius || this.feature._mRadius) * Math.cos(Math.PI / 4),
1649
+ point = this.map.project(this.feature._latlng);
1650
+ return this.map.unproject([point.x + delta, point.y - delta]);
1651
+ },
1652
+
1653
+ updateResizeLatLng: function () {
1654
+ this._resizeLatLng.update(this.computeResizeLatLng());
1655
+ this._resizeLatLng.__vertex.update();
1656
+ },
1657
+
1658
+ getLatLngs: function () {
1659
+ return [this.feature._latlng, this._resizeLatLng];
1660
+ },
1661
+
1662
+ getDefaultLatLngs: function () {
1663
+ return this.getLatLngs();
1664
+ },
1665
+
1666
+ onVertexMarkerDrag: function (e) {
1667
+ if (e.vertex.getIndex() === 1) this.resize(e);
1668
+ else this.updateResizeLatLng(e);
1669
+ L.Editable.PathEditor.prototype.onVertexMarkerDrag.call(this, e);
1670
+ },
1671
+
1672
+ resize: function (e) {
1673
+ var radius = this.feature._latlng.distanceTo(e.latlng)
1674
+ this.feature.setRadius(radius);
1675
+ },
1676
+
1677
+ onDrawingMouseDown: function (e) {
1678
+ L.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e);
1679
+ this._resizeLatLng.update(e.latlng);
1680
+ this.feature._latlng.update(e.latlng);
1681
+ this.connect();
1682
+ // Stop dragging map.
1683
+ e.originalEvent._simulated = false;
1684
+ this.map.dragging._draggable._onUp(e.originalEvent);
1685
+ // Now transfer ongoing drag action to the radius handler.
1686
+ this._resizeLatLng.__vertex.dragging._draggable._onDown(e.originalEvent);
1687
+ },
1688
+
1689
+ onDrawingMouseUp: function (e) {
1690
+ this.commitDrawing(e);
1691
+ e.originalEvent._simulated = false;
1692
+ L.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e);
1693
+ },
1694
+
1695
+ onDrawingMouseMove: function (e) {
1696
+ e.originalEvent._simulated = false;
1697
+ L.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e);
1698
+ },
1699
+
1700
+ onDrag: function (e) {
1701
+ L.Editable.PathEditor.prototype.onDrag.call(this, e);
1702
+ this.feature.dragging.updateLatLng(this._resizeLatLng);
1703
+ }
1704
+
1705
+ });
1706
+
1707
+ // 🍂namespace Editable; 🍂class EditableMixin
1708
+ // `EditableMixin` is included to `L.Polyline`, `L.Polygon`, `L.Rectangle`, `L.Circle`
1709
+ // and `L.Marker`. It adds some methods to them.
1710
+ // *When editing is enabled, the editor is accessible on the instance with the
1711
+ // `editor` property.*
1712
+ var EditableMixin = {
1713
+
1714
+ createEditor: function (map) {
1715
+ map = map || this._map;
1716
+ var tools = (this.options.editOptions || {}).editTools || map.editTools;
1717
+ if (!tools) throw Error('Unable to detect Editable instance.')
1718
+ var Klass = this.options.editorClass || this.getEditorClass(tools);
1719
+ return new Klass(map, this, this.options.editOptions);
1720
+ },
1721
+
1722
+ // 🍂method enableEdit(map?: L.Map): this.editor
1723
+ // Enable editing, by creating an editor if not existing, and then calling `enable` on it.
1724
+ enableEdit: function (map) {
1725
+ if (!this.editor) this.createEditor(map);
1726
+ this.editor.enable();
1727
+ return this.editor;
1728
+ },
1729
+
1730
+ // 🍂method editEnabled(): boolean
1731
+ // Return true if current instance has an editor attached, and this editor is enabled.
1732
+ editEnabled: function () {
1733
+ return this.editor && this.editor.enabled();
1734
+ },
1735
+
1736
+ // 🍂method disableEdit()
1737
+ // Disable editing, also remove the editor property reference.
1738
+ disableEdit: function () {
1739
+ if (this.editor) {
1740
+ this.editor.disable();
1741
+ delete this.editor;
1742
+ }
1743
+ },
1744
+
1745
+ // 🍂method toggleEdit()
1746
+ // Enable or disable editing, according to current status.
1747
+ toggleEdit: function () {
1748
+ if (this.editEnabled()) this.disableEdit();
1749
+ else this.enableEdit();
1750
+ },
1751
+
1752
+ _onEditableAdd: function () {
1753
+ if (this.editor) this.enableEdit();
1754
+ }
1755
+
1756
+ };
1757
+
1758
+ var PolylineMixin = {
1759
+
1760
+ getEditorClass: function (tools) {
1761
+ return (tools && tools.options.polylineEditorClass) ? tools.options.polylineEditorClass : L.Editable.PolylineEditor;
1762
+ },
1763
+
1764
+ shapeAt: function (latlng, latlngs) {
1765
+ // We can have those cases:
1766
+ // - latlngs are just a flat array of latlngs, use this
1767
+ // - latlngs is an array of arrays of latlngs, loop over
1768
+ var shape = null;
1769
+ latlngs = latlngs || this._latlngs;
1770
+ if (!latlngs.length) return shape;
1771
+ else if (L.Polyline._flat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs;
1772
+ else for (var i = 0; i < latlngs.length; i++) if (this.isInLatLngs(latlng, latlngs[i])) return latlngs[i];
1773
+ return shape;
1774
+ },
1775
+
1776
+ isInLatLngs: function (l, latlngs) {
1777
+ if (!latlngs) return false;
1778
+ var i, k, len, part = [], p,
1779
+ w = this._clickTolerance();
1780
+ this._projectLatlngs(latlngs, part, this._pxBounds);
1781
+ part = part[0];
1782
+ p = this._map.latLngToLayerPoint(l);
1783
+
1784
+ if (!this._pxBounds.contains(p)) { return false; }
1785
+ for (i = 1, len = part.length, k = 0; i < len; k = i++) {
1786
+
1787
+ if (L.LineUtil.pointToSegmentDistance(p, part[k], part[i]) <= w) {
1788
+ return true;
1789
+ }
1790
+ }
1791
+ return false;
1792
+ }
1793
+
1794
+ };
1795
+
1796
+ var PolygonMixin = {
1797
+
1798
+ getEditorClass: function (tools) {
1799
+ return (tools && tools.options.polygonEditorClass) ? tools.options.polygonEditorClass : L.Editable.PolygonEditor;
1800
+ },
1801
+
1802
+ shapeAt: function (latlng, latlngs) {
1803
+ // We can have those cases:
1804
+ // - latlngs are just a flat array of latlngs, use this
1805
+ // - latlngs is an array of arrays of latlngs, this is a simple polygon (maybe with holes), use the first
1806
+ // - latlngs is an array of arrays of arrays, this is a multi, loop over
1807
+ var shape = null;
1808
+ latlngs = latlngs || this._latlngs;
1809
+ if (!latlngs.length) return shape;
1810
+ else if (L.Polyline._flat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs;
1811
+ else if (L.Polyline._flat(latlngs[0]) && this.isInLatLngs(latlng, latlngs[0])) shape = latlngs;
1812
+ else for (var i = 0; i < latlngs.length; i++) if (this.isInLatLngs(latlng, latlngs[i][0])) return latlngs[i];
1813
+ return shape;
1814
+ },
1815
+
1816
+ isInLatLngs: function (l, latlngs) {
1817
+ var inside = false, l1, l2, j, k, len2;
1818
+
1819
+ for (j = 0, len2 = latlngs.length, k = len2 - 1; j < len2; k = j++) {
1820
+ l1 = latlngs[j];
1821
+ l2 = latlngs[k];
1822
+
1823
+ if (((l1.lat > l.lat) !== (l2.lat > l.lat)) &&
1824
+ (l.lng < (l2.lng - l1.lng) * (l.lat - l1.lat) / (l2.lat - l1.lat) + l1.lng)) {
1825
+ inside = !inside;
1826
+ }
1827
+ }
1828
+
1829
+ return inside;
1830
+ },
1831
+
1832
+ parentShape: function (shape, latlngs) {
1833
+ latlngs = latlngs || this._latlngs;
1834
+ if (!latlngs) return;
1835
+ var idx = L.Util.indexOf(latlngs, shape);
1836
+ if (idx !== -1) return latlngs;
1837
+ for (var i = 0; i < latlngs.length; i++) {
1838
+ idx = L.Util.indexOf(latlngs[i], shape);
1839
+ if (idx !== -1) return latlngs[i];
1840
+ }
1841
+ }
1842
+
1843
+ };
1844
+
1845
+
1846
+ var MarkerMixin = {
1847
+
1848
+ getEditorClass: function (tools) {
1849
+ return (tools && tools.options.markerEditorClass) ? tools.options.markerEditorClass : L.Editable.MarkerEditor;
1850
+ }
1851
+
1852
+ };
1853
+
1854
+ var RectangleMixin = {
1855
+
1856
+ getEditorClass: function (tools) {
1857
+ return (tools && tools.options.rectangleEditorClass) ? tools.options.rectangleEditorClass : L.Editable.RectangleEditor;
1858
+ }
1859
+
1860
+ };
1861
+
1862
+ var CircleMixin = {
1863
+
1864
+ getEditorClass: function (tools) {
1865
+ return (tools && tools.options.circleEditorClass) ? tools.options.circleEditorClass : L.Editable.CircleEditor;
1866
+ }
1867
+
1868
+ };
1869
+
1870
+ var keepEditable = function () {
1871
+ // Make sure you can remove/readd an editable layer.
1872
+ this.on('add', this._onEditableAdd);
1873
+ };
1874
+
1875
+
1876
+
1877
+ if (L.Polyline) {
1878
+ L.Polyline.include(EditableMixin);
1879
+ L.Polyline.include(PolylineMixin);
1880
+ L.Polyline.addInitHook(keepEditable);
1881
+ }
1882
+ if (L.Polygon) {
1883
+ L.Polygon.include(EditableMixin);
1884
+ L.Polygon.include(PolygonMixin);
1885
+ }
1886
+ if (L.Marker) {
1887
+ L.Marker.include(EditableMixin);
1888
+ L.Marker.include(MarkerMixin);
1889
+ L.Marker.addInitHook(keepEditable);
1890
+ }
1891
+ if (L.Rectangle) {
1892
+ L.Rectangle.include(EditableMixin);
1893
+ L.Rectangle.include(RectangleMixin);
1894
+ }
1895
+ if (L.Circle) {
1896
+ L.Circle.include(EditableMixin);
1897
+ L.Circle.include(CircleMixin);
1898
+ }
1899
+
1900
+ L.LatLng.prototype.update = function (latlng) {
1901
+ latlng = L.latLng(latlng);
1902
+ this.lat = latlng.lat;
1903
+ this.lng = latlng.lng;
1904
+ }
1905
+
1906
+ }, window));