rebuild editor
This commit is contained in:
parent
769343f78a
commit
19da48c915
16 changed files with 457 additions and 414 deletions
|
@ -13,7 +13,7 @@ from c3nav.mapdata.models.geometry import Area, Building, Door, Obstacle
|
||||||
from c3nav.mapdata.permissions import get_unlocked_packages
|
from c3nav.mapdata.permissions import get_unlocked_packages
|
||||||
|
|
||||||
|
|
||||||
class FeatureFormMixin(ModelForm):
|
class MapitemFormMixin(ModelForm):
|
||||||
def __init__(self, *args, request=None, **kwargs):
|
def __init__(self, *args, request=None, **kwargs):
|
||||||
self.request = request
|
self.request = request
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -73,13 +73,13 @@ class FeatureFormMixin(ModelForm):
|
||||||
self.titles = titles
|
self.titles = titles
|
||||||
|
|
||||||
|
|
||||||
def create_editor_form(feature_model, add_fields=None):
|
def create_editor_form(mapdata_model, add_fields=None):
|
||||||
class EditorForm(FeatureFormMixin, ModelForm):
|
class EditorForm(MapitemFormMixin, ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = feature_model
|
model = mapdata_model
|
||||||
fields = ['name', 'package', 'level', 'geometry'] + (add_fields if add_fields is not None else [])
|
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():
|
def create_editor_forms():
|
||||||
|
|
|
@ -30,46 +30,39 @@ body {
|
||||||
|
|
||||||
|
|
||||||
#mapeditcontrols {
|
#mapeditcontrols {
|
||||||
position:absolute;
|
position: absolute;
|
||||||
top:54px;
|
top: 54px;
|
||||||
bottom:0;
|
bottom: 0;
|
||||||
padding:0;
|
width: 350px;
|
||||||
width:350px;
|
right: 0;
|
||||||
right:0;
|
overflow: auto;
|
||||||
overflow:hidden;
|
padding:8px;
|
||||||
|
}
|
||||||
|
#mapeditcontrols.loading {
|
||||||
background-image:url('/static/img/loader.gif');
|
background-image:url('/static/img/loader.gif');
|
||||||
background-repeat:no-repeat;
|
background-repeat:no-repeat;
|
||||||
background-position:center;
|
background-position:center;
|
||||||
}
|
}
|
||||||
#mapeditcontrols > div {
|
#mapeditcontrols h3 {
|
||||||
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 {
|
|
||||||
margin-top:5px;
|
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 {
|
#mapeditlist legend .btn {
|
||||||
padding-left:5.5px;
|
padding-left:5.5px;
|
||||||
padding-right:5.5px;
|
padding-right:5.5px;
|
||||||
|
@ -89,36 +82,6 @@ legend {
|
||||||
.leaflet-editable-drawing .leaflet-overlay-pane .leaflet-interactive {
|
.leaflet-editable-drawing .leaflet-overlay-pane .leaflet-interactive {
|
||||||
cursor:crosshair;
|
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 {
|
#map .leaflet-overlay-pane .c3nav-highlight {
|
||||||
pointer-events:none;
|
pointer-events:none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,11 @@ editor = {
|
||||||
editable: true,
|
editable: true,
|
||||||
closePopupOnClick: false
|
closePopupOnClick: false
|
||||||
});
|
});
|
||||||
|
editor.map.on('click', function (e) {
|
||||||
|
editor.map.doubleClickZoom.enable();
|
||||||
|
});
|
||||||
|
|
||||||
L.control.scale({imperial: false}).addTo(editor.map);
|
L.control.scale({imperial: false}).addTo(editor.map);
|
||||||
editor._highlight_layer = L.layerGroup().addTo(editor.map);
|
|
||||||
|
|
||||||
$('#show_map').click(function() {
|
$('#show_map').click(function() {
|
||||||
$('body').removeClass('controls');
|
$('body').removeClass('controls');
|
||||||
|
@ -24,50 +26,17 @@ editor = {
|
||||||
$('body').addClass('controls');
|
$('body').addClass('controls');
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.get_feature_types();
|
editor.init_geometries();
|
||||||
|
editor.init_sidebar();
|
||||||
editor.get_packages();
|
editor.get_packages();
|
||||||
editor.get_sources();
|
editor.get_sources();
|
||||||
},
|
|
||||||
|
|
||||||
_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(
|
|
||||||
$('<div class="feature_list">').attr('name', feature_type.name)
|
|
||||||
);
|
|
||||||
dropdown.append(
|
|
||||||
$('<li>').attr('data-name', feature_type.name).append(
|
|
||||||
$('<a href="#">').text(feature_type.title_plural)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
editor.set_current_feature_type(editor.feature_types_order[0]);
|
|
||||||
editor.get_levels();
|
editor.get_levels();
|
||||||
});
|
|
||||||
},
|
|
||||||
set_current_feature_type: function(feature_type) {
|
|
||||||
editor._feature_type = feature_type;
|
|
||||||
$('.feature_list').hide();
|
|
||||||
$('.feature_list[name='+feature_type+']').show();
|
|
||||||
$('#current_featuretype_title').text(editor.feature_types[feature_type].title_plural);
|
|
||||||
$('#create_featuretype_title').text(editor.feature_types[feature_type].title);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// packages
|
||||||
packages: {},
|
packages: {},
|
||||||
get_packages: function () {
|
get_packages: function () {
|
||||||
|
// load packages
|
||||||
$.getJSON('/api/packages/', function (packages) {
|
$.getJSON('/api/packages/', function (packages) {
|
||||||
var bounds = [[0, 0], [0, 0]];
|
var bounds = [[0, 0], [0, 0]];
|
||||||
var pkg;
|
var pkg;
|
||||||
|
@ -83,8 +52,10 @@ editor = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// sources
|
||||||
sources: {},
|
sources: {},
|
||||||
get_sources: function () {
|
get_sources: function () {
|
||||||
|
// load sources
|
||||||
$.getJSON('/api/sources/', function (sources) {
|
$.getJSON('/api/sources/', function (sources) {
|
||||||
var layers = {};
|
var layers = {};
|
||||||
var source;
|
var source;
|
||||||
|
@ -98,10 +69,11 @@ editor = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// levels
|
||||||
levels: {},
|
levels: {},
|
||||||
_level: null,
|
_level: null,
|
||||||
level_feature_layers: {},
|
|
||||||
get_levels: function () {
|
get_levels: function () {
|
||||||
|
// load levels and set the lowest one afterwards
|
||||||
$.getJSON('/api/levels/?ordering=-altitude', function (levels) {
|
$.getJSON('/api/levels/?ordering=-altitude', function (levels) {
|
||||||
L.LevelControl = L.Control.extend({
|
L.LevelControl = L.Control.extend({
|
||||||
options: {
|
options: {
|
||||||
|
@ -127,284 +99,299 @@ editor = {
|
||||||
editor.set_current_level($(this).attr('name'));
|
editor.set_current_level($(this).attr('name'));
|
||||||
});
|
});
|
||||||
|
|
||||||
var level, feature_type;
|
|
||||||
for (var i = 0; i < levels.length; i++) {
|
|
||||||
level = levels[i];
|
|
||||||
editor.levels[level.name] = level;
|
|
||||||
editor.level_feature_layers[level.name] = {};
|
|
||||||
for (var j = 0; j < editor.feature_types_order.length; j++) {
|
|
||||||
feature_type = editor.feature_types_order[j];
|
|
||||||
editor.level_feature_layers[level.name][feature_type] = L.layerGroup();
|
|
||||||
$('.feature_list[name='+feature_type+']').append(
|
|
||||||
$('<ul class="feature_level_list">').attr('data-level', level.name)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
editor.set_current_level(levels[levels.length - 1].name);
|
editor.set_current_level(levels[levels.length - 1].name);
|
||||||
editor.init_features();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
set_current_level: function(level_name) {
|
set_current_level: function(level_name) {
|
||||||
if (editor._creating !== null || editor._editing !== null) return;
|
// sets the current level if the sidebar allows it
|
||||||
for (var i = 0; i < editor.feature_types_order.length; i++) {
|
var level_switch = $('#mapeditcontrols ').find('[data-level-switch]');
|
||||||
if (editor._level !== null) {
|
if (level_switch.length === 0) return;
|
||||||
editor.level_feature_layers[editor._level][editor.feature_types_order[i]].remove();
|
|
||||||
}
|
|
||||||
editor.level_feature_layers[level_name][editor.feature_types_order[i]].addTo(editor.map);
|
|
||||||
}
|
|
||||||
editor._level = level_name;
|
editor._level = level_name;
|
||||||
$('.leaflet-levels .current').removeClass('current');
|
$('.leaflet-levels .current').removeClass('current');
|
||||||
$('.leaflet-levels a[name='+level_name+']').addClass('current');
|
$('.leaflet-levels a[name='+level_name+']').addClass('current');
|
||||||
$('.feature_level_list').hide();
|
editor.get_geometries();
|
||||||
$('.feature_level_list[data-level='+level_name+']').show();
|
|
||||||
},
|
|
||||||
|
|
||||||
_creating: null,
|
var level_switch_href = level_switch.attr('data-level-switch');
|
||||||
_editing: null,
|
if (level_switch_href) {
|
||||||
init_features: function () {
|
editor.sidebar_get(level_switch_href.replace('LEVEL', level_name));
|
||||||
$('#start-drawing').click(editor._click_start_drawing);
|
|
||||||
$('#cancel-drawing').click(editor.cancel_creating);
|
|
||||||
|
|
||||||
$('#mapeditlist').on('mouseenter', '.feature_level_list li', editor._hover_feature_detail)
|
|
||||||
.on('mouseleave', '.feature_level_list li', editor._unhover_feature_detail)
|
|
||||||
.on('click', '.feature_level_list li', editor._click_feature_detail);
|
|
||||||
|
|
||||||
editor.map.on('editable:drawing:commit', editor.done_creating);
|
|
||||||
editor.map.on('editable:editing', editor.update_editing);
|
|
||||||
editor.map.on('editable:drawing:cancel', editor._canceled_creating);
|
|
||||||
|
|
||||||
$('#mapeditdetail').on('click', '#btn_editing_cancel', editor.cancel_editing)
|
|
||||||
.on('click', 'button[type=submit]', editor.submit_editing_btn_click)
|
|
||||||
.on('submit', 'form', editor.submit_editing)
|
|
||||||
|
|
||||||
|
|
||||||
editor.get_features();
|
|
||||||
},
|
|
||||||
|
|
||||||
features: {},
|
|
||||||
get_features: function () {
|
|
||||||
$.getJSON('/api/features/?ordering=name', function(all_features) {
|
|
||||||
$('.feature_level_list li').remove();
|
|
||||||
var feature_type, features, feature, layergroup;
|
|
||||||
for (var j = 0; j < editor.feature_types_order.length; j++) {
|
|
||||||
feature_type = editor.feature_types_order[j];
|
|
||||||
for (var level in editor.levels) {
|
|
||||||
editor.level_feature_layers[level][feature_type].clearLayers();
|
|
||||||
}
|
}
|
||||||
features = all_features[editor.feature_types[feature_type].endpoint]
|
},
|
||||||
|
|
||||||
for (var i = 0; i < features.length; i++) {
|
// geometries
|
||||||
feature = features[i];
|
_geometries_layer: null,
|
||||||
layergroup = L.geoJSON({
|
_highlight_layer: null,
|
||||||
|
_editing_layer: null,
|
||||||
|
_get_geometries_next_time: false,
|
||||||
|
_geometries: {},
|
||||||
|
_creating: false,
|
||||||
|
_editing: null,
|
||||||
|
init_geometries: function () {
|
||||||
|
// init geometries and edit listeners
|
||||||
|
editor._highlight_layer = L.layerGroup().addTo(editor.map);
|
||||||
|
editor._editing_layer = L.layerGroup().addTo(editor.map);
|
||||||
|
|
||||||
|
$('#mapeditcontrols').on('mouseenter', '.itemtable tr[name]', editor._hover_mapitem_row)
|
||||||
|
.on('mouseleave', '.itemtable tr[name]', editor._unhighlight_geometry);
|
||||||
|
|
||||||
|
editor.map.on('editable:drawing:commit', editor._done_creating);
|
||||||
|
editor.map.on('editable:editing', editor._update_editing);
|
||||||
|
editor.map.on('editable:drawing:cancel', editor._canceled_creating);
|
||||||
|
},
|
||||||
|
get_geometries: function () {
|
||||||
|
// reload geometries of current level
|
||||||
|
editor._geometries = {};
|
||||||
|
if (editor._geometries_layer !== null) {
|
||||||
|
editor.map.removeLayer(editor._geometries_layer);
|
||||||
|
}
|
||||||
|
$.getJSON('/api/geometries/?level='+String(editor._level), function(geometries) {
|
||||||
|
editor._geometries_layer = L.geoJSON(geometries, {
|
||||||
|
style: editor._get_geometry_style,
|
||||||
|
onEachFeature: editor._register_geojson_feature,
|
||||||
|
});
|
||||||
|
|
||||||
|
editor._geometries_layer.addTo(editor.map);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
_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)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 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_geometry_layer: function (e) {
|
||||||
|
// hover callback for a geometry layer
|
||||||
|
editor._highlight_geometry(e.target.feature.properties.type, e.target.feature.properties.name);
|
||||||
|
},
|
||||||
|
_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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_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',
|
||||||
|
weight: 3,
|
||||||
|
opacity: 0.7,
|
||||||
|
fillOpacity: 0,
|
||||||
|
className: 'c3nav-highlight'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}).addTo(editor._highlight_layer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_unhighlight_geometry: function() {
|
||||||
|
// unhighlight whatever is highlighted currently
|
||||||
|
editor._highlight_layer.clearLayers();
|
||||||
|
$('.itemtable .highlight').removeClass('highlight');
|
||||||
|
},
|
||||||
|
|
||||||
|
// 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',
|
type: 'Feature',
|
||||||
geometry: feature.geometry,
|
geometry: JSON.parse(geometry_field.val()),
|
||||||
properties: {
|
properties: {
|
||||||
name: feature.name,
|
type: mapitem_type,
|
||||||
feature_type: feature_type
|
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
style: editor._get_feature_style
|
style: editor._get_geometry_style
|
||||||
}).on('mouseover', editor._hover_feature_layer)
|
}).getLayers()[0];
|
||||||
.on('mouseout', editor._unhover_feature_layer)
|
editor._editing.on('click', editor._click_editing_layer);
|
||||||
.on('click', editor._click_feature_layer)
|
editor._editing.addTo(editor._editing_layer);
|
||||||
.addTo(editor.level_feature_layers[feature.level][feature_type]);
|
editor._editing.enableEdit();
|
||||||
feature.layer = layergroup.getLayers()[0];
|
} else if (form.is('[data-geomtype]')) {
|
||||||
editor.features[feature.name] = feature;
|
// create new geometry
|
||||||
|
var geomtype = form.attr('data-geomtype');
|
||||||
|
|
||||||
$('.feature_list[name=' + feature_type + '] > [data-level=' + feature.level + ']').append(
|
var options = editor._get_mapitem_type_style(mapitem_type);
|
||||||
$('<li>').attr('name', feature.name).append(
|
if (geomtype == 'polygon') {
|
||||||
$('<p>').text(feature.title).append(' ').append(
|
|
||||||
$('<em>').text(feature.name)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$('#start-drawing').show();
|
|
||||||
$('#mapeditcontrols').addClass('list');
|
|
||||||
editor.set_current_level(editor._level);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
_get_feature_style: function (feature) {
|
|
||||||
return editor.feature_types[feature.properties.feature_type];
|
|
||||||
},
|
|
||||||
|
|
||||||
_click_start_drawing: function (e) {
|
|
||||||
editor.start_creating(editor._feature_type);
|
|
||||||
},
|
|
||||||
_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);
|
|
||||||
},
|
|
||||||
_unhover_feature_detail: function () {
|
|
||||||
editor._highlight_layer.clearLayers();
|
|
||||||
},
|
|
||||||
_click_feature_detail: function() {
|
|
||||||
editor.start_editing($(this).attr('name'));
|
|
||||||
},
|
|
||||||
|
|
||||||
_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(), {
|
|
||||||
style: function() {
|
|
||||||
return {
|
|
||||||
color: '#FFFFDD',
|
|
||||||
weight: 3,
|
|
||||||
opacity: 0.7,
|
|
||||||
fillOpacity: 0,
|
|
||||||
className: 'c3nav-highlight'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}).addTo(editor._highlight_layer);
|
|
||||||
}
|
|
||||||
$('.feature_list li[name='+e.layer.feature.properties.name+']').addClass('hover');
|
|
||||||
},
|
|
||||||
_unhover_feature_layer: function (e) {
|
|
||||||
editor._highlight_layer.clearLayers();
|
|
||||||
$('.feature_list .hover').removeClass('hover');
|
|
||||||
},
|
|
||||||
_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') {
|
|
||||||
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);
|
editor.map.editTools.startPolygon(null, options);
|
||||||
} else if (options.geomtype == 'polyline') {
|
} else if (geomtype == 'polyline') {
|
||||||
editor.map.editTools.startPolyline(null, options);
|
editor.map.editTools.startPolyline(null, options);
|
||||||
}
|
}
|
||||||
$('#cancel-drawing').show();
|
editor._creating = true;
|
||||||
$('#start-drawing').hide();
|
$('#id_level').val(editor._level);
|
||||||
$('body').removeClass('controls');
|
}
|
||||||
|
} else if (editor._get_geometries_next_time) {
|
||||||
|
editor.get_geometries();
|
||||||
|
editor._get_geometries_next_time = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
cancel_creating: function () {
|
_cancel_editing: function() {
|
||||||
if (editor._creating === null || editor._editing !== null) return;
|
// 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();
|
editor.map.editTools.stopDrawing();
|
||||||
editor._creating = null;
|
}
|
||||||
$('#cancel-drawing').hide();
|
|
||||||
},
|
},
|
||||||
_canceled_creating: function (e) {
|
_canceled_creating: function (e) {
|
||||||
if (editor._creating !== null && editor._editing === null) {
|
// called after we canceled creating so we can remove the temporary layer.
|
||||||
|
if (!editor._creating) {
|
||||||
e.layer.remove();
|
e.layer.remove();
|
||||||
$('#start-drawing').show();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
done_creating: function(e) {
|
_click_editing_layer: function(e) {
|
||||||
if (editor._creating !== null && editor._editing === null) {
|
// click callback for a currently edited layer. create a hole on ctrl+click.
|
||||||
editor._editing = e.layer;
|
if ((e.originalEvent.ctrlKey || e.originalEvent.metaKey)) {
|
||||||
editor._editing.disableEdit();
|
if (e.target.feature.geometry.type == 'Polygon') {
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
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);
|
this.editor.newHole(e.latlng);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
update_editing: function () {
|
_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.addTo(editor._editing_layer);
|
||||||
|
editor._editin.on('click', editor._click_editing_layer);
|
||||||
|
editor._update_editing();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_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) {
|
if (editor._editing !== null) {
|
||||||
$('#id_geometry').val(JSON.stringify(editor._editing.toGeoJSON().geometry));
|
$('#id_geometry').val(JSON.stringify(editor._editing.toGeoJSON().geometry));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cancel_editing: function() {
|
|
||||||
if (editor._editing !== null) {
|
// sidebar
|
||||||
if (editor._creating !== null) {
|
sidebar_location: null,
|
||||||
editor._editing.remove();
|
init_sidebar: function() {
|
||||||
}
|
// init the sidebar. sed listeners for form submits and link clicks
|
||||||
editor._editing = null;
|
$('#mapeditcontrols').on('click', 'a[href]', editor._sidebar_link_click)
|
||||||
editor._creating = null;
|
.on('click', 'button[type=submit]', editor._sidebar_submit_btn_click)
|
||||||
$('#mapeditcontrols').removeClass('detail');
|
.on('submit', 'form', editor._sidebar_submit);;
|
||||||
$('#mapeditdetail').html('');
|
|
||||||
editor.get_features();
|
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).closest('form').data('btn', $(this)).clearQueue().delay(300).queue(function() {
|
||||||
$(this).data('button', null);
|
$(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;
|
if ($(this).attr('name') == 'redirect') return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
editor._sidebar_unload();
|
||||||
var data = $(this).serialize();
|
var data = $(this).serialize();
|
||||||
var btn = $(this).data('btn');
|
var btn = $(this).data('btn');
|
||||||
if (btn !== undefined && btn !== null && $(btn).is('[name]')) {
|
if (btn !== undefined && btn !== null && $(btn).is('[name]')) {
|
||||||
data += '&'+$('<input>').attr('name', $(btn).attr('name')).val($(btn).val()).serialize();
|
data += '&'+$('<input>').attr('name', $(btn).attr('name')).val($(btn).val()).serialize();
|
||||||
}
|
}
|
||||||
var action = $(this).attr('action');
|
var action = $(this).attr('action');
|
||||||
$('#mapeditcontrols').removeClass('detail');
|
$.post(action, data, editor._sidebar_loaded);
|
||||||
$('#mapeditdetail').html('');
|
|
||||||
editor._editing.disableEdit();
|
|
||||||
$.post(action, data, function (data) {
|
|
||||||
var content = $(data);
|
|
||||||
if ($('<div>').append(content).find('form').length > 0) {
|
|
||||||
$('#mapeditcontrols').addClass('detail');
|
|
||||||
$('#mapeditdetail').html(content).find('form[name=redirect]').submit();
|
|
||||||
if ($('#id_geometry').length) {
|
|
||||||
editor._editing.enableEdit();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (editor._creating !== null) {
|
|
||||||
editor._editing.remove();
|
|
||||||
}
|
|
||||||
editor._editing = null;
|
|
||||||
editor._creating = null;
|
|
||||||
editor.get_features();
|
|
||||||
$('body').removeClass('controls');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if ($('#mapeditlist').length) {
|
if ($('#mapeditcontrols').length) {
|
||||||
editor.init();
|
editor.init();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
{% load bootstrap3 %}
|
|
||||||
|
|
||||||
<h3>{% if new %}New{% else %}Edit{% endif %} {{ feature_type.title }}</h3>
|
|
||||||
<form action="{{ path }}" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{% bootstrap_form form %}
|
|
||||||
{% buttons %}
|
|
||||||
{% if not new %}
|
|
||||||
<button type="submit" name="delete" value="1" class="btn btn-danger">
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
<button type="submit" class="btn btn-primary pull-right">
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
<button type="button" id="btn_editing_cancel" class="btn {% if new %}btn-danger{% else %}btn-default pull-right{% endif %}">
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
{% endbuttons %}
|
|
||||||
</form>
|
|
|
@ -12,22 +12,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="map"></div>
|
<div id="map"></div>
|
||||||
<div id="mapeditcontrols">
|
<div id="mapeditcontrols" class="loading">
|
||||||
<div id="mapeditlist">
|
<span data-level-switch="{% url 'editor.mapitemtypes' level='LEVEL' %}"></span>
|
||||||
<fieldset>
|
|
||||||
<legend>
|
|
||||||
<div class="dropdown">
|
|
||||||
<span id="featuretype_dropdown_btn" class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
||||||
<span id="current_featuretype_title"></span>
|
|
||||||
<span class="caret"></span>
|
|
||||||
</span>
|
|
||||||
<ul class="dropdown-menu" id="featuretype_dropdown" aria-labelledby="featuretype_dropdown_btn"></ul>
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-default btn-xs pull-right" id="start-drawing">create new <span id="create_featuretype_title"></span></button>
|
|
||||||
<button class="btn btn-danger btn-xs pull-right" id="cancel-drawing">cancel creating</button>
|
|
||||||
</legend>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
<div id="mapeditdetail"></div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
21
src/c3nav/editor/templates/editor/mapitem.html
Normal file
21
src/c3nav/editor/templates/editor/mapitem.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
|
<h3>{% if new %}New{% else %}Edit{% endif %} {{ mapitem_type.title }}</h3>
|
||||||
|
<form action="{{ path }}" method="post" data-mapitem-type="{{ mapitem_type }}"{% if not new %} data-name="{{ name }}"{% elif geomtype %} data-geomtype="{{ geomtype }}"{% endif %}>
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
{% buttons %}
|
||||||
|
{% if not new %}
|
||||||
|
<button type="submit" name="delete" value="1" class="btn btn-danger">
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
<button type="submit" class="btn btn-primary pull-right">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
<a class="btn {% if new %}btn-danger{% else %}btn-default pull-right{% endif %} cancel-btn"
|
||||||
|
href="{% url 'editor.mapitems.level' mapitem_type=mapitem_type level='LEVEL' %}" data-insert-level>
|
||||||
|
Cancel
|
||||||
|
</a>
|
||||||
|
{% endbuttons %}
|
||||||
|
</form>
|
|
@ -1,15 +1,15 @@
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
<h3>Delete {{ feature_type.title }}</h3>
|
<h3>Delete {{ mapitem_type.title }}</h3>
|
||||||
<form action="{{ path }}" method="post">
|
<form action="{{ path }}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p>Please confirm deleting: {{ name }}</p>
|
<p>Please confirm deleting: {{ name }}</p>
|
||||||
<input type="hidden" name="delete" value="1">
|
<input type="hidden" name="delete" value="1">
|
||||||
<input type="hidden" name="name" value="{{ name }}">
|
<input type="hidden" name="name" value="{{ name }}">
|
||||||
{% buttons %}
|
{% buttons %}
|
||||||
<button type="button" id="btn_editing_cancel" class="btn btn-default">
|
<a class="btn btn-default" href="{% url 'editor.mapitems' mapitem_type=mapitem_type level='LEVEL' %}" data-insert-level>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</a>
|
||||||
<button type="submit" name="delete_confirm" value="1" class="btn btn-danger pull-right">
|
<button type="submit" name="delete_confirm" value="1" class="btn btn-danger pull-right">
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
|
@ -6,4 +6,6 @@
|
||||||
<img src="{% static 'img/loader.gif' %}">
|
<img src="{% static 'img/loader.gif' %}">
|
||||||
Redirecting…
|
Redirecting…
|
||||||
</form>
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<span data-redirect="{% url 'editor.mapitems.level' mapitem_type=mapitem_type level='LEVEL' %}"></span>
|
||||||
{% endif %}
|
{% endif %}
|
22
src/c3nav/editor/templates/editor/mapitems.html
Normal file
22
src/c3nav/editor/templates/editor/mapitems.html
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
|
<h3>{{ title }}{% if has_level %} <small>on level {{ level }}</small>{% endif %}</h3>
|
||||||
|
<p><a href="{% url 'editor.mapitemtypes' level='LEVEL' %}" data-insert-level>« Back</a><p>
|
||||||
|
<p><a href="{% url 'editor.mapitems.add' mapitem_type=mapitem_type %}">Add new</a><p>
|
||||||
|
|
||||||
|
<table class="table table-condensed itemtable" data-mapitem-type="{{ mapitem_type }}">
|
||||||
|
<tbody>
|
||||||
|
{% for item in items %}
|
||||||
|
<tr name="{{ item.name }}">
|
||||||
|
<td>{{ item.name }}</td>
|
||||||
|
<td><a href="{% url 'editor.mapitems.edit' mapitem_type=mapitem_type name=item.name %}">Edit</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% if has_level %}
|
||||||
|
<span data-level-switch="{% url 'editor.mapitems.level' mapitem_type=mapitem_type level='LEVEL' %}"></span>
|
||||||
|
{% else %}
|
||||||
|
<span data-level-switch></span>
|
||||||
|
{% endif %}
|
19
src/c3nav/editor/templates/editor/mapitemtypes.html
Normal file
19
src/c3nav/editor/templates/editor/mapitemtypes.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
|
<h3>Mapitem types</h3>
|
||||||
|
|
||||||
|
<div class="list-group">
|
||||||
|
{% for mapitemtype in mapitemtypes %}
|
||||||
|
{% if mapitemtype.has_level %}
|
||||||
|
{% url 'editor.mapitems.level' mapitem_type=mapitemtype.name level=level as list_url %}
|
||||||
|
{% else %}
|
||||||
|
{% url 'editor.mapitems' mapitem_type=mapitemtype.name as list_url %}
|
||||||
|
{% endif %}
|
||||||
|
<a href="{{ list_url }}" class="list-group-item">
|
||||||
|
{% if mapitemtype.has_level %}<span class="badge">{{ mapitemtype.count }}</span>{% endif %}
|
||||||
|
{{ mapitemtype.title }}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span data-level-switch="{% url 'editor.mapitemtypes' level='LEVEL' %}"></span>
|
|
@ -1,12 +1,15 @@
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from c3nav.editor.views import edit_feature, finalize, oauth_callback
|
from c3nav.editor.views import edit_mapitem, finalize, list_mapitems, list_mapitemtypes, oauth_callback
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', TemplateView.as_view(template_name='editor/map.html'), name='editor.index'),
|
url(r'^$', TemplateView.as_view(template_name='editor/map.html'), name='editor.index'),
|
||||||
url(r'^features/(?P<feature_type>[^/]+)/add/$', edit_feature, name='editor.feature.add'),
|
url(r'^mapitemtypes/(?P<level>[^/]+)/$', list_mapitemtypes, name='editor.mapitemtypes'),
|
||||||
url(r'^features/(?P<feature_type>[^/]+)/edit/(?P<name>[^/]+)/$', edit_feature, name='editor.feature.edit'),
|
url(r'^mapitems/(?P<mapitem_type>[^/]+)/list/$', list_mapitems, name='editor.mapitems'),
|
||||||
|
url(r'^mapitems/(?P<mapitem_type>[^/]+)/list/(?P<level>[^/]+)/$', list_mapitems, name='editor.mapitems.level'),
|
||||||
|
url(r'^mapitems/(?P<mapitem_type>[^/]+)/add/$', edit_mapitem, name='editor.mapitems.add'),
|
||||||
|
url(r'^mapitems/(?P<mapitem_type>[^/]+)/edit/(?P<name>[^/]+)/$', edit_mapitem, name='editor.mapitems.edit'),
|
||||||
url(r'^finalize/$', finalize, name='editor.finalize'),
|
url(r'^finalize/$', finalize, name='editor.finalize'),
|
||||||
url(r'^oauth/(?P<hoster>[^/]+)/callback$', oauth_callback, name='editor.oauth.callback')
|
url(r'^oauth/(?P<hoster>[^/]+)/callback$', oauth_callback, name='editor.oauth.callback')
|
||||||
]
|
]
|
||||||
|
|
|
@ -3,95 +3,147 @@ from django.core import signing
|
||||||
from django.core.exceptions import PermissionDenied, SuspiciousOperation
|
from django.core.exceptions import PermissionDenied, SuspiciousOperation
|
||||||
from django.core.signing import BadSignature
|
from django.core.signing import BadSignature
|
||||||
from django.http.response import Http404
|
from django.http.response import Http404
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
|
|
||||||
from c3nav.editor.hosters import get_hoster_for_package, hosters
|
from c3nav.editor.hosters import get_hoster_for_package, hosters
|
||||||
from c3nav.mapdata.models import GEOMETRY_MAPITEM_TYPES
|
from c3nav.mapdata.models.base import MAPITEM_TYPES
|
||||||
from c3nav.mapdata.models.package import Package
|
from c3nav.mapdata.models.package import Package
|
||||||
from c3nav.mapdata.packageio.write import json_encode
|
from c3nav.mapdata.packageio.write import json_encode
|
||||||
from c3nav.mapdata.permissions import can_access_package
|
from c3nav.mapdata.permissions import can_access_package, filter_queryset_by_package_access
|
||||||
|
|
||||||
|
|
||||||
def edit_feature(request, feature_type, name=None):
|
def list_mapitemtypes(request, level):
|
||||||
model = GEOMETRY_MAPITEM_TYPES.get(feature_type)
|
def get_item_count(mapitemtype):
|
||||||
if model is None:
|
if not hasattr(mapitemtype, 'level'):
|
||||||
|
return 0
|
||||||
|
return filter_queryset_by_package_access(request, mapitemtype.objects.filter(level__name=level)).count()
|
||||||
|
|
||||||
|
return render(request, 'editor/mapitemtypes.html', {
|
||||||
|
'level': level,
|
||||||
|
'mapitemtypes': [
|
||||||
|
{
|
||||||
|
'name': name,
|
||||||
|
'title': mapitemtype._meta.verbose_name_plural,
|
||||||
|
'has_level': hasattr(mapitemtype, 'level'),
|
||||||
|
'count': get_item_count(mapitemtype),
|
||||||
|
} for name, mapitemtype in MAPITEM_TYPES.items()
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def list_mapitems(request, mapitem_type, level=None):
|
||||||
|
mapitemtype = MAPITEM_TYPES.get(mapitem_type)
|
||||||
|
if mapitemtype is None:
|
||||||
|
raise Http404('Unknown mapitemtype.')
|
||||||
|
|
||||||
|
if hasattr(mapitemtype, 'level') and level is None:
|
||||||
|
raise Http404('Missing level.')
|
||||||
|
elif not hasattr(mapitemtype, 'level') and level is not None:
|
||||||
|
raise redirect('editor.mapitems', mapitem_type=mapitem_type)
|
||||||
|
|
||||||
|
queryset = mapitemtype.objects.all()
|
||||||
|
if level is not None:
|
||||||
|
queryset = queryset.filter(level__name=level)
|
||||||
|
|
||||||
|
return render(request, 'editor/mapitems.html', {
|
||||||
|
'mapitem_type': mapitem_type,
|
||||||
|
'title': mapitemtype._meta.verbose_name_plural,
|
||||||
|
'has_level': level is not None,
|
||||||
|
'level': level,
|
||||||
|
'items': filter_queryset_by_package_access(request, queryset),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def edit_mapitem(request, mapitem_type, name=None):
|
||||||
|
mapitemtype = MAPITEM_TYPES.get(mapitem_type)
|
||||||
|
if mapitemtype is None:
|
||||||
raise Http404()
|
raise Http404()
|
||||||
|
|
||||||
feature = None
|
mapitem = None
|
||||||
if name is not None:
|
if name is not None:
|
||||||
# Edit existing feature
|
# Edit existing map item
|
||||||
feature = get_object_or_404(model, name=name)
|
mapitem = get_object_or_404(mapitemtype, name=name)
|
||||||
if not can_access_package(request, feature.package):
|
if not can_access_package(request, mapitem.package):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
|
new = mapitem is None
|
||||||
|
orig_name = mapitem.name if mapitem is not None else None
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
if feature is not None and request.POST.get('delete') == '1':
|
if mapitem is not None and request.POST.get('delete') == '1':
|
||||||
# Delete this feature!
|
# Delete this mapitem!
|
||||||
if request.POST.get('delete_confirm') == '1':
|
if request.POST.get('delete_confirm') == '1':
|
||||||
if not settings.DIRECT_EDITING:
|
if not settings.DIRECT_EDITING:
|
||||||
with translation.override('en'):
|
with translation.override('en'):
|
||||||
commit_msg = 'Deleted %s: %s' % (model._meta.verbose_name, feature.title)
|
commit_msg = 'Deleted %s: %s' % (mapitemtype._meta.verbose_name, mapitem.title)
|
||||||
return render(request, 'editor/feature_success.html', {
|
return render(request, 'editor/mapitem_success.html', {
|
||||||
'data': signing.dumps({
|
'data': signing.dumps({
|
||||||
'type': 'editor.edit',
|
'type': 'editor.edit',
|
||||||
'action': 'delete',
|
'action': 'delete',
|
||||||
'package_name': feature.package.name,
|
'package_name': mapitem.package.name,
|
||||||
'commit_id': feature.package.commit_id,
|
'commit_id': mapitem.package.commit_id,
|
||||||
'commit_msg': commit_msg,
|
'commit_msg': commit_msg,
|
||||||
'file_path': feature.get_filename(),
|
'file_path': mapitem.get_filename(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
feature.delete()
|
mapitem.delete()
|
||||||
return render(request, 'editor/feature_success.html', {})
|
return render(request, 'editor/mapitem_success.html', {
|
||||||
|
'mapitem_type': mapitem_type
|
||||||
|
})
|
||||||
|
|
||||||
return render(request, 'editor/feature_delete.html', {
|
return render(request, 'editor/mapitem_delete.html', {
|
||||||
'name': feature.name,
|
'name': mapitem.name,
|
||||||
'feature_type': feature_type,
|
'mapitem_type': mapitem_type,
|
||||||
'path': request.path
|
'path': request.path
|
||||||
})
|
})
|
||||||
|
|
||||||
form = model.EditorForm(instance=feature, data=request.POST, request=request)
|
form = mapitemtype.EditorForm(instance=mapitem, data=request.POST, request=request)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
# Update/create feature
|
# Update/create mapitem
|
||||||
commit_type = 'Created' if feature is None else 'Updated'
|
commit_type = 'Created' if mapitem is None else 'Updated'
|
||||||
action = 'create' if feature is None else 'edit'
|
action = 'create' if mapitem is None else 'edit'
|
||||||
feature = form.instance
|
mapitem = form.instance
|
||||||
|
|
||||||
if form.titles is not None:
|
if form.titles is not None:
|
||||||
feature.titles = {}
|
mapitem.titles = {}
|
||||||
for language, title in form.titles.items():
|
for language, title in form.titles.items():
|
||||||
if title:
|
if title:
|
||||||
feature.titles[language] = title
|
mapitem.titles[language] = title
|
||||||
|
|
||||||
if not settings.DIRECT_EDITING:
|
if not settings.DIRECT_EDITING:
|
||||||
content = json_encode(feature.tofile())
|
content = json_encode(mapitem.tofile())
|
||||||
with translation.override('en'):
|
with translation.override('en'):
|
||||||
commit_msg = '%s %s: %s' % (commit_type, model._meta.verbose_name, feature.title)
|
commit_msg = '%s %s: %s' % (commit_type, mapitemtype._meta.verbose_name, mapitem.title)
|
||||||
return render(request, 'editor/feature_success.html', {
|
return render(request, 'editor/mapitem_success.html', {
|
||||||
'data': signing.dumps({
|
'data': signing.dumps({
|
||||||
'type': 'editor.edit',
|
'type': 'editor.edit',
|
||||||
'action': action,
|
'action': action,
|
||||||
'package_name': feature.package.name,
|
'package_name': mapitem.package.name,
|
||||||
'commit_id': feature.package.commit_id,
|
'commit_id': mapitem.package.commit_id,
|
||||||
'commit_msg': commit_msg,
|
'commit_msg': commit_msg,
|
||||||
'file_path': feature.get_filename(),
|
'file_path': mapitem.get_filename(),
|
||||||
'content': content,
|
'content': content,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
feature.save()
|
mapitem.save()
|
||||||
|
|
||||||
return render(request, 'editor/feature_success.html', {})
|
return render(request, 'editor/mapitem_success.html', {
|
||||||
|
'mapitem_type': mapitem_type
|
||||||
|
})
|
||||||
else:
|
else:
|
||||||
form = model.EditorForm(instance=feature, request=request)
|
form = mapitemtype.EditorForm(instance=mapitem, request=request)
|
||||||
|
|
||||||
return render(request, 'editor/feature.html', {
|
return render(request, 'editor/mapitem.html', {
|
||||||
'form': form,
|
'form': form,
|
||||||
'feature_type': feature_type,
|
'mapitem_type': mapitem_type,
|
||||||
|
'has_geometry': hasattr(mapitemtype, 'geometry'),
|
||||||
|
'name': orig_name,
|
||||||
|
'geomtype': getattr(mapitemtype, 'geomtype', None),
|
||||||
'path': request.path,
|
'path': request.path,
|
||||||
'new': feature is None
|
'new': new
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ class GeometryViewSet(ViewSet):
|
||||||
else:
|
else:
|
||||||
types = [t for t in types if t in valid_types]
|
types = [t for t in types if t in valid_types]
|
||||||
|
|
||||||
levels = request.GET.getlist('levels')
|
levels = request.GET.getlist('level')
|
||||||
packages = request.GET.getlist('package')
|
packages = request.GET.getlist('package')
|
||||||
names = request.GET.getlist('name')
|
names = request.GET.getlist('name')
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from c3nav.mapdata.models.geometry import GEOMETRY_MAPITEM_TYPES # noqa
|
|
||||||
from .level import Level # noqa
|
from .level import Level # noqa
|
||||||
from .package import Package # noqa
|
from .package import Package # noqa
|
||||||
from .source import Source # noqa
|
from .source import Source # noqa
|
||||||
from .geometry import GeometryMapItem # noqa
|
from .geometry import GeometryMapItem, GEOMETRY_MAPITEM_TYPES # noqa
|
||||||
|
|
|
@ -1,10 +1,21 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models.base import ModelBase
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
MAPITEM_TYPES = OrderedDict()
|
||||||
|
|
||||||
class MapItem(models.Model):
|
|
||||||
|
class MapItemMeta(ModelBase):
|
||||||
|
def __new__(mcs, name, bases, attrs):
|
||||||
|
cls = super().__new__(mcs, name, bases, attrs)
|
||||||
|
if not cls._meta.abstract:
|
||||||
|
MAPITEM_TYPES[name.lower()] = cls
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
class MapItem(models.Model, metaclass=MapItemMeta):
|
||||||
name = models.SlugField(_('Name'), unique=True, max_length=50)
|
name = models.SlugField(_('Name'), unique=True, max_length=50)
|
||||||
package = models.ForeignKey('mapdata.Package', on_delete=models.CASCADE, verbose_name=_('map package'))
|
package = models.ForeignKey('mapdata.Package', on_delete=models.CASCADE, verbose_name=_('map package'))
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.base import ModelBase
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from shapely.geometry.geo import mapping, shape
|
from shapely.geometry.geo import mapping, shape
|
||||||
|
|
||||||
from c3nav.mapdata.fields import GeometryField
|
from c3nav.mapdata.fields import GeometryField
|
||||||
from c3nav.mapdata.models.base import MapItem
|
from c3nav.mapdata.models.base import MapItem, MapItemMeta
|
||||||
from c3nav.mapdata.utils import format_geojson
|
from c3nav.mapdata.utils import format_geojson
|
||||||
|
|
||||||
GEOMETRY_MAPITEM_TYPES = OrderedDict()
|
GEOMETRY_MAPITEM_TYPES = OrderedDict()
|
||||||
|
|
||||||
|
|
||||||
class GeometryMapItemMeta(ModelBase):
|
class GeometryMapItemMeta(MapItemMeta):
|
||||||
def __new__(mcs, name, bases, attrs):
|
def __new__(mcs, name, bases, attrs):
|
||||||
cls = super().__new__(mcs, name, bases, attrs)
|
cls = super().__new__(mcs, name, bases, attrs)
|
||||||
if not cls._meta.abstract:
|
if not cls._meta.abstract:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue