overlays
default geomtype, permission fix, name fix, remove ungrouped header, fix id filter
This commit is contained in:
parent
13cf207ee6
commit
934aa60be4
10 changed files with 96 additions and 41 deletions
|
@ -422,6 +422,7 @@ def create_editor_form(editor_model):
|
||||||
'color_ground_fill', 'color_obstacles_default_fill', 'color_obstacles_default_border',
|
'color_ground_fill', 'color_obstacles_default_fill', 'color_obstacles_default_border',
|
||||||
'stroke_color', 'stroke_width', 'fill_color', 'interactive', 'point_icon', 'extra_data',
|
'stroke_color', 'stroke_width', 'fill_color', 'interactive', 'point_icon', 'extra_data',
|
||||||
'show_label', 'show_geometry', 'external_url',
|
'show_label', 'show_geometry', 'external_url',
|
||||||
|
'show_label', 'show_geometry', 'external_url', 'default_geomtype',
|
||||||
]
|
]
|
||||||
field_names = [field.name for field in editor_model._meta.get_fields()
|
field_names = [field.name for field in editor_model._meta.get_fields()
|
||||||
if not field.one_to_many and not isinstance(field, ManyToManyRel)]
|
if not field.one_to_many and not isinstance(field, ManyToManyRel)]
|
||||||
|
|
|
@ -1370,6 +1370,7 @@ editor = {
|
||||||
}
|
}
|
||||||
form.addClass('creation-lock');
|
form.addClass('creation-lock');
|
||||||
const geomtypes = form.attr('data-geomtype').split(',');
|
const geomtypes = form.attr('data-geomtype').split(',');
|
||||||
|
const default_geomtype = form.attr('data-default-geomtype');
|
||||||
|
|
||||||
const startGeomEditing = (geomtype) => {
|
const startGeomEditing = (geomtype) => {
|
||||||
editor._creating_type = geomtype;
|
editor._creating_type = geomtype;
|
||||||
|
@ -1387,6 +1388,8 @@ editor = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let selected_geomtype = geomtypes[0];
|
||||||
|
|
||||||
if (geomtypes.length > 1) {
|
if (geomtypes.length > 1) {
|
||||||
const selector = $('<select id="geomtype-selector"></select>');
|
const selector = $('<select id="geomtype-selector"></select>');
|
||||||
const geomtypeNames = {
|
const geomtypeNames = {
|
||||||
|
@ -1395,13 +1398,18 @@ editor = {
|
||||||
point: 'Point'
|
point: 'Point'
|
||||||
}; // TODO: translations
|
}; // TODO: translations
|
||||||
for(const geomtype of geomtypes) {
|
for(const geomtype of geomtypes) {
|
||||||
selector.append(`<option value="${geomtype}">${geomtypeNames[geomtype]}</option>`);
|
const option = $(`<option value="${geomtype}">${geomtypeNames[geomtype]}</option>`);
|
||||||
|
if (geomtype === default_geomtype) {
|
||||||
|
option.attr('selected', true);
|
||||||
|
selected_geomtype = geomtype;
|
||||||
|
}
|
||||||
|
selector.append(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
selector.on('change', e => startGeomEditing(e.target.value));
|
selector.on('change', e => startGeomEditing(e.target.value));
|
||||||
form.prepend(selector);
|
form.prepend(selector);
|
||||||
}
|
}
|
||||||
startGeomEditing(geomtypes[0]);
|
startGeomEditing(selected_geomtype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h3>
|
</h3>
|
||||||
{% bootstrap_messages %}
|
{% bootstrap_messages %}
|
||||||
<form action="{{ path }}" method="post" {% if nozoom %}data-nozoom {% endif %}data-onbeforeunload {% if new %}data-new="{{ model_name }}" data-geomtype="{{ geomtype }}"{% else %}data-editing="{{ model_name }}-{{ pk }}"{% endif %}{% if access_restriction_select %} data-access-restriction-select{% endif %}>
|
<form action="{{ path }}" method="post" {% if nozoom %}data-nozoom {% endif %}data-onbeforeunload {% if new %}data-new="{{ model_name }}" data-geomtype="{{ geomtype }}" {% if default_geomtype %}data-default-geomtype="{{ default_geomtype }}{% endif %}"{% else %}data-editing="{{ model_name }}-{{ pk }}"{% endif %}{% if access_restriction_select %} data-access-restriction-select{% endif %}>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% bootstrap_form form %}
|
{% bootstrap_form form %}
|
||||||
{% buttons %}
|
{% buttons %}
|
||||||
|
|
|
@ -130,6 +130,7 @@ def overlay_feature_edit(request, level=None, overlay=None, pk=None):
|
||||||
'title': obj.title if obj else None,
|
'title': obj.title if obj else None,
|
||||||
'geometry_url': geometry_url,
|
'geometry_url': geometry_url,
|
||||||
'geomtype': 'polygon,linestring,point',
|
'geomtype': 'polygon,linestring,point',
|
||||||
|
'default_geomtype': overlay.default_geomtype,
|
||||||
}
|
}
|
||||||
|
|
||||||
space_id = None
|
space_id = None
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.1.3 on 2024-12-16 11:25
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mapdata', '0117_alter_dataoverlay_fill_color_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='dataoverlay',
|
||||||
|
name='default_geomtype',
|
||||||
|
field=models.CharField(blank=True, choices=[('polygon', 'Polygon'), ('line', 'Line'), ('point', 'Point')], max_length=255, null=True, verbose_name='default geometry type'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -13,10 +13,18 @@ from c3nav.mapdata.utils.json import format_geojson
|
||||||
|
|
||||||
|
|
||||||
class DataOverlay(TitledMixin, AccessRestrictionMixin, models.Model):
|
class DataOverlay(TitledMixin, AccessRestrictionMixin, models.Model):
|
||||||
|
class GeometryType(models.TextChoices):
|
||||||
|
POLYGON = "polygon", _("Polygon")
|
||||||
|
LINESTRING = "linestring", _("Line string")
|
||||||
|
POINT = "point", _("Point")
|
||||||
|
|
||||||
description = models.TextField(blank=True, verbose_name=_('Description'))
|
description = models.TextField(blank=True, verbose_name=_('Description'))
|
||||||
stroke_color = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('default stroke color'))
|
stroke_color = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('default stroke color'))
|
||||||
stroke_width = models.FloatField(blank=True, null=True, verbose_name=_('default stroke width'))
|
stroke_width = models.FloatField(blank=True, null=True, verbose_name=_('default stroke width'))
|
||||||
fill_color = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('default fill color'))
|
fill_color = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('default fill color'))
|
||||||
|
|
||||||
|
default_geomtype = models.CharField(max_length=255, blank=True, null=True, choices=GeometryType, verbose_name=_('default geometry type'))
|
||||||
|
|
||||||
pull_url = models.URLField(blank=True, null=True, verbose_name=_('pull URL'))
|
pull_url = models.URLField(blank=True, null=True, verbose_name=_('pull URL'))
|
||||||
pull_headers: dict[str, str] = SchemaField(schema=dict[str, str], null=True,
|
pull_headers: dict[str, str] = SchemaField(schema=dict[str, str], null=True,
|
||||||
verbose_name=_('headers for pull http request (JSON object)'))
|
verbose_name=_('headers for pull http request (JSON object)'))
|
||||||
|
|
|
@ -6,7 +6,8 @@ from pydantic import Field as APIField
|
||||||
|
|
||||||
from c3nav.api.exceptions import APIRequestValidationFailed
|
from c3nav.api.exceptions import APIRequestValidationFailed
|
||||||
from c3nav.api.schema import BaseSchema
|
from c3nav.api.schema import BaseSchema
|
||||||
from c3nav.mapdata.models import Level, LocationGroup, LocationGroupCategory, MapUpdate, Space, Door, Building
|
from c3nav.mapdata.models import Level, LocationGroup, LocationGroupCategory, MapUpdate, Space, Door, Building, \
|
||||||
|
DataOverlay
|
||||||
from c3nav.mapdata.models.access import AccessPermission
|
from c3nav.mapdata.models.access import AccessPermission
|
||||||
|
|
||||||
|
|
||||||
|
@ -145,7 +146,7 @@ class ByOverlayFilter(FilterSchema):
|
||||||
def validate(self, request):
|
def validate(self, request):
|
||||||
super().validate(request)
|
super().validate(request)
|
||||||
if self.overlay is not None:
|
if self.overlay is not None:
|
||||||
assert_valid_value(request, Level, "pk", {self.overlay})
|
assert_valid_value(request, DataOverlay, "pk", {self.overlay})
|
||||||
|
|
||||||
def filter_qs(self, request, qs: QuerySet) -> QuerySet:
|
def filter_qs(self, request, qs: QuerySet) -> QuerySet:
|
||||||
if self.overlay is not None:
|
if self.overlay is not None:
|
||||||
|
|
|
@ -6,6 +6,7 @@ from django.utils.translation import ngettext_lazy
|
||||||
from c3nav.mapdata.models import DataOverlay
|
from c3nav.mapdata.models import DataOverlay
|
||||||
from c3nav.mapdata.models.access import AccessPermission, AccessRestriction
|
from c3nav.mapdata.models.access import AccessPermission, AccessRestriction
|
||||||
from c3nav.mapdata.models.locations import Position
|
from c3nav.mapdata.models.locations import Position
|
||||||
|
from c3nav.mapdata.schemas.models import DataOverlaySchema
|
||||||
|
|
||||||
|
|
||||||
def get_user_data(request):
|
def get_user_data(request):
|
||||||
|
@ -32,16 +33,13 @@ def get_user_data(request):
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
result['title'] = request.user.username
|
result['title'] = request.user.username
|
||||||
|
|
||||||
# TODO: permissions for overlays
|
|
||||||
result.update({
|
result.update({
|
||||||
'overlays': [{
|
'overlays': [
|
||||||
'id': overlay.pk,
|
DataOverlaySchema.model_validate(overlay).model_dump()
|
||||||
'name': overlay.title,
|
for overlay
|
||||||
'group': None, # TODO
|
in DataOverlay.qs_for_request(request)
|
||||||
'stroke_color': overlay.stroke_color,
|
]
|
||||||
'stroke_width': overlay.stroke_width,
|
|
||||||
'fill_color': overlay.fill_color,
|
|
||||||
} for overlay in DataOverlay.objects.all()]
|
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
|
@ -1811,14 +1811,7 @@ blink {
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
cursor: pointer;
|
|
||||||
margin-left: 3ch;
|
margin-left: 3ch;
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=checkbox] {
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1832,6 +1825,16 @@ blink {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox] {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.leaflet-control-overlays-expanded > .content {
|
&.leaflet-control-overlays-expanded > .content {
|
||||||
|
|
|
@ -2615,6 +2615,7 @@ KeyControl = L.Control.extend({
|
||||||
OverlayControl = L.Control.extend({
|
OverlayControl = L.Control.extend({
|
||||||
options: {position: 'topright', addClasses: '', levels: {}},
|
options: {position: 'topright', addClasses: '', levels: {}},
|
||||||
_overlays: {},
|
_overlays: {},
|
||||||
|
_ungrouped: [],
|
||||||
_groups: {},
|
_groups: {},
|
||||||
_initialActiveOverlays: null,
|
_initialActiveOverlays: null,
|
||||||
_initialCollapsedGroups: null,
|
_initialCollapsedGroups: null,
|
||||||
|
@ -2685,14 +2686,19 @@ OverlayControl = L.Control.extend({
|
||||||
|
|
||||||
addOverlay: function (overlay) {
|
addOverlay: function (overlay) {
|
||||||
this._overlays[overlay.id] = overlay;
|
this._overlays[overlay.id] = overlay;
|
||||||
if (overlay.group in this._groups) {
|
if (overlay.group == null) {
|
||||||
this._groups[overlay.group].overlays.push(overlay);
|
this._ungrouped.push(overlay);
|
||||||
} else {
|
} else {
|
||||||
this._groups[overlay.group] = {
|
if (overlay.group in this._groups) {
|
||||||
expanded: this._initialCollapsedGroups === null || !this._initialCollapsedGroups.includes(overlay.group),
|
this._groups[overlay.group].overlays.push(overlay);
|
||||||
overlays: [overlay],
|
} else {
|
||||||
};
|
this._groups[overlay.group] = {
|
||||||
|
expanded: this._initialCollapsedGroups === null || !this._initialCollapsedGroups.includes(overlay.group),
|
||||||
|
overlays: [overlay],
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2709,7 +2715,28 @@ OverlayControl = L.Control.extend({
|
||||||
|
|
||||||
render: function () {
|
render: function () {
|
||||||
if (!this._content) return;
|
if (!this._content) return;
|
||||||
|
|
||||||
|
const ungrouped = document.createDocumentFragment();
|
||||||
const groups = document.createDocumentFragment();
|
const groups = document.createDocumentFragment();
|
||||||
|
|
||||||
|
|
||||||
|
const render_overlays = (overlays, container) => {
|
||||||
|
for (const overlay of overlays) {
|
||||||
|
const label = document.createElement('label');
|
||||||
|
const checkbox = document.createElement('input');
|
||||||
|
checkbox.type = 'checkbox';
|
||||||
|
checkbox.dataset.id = overlay.id;
|
||||||
|
if (overlay.visible) {
|
||||||
|
checkbox.checked = true;
|
||||||
|
}
|
||||||
|
label.append(checkbox, overlay.title);
|
||||||
|
container.append(label);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render_overlays(this._ungrouped, ungrouped);
|
||||||
|
|
||||||
|
|
||||||
for (const group in this._groups) {
|
for (const group in this._groups) {
|
||||||
const group_container = document.createElement('div');
|
const group_container = document.createElement('div');
|
||||||
group_container.classList.add('overlay-group');
|
group_container.classList.add('overlay-group');
|
||||||
|
@ -2721,20 +2748,10 @@ OverlayControl = L.Control.extend({
|
||||||
const title = document.createElement('h4');
|
const title = document.createElement('h4');
|
||||||
title.innerText = group;
|
title.innerText = group;
|
||||||
group_container.append(title);
|
group_container.append(title);
|
||||||
for (const overlay of this._groups[group].overlays) {
|
render_overlays(this._groups[group].overlays, group_container);
|
||||||
const label = document.createElement('label');
|
|
||||||
const checkbox = document.createElement('input');
|
|
||||||
checkbox.type = 'checkbox';
|
|
||||||
checkbox.dataset.id = overlay.id;
|
|
||||||
if (overlay.visible) {
|
|
||||||
checkbox.checked = true;
|
|
||||||
}
|
|
||||||
label.append(checkbox, overlay.name);
|
|
||||||
group_container.append(label);
|
|
||||||
}
|
|
||||||
groups.append(group_container);
|
groups.append(group_container);
|
||||||
}
|
}
|
||||||
this._content.replaceChildren(...groups.children);
|
this._content.replaceChildren(...ungrouped.children, ...groups.children);
|
||||||
},
|
},
|
||||||
|
|
||||||
expand: function () {
|
expand: function () {
|
||||||
|
@ -2832,8 +2849,8 @@ class DataOverlay {
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
this.id = options.id;
|
this.id = options.id;
|
||||||
this.name = options.name;
|
this.title = options.title;
|
||||||
this.group = options.group ?? 'ungrouped';
|
this.group = options.group;
|
||||||
this.default_stroke_color = options.stroke_color;
|
this.default_stroke_color = options.stroke_color;
|
||||||
this.default_stroke_width = options.stroke_width;
|
this.default_stroke_width = options.stroke_width;
|
||||||
this.default_fill_color = options.fill_color;
|
this.default_fill_color = options.fill_color;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue