diff --git a/src/c3nav/mapdata/api/map.py b/src/c3nav/mapdata/api/map.py index da993ae2..3d997291 100644 --- a/src/c3nav/mapdata/api/map.py +++ b/src/c3nav/mapdata/api/map.py @@ -275,6 +275,18 @@ def location_by_slug_geometry(request, location_slug: NonEmptyStr): ) +@map_api_router.get('/positions/my/', summary="all moving position coordinates", + description="get current coordinates of all moving positions owned be the current users", + response={200: list[AnyPositionStatusSchema], **API404.dict(), **auth_responses}) +@api_stats('get_positions') +def get_my_positions(request): + # no caching for obvious reasons! + return [ + position.serialize_position(request=request) + for position in Position.objects.filter(owner=request.user) + ] + + @map_api_router.get('/positions/{position_id}/', summary="moving position coordinates", description="get current coordinates of a moving position / dynamic location", response={200: AnyPositionStatusSchema, **API404.dict(), **auth_responses}) @@ -295,18 +307,6 @@ def get_position_by_id(request, position_id: AnyPositionID): return location.serialize_position(request=request) -@map_api_router.get('/positions/my/', summary="all moving position coordinates", - description="get current coordinates of all moving positions owned be the current users", - response={200: list[AnyPositionStatusSchema], **API404.dict(), **auth_responses}) -@api_stats('get_position') -def get_my_positions(request, position_id: AnyPositionID): - # no caching for obvious reasons! - return [ - position.serialize_position(request=request) - for position in Position.objects.filter(owner=request.user) - ] - - class UpdatePositionSchema(BaseSchema): coordinates_id: Union[ Annotated[CustomLocationID, APIField(title="set coordinates")], diff --git a/src/c3nav/mapdata/models/locations.py b/src/c3nav/mapdata/models/locations.py index 8d8d79dd..3302c00b 100644 --- a/src/c3nav/mapdata/models/locations.py +++ b/src/c3nav/mapdata/models/locations.py @@ -635,9 +635,12 @@ class Position(CustomLocationProxyMixin, models.Model): return { 'id': 'm:%s' % self.secret, 'slug': 'm:%s' % self.secret, + 'effective_slug': 'm:%s' % self.secret, 'available': False, 'icon': 'my_location', + 'effective_icon': 'my_location', 'title': self.name, + 'short_name': self.short_name, 'subtitle': _('currently unavailable'), } # todo: is this good? @@ -647,8 +650,10 @@ class Position(CustomLocationProxyMixin, models.Model): 'available': True, 'id': 'm:%s' % self.secret, 'slug': 'm:%s' % self.secret, + 'effective_slug': 'm:%s' % self.secret, 'icon': 'my_location', 'title': self.name, + 'short_name': self.short_name, 'subtitle': '%s, %s, %s' % ( _('Position'), result['title'], diff --git a/src/c3nav/site/static/site/css/c3nav.scss b/src/c3nav/site/static/site/css/c3nav.scss index 8733d72d..9a61a719 100644 --- a/src/c3nav/site/static/site/css/c3nav.scss +++ b/src/c3nav/site/static/site/css/c3nav.scss @@ -1933,7 +1933,7 @@ blink { } } -.symbol-icon { +.symbol-icon, .text-icon { --icon-color: var(--color-primary); > span { display: inline-block; @@ -1942,7 +1942,6 @@ blink { line-height: 30px; text-align: center; font-size: 22px; - font-family: 'Material Symbols Outlined'; background-color: white; color: var(--icon-color); border-radius: 100%; @@ -1953,8 +1952,11 @@ blink { cursor: default; } + &.symbol-icon { + font-family: 'Material Symbols Outlined'; + } - &.symbol-icon-interactive { + &.symbol-icon-interactive, &.text-icon-interactive { > span { cursor: pointer; diff --git a/src/c3nav/site/static/site/js/c3nav.js b/src/c3nav/site/static/site/js/c3nav.js index e8b3f6b0..3302c177 100644 --- a/src/c3nav/site/static/site/js/c3nav.js +++ b/src/c3nav/site/static/site/js/c3nav.js @@ -1765,6 +1765,7 @@ c3nav = { c3nav._userLocationLayers = {}; c3nav._overlayLayers = {}; c3nav._questsLayers = {}; + c3nav._positionsLayers = {}; c3nav._firstRouteLevel = null; c3nav._labelLayer = L.LayerGroup.collision({margin: 5}).addTo(c3nav.map); c3nav._loadIndicatorLayer = L.LayerGroup.collision({margin: 5}).addTo(c3nav.map); @@ -1791,6 +1792,7 @@ c3nav = { showCoverageOnHover: false, iconCreateFunction: makeClusterIconCreate('var(--color-primary)'), }).addTo(layerGroup); + c3nav._positionsLayers[level[0]] = L.layerGroup().addTo(layerGroup); } c3nav._levelControl.finalize(); c3nav._levelControl.setLevel(c3nav.initial_level); @@ -1849,6 +1851,7 @@ c3nav = { c3nav._update_overlays(); c3nav._update_quests(); + c3nav._update_positions(); c3nav._update_loadinfo_labels(); c3nav.map.on('click', c3nav._click_anywhere); @@ -2278,6 +2281,18 @@ c3nav = { } }, + _update_positions: function () { + if (!c3nav.map) return; + if (c3nav._positionsControl) { + if (!c3nav.user_data.has_positions) { + c3nav.map.removeControl(c3nav._positionsControl); + c3nav._positionsControl = null; + } + } else { + if (c3nav.user_data.has_positions) c3nav._positionsControl = (new PositionsControl()).addTo(c3nav.map); + } + }, + _hasLocationPermission: undefined, hasLocationPermission: function (nocache) { if (c3nav._hasLocationPermission === undefined || (nocache !== undefined && nocache === true)) { @@ -2996,7 +3011,7 @@ QuestsControl = ExpandingControl.extend({ this.reloadQuests().catch(err => console.error(err)); }, - reloadQuests: async function (force = false) { + reloadQuests: async function (force = false) { const activeQuests = this._activeQuests; const removed = this._loadedQuests.difference(activeQuests); const added = force ? activeQuests : activeQuests.difference(this._loadedQuests); @@ -3059,6 +3074,54 @@ QuestsControl = ExpandingControl.extend({ }, }); +PositionsControl = ToggleControl.extend({ + options: { + position: 'topright', + addClasses: 'leaflet-control-positions', + enabledIcon: c3nav._map_material_icon('location_on'), + disabledIcon: c3nav._map_material_icon('location_off'), + storageId: 'positions', + onEnable: () => { + c3nav._positionsControl.reloadPositions().catch(err => console.error(err)); + }, + }, + + reloadPositions: async function () { + console.log("abc"); + for (const level_id in c3nav._positionsLayers) { + c3nav._positionsLayers[level_id].clearLayers(); + } + + const data = await c3nav_api.get(`map/positions/my/`); + + for (const position of data) { + if (!position.available) continue; + L.geoJson(position.geometry, { + pointToLayer: (geom, latlng) => { + const span = document.createElement('span'); + span.innerText = position.short_name; + return L.marker(latlng, { + icon: L.divIcon({ + className: 'text-icon symbol-icon-interactive', + html: span, + iconSize: [24, 24], + iconAnchor: [12, 12], + }) + }); + } + }) + .addTo(c3nav._positionsLayers[position.level]) + .bindPopup(() => { + const span = document.createElement('span'); + span.innerText = position.name; + return span; + }, { + className: 'data-overlay-popup' + }) + } + }, +}); + LegendControl = ExpandingControl.extend({ options: { position: 'topright',