theme selector

This commit is contained in:
Gwendolyn 2024-01-06 20:37:13 +01:00
parent 124b0e82df
commit da5346d0a9
9 changed files with 99 additions and 20 deletions

View file

@ -0,0 +1,18 @@
# Generated by Django 4.2.7 on 2024-01-06 19:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('control', '0012_userpermissions_grant_unlimited_access'),
]
operations = [
migrations.AddField(
model_name='userpermissions',
name='nonpublic_themes',
field=models.BooleanField(default=False, verbose_name='show non-public themes in theme selector'),
),
]

View file

@ -38,6 +38,8 @@ class UserPermissions(models.Model):
mesh_control = models.BooleanField(default=False, verbose_name=_('can access mesh control'))
nonpublic_themes = models.BooleanField(default=False, verbose_name=_('show non-public themes in theme selector'))
class Meta:
verbose_name = _('User Permissions')
verbose_name_plural = _('User Permissions')

View file

@ -30,7 +30,8 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', c3nav.mapdata.fields.I18nField(blank=True, fallback_any=True, fallback_value='{model} {pk}', plural_name='titles', verbose_name='Title')),
('description', models.TextField()),
('description', models.TextField(verbose_name='Description')),
('public', models.BooleanField(default=False, verbose_name='Public')),
('color_background', models.CharField(max_length=32, verbose_name='background color')),
('color_wall_fill', models.CharField(max_length=32, verbose_name='wall fill color')),
('color_wall_border', models.CharField(max_length=32, verbose_name='wall border color')),

View file

@ -11,7 +11,8 @@ class Theme(TitledMixin, models.Model):
A theme
"""
# TODO: when a theme base colors change we need to bust the cache somehow
description = models.TextField()
description = models.TextField(verbose_name=('Description'))
public = models.BooleanField(default=False, verbose_name=_('Public'))
color_background = models.CharField(max_length=32, verbose_name=_('background color'))
color_wall_fill = models.CharField(max_length=32, verbose_name=_('wall fill color'))
color_wall_border = models.CharField(max_length=32, verbose_name=_('wall border color'))

View file

@ -13,6 +13,7 @@ def get_user_data(request):
'logged_in': bool(request.user.is_authenticated),
'allow_editor': can_access_editor(request),
'allow_control_panel': request.user_permissions.control_panel,
'show_nonpublic_themes': request.user_permissions.nonpublic_themes,
'has_positions': Position.user_has_positions(request.user)
}
if permissions:

View file

@ -1100,7 +1100,7 @@ body:not(.mobileclient) .share-ui p {
body:not(.mobileclient) .locationinput.empty .locate {
opacity: 0.4;
}
main > .share-ui, #reload-msg, #app-ad {
main > .share-ui, #reload-msg, #app-ad, main > .theme-selection {
display: none;
}

View file

@ -135,6 +135,16 @@ c3nav = {
if ($body.is('[data-user-data]')) {
c3nav._set_user_data(JSON.parse($body.attr('data-user-data')));
}
const theme = localStorageWrapper.getItem('c3nav-theme');
if (theme) {
c3nav.theme = parseInt(theme);
}
c3nav.themes = JSON.parse(document.getElementById('available-themes').textContent);
if (!(c3nav.theme in c3nav.themes)) {
c3nav.theme = 0;
}
},
_searchable_locations_timer: null,
load_searchable_locations: function(firstTime) {
@ -207,6 +217,9 @@ c3nav = {
c3nav.random_location_groups = $main.is('[data-random-location-groups]') ? $main.attr('data-random-location-groups').split(',').map(id => parseInt(id)) : null;
$(document).on('click', '.theme-selection>button', c3nav.select_theme);
history.replaceState(state, window.location.path);
c3nav.load_state(state, true);
c3nav.update_map_locations();
@ -1411,7 +1424,7 @@ c3nav = {
L.control.scale({imperial: false}).addTo(c3nav.map);
// setup level control
c3nav._levelControl = new LevelControl().addTo(c3nav.map);
c3nav._levelControl = new LevelControl({initialTheme: c3nav.theme}).addTo(c3nav.map);
c3nav._locationLayers = {};
c3nav._locationLayerBounds = {};
c3nav._detailLayers = {};
@ -1438,8 +1451,10 @@ c3nav = {
c3nav._gridLayer = new L.SquareGridLayer(JSON.parse($map.attr('data-grid')));
c3nav._gridControl = new SquareGridControl().addTo(c3nav.map);
}
new ThemeControl().addTo(c3nav.map);
if (Object.values(c3nav.themes)
.filter(([_, isPublic]) => isPublic || c3nav.user_data.show_nonpublic_themes).length > 0) {
new ThemeControl().addTo(c3nav.map);
}
// setup user location control
c3nav._userLocationControl = new UserLocationControl().addTo(c3nav.map);
@ -1454,16 +1469,36 @@ c3nav = {
},
theme: 0,
show_theme_select: function() {
// TODO: actual theme selection
if (c3nav.theme === 0) {
c3nav.theme = 1;
} else {
c3nav.theme = 0;
}
setTheme: function(theme) {
if (theme === c3nav.theme) return;
c3nav.theme = theme;
localStorageWrapper.setItem('c3nav-theme', c3nav.theme);
c3nav._levelControl.setTheme(c3nav.theme);
// openInModal('/theme')
},
show_theme_select: function(e) {
e.preventDefault();
c3nav.open_modal(document.querySelector('main>.theme-selection').outerHTML);
const select = document.querySelector('#modal .theme-selection select');
for (const id in c3nav.themes) {
const [name, is_public] = c3nav.themes[id];
if (c3nav.user_data.show_nonpublic_themes || is_public) {
const option = document.createElement('option');
option.value = id;
option.innerText = name;
select.append(option);
}
}
const currentThemeOption = select.querySelector(`[value="${c3nav.theme}"]`);
if (currentThemeOption) {
currentThemeOption.selected = true;
}
},
select_theme: function(e) {
var theme = parseInt(e.target.parentElement.querySelector('select').value);
c3nav.setTheme(theme);
history.back(); // close the modal
},
_click_anywhere_popup: null,
_click_anywhere: function(e) {
if (e.originalEvent.target.id !== 'map') return;
@ -2020,7 +2055,8 @@ function mobileclientOnResume() {
LevelControl = L.Control.extend({
options: {
position: 'bottomright',
addClasses: ''
addClasses: '',
initialTheme: 0,
},
onAdd: function () {
@ -2029,11 +2065,12 @@ LevelControl = L.Control.extend({
this._overlayLayers = {};
this._levelButtons = {};
this.currentLevel = null;
this.currentTheme = this.options.initialTheme;
return this._container;
},
createTileLayer: function(id, theme) {
let urlPattern = (c3nav.tile_server || '/map/') + `${id}/{z}/{x}/{y}/${theme}.png`;
createTileLayer: function(id) {
let urlPattern = (c3nav.tile_server || '/map/') + `${id}/{z}/{x}/{y}/${this.currentTheme}.png`;
return L.tileLayer(urlPattern, {
minZoom: -2,
maxZoom: 5,
@ -2041,12 +2078,14 @@ LevelControl = L.Control.extend({
});
},
setTheme: function(theme) {
if (theme === this.currentTheme) return;
this.currentTheme = theme;
if (this.currentLevel !== null) {
this._tileLayers[this.currentLevel].remove();
}
for (const id in this._tileLayers) {
this._tileLayers[id] = this.createTileLayer(id, theme);
this._tileLayers[id] = this.createTileLayer(id);
}
if (this.currentLevel !== null) {
@ -2054,7 +2093,7 @@ LevelControl = L.Control.extend({
}
},
addLevel: function (id, title) {
this._tileLayers[id] = this.createTileLayer(id, 0);
this._tileLayers[id] = this.createTileLayer(id);
var overlay = L.layerGroup();
this._overlayLayers[id] = overlay;
@ -2105,7 +2144,7 @@ LevelControl = L.Control.extend({
buttons.removeClass('current');
},
reloadMap: function() {
reloadMap: function() { // TODO: create fresh tile layers
if (this.currentLevel === null) return;
var old_tile_layer = this._tileLayers[this.currentLevel],
new_tile_layer = this.createTileLayer(this.currentLevel);

View file

@ -29,6 +29,8 @@
<meta property="og:url" content="{{ meta.canonical_url }}"/>
<meta property="twitter:url" content="{{ meta.canonical_url }}"/>
{% endif %}
{{ available_themes|json_script:"available-themes" }}
{% endblock %}
{% block content %}
@ -248,6 +250,16 @@
{% blocktrans with play_url="https://play.google.com/store/apps/details?id=de.c3nav.droid" apk_url="https://github.com/c3nav/c3nav-android/releases" %}Get the c3nav Android app on <a href="{{ play_url }}" target="_blank">Google Play</a> or <a href="{{ apk_url }}" target="_blank">download the APK!</a>{% endblocktrans %}
</div>
</div>
<div class="theme-selection">
<h2>{% trans 'Select theme' %}</h2>
<p>
<label for="id_theme">Theme:</label>
<select name="theme" required="" id="id_theme">
<option value="0">{% trans 'Default theme' %}</option>
</select>
</p>
<button>Save theme</button>
</div>
{% else %}
<a id="embed-logo" class="embed-link" target="_blank">{% if header_logo %}<img src="{% static header_logo %}">{% else %}<span>c3nav</span>{% endif %}</a>
<a id="embed-open" class="embed-link" target="_blank">{% trans 'open in c3nav' %}</a>

View file

@ -179,6 +179,7 @@ def map_index(request, mode=None, slug=None, slug2=None, details=None, options=N
else:
metadata = None
from c3nav.mapdata.models.theme import Theme
ctx = {
'bounds': json.dumps(Source.max_bounds(), separators=(',', ':')),
'levels': json.dumps(tuple((level.pk, level.short_label) for level in levels.values()), separators=(',', ':')),
@ -196,6 +197,10 @@ def map_index(request, mode=None, slug=None, slug2=None, details=None, options=N
'embed': bool(embed),
'imprint': settings.IMPRINT_LINK,
'meta': metadata,
'available_themes': {
theme.pk: [theme.title, theme.public]
for theme in Theme.objects.all()
}
}
if grid.enabled: