Commit 57b5bad0 authored by Yohan Boniface's avatar Yohan Boniface

First shot of vertex in place toolbar (cf #94)

parent a8b91caf
......@@ -3,13 +3,13 @@
"browser": true
},
"rules": {
"quotes": "single",
"no-underscore-dangle": false,
"curly": false,
"consistent-return": false,
"new-cap": false,
"strict": global,
"indent": 4
"quotes": [2, "single"],
"no-underscore-dangle": 0,
"curly": 0,
"consistent-return": 0,
"new-cap": 0,
"strict": [2, "global"],
"semi-spacing": 0
},
"globals": {L: true}
}
......@@ -150,7 +150,7 @@ fieldset.toggle.on .more_style_options {
border-left: 1px solid #ddd;
display: none;
overflow-x: auto;
z-index: 10;
z-index: 1000;
background-color: #fff;
opacity: 0.93;
}
......
......@@ -562,11 +562,20 @@ a.storage-control-text {
.storage-new-hole {
background-position: -125px -165px;
}
.storage-delete-feature {
.storage-delete-all {
background-position: -165px -85px;
}
.storage-delete-one-of-multi {
background-position: -165px -125px;
}
.storage-delete-one-of-one {
background-position: -165px -165px;
}
.storage-delete-shape {
background-position: -45px -5px;
.storage-delete-vertex {
background-position: -205px -165px;
}
.storage-continue-line {
background-position: -165px -5px;
}
.storage-browse-features .feature-title,
.leaflet-control-browse .storage-browse-actions .layer-title {
......
src/img/16.png

7.34 KB | W: | H:

src/img/16.png

9.6 KB | W: | H:

src/img/16.png
src/img/16.png
src/img/16.png
src/img/16.png
  • 2-up
  • Swipe
  • Onion skin
This diff is collapsed.
......@@ -160,16 +160,19 @@ L.Storage.BaseFeatureAction = L.ToolbarAction.extend({
this.feature = feature;
this.latlng = latlng;
L.ToolbarAction.prototype.initialize.call(this);
this.postInit();
},
hideToolbar: function () {
postInit: function () {},
hideToolbar: function () {
this.map.removeLayer(this.toolbar);
},
},
addHooks: function () {
addHooks: function () {
this.onClick({latlng: this.latlng});
this.hideToolbar();
}
}
});
......@@ -208,11 +211,15 @@ L.Storage.DeleteFeatureAction = L.S.BaseFeatureAction.extend({
options: {
toolbarIcon: {
className: 'storage-delete-feature',
className: 'storage-delete-all',
tooltip: L._('Delete this feature')
}
},
postInit: function () {
if (!this.feature.isMulti()) this.options.toolbarIcon.className = 'storage-delete-one-of-one';
},
onClick: function (e) {
this.feature.confirmDelete(e);
}
......@@ -223,7 +230,7 @@ L.Storage.DeleteShapeAction = L.S.BaseFeatureAction.extend({
options: {
toolbarIcon: {
className: 'storage-delete-shape',
className: 'storage-delete-one-of-multi',
tooltip: L._('Delete this shape')
}
},
......@@ -234,6 +241,45 @@ L.Storage.DeleteShapeAction = L.S.BaseFeatureAction.extend({
});
L.Storage.BaseVertexAction = L.S.BaseFeatureAction.extend({
initialize: function (map, feature, latlng, vertex) {
this.vertex = vertex;
L.S.BaseFeatureAction.prototype.initialize.call(this, map, feature, latlng);
}
});
L.Storage.DeleteVertexAction = L.S.BaseVertexAction.extend({
options: {
toolbarIcon: {
className: 'storage-delete-vertex',
tooltip: L._('Delete this vertex')
}
},
onClick: function () {
this.vertex.delete();
}
});
L.Storage.ContinueLineAction = L.S.BaseVertexAction.extend({
options: {
toolbarIcon: {
className: 'storage-continue-line',
tooltip: L._('Continue line')
}
},
onClick: function () {
this.vertex.continue();
}
});
// Leaflet.Toolbar doesn't allow twice same toolbar class…
L.Storage.SettingsToolbar = L.Toolbar.Control.extend({});
L.Storage.DrawToolbar = L.Toolbar.Control.extend({
......@@ -323,7 +369,7 @@ L.Storage.MoreControls = L.Control.extend({
position: 'topleft'
},
onAdd: function (map) {
onAdd: function () {
var container = L.DomUtil.create('div', ''),
more = L.DomUtil.create('a', 'storage-control-more storage-control-text', container),
less = L.DomUtil.create('a', 'storage-control-less storage-control-text', container);
......@@ -423,12 +469,12 @@ L.Storage.DataLayersControl = L.Control.extend({
},
addDataLayer: function (datalayer) {
var datalayer_li = L.DomUtil.create('li', '', this._datalayers_container);
datalayer.renderToolbox(datalayer_li);
var title = L.DomUtil.add('span', 'layer-title', datalayer_li, datalayer.options.name);
var datalayerLi = L.DomUtil.create('li', '', this._datalayers_container);
datalayer.renderToolbox(datalayerLi);
var title = L.DomUtil.add('span', 'layer-title', datalayerLi, datalayer.options.name);
datalayer_li.id = 'browse_data_toggle_' + datalayer.storage_id;
L.DomUtil.classIf(datalayer_li, 'off', !datalayer.isVisible());
datalayerLi.id = 'browse_data_toggle_' + datalayer.storage_id;
L.DomUtil.classIf(datalayerLi, 'off', !datalayer.isVisible());
title.innerHTML = datalayer.options.name;
},
......@@ -444,15 +490,15 @@ L.Storage.DataLayer.include({
renderToolbox: function (container) {
var toggle = L.DomUtil.create('i', 'layer-toggle', container),
zoom_to = L.DomUtil.create('i', 'layer-zoom_to', container),
zoomTo = L.DomUtil.create('i', 'layer-zoom_to', container),
edit = L.DomUtil.create('i', 'layer-edit show-on-edit', container),
table = L.DomUtil.create('i', 'layer-table-edit show-on-edit', container);
zoom_to.title = L._('Zoom to layer extent');
zoomTo.title = L._('Zoom to layer extent');
toggle.title = L._('Show/hide layer');
edit.title = L._('Edit');
table.title = L._('Edit properties in a table');
L.DomEvent.on(toggle, 'click', this.toggle, this);
L.DomEvent.on(zoom_to, 'click', this.zoomTo, this);
L.DomEvent.on(zoomTo, 'click', this.zoomTo, this);
L.DomEvent.on(edit, 'click', this.edit, this);
L.DomEvent.on(table, 'click', this.tableEdit, this);
L.DomUtil.addClass(container, this.getHidableClass());
......@@ -463,7 +509,7 @@ L.Storage.DataLayer.include({
return this.storage_id || 'tmp' + L.Util.stamp(this);
},
getHidableElements: function () {
getHidableElements: function () {
return document.querySelectorAll('.' + this.getHidableClass());
},
......@@ -592,7 +638,7 @@ L.Storage.TileLayerControl = L.Control.extend({
position: 'topleft'
},
onAdd: function (map) {
onAdd: function () {
var container = L.DomUtil.create('div', 'leaflet-control-tilelayers storage-control');
var link = L.DomUtil.create('a', '', container);
......@@ -609,7 +655,6 @@ L.Storage.TileLayerControl = L.Control.extend({
},
openSwitcher: function (options) {
var self = this;
this._tilelayers_container = L.DomUtil.create('ul', 'storage-tilelayer-switcher-container');
this.buildList(options);
},
......@@ -622,13 +667,13 @@ L.Storage.TileLayerControl = L.Control.extend({
},
addTileLayerElement: function (tilelayer, options) {
var selectedClass = this._map.hasLayer(tilelayer) ? 'selected': '',
var selectedClass = this._map.hasLayer(tilelayer) ? 'selected' : '',
el = L.DomUtil.create('li', selectedClass, this._tilelayers_container),
img = L.DomUtil.create('img', '', el),
name = L.DomUtil.create('div', '', el);
img.src = L.Util.template(tilelayer.options.url_template, this._map.demoTileInfos);
name.innerHTML = tilelayer.options.name;
L.DomEvent.on(el, 'click', function (e) {
L.DomEvent.on(el, 'click', function () {
this._map.selectTileLayer(tilelayer);
L.S.fire('ui:end');
if (options && options.callback) {
......@@ -667,7 +712,7 @@ L.Storage.HomeControl = L.Control.extend({
position: 'topleft'
},
onAdd: function (map) {
onAdd: function () {
var container = L.DomUtil.create('div', 'leaflet-control-home storage-control'),
link = L.DomUtil.create('a', '', container);
......@@ -690,7 +735,7 @@ L.Storage.LocateControl = L.Control.extend({
link = L.DomUtil.create('a', '', container);
link.href = '#';
link.title = L._('Center map on your location');
var fn = function (e) {
var fn = function () {
map.locate({
setView: true,
enableHighAccuracy: true
......@@ -703,7 +748,8 @@ L.Storage.LocateControl = L.Control.extend({
.on(link, 'click', fn, map)
.on(link, 'dblclick', L.DomEvent.stopPropagation);
return container; }
return container;
}
});
......@@ -726,8 +772,8 @@ L.Storage.JumpToLocationControl = L.Control.extend({
link.title = input.placeholder = L._('Jump to location');
link.innerHTML = ' ';
var fn = function () {
var search_terms = input.value;
if (!search_terms) {
var searchTerms = input.value;
if (!searchTerms) {
return;
}
L.DomUtil.addClass(link, 'loading');
......@@ -743,7 +789,7 @@ L.Storage.JumpToLocationControl = L.Control.extend({
viewbox = viewbox.join(',');
var params = {
format: 'json',
q: search_terms,
q: searchTerms,
viewbox: viewbox, // this is just a preferred area, not a constraint
limit: 1
};
......@@ -756,7 +802,7 @@ L.Storage.JumpToLocationControl = L.Control.extend({
map.setZoom(map.getOption('zoomTo'));
}
else {
L.S.fire('ui:alert', {content: L._('Sorry, no location found for {location}', {location: search_terms})});
L.S.fire('ui:alert', {content: L._('Sorry, no location found for {location}', {location: searchTerms})});
}
}
});
......@@ -772,7 +818,8 @@ L.Storage.JumpToLocationControl = L.Control.extend({
.on(link, 'click', fn)
.on(link, 'dblclick', L.DomEvent.stopPropagation);
return container; }
return container;
}
});
......@@ -933,9 +980,9 @@ L.S.Editable = L.Editable.extend({
});
this.on('editable:vertex:ctrlclick', function (e) {
var index = e.vertex.getIndex();
if (index === 0) e.layer.editor.continueBackward();
else if (index === e.vertex.getLastIndex()) e.layer.editor.continueForward();
if (index === 0 || index === e.vertex.getLastIndex() && e.vertex.continue) e.vertex.continue();
});
this.on('editable:vertex:rawclick', this.onVertexRawClick);
},
createPolyline: function (latlngs) {
......@@ -977,6 +1024,12 @@ L.S.Editable = L.Editable.extend({
closeTooltip: function () {
L.S.fire('ui:tooltip:abort');
},
onVertexRawClick: function (e) {
e.layer.onVertexRawClick(e);
L.DomEvent.stop(e);
e.cancel();
}
});
......@@ -393,6 +393,17 @@ L.Storage.FeatureMixin = {
if ((this.properties[keys[i]] || '').toLowerCase().indexOf(filter) !== -1) return true;
}
return false;
},
onVertexRawClick: function (e) {
new L.Toolbar.Popup(e.latlng, {
className: 'leaflet-inplace-toolbar',
actions: this.getVertexActions(e)
}).addTo(this.map, this, e.latlng, e.vertex);
},
getVertexActions: function () {
return [L.S.DeleteVertexAction];
}
};
......@@ -668,7 +679,7 @@ L.Storage.PathMixin = {
this.disableEdit();
if (!shape) return;
to.enableEdit().appendShape(shape);
if (!this._latlngs.length) this.del();
if (!this._latlngs.length || !this._latlngs[0].length) this.del();
},
getContextMenuItems: function (e) {
......@@ -859,12 +870,15 @@ L.Storage.Polyline = L.Polyline.extend({
this.isDirty = true;
},
defaultShape: function () {
return this._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
isMulti: function () {
return !L.Polyline._flat(this._latlngs) && this._latlngs.length > 1;
},
isMulti: function () {
return !this._flat(this._latlngs) && this._latlngs.length > 1;
getVertexActions: function (e) {
var actions = L.S.FeatureMixin.getVertexActions.call(this, e),
index = e.vertex.getIndex();
if (index === 0 || index === e.vertex.getLastIndex()) actions.push(L.S.ContinueLineAction);
return actions;
}
});
......@@ -902,7 +916,7 @@ L.Storage.Polygon = L.Polygon.extend({
var items = L.S.PathMixin.getContextMenuEditItems.call(this, e),
shape = this.shapeAt(e.latlng);
// No multi and no holes.
if (shape && !this.isMulti() && (this._flat(shape) || shape.length === 1)) {
if (shape && !this.isMulti() && (L.Polyline._flat(shape) || shape.length === 1)) {
items.push({
text: L._('Transform to lines'),
callback: this.toPolyline,
......@@ -938,14 +952,9 @@ L.Storage.Polygon = L.Polygon.extend({
L.DomEvent.on(toPolyline, 'click', this.toPolyline, this);
},
defaultShape: function () {
// Change me when Leaflet#3279 is merged.
return this._flat(this._latlngs) ? this._latlngs : this._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
},
isMulti: function () {
// Change me when Leaflet#3279 is merged.
return !this._flat(this._latlngs) && !this._flat(this._latlngs[0]) && this._latlngs.length > 1;
return !L.Polyline._flat(this._latlngs) && !L.Polyline._flat(this._latlngs[0]) && this._latlngs.length > 1;
},
getInplaceToolbarActions: function (e) {
......
......@@ -1609,8 +1609,8 @@ L.Storage.Map.include({
},
closeInplaceToolbar: function () {
var toolbar = this._toolbars[L.Toolbar.Popup._toolbar_class_id];
if (toolbar) toolbar.remove();
var toolbar = this._toolbars[L.Toolbar.Popup._toolbar_class_id];
if (toolbar) toolbar.remove();
}
});
......@@ -5,6 +5,15 @@
"assert": true,
"before": true,
"after": true,
"it"
"it": true,
"sinon": true,
"qs": true,
"enableEdit": true,
"disableEdit": true,
"changeInputValue": true,
"resetMap": true,
"initMap": true,
"clickCancel": true,
"map": true
}
}
......@@ -13,7 +13,7 @@ describe('L.Storage.FeatureMixin', function () {
});
describe('#edit()', function () {
var submitButton;
var link;
it('should have datalayer features created', function () {
assert.equal(document.querySelectorAll('#map > .leaflet-map-pane > .leaflet-overlay-pane path.leaflet-interactive').length, 2);
......@@ -25,15 +25,23 @@ describe('L.Storage.FeatureMixin', function () {
enableEdit();
happen.click(qs('#browse_data_toggle_62 .layer-edit'));
var colorInput = qs('form#datalayer-advanced-properties input[name=color]');
changeInputValue(colorInput, "DarkRed");
changeInputValue(colorInput, 'DarkRed');
assert.ok(qs('path[fill="none"]')); // Polyline fill is unchanged
assert.notOk(qs('path[fill="DarkBlue"]'));
assert.ok(qs('path[fill="DarkRed"]'));
});
it('should open a form on feature double click', function () {
it('should open a popup toolbar on feature click', function () {
enableEdit();
happen.dblclick(qs('path[fill="DarkRed"]'));
happen.click(qs('path[fill="DarkRed"]'));
var toolbar = qs('ul.leaflet-inplace-toolbar');
assert.ok(toolbar);
link = qs('a.storage-toggle-edit', toolbar);
assert.ok(link);
});
it('should open a form on popup toolbar toggle edit click', function () {
happen.click(link);
var form = qs('form#storage-feature-properties');
var input = qs('form#storage-feature-properties input[name="name"]');
assert.ok(form);
......@@ -47,7 +55,7 @@ describe('L.Storage.FeatureMixin', function () {
it('should give precedence to feature style over datalayer styles', function () {
var input = qs('form#storage-feature-advanced-properties input[name="color"]');
assert.ok(input);
changeInputValue(input, "DarkGreen");
changeInputValue(input, 'DarkGreen');
assert.notOk(qs('path[fill="DarkRed"]'));
assert.notOk(qs('path[fill="DarkBlue"]'));
assert.ok(qs('path[fill="DarkGreen"]'));
......@@ -66,7 +74,7 @@ describe('L.Storage.FeatureMixin', function () {
it('should not override already set style on features', function () {
happen.click(qs('#browse_data_toggle_62 .layer-edit'));
changeInputValue(qs('form#datalayer-advanced-properties input[name=color]'), "Chocolate");
changeInputValue(qs('form#datalayer-advanced-properties input[name=color]'), 'Chocolate');
assert.notOk(qs('path[fill="DarkBlue"]'));
assert.notOk(qs('path[fill="DarkRed"]'));
assert.notOk(qs('path[fill="Chocolate"]'));
......@@ -84,7 +92,8 @@ describe('L.Storage.FeatureMixin', function () {
it('should set map.editedFeature on edit', function () {
enableEdit();
assert.notOk(this.map.editedFeature);
happen.dblclick(qs('path[fill="DarkBlue"]'));
happen.click(qs('path[fill="DarkBlue"]'));
happen.click(qs('ul.leaflet-inplace-toolbar a.storage-toggle-edit'));
assert.ok(this.map.editedFeature);
disableEdit();
});
......@@ -92,7 +101,8 @@ describe('L.Storage.FeatureMixin', function () {
it('should reset map.editedFeature on panel open', function () {
enableEdit();
assert.notOk(this.map.editedFeature);
happen.dblclick(qs('path[fill="DarkBlue"]'));
happen.click(qs('path[fill="DarkBlue"]'));
happen.click(qs('ul.leaflet-inplace-toolbar a.storage-toggle-edit'));
assert.ok(this.map.editedFeature);
this.map.displayCaption();
assert.notOk(this.map.editedFeature);
......@@ -116,7 +126,7 @@ describe('L.Storage.FeatureMixin', function () {
it('should generate a valid geojson', function () {
setFeatures(this.datalayer);
assert.ok(poly);
assert.deepEqual(poly.toGeoJSON().geometry, {"type":"Polygon","coordinates":[[[11.25,53.585983654559804],[10.1513671875,52.9751081817353],[12.689208984375,52.16719363541221],[14.084472656249998,53.199451902831555],[12.63427734375,53.61857936489517],[11.25,53.585983654559804],[11.25,53.585983654559804]]]});
assert.deepEqual(poly.toGeoJSON().geometry, {'type': 'Polygon', 'coordinates': [[[11.25, 53.585983654559804], [10.1513671875, 52.9751081817353], [12.689208984375, 52.16719363541221], [14.084472656249998, 53.199451902831555], [12.63427734375, 53.61857936489517], [11.25, 53.585983654559804], [11.25, 53.585983654559804]]]});
// Ensure original latlngs has not been modified
assert.equal(poly.getLatLngs()[0].length, 6);
});
......@@ -124,9 +134,9 @@ describe('L.Storage.FeatureMixin', function () {
it('should remove empty _storage_options from exported geojson', function () {
setFeatures(this.datalayer);
assert.ok(poly);
assert.deepEqual(poly.toGeoJSON().properties, {name: "name poly"});
assert.deepEqual(poly.toGeoJSON().properties, {name: 'name poly'});
assert.ok(marker);
assert.deepEqual(marker.toGeoJSON().properties, {_storage_options: {color: "OliveDrab"}, name: "test"});
assert.deepEqual(marker.toGeoJSON().properties, {_storage_options: {color: 'OliveDrab'}, name: 'test'});
});
});
......@@ -136,9 +146,10 @@ describe('L.Storage.FeatureMixin', function () {
it('should change style on datalayer select change', function () {
enableEdit();
happen.click(qs('.leaflet-control-browse .add-datalayer'));
changeInputValue(qs('form.storage-form input[name="name"]'), "New layer");
changeInputValue(qs('form#datalayer-advanced-properties input[name=color]'), "MediumAquaMarine");
happen.dblclick(qs('path[fill="DarkBlue"]'));
changeInputValue(qs('form.storage-form input[name="name"]'), 'New layer');
changeInputValue(qs('form#datalayer-advanced-properties input[name=color]'), 'MediumAquaMarine');
happen.click(qs('path[fill="DarkBlue"]'));
happen.click(qs('ul.leaflet-inplace-toolbar a.storage-toggle-edit'));
var select = document.querySelector('select[name=datalayer]');
select.selectedIndex = 1;
happen.once(select, {type: 'change'});
......
var qs = function (selector) {return document.querySelector(selector);};
var qs = function (selector, element) {return (element || document).querySelector(selector);};
var qsa = function (selector) {return document.querySelectorAll(selector);};
var qst = function (text, parent) {
// find element by its text content
var r = document.evaluate("descendant::*[contains(text(),'" + text + "')]", parent || qs('#map'), null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null), count = 0;
while(r.iterateNext()) console.log(++count);
return count;
}
};
happen.at = function (what, x, y, props) {
this.once(document.elementFromPoint(x, y), L.Util.extend({
type: what,
......
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