From da5346d0a9ac797fb94cf988d828972a4eadf256 Mon Sep 17 00:00:00 2001 From: Gwendolyn Date: Sat, 6 Jan 2024 20:37:13 +0100 Subject: [PATCH] theme selector --- .../0013_userpermissions_nonpublic_themes.py | 18 +++++ src/c3nav/control/models.py | 2 + src/c3nav/mapdata/migrations/0099_theming.py | 3 +- src/c3nav/mapdata/models/theme.py | 3 +- src/c3nav/mapdata/utils/user.py | 1 + src/c3nav/site/static/site/css/c3nav.scss | 2 +- src/c3nav/site/static/site/js/c3nav.js | 73 ++++++++++++++----- src/c3nav/site/templates/site/map.html | 12 +++ src/c3nav/site/views.py | 5 ++ 9 files changed, 99 insertions(+), 20 deletions(-) create mode 100644 src/c3nav/control/migrations/0013_userpermissions_nonpublic_themes.py diff --git a/src/c3nav/control/migrations/0013_userpermissions_nonpublic_themes.py b/src/c3nav/control/migrations/0013_userpermissions_nonpublic_themes.py new file mode 100644 index 00000000..707cded4 --- /dev/null +++ b/src/c3nav/control/migrations/0013_userpermissions_nonpublic_themes.py @@ -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'), + ), + ] diff --git a/src/c3nav/control/models.py b/src/c3nav/control/models.py index 277ab5d2..691b69ec 100644 --- a/src/c3nav/control/models.py +++ b/src/c3nav/control/models.py @@ -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') diff --git a/src/c3nav/mapdata/migrations/0099_theming.py b/src/c3nav/mapdata/migrations/0099_theming.py index 8701f826..2452ec9d 100644 --- a/src/c3nav/mapdata/migrations/0099_theming.py +++ b/src/c3nav/mapdata/migrations/0099_theming.py @@ -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')), diff --git a/src/c3nav/mapdata/models/theme.py b/src/c3nav/mapdata/models/theme.py index ea6246b0..a3e7cf46 100644 --- a/src/c3nav/mapdata/models/theme.py +++ b/src/c3nav/mapdata/models/theme.py @@ -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')) diff --git a/src/c3nav/mapdata/utils/user.py b/src/c3nav/mapdata/utils/user.py index 93432ce4..2faa3b5a 100644 --- a/src/c3nav/mapdata/utils/user.py +++ b/src/c3nav/mapdata/utils/user.py @@ -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: diff --git a/src/c3nav/site/static/site/css/c3nav.scss b/src/c3nav/site/static/site/css/c3nav.scss index d67bfb99..3d53f73f 100644 --- a/src/c3nav/site/static/site/css/c3nav.scss +++ b/src/c3nav/site/static/site/css/c3nav.scss @@ -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; } diff --git a/src/c3nav/site/static/site/js/c3nav.js b/src/c3nav/site/static/site/js/c3nav.js index 732bc742..d641c7df 100644 --- a/src/c3nav/site/static/site/js/c3nav.js +++ b/src/c3nav/site/static/site/js/c3nav.js @@ -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); diff --git a/src/c3nav/site/templates/site/map.html b/src/c3nav/site/templates/site/map.html index f7dada73..37247c4f 100644 --- a/src/c3nav/site/templates/site/map.html +++ b/src/c3nav/site/templates/site/map.html @@ -29,6 +29,8 @@ {% 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 Google Play or download the APK!{% endblocktrans %} +
+

{% trans 'Select theme' %}

+

+ + +

+ +
{% else %} {% trans 'open in c3nav' %} diff --git a/src/c3nav/site/views.py b/src/c3nav/site/views.py index c1bc0ae6..ac801d53 100644 --- a/src/c3nav/site/views.py +++ b/src/c3nav/site/views.py @@ -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: