diff --git a/app/assets/javascripts/channels/concurrent_editing.js b/app/assets/javascripts/channels/concurrent_editing.js
index f93e01e45..872022784 100644
--- a/app/assets/javascripts/channels/concurrent_editing.js
+++ b/app/assets/javascripts/channels/concurrent_editing.js
@@ -1,6 +1,10 @@
/* Handles all the frontend interactions with action cable and the server. */
-App.concurrent_editing = App.cable.subscriptions.create("ConcurrentEditingChannel", {
+App.concurrent_editing = App.cable.subscriptions.create(
+ {
+ channel: "ConcurrentEditingChannel",
+ mapSlug: window.location.href.split("/").pop()
+ }, {
connected: function() {
// Called when the subscription is ready for use on the server
},
@@ -11,7 +15,7 @@ App.concurrent_editing = App.cable.subscriptions.create("ConcurrentEditingChanne
received: function(data) {
// Called when there's incoming data on the websocket for this channel
- window.mapKnitter.synchronizeData(data.changes);
+ window.mapknitter.synchronizeData(data.changes);
},
speak: function(changes) {
@@ -20,7 +24,8 @@ App.concurrent_editing = App.cable.subscriptions.create("ConcurrentEditingChanne
* which is responsible for broadcasting the updated warpables
* to all the user's connected to the concurrent_editing channel. */
return this.perform("sync", {
- changes: changes
+ changes: changes,
+ map_slug: window.location.href.split("/").pop()
});
}
});
diff --git a/app/assets/javascripts/mapknitter/Map.js b/app/assets/javascripts/mapknitter/Map.js
index c7659757e..a7051a25a 100644
--- a/app/assets/javascripts/mapknitter/Map.js
+++ b/app/assets/javascripts/mapknitter/Map.js
@@ -405,11 +405,123 @@ MapKnitter.Map = MapKnitter.Class.extend({
corners [2] = y;
corners [3] = x;
- console.log(corners);
-
layer = layers.filter(l => l._url==warpable.srcmedium)[0];
- layer.setCorners(corners);
+
+ if(layer == null || layer == undefined) {
+ window.mapknitter.synchronizeNewAddedImage(warpable);
+ } else {
+ layer.setCorners(corners);
+ var index = layers.indexOf(layer);
+ if (index > -1) {
+ layers.splice(index, 1);
+ }
+ }
});
+
+ // remove images if deleted from any user's browser
+ layers.forEach(function(layer) {
+ edit = layer.editing
+ edit._removeToolbar();
+ edit.disable();
+ // remove from Leaflet map:
+ map.removeLayer(layer);
+ // remove from sidebar too:
+ $('#warpable-' + layer.warpable_id).remove();
+ });
+ },
+
+ synchronizeNewAddedImage: function(warpable) {
+ var wn = warpable.nodes;
+ bounds = [];
+
+ // only already-placed images:
+ if (wn.length > 0) {
+ var downloadEl = $('.img-download-' + warpable.id),
+ imgEl = $('#full-img-' + warpable.id);
+
+ downloadEl.click(function () {
+ downloadEl.html('');
+
+ imgEl[0].onload = function () {
+ var height = imgEl.height(),
+ width = imgEl.width(),
+ nw = map.latLngToContainerPoint(wn[0]),
+ ne = map.latLngToContainerPoint(wn[1]),
+ se = map.latLngToContainerPoint(wn[2]),
+ sw = map.latLngToContainerPoint(wn[3]),
+ offsetX = nw.x,
+ offsetY = nw.y,
+ displayedWidth = $('#warpable-img-' + warpable.id).width(),
+ ratio = width / displayedWidth;
+
+ nw.x -= offsetX;
+ ne.x -= offsetX;
+ se.x -= offsetX;
+ sw.x -= offsetX;
+
+ nw.y -= offsetY;
+ ne.y -= offsetY;
+ se.y -= offsetY;
+ sw.y -= offsetY;
+
+ warpWebGl(
+ 'full-img-' + warpable.id,
+ [0, 0, width, 0, width, height, 0, height],
+ [nw.x, nw.y, ne.x, ne.y, se.x, se.y, sw.x, sw.y],
+ true // trigger download
+ )
+
+ downloadEl.html('');
+ }
+
+ imgEl[0].src = $('.img-download-' + warpable.id).attr('data-image');
+ });
+
+ var corners = [
+ L.latLng(wn[0].lat, wn[0].lon),
+ L.latLng(wn[1].lat, wn[1].lon),
+ L.latLng(wn[3].lat, wn[3].lon),
+ L.latLng(wn[2].lat, wn[2].lon)
+ ];
+
+ var img = L.distortableImageOverlay(warpable.srcmedium, {
+ corners: corners,
+ mode: 'lock'
+ }).addTo(map);
+
+ var customExports = mapknitter.customExportAction();
+ var imgGroup = L.distortableCollection({
+ actions: [customExports]
+ }).addTo(map);
+
+ imgGroup.addLayer(img);
+
+ /**
+ * TODO: toolbar may still appear outside of frame. Create a getter for toolbar corners in LDI and then include them in this calculation
+ */
+ bounds = bounds.concat(corners);
+ var newImgBounds = L.latLngBounds(corners);
+
+ if (!map._initialBounds.contains(newImgBounds) && !map._initialBounds.equals(newImgBounds)) {
+ map._initialBounds.extend(newImgBounds);
+ mapknitter._map.flyToBounds(map._initialBounds);
+ }
+
+ images.push(img);
+ img.warpable_id = warpable.id;
+
+ if (!mapknitter.readOnly) {
+ L.DomEvent.on(img._image, {
+ click: mapknitter.selectImage,
+ dblclick: mapknitter.dblClickImage,
+ load: mapknitter.setupToolbar
+ }, img);
+
+ L.DomEvent.on(imgGroup, 'layeradd', mapknitter.setupEvents, img);
+ }
+
+ img.editing.disable()
+ }
},
saveImageIfChanged: function () {
@@ -434,7 +546,7 @@ MapKnitter.Map = MapKnitter.Class.extend({
saveImage: function () {
var img = this;
img._corner_state = JSON.stringify(img._corners); // reset change state string:
- $.ajax('/images', { // send save request
+ $.ajax('/images/'+img.warpable_id, { // send save request
type: 'PATCH',
data: {
warpable_id: img.warpable_id,
@@ -474,6 +586,9 @@ MapKnitter.Map = MapKnitter.Class.extend({
beforeSend: function (e) {
$('.mk-save').removeClass('fa-check-circle fa-times-circle fa-green fa-red').addClass('fa-spinner fa-spin')
},
+ success: function(data) {
+ App.concurrent_editing.speak(data);
+ },
complete: function (e) {
$('.mk-save').removeClass('fa-spinner fa-spin').addClass('fa-check-circle fa-green')
// disable interactivity:
diff --git a/app/channels/concurrent_editing_channel.rb b/app/channels/concurrent_editing_channel.rb
index 79991c3b1..35ea0bb7c 100644
--- a/app/channels/concurrent_editing_channel.rb
+++ b/app/channels/concurrent_editing_channel.rb
@@ -3,7 +3,7 @@ class ConcurrentEditingChannel < ApplicationCable::Channel
def subscribed
# Called first to connect user to the channel.
- stream_from "concurrent_editing_channel"
+ stream_from "concurrent_editing_channel:#{params[:mapSlug]}"
end
def unsubscribed
@@ -12,6 +12,6 @@ def unsubscribed
def sync(changes)
# Responsible for broadcasting the updated warpables or simply images to the user's connected on this channel.
- ActionCable.server.broadcast 'concurrent_editing_channel', changes
+ ActionCable.server.broadcast "concurrent_editing_channel:#{changes['map_slug']}", changes
end
end
diff --git a/app/controllers/images_controller.rb b/app/controllers/images_controller.rb
index 30213e913..88300b8db 100644
--- a/app/controllers/images_controller.rb
+++ b/app/controllers/images_controller.rb
@@ -92,10 +92,7 @@ def update
@warpable.locked = params[:locked]
@warpable.cm_per_pixel = @warpable.get_cm_per_pixel
@warpable.save
- respond_to do |format|
- format.html { render html: 'success' }
- format.json { render json: @warpable.map.fetch_map_data }
- end
+ render json: @warpable.map.fetch_map_data
else
render plain: 'You must be logged in to update the image, unless the map is anonymous.'
end
@@ -114,7 +111,7 @@ def destroy
@warpable.destroy
respond_to do |format|
format.html { redirect_to @warpable.map }
- format.json { render json: @warpable }
+ format.json { render json: @warpable.map.fetch_map_data }
end
else
flash[:error] = 'You must be logged in to delete images.'
diff --git a/test/controllers/images_controller_test.rb b/test/controllers/images_controller_test.rb
index 042096f72..41ae79767 100644
--- a/test/controllers/images_controller_test.rb
+++ b/test/controllers/images_controller_test.rb
@@ -62,7 +62,7 @@ def fetch_in_production
points = "-71.39,41.83:-71.39,41.83:-71.39,41.83:-71.39,41.83"
patch :update, params: { id: @map.id, warpable_id: @warp.id, locked: false, points: points}
assert_not_nil @warp.nodes
- assert_equal "text/html", response.content_type
+ assert_equal "application/json", response.content_type
assert_response :success
end