diff --git a/src/c3nav/editor/forms.py b/src/c3nav/editor/forms.py index 79954fe8..debc5b3b 100644 --- a/src/c3nav/editor/forms.py +++ b/src/c3nav/editor/forms.py @@ -13,7 +13,7 @@ from c3nav.mapdata.models.geometry import Area, Building, Door, Obstacle from c3nav.mapdata.permissions import get_unlocked_packages -class FeatureFormMixin(ModelForm): +class MapitemFormMixin(ModelForm): def __init__(self, *args, request=None, **kwargs): self.request = request super().__init__(*args, **kwargs) @@ -73,13 +73,13 @@ class FeatureFormMixin(ModelForm): self.titles = titles -def create_editor_form(feature_model, add_fields=None): - class EditorForm(FeatureFormMixin, ModelForm): +def create_editor_form(mapdata_model, add_fields=None): + class EditorForm(MapitemFormMixin, ModelForm): class Meta: - model = feature_model + model = mapdata_model fields = ['name', 'package', 'level', 'geometry'] + (add_fields if add_fields is not None else []) - feature_model.EditorForm = EditorForm + mapdata_model.EditorForm = EditorForm def create_editor_forms(): diff --git a/src/c3nav/editor/static/editor/css/editor.css b/src/c3nav/editor/static/editor/css/editor.css index b0afe7e9..d8c019fd 100644 --- a/src/c3nav/editor/static/editor/css/editor.css +++ b/src/c3nav/editor/static/editor/css/editor.css @@ -30,46 +30,39 @@ body { #mapeditcontrols { - position:absolute; - top:54px; - bottom:0; - padding:0; - width:350px; - right:0; - overflow:hidden; + position: absolute; + top: 54px; + bottom: 0; + width: 350px; + right: 0; + overflow: auto; + padding:8px; +} +#mapeditcontrols.loading { background-image:url('/static/img/loader.gif'); background-repeat:no-repeat; background-position:center; } -#mapeditcontrols > div { - position:absolute; - top:0; - bottom:0; - background-color:white; - padding:8px; - width:350px; - right:0; - transition: right 300ms; - -webkit-transition: right 300ms; - overflow:auto; -} -#mapeditcontrols > #mapeditdetail { - right:-350px; - pointer-events: none; -} -#mapeditcontrols.detail #mapeditdetail { - right:0; - pointer-events: auto; -} -#mapeditcontrols > #mapeditlist { - display:none; -} -#mapeditcontrols.list > #mapeditlist { - display:block; -} -#mapeditdetail h3 { +#mapeditcontrols h3 { margin-top:5px; } +a.list-group-item, a.list-group-item:hover { + color:#158cba; +} +a.list-group-item .badge { + font-size:14px; + font-weight:bold; +} +.itemtable td:last-child { + text-align:right; +} +.itemtable tr.highlight td { + background-color:#FFFFDD; +} +.cancel-btn { + margin-right:8px; +} + #mapeditlist legend .btn { padding-left:5.5px; padding-right:5.5px; @@ -89,36 +82,6 @@ legend { .leaflet-editable-drawing .leaflet-overlay-pane .leaflet-interactive { cursor:crosshair; } -.feature_list { - display:none; -} -.feature_level_list { - list-style-type:none; - display:none; - padding:0; -} -.feature_level_list li { - padding:5px 0 0; - border-style:solid; - border-width:0 0 1px; - border-color:#EEEEEE; -} -.feature_level_list li:hover, .feature_level_list li.hover { - background-color:#FFFFEE; -} -.feature_level_list p { - margin:0 0 5px; - cursor:default; -} -.feature_level_list p:first-child { - font-weight:bold; - font-size:16px; -} -.feature_level_list p:first-child em { - font-weight:normal; - font-size:14px; - padding-left:5px; -} #map .leaflet-overlay-pane .c3nav-highlight { pointer-events:none; } diff --git a/src/c3nav/editor/static/editor/js/editor.js b/src/c3nav/editor/static/editor/js/editor.js index e4afdbc7..ec70e6fd 100644 --- a/src/c3nav/editor/static/editor/js/editor.js +++ b/src/c3nav/editor/static/editor/js/editor.js @@ -13,9 +13,11 @@ editor = { editable: true, closePopupOnClick: false }); + editor.map.on('click', function (e) { + editor.map.doubleClickZoom.enable(); + }); L.control.scale({imperial: false}).addTo(editor.map); - editor._highlight_layer = L.layerGroup().addTo(editor.map); $('#show_map').click(function() { $('body').removeClass('controls'); @@ -24,50 +26,17 @@ editor = { $('body').addClass('controls'); }); - editor.get_feature_types(); + editor.init_geometries(); + editor.init_sidebar(); editor.get_packages(); editor.get_sources(); + editor.get_levels(); }, - _feature_type: null, - get_feature_types: function () { - $.getJSON('/api/featuretypes/', function (feature_types) { - var feature_type; - var listcontainer = $('#mapeditlist fieldset'); - var dropdown = $('#featuretype_dropdown').on('click', 'a', function(e) { - e.preventDefault(); - editor.set_current_feature_type($(this).parent().attr('data-name')); - }); - for (var i = 0; i < feature_types.length; i++) { - feature_type = feature_types[i]; - editor.feature_types[feature_type.name] = feature_type; - feature_type.weight = 0; - feature_type.fillOpacity = 0.6; - feature_type.smoothFactor = 0; - editor.feature_types_order.push(feature_type.name); - listcontainer.append( - $('
').text(feature.title).append(' ').append(
- $('').text(feature.name)
- )
- )
- );
- }
- }
- $('#start-drawing').show();
- $('#mapeditcontrols').addClass('list');
- editor.set_current_level(editor._level);
+ editor._geometries_layer.addTo(editor.map);
});
},
- _get_feature_style: function (feature) {
- return editor.feature_types[feature.properties.feature_type];
+ _geometry_colors: {
+ 'building': '#333333',
+ 'area': '#FFFFFF',
+ 'obstacle': '#999999',
+ 'door': '#FF00FF',
+ },
+ _get_geometry_style: function (feature) {
+ // style callback for GeoJSON loader
+ return editor._get_mapitem_type_style(feature.properties.type);
+ },
+ _get_mapitem_type_style: function (mapitem_type) {
+ // get styles for a specific mapitem
+ return {
+ fillColor: editor._geometry_colors[mapitem_type],
+ weight: 0,
+ fillOpacity: 0.6,
+ smoothFactor: 0,
+ };
+ },
+ _register_geojson_feature: function (feature, layer) {
+ // onEachFeature callback for GeoJSON loader – register all needed events
+ editor._geometries[feature.properties.type+'-'+feature.properties.name] = layer;
+ layer.on('mouseover', editor._hover_geometry_layer)
+ .on('mouseout', editor._unhighlight_geometry)
+ .on('click', editor._click_geometry_layer)
+ .on('dblclick', editor._dblclick_geometry_layer)
},
- _click_start_drawing: function (e) {
- editor.start_creating(editor._feature_type);
+ // hover and highlight geometries
+ _hover_mapitem_row: function (e) {
+ // hover callback for a itemtable row
+ editor._highlight_geometry($(this).closest('.itemtable').attr('data-mapitem-type'), $(this).attr('name'));
},
- _hover_feature_detail: function (e) {
- editor._highlight_layer.clearLayers();
- L.geoJSON(editor.features[$(this).attr('name')].geometry, {
- style: function() {
- return {
- color: '#FFFFDD',
- weight: 3,
- opacity: 0.7,
- fillOpacity: 0,
- className: 'c3nav-highlight'
- };
- }
- }).addTo(editor._highlight_layer);
+ _hover_geometry_layer: function (e) {
+ // hover callback for a geometry layer
+ editor._highlight_geometry(e.target.feature.properties.type, e.target.feature.properties.name);
},
- _unhover_feature_detail: function () {
- editor._highlight_layer.clearLayers();
+ _click_geometry_layer: function (e) {
+ // click callback for a geometry layer – scroll the corresponding itemtable row into view if it exists
+ var properties = e.target.feature.properties;
+ var row = $('.itemtable[data-mapitem-type='+properties.type+'] tr[name='+properties.name+']');
+ if (row.length) {
+ row[0].scrollIntoView();
+ }
},
- _click_feature_detail: function() {
- editor.start_editing($(this).attr('name'));
+ _dblclick_geometry_layer: function (e) {
+ // dblclick callback for a geometry layer - edit this feature if the corresponding itemtable row exists
+ var properties = e.target.feature.properties;
+ var row = $('.itemtable[data-mapitem-type='+properties.type+'] tr[name='+properties.name+']');
+ if (row.length) {
+ row.find('td:last-child a').click();
+ editor.map.doubleClickZoom.disable();
+ }
},
-
- _hover_feature_layer: function (e) {
- editor._unhover_feature_layer();
- if (editor._editing === null && editor._creating === null && e.layer.feature.properties.feature_type == editor._feature_type) {
- editor._highlight_layer.clearLayers();
- L.geoJSON(e.layer.toGeoJSON(), {
+ _highlight_geometry: function(mapitem_type, name) {
+ // highlight a geometries layer and itemtable row if they both exist
+ var pk = mapitem_type+'-'+name;
+ editor._unhighlight_geometry();
+ var layer = editor._geometries[pk];
+ var row = $('.itemtable[data-mapitem-type='+mapitem_type+'] tr[name='+name+']');
+ if (layer !== undefined && row.length) {
+ row.addClass('highlight');
+ L.geoJSON(layer.feature, {
style: function() {
return {
color: '#FFFFDD',
@@ -269,142 +226,172 @@ editor = {
}
}).addTo(editor._highlight_layer);
}
- $('.feature_list li[name='+e.layer.feature.properties.name+']').addClass('hover');
},
- _unhover_feature_layer: function (e) {
+ _unhighlight_geometry: function() {
+ // unhighlight whatever is highlighted currently
editor._highlight_layer.clearLayers();
- $('.feature_list .hover').removeClass('hover');
+ $('.itemtable .highlight').removeClass('highlight');
},
- _click_feature_layer: function(e) {
- if (e.layer.feature.properties.feature_type != editor._feature_type) return;
- editor.start_editing(e.layer.feature.properties.name);
- if ((e.originalEvent.ctrlKey || e.originalEvent.metaKey) && this.editEnabled()) {
- if (e.layer.feature.properties.geomtype == 'polygon') {
+
+ // edit and create geometries
+ _check_start_editing: function() {
+ // called on sidebar load. start editing or creating depending on how the sidebar may require it
+ var geometry_field = $('#mapeditcontrols').find('input[name=geometry]');
+ if (geometry_field.length) {
+ var form = geometry_field.closest('form');
+ var mapitem_type = form.attr('data-mapitem-type');
+ if (form.is('[data-name]')) {
+ // edit existing geometry
+ var name = form.attr('data-name');
+ var pk = mapitem_type+'-'+name;
+ editor._geometries_layer.removeLayer(editor._geometries[pk]);
+
+ editor._editing = L.geoJSON({
+ type: 'Feature',
+ geometry: JSON.parse(geometry_field.val()),
+ properties: {
+ type: mapitem_type,
+ }
+ }, {
+ style: editor._get_geometry_style
+ }).getLayers()[0];
+ editor._editing.on('click', editor._click_editing_layer);
+ editor._editing.addTo(editor._editing_layer);
+ editor._editing.enableEdit();
+ } else if (form.is('[data-geomtype]')) {
+ // create new geometry
+ var geomtype = form.attr('data-geomtype');
+
+ var options = editor._get_mapitem_type_style(mapitem_type);
+ if (geomtype == 'polygon') {
+ editor.map.editTools.startPolygon(null, options);
+ } else if (geomtype == 'polyline') {
+ editor.map.editTools.startPolyline(null, options);
+ }
+ editor._creating = true;
+ $('#id_level').val(editor._level);
+ }
+ } else if (editor._get_geometries_next_time) {
+ editor.get_geometries();
+ editor._get_geometries_next_time = false;
+ }
+ },
+ _cancel_editing: function() {
+ // called on sidebar unload. cancel all editing and creating.
+ if (editor._editing !== null) {
+ editor._editing_layer.clearLayers();
+ editor._editing.disableEdit();
+ editor._editing = null;
+ editor._get_geometries_next_time = true;
+ }
+ if (editor._creating) {
+ editor._creating = false;
+ editor.map.editTools.stopDrawing();
+ }
+ },
+ _canceled_creating: function (e) {
+ // called after we canceled creating so we can remove the temporary layer.
+ if (!editor._creating) {
+ e.layer.remove();
+ }
+ },
+ _click_editing_layer: function(e) {
+ // click callback for a currently edited layer. create a hole on ctrl+click.
+ if ((e.originalEvent.ctrlKey || e.originalEvent.metaKey)) {
+ if (e.target.feature.geometry.type == 'Polygon') {
this.editor.newHole(e.latlng);
}
}
},
-
- start_creating: function (feature_type) {
- if (editor._creating !== null || editor._editing !== null) return;
- editor._highlight_layer.clearLayers();
- editor._creating = feature_type;
- var options = editor.feature_types[feature_type];
- if (options.geomtype == 'polygon') {
- editor.map.editTools.startPolygon(null, options);
- } else if (options.geomtype == 'polyline') {
- editor.map.editTools.startPolyline(null, options);
- }
- $('#cancel-drawing').show();
- $('#start-drawing').hide();
- $('body').removeClass('controls');
- },
- cancel_creating: function () {
- if (editor._creating === null || editor._editing !== null) return;
- editor.map.editTools.stopDrawing();
- editor._creating = null;
- $('#cancel-drawing').hide();
- },
- _canceled_creating: function (e) {
- if (editor._creating !== null && editor._editing === null) {
- e.layer.remove();
- $('#start-drawing').show();
- }
- },
- done_creating: function(e) {
- if (editor._creating !== null && editor._editing === null) {
+ _done_creating: function(e) {
+ // called when creating is completed (by clicking on the last point). fills in the form and switches to editing.
+ if (editor._creating) {
+ editor._creating = false;
editor._editing = e.layer;
- editor._editing.disableEdit();
- editor.map.fitBounds(editor._editing.getBounds());
-
- $('#cancel-drawing').hide();
- var path = '/editor/features/' + editor._creating + '/add/';
- $('#mapeditcontrols').removeClass('list');
- $('body').addClass('controls');
- $('#mapeditdetail').load(path, editor.edit_form_loaded);
+ editor._editing.addTo(editor._editing_layer);
+ editor._editin.on('click', editor._click_editing_layer);
+ editor._update_editing();
}
},
-
- start_editing: function (name) {
- if (editor._creating !== null || editor._editing !== null) return;
- editor._highlight_layer.clearLayers();
- editor._editing = editor.features[name].layer;
- var path = '/editor/features/'+ editor._editing.feature.properties.feature_type +'/edit/' + name + '/';
- $('#mapeditcontrols').removeClass('list');
- $('#mapeditdetail').load(path, editor.edit_form_loaded);
- $('body').addClass('controls');
- },
- edit_form_loaded: function() {
- $('#mapeditcontrols').addClass('detail');
- $('#id_level').val(editor._level);
- if ($('#id_geometry').length) {
- $('#id_geometry').val(JSON.stringify(editor._editing.toGeoJSON().geometry));
- editor._editing.enableEdit();
- }
- if (editor._editing.options.geomtype == 'polygon') {
- editor._editing.on('click', function (e) {
- if ((e.originalEvent.ctrlKey || e.originalEvent.metaKey) && this.editEnabled()) {
- this.editor.newHole(e.latlng);
- }
- });
- }
- },
- update_editing: function () {
+ _update_editing: function () {
+ // called if the temporary drawing layer changes. if we are in editing mode (not creating), update the form.
if (editor._editing !== null) {
$('#id_geometry').val(JSON.stringify(editor._editing.toGeoJSON().geometry));
}
},
- cancel_editing: function() {
- if (editor._editing !== null) {
- if (editor._creating !== null) {
- editor._editing.remove();
- }
- editor._editing = null;
- editor._creating = null;
- $('#mapeditcontrols').removeClass('detail');
- $('#mapeditdetail').html('');
- editor.get_features();
- }
+
+ // sidebar
+ sidebar_location: null,
+ init_sidebar: function() {
+ // init the sidebar. sed listeners for form submits and link clicks
+ $('#mapeditcontrols').on('click', 'a[href]', editor._sidebar_link_click)
+ .on('click', 'button[type=submit]', editor._sidebar_submit_btn_click)
+ .on('submit', 'form', editor._sidebar_submit);;
+
+ editor.sidebar_get('mapitemtypes/'+String(editor._level)+'/');
},
- submit_editing_btn_click: function(e) {
+ sidebar_get: function(location) {
+ // load a new page into the sidebar using a GET request
+ editor._sidebar_unload();
+ $.get(location, editor._sidebar_loaded);
+ },
+ _sidebar_unload: function(location) {
+ // unload the sidebar. called on sidebar_get and form submit.
+ $('#mapeditcontrols').html('').addClass('loading');
+ editor._unhighlight_geometry();
+ editor._cancel_editing();
+ },
+ _sidebar_loaded: function(data) {
+ // sidebar was loaded. load the content. check if there are any redirects. call _check_start_editing.
+ var content = $(data);
+ var mapeditcontrols = $('#mapeditcontrols');
+ mapeditcontrols.html(content).removeClass('loading');
+
+ var redirect = mapeditcontrols.find('form[name=redirect]');
+ if (redirect.length) {
+ redirect.submit();
+ return;
+ }
+
+ redirect = $('span[data-redirect]');
+ if (redirect.length) {
+ editor.sidebar_get(redirect.attr('data-redirect').replace('LEVEL', editor._level));
+ return;
+ }
+
+ editor._check_start_editing();
+ },
+ _sidebar_link_click: function(e) {
+ // listener for link-clicks in the sidebar.
+ e.preventDefault();
+ var href = $(this).attr('href');
+ if ($(this).is('[data-insert-level]')) {
+ href = href.replace('LEVEL', editor._level);
+ }
+ editor.sidebar_get(href);
+ },
+ _sidebar_submit_btn_click: function(e) {
+ // listener for submit-button-clicks in the sidebar, so the submit event will know which button submitted.
$(this).closest('form').data('btn', $(this)).clearQueue().delay(300).queue(function() {
$(this).data('button', null);
});
},
- submit_editing: function(e) {
+ _sidebar_submit: function(e) {
+ // listener for form submits in the sidebar.
if ($(this).attr('name') == 'redirect') return;
e.preventDefault();
+ editor._sidebar_unload();
var data = $(this).serialize();
var btn = $(this).data('btn');
if (btn !== undefined && btn !== null && $(btn).is('[name]')) {
data += '&'+$('').attr('name', $(btn).attr('name')).val($(btn).val()).serialize();
}
var action = $(this).attr('action');
- $('#mapeditcontrols').removeClass('detail');
- $('#mapeditdetail').html('');
- editor._editing.disableEdit();
- $.post(action, data, function (data) {
- var content = $(data);
- if ($('
+
+
+{% if new %}New{% else %}Edit{% endif %} {{ feature_type.title }}
-
diff --git a/src/c3nav/editor/templates/editor/map.html b/src/c3nav/editor/templates/editor/map.html
index 09057585..411b96a2 100644
--- a/src/c3nav/editor/templates/editor/map.html
+++ b/src/c3nav/editor/templates/editor/map.html
@@ -12,22 +12,7 @@
{% endblock %}
{% block content %}
-{% if new %}New{% else %}Edit{% endif %} {{ mapitem_type.title }}
+
diff --git a/src/c3nav/editor/templates/editor/feature_delete.html b/src/c3nav/editor/templates/editor/mapitem_delete.html
similarity index 70%
rename from src/c3nav/editor/templates/editor/feature_delete.html
rename to src/c3nav/editor/templates/editor/mapitem_delete.html
index 9eb8343d..e46e9d24 100644
--- a/src/c3nav/editor/templates/editor/feature_delete.html
+++ b/src/c3nav/editor/templates/editor/mapitem_delete.html
@@ -1,15 +1,15 @@
{% load bootstrap3 %}
-Delete {{ feature_type.title }}
+Delete {{ mapitem_type.title }}
+{% else %}
+
{% endif %}
diff --git a/src/c3nav/editor/templates/editor/mapitems.html b/src/c3nav/editor/templates/editor/mapitems.html
new file mode 100644
index 00000000..f5c33ff1
--- /dev/null
+++ b/src/c3nav/editor/templates/editor/mapitems.html
@@ -0,0 +1,22 @@
+{% load bootstrap3 %}
+
+{{ title }}{% if has_level %} on level {{ level }}{% endif %}
+
+
+ {% for item in items %}
+
+
+{% if has_level %}
+
+{% else %}
+
+{% endif %}
diff --git a/src/c3nav/editor/templates/editor/mapitemtypes.html b/src/c3nav/editor/templates/editor/mapitemtypes.html
new file mode 100644
index 00000000..3989e314
--- /dev/null
+++ b/src/c3nav/editor/templates/editor/mapitemtypes.html
@@ -0,0 +1,19 @@
+{% load bootstrap3 %}
+
+
+
+ {% endfor %}
+
+{{ item.name }}
+ Edit
+ Mapitem types
+
+