Commit 0f506819 authored by Adrien Pavie's avatar Adrien Pavie
Browse files

Allow undo of last drawn features

parent 0e8855fa
......@@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## Next version (to be released)
## 0.1.1
### Added
- User can undo last drawn features
## 0.1.0 - 2020-02-11
### Added
......
......@@ -36,7 +36,8 @@ exports.loadSketch = sid => {
SELECT COALESCE(array_agg(json_build_object(
'type', 'Feature',
'geometry', ST_AsGeoJSON(geom)::json,
'properties', properties
'properties', properties,
'id', id
)), ARRAY[]::json[]) AS features
FROM shape
WHERE sketch = $1
......@@ -69,3 +70,24 @@ exports.createShape = (sid, feature) => {
)
.then(result => result.rows[0].id);
};
/**
* Deletes existing shape
* @param {string} sid The sketch ID
* @param {int} fid The shape ID
* @return {Promise} Resolves on success
*/
exports.deleteShape = (sid, fid) => {
return pool.query(
"DELETE FROM shape WHERE id = $1 AND sketch = $2 RETURNING id",
[ fid, sid ]
)
.then(result => {
if(result.rows.length === 1) {
return true;
}
else {
return Promise.reject(new Error("Shape was not found"));
}
});
};
......@@ -68,14 +68,35 @@ io.on("connection", socket => {
socket.on("shape_create", opts => {
db.createShape(opts.sid, opts.feature)
.then(fid => {
opts.feature.id = fid;
if(!socket._createdShapes) { socket._createdShapes = []; }
socket._createdShapes.push(fid);
socket.to("sketch_"+opts.sid).emit("shape_created", opts.feature);
socket.emit("shape_creation_done", fid);
socket.emit("shape_creation_done", { fid: fid, tmpId: opts.tmpId });
})
.catch(e => {
console.error(e);
socket.emit("shape_creation_failed");
socket.emit("shape_creation_failed", opts.tmpId);
});
});
// Delete created shape
socket.on("shape_delete", opts => {
if(socket._createdShapes && socket._createdShapes.includes(opts.fid.toString())) {
db.deleteShape(opts.sid, opts.fid)
.then(() => {
socket.to("sketch_"+opts.sid).emit("shape_deleted", opts.fid);
socket.emit("shape_deletion_done", opts.fid);
})
.catch(e => {
console.error(e);
socket.emit("shape_deletion_failed", opts.fid);
});
}
else {
socket.emit("shape_deletion_failed", opts.fid);
}
});
});
......
......@@ -12,17 +12,21 @@ const COLORS = [
// Script-wide variables
let map, sid, shapesLayer, tmpShapeCoordinates,
tmpShapeLayer, lastCreatedShape, timerGeocoder;
tmpShapeLayer, timerGeocoder;
const drawOpts = {
color: "red",
penSize: 20,
opacity: 1
};
const shapesLayersById = {};
let shapeLayerTmpId = 0;
const shapesLayersWithoutId = {};
const socket = io();
const domLoader = document.getElementById("loader");
const domInputPenSize = document.getElementById("pensize");
const domColorPicker = document.getElementById("color-picker");
const domBtnColor = document.getElementById("btn-color");
const domBtnUndo = document.getElementById("btn-undo");
const domSearchAddress = document.getElementById("searchbar");
......@@ -87,19 +91,62 @@ function registerSketch() {
/**
* Create a new shape
*/
function createShape(feature) {
socket.emit("shape_create", { sid: sid, feature: feature });
function createShape(layer) {
const feature = layer.toGeoJSON();
feature.properties = layer.options;
const tmpId = shapeLayerTmpId++;
shapesLayersWithoutId[tmpId] = layer;
socket.emit("shape_create", { sid: sid, feature: feature, tmpId: tmpId });
}
/**
* Delete a shape
*/
function deleteShape(fid) {
socket.emit("shape_delete", { fid: fid, sid: sid });
}
/**
* Listen to upstream changes
*/
function listenAPIChanges() {
// External shape creation
socket.on("shape_created", showShapes);
socket.on("shape_creation_failed", () => {
// Callback after own shape creation
socket.on("shape_creation_failed", tmpId => {
delete shapesLayersWithoutId[tmpId];
alert("There was an error when saving your edits. Please reload the page");
});
socket.on("shape_creation_done", fid => lastCreatedShape = fid);
socket.on("shape_creation_done", opts => {
shapesLayersById[opts.fid] = shapesLayersWithoutId[opts.tmpId];
delete shapesLayersWithoutId[opts.tmpId];
domBtnUndo.removeAttribute("disabled");
});
// Callback after own shape deletion
socket.on("shape_deleted", fid => {
shapesLayer.eachLayer(layer => {
// Single feature
if(layer.feature && layer.feature.id == fid) {
shapesLayer.removeLayer(layer);
}
// GeoJSON collection
else if(layer.eachLayer) {
layer.eachLayer(sublayer => {
if(sublayer.feature && sublayer.feature.id == fid) {
layer.removeLayer(sublayer);
}
});
}
});
});
socket.on("shape_deletion_failed", fid => {
alert("There was a deletion error. Please reload the page");
});
socket.on("shape_deletion_done", fid => {
// console.log("Deleted shape", fid);
});
}
/**
......@@ -199,17 +246,16 @@ function finishShapeDrawing(e) {
let layerOpts;
if(cleanNodes.length === 1) {
layerOpts = { stroke: false, fillColor: drawOpts.color, radius: drawOpts.penSize / 2, fillOpacity: drawOpts.opacity };
layer = L.circleMarker(cleanNodes[0], layerOpts).addTo(map);
layer = L.circleMarker(cleanNodes[0], layerOpts);
}
else {
layerOpts = { color: drawOpts.color, weight: drawOpts.penSize, opacity: drawOpts.opacity };
layer = L.polyline(cleanNodes, layerOpts).addTo(map);
layer = L.polyline(cleanNodes, layerOpts);
}
const feature = layer.toGeoJSON();
feature.properties = layerOpts;
shapesLayer.addLayer(layer);
// Send to API
createShape(feature);
createShape(layer);
removeTmpShape();
}
}
......@@ -313,6 +359,24 @@ function showShapes(geojson) {
shapesLayer.addLayer(layer);
}
/**
* Remove the last drawn shape
*/
function undoShapeDrawn() {
const fids = Object.keys(shapesLayersById).map(k => parseInt(k)).sort();
if(fids.length <= 1) {
domBtnUndo.setAttribute("disabled", true);
}
if(fids.length > 0) {
const fid = fids.pop();
shapesLayer.removeLayer(shapesLayersById[fid]);
delete shapesLayersById[fid];
socket.emit("shape_delete", { fid: fid, sid: sid });
}
}
/**
* Change pen size
*/
......@@ -366,6 +430,7 @@ function init() {
});
domInputPenSize.addEventListener("change", setPenSize);
domInputPenSize.addEventListener("input", setPenSize);
domBtnUndo.addEventListener("click", undoShapeDrawn);
// Color picker
COLORS.forEach(range => {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment