-
Notifications
You must be signed in to change notification settings - Fork 207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Setting Action Cable #956
Setting Action Cable #956
Changes from all commits
20c81a2
d4131fd
9c21952
c4a3cba
58b80a1
76d6e33
3d7d8ba
8b2eaaf
ddb6d7c
a79930a
a1d0aaf
7cb7a4d
2ef8715
fc838a8
b5b6340
6dc3a69
06f4d00
4db8646
6ddb5bc
4f31616
76e078a
5d613e8
80e12b7
01d86fa
db6c5fa
290273f
36c9208
c53bf27
2f23252
0f76698
de27727
685143c
74a9ecb
daac614
528ec09
1d220ec
5bb19c2
11dd71f
fbb29ba
bba8fad
6bc9a8c
116f385
75c2fef
1f1dedd
f7f526f
68385c3
ae88815
49af138
b34dd84
5c0c7c7
22e5312
5da06ce
2ecc4be
d71d9ad
1d1211f
13cdef5
c20a903
5a28772
df7b02f
1c1e2c2
3385812
4cd47a4
f0297dd
40ad993
91d082f
0607a4f
a3e7ff8
525eb1a
4fb5be3
d058ce3
d90db0e
6e28143
6545537
24bd1f7
e60bb83
6bedcc1
642beb4
2c746c1
88b3c8a
17f8e88
463fd8d
154f8b5
c9e8b1b
bccec1b
f678d95
f39d468
c1837f1
cfca3ce
47ce35a
10cec89
ca9a118
d86282c
616f1fe
2495a8a
b2fdaea
03bcfd3
672fabd
a146edb
3a10c73
e2e17e5
1340e4d
37efb69
d81e8e0
87bb313
a5726c7
b0b1dc0
6cfd543
4fa14f2
b40bebd
d5ed24a
5db0ac9
6cf32f7
8512b65
18436ae
3f12c76
e67ed3b
297bcb0
e3f3c9d
3291e16
553d8ea
85f54a5
3b1f578
b57356d
9b0487e
3b7b2bf
10d42f4
c7af515
826745f
63b0a89
5678f71
43d86e1
658d4ea
0c685d1
1c61d3a
381704c
85bb1eb
431164d
63c6a59
2abced6
8d85686
d91554e
6e8e33b
f4005d0
9d7267d
58bc692
f8d2b8d
bf66f69
997a6fa
ab615b5
3dd385a
c785c63
cc22333
81d496f
4cb16b0
1fbba3a
ccafe26
b80b315
85f1b3c
2d12491
906beb3
552fc70
2773d0e
15369b4
41a0c3e
4d2c947
0970e84
de5774b
2211dc4
53d8aad
193f553
ca0c2fc
454cf2f
852d5f9
8fd235d
a158e77
35aa34f
2610522
4040bc3
e231418
55baec4
4904217
53084a1
add9f16
87deccd
2432805
98a6ca7
2f9f6eb
a389a97
54b5ef7
74b75ca
fdcd38a
9191cf6
d0bdd43
0148e7a
855089a
df2f8f1
6b59103
d07cd13
f271a84
fe95fc8
53bd5ab
3f09e8d
a1fe6ad
a900b99
0a18f29
ad142c5
cacf26d
a522eaa
3b2274a
ac8e40c
f002ad0
1464157
b033cc5
e568fbe
87e18ee
7779a96
843a719
f78cbc9
1627b96
6823601
d14c56b
6c4a998
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
passenger: passenger start | ||
puma: puma -C config/puma.rb |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
The new synchronous editing feature | ||
=================================== | ||
|
||
With the introduction of ActionCable to our system, it has been possible | ||
to do perform real-time tasks quite easily. We have used rail's default | ||
action cable to make a _concurrent_editing_channel.rb_ in the _app/channels_ folder, | ||
to handle all the incoming requests and consists of all the business | ||
logic as well. At the frontend we have, _app/javascripts/channels/concurrent_editing.js_ which | ||
handles the logic at the browser or the frontend. | ||
|
||
## Flow of the feature: | ||
|
||
1. When the map is updated, the _speak_ method of _concurrent_editing.js_ is called which requests | ||
the _sync_ method of _concurrent_editing_channel.rb_ to broadcast the updated data to | ||
the connected users. | ||
|
||
2. The broadcasted data is finally caught by the _received_ function of _app/javascripts/channels/concurrent_editing.js_ | ||
|
||
3. Finally the _received_ function calls the _synchronizeData_ function to update | ||
all the fresh data on the map. | ||
|
||
|
||
## Testing: | ||
|
||
1. The _action-cable-testing_ gem is used for the feature's testing. It has some really | ||
cool testing functionality which was required for our use case. | ||
|
||
2. Currently we have separate tests written for connection related features and channel | ||
specific features. The relevant files are test/channels/concurrent_editing_channel_test.rb and | ||
test/channels/connection_test.rb |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// | ||
//= require action_cable | ||
//= require_self | ||
//= require_tree ./channels | ||
|
||
(function() { | ||
this.App || (this.App = {}); | ||
|
||
App.cable = ActionCable.createConsumer(); | ||
|
||
}).call(this); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* Handles all the frontend interactions with action cable and the server. */ | ||
|
||
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 | ||
}, | ||
|
||
disconnected: function() { | ||
// Called when the subscription has been terminated by the server | ||
}, | ||
|
||
received: function(data) { | ||
// Called when there's incoming data on the websocket for this channel | ||
window.mapknitter.synchronizeData(data.changes); | ||
}, | ||
|
||
speak: function(changes) { | ||
/* Called when an image is updated from Map.js ('saveImage' function). | ||
* This function calls concurrent_editing_channel.rb's 'sync' method | ||
* 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, | ||
map_slug: window.location.href.split("/").pop() | ||
}); | ||
} | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -386,6 +386,144 @@ MapKnitter.Map = MapKnitter.Class.extend({ | |
if (this.editing._mode !== "lock") { e.stopPropagation(); } | ||
}, | ||
|
||
/* Called by the concurrent_editing.js channel's 'received' function (app/assets/javascripts/channels/concurrent_editing.js). | ||
* It recieves a list of updated warpables,i.e. list of images with updated corner points. The aim of writing this function | ||
* is to reposition the updated images onto the map on every connected browser (via the ActionCable). */ | ||
|
||
synchronizeData: function(warpables) { | ||
var layers = []; | ||
map.eachLayer(function(l) {layers.push(l)}); | ||
layers = layers.filter(image => (image._url!=undefined || image._url!=null)); | ||
warpables.forEach(function(warpable) { | ||
corners = []; | ||
warpable.nodes.forEach(function(node) { | ||
corners.push(L.latLng(node.lat, node.lon)); | ||
}); | ||
|
||
x = corners[2]; | ||
y = corners [3]; | ||
corners [2] = y; | ||
corners [3] = x; | ||
|
||
layer = layers.filter(l => l._url==warpable.srcmedium)[0]; | ||
|
||
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 () { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Identical blocks of code found in 2 locations. Consider refactoring. |
||
downloadEl.html('<i class="fa fa-circle-o-notch fa-spin"></i>'); | ||
|
||
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('<i class="fa fa-download"></i>'); | ||
} | ||
|
||
imgEl[0].src = $('.img-download-' + warpable.id).attr('data-image'); | ||
}); | ||
|
||
var corners = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Identical blocks of code found in 2 locations. Consider refactoring. |
||
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)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Identical blocks of code found in 2 locations. Consider refactoring. |
||
map._initialBounds.extend(newImgBounds); | ||
mapknitter._map.flyToBounds(map._initialBounds); | ||
} | ||
|
||
images.push(img); | ||
img.warpable_id = warpable.id; | ||
|
||
if (!mapknitter.readOnly) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Identical blocks of code found in 2 locations. Consider refactoring. |
||
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 () { | ||
var img = this, | ||
edit = img.editing; | ||
|
@@ -407,10 +545,8 @@ MapKnitter.Map = MapKnitter.Class.extend({ | |
|
||
saveImage: function () { | ||
var img = this; | ||
// reset change state string: | ||
img._corner_state = JSON.stringify(img._corners); | ||
// send save request | ||
$.ajax('/images', { | ||
img._corner_state = JSON.stringify(img._corners); // reset change state string: | ||
$.ajax('/images/'+img.warpable_id, { // send save request | ||
type: 'PATCH', | ||
data: { | ||
warpable_id: img.warpable_id, | ||
|
@@ -424,6 +560,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') | ||
}, | ||
|
@@ -447,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: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Function
synchronizeNewAddedImage
has 69 lines of code (exceeds 25 allowed). Consider refactoring.