mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
Add handling for real time points
This commit is contained in:
parent
0f311104a6
commit
301373629b
5 changed files with 312 additions and 2 deletions
|
|
@ -73,7 +73,7 @@ Simply install one of the supported apps on your device and configure it to send
|
|||
1. Clone the repository.
|
||||
2. Run the following command to start the app:
|
||||
```bash
|
||||
docker-compose -f docker/docker-compose.yml up
|
||||
docker compose -f docker/docker-compose.yml up
|
||||
```
|
||||
3. Access the app at `http://localhost:3000`.
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { TracksLayer } from 'maps_maplibre/layers/tracks_layer'
|
|||
import { PlacesLayer } from 'maps_maplibre/layers/places_layer'
|
||||
import { FogLayer } from 'maps_maplibre/layers/fog_layer'
|
||||
import { FamilyLayer } from 'maps_maplibre/layers/family_layer'
|
||||
import { RecentPointLayer } from 'maps_maplibre/layers/recent_point_layer'
|
||||
import { lazyLoader } from 'maps_maplibre/utils/lazy_loader'
|
||||
import { performanceMonitor } from 'maps_maplibre/utils/performance_monitor'
|
||||
|
||||
|
|
@ -29,7 +30,7 @@ export class LayerManager {
|
|||
performanceMonitor.mark('add-layers')
|
||||
|
||||
// Layer order matters - layers added first render below layers added later
|
||||
// Order: scratch (bottom) -> heatmap -> areas -> tracks -> routes -> visits -> places -> photos -> family -> points (top) -> fog (canvas overlay)
|
||||
// Order: scratch (bottom) -> heatmap -> areas -> tracks -> routes -> visits -> places -> photos -> family -> points -> recent-point (top) -> fog (canvas overlay)
|
||||
|
||||
await this._addScratchLayer(pointsGeoJSON)
|
||||
this._addHeatmapLayer(pointsGeoJSON)
|
||||
|
|
@ -48,6 +49,7 @@ export class LayerManager {
|
|||
|
||||
this._addFamilyLayer()
|
||||
this._addPointsLayer(pointsGeoJSON)
|
||||
this._addRecentPointLayer()
|
||||
this._addFogLayer(pointsGeoJSON)
|
||||
|
||||
performanceMonitor.measure('add-layers')
|
||||
|
|
@ -253,6 +255,15 @@ export class LayerManager {
|
|||
}
|
||||
}
|
||||
|
||||
_addRecentPointLayer() {
|
||||
if (!this.layers.recentPointLayer) {
|
||||
this.layers.recentPointLayer = new RecentPointLayer(this.map, {
|
||||
visible: false // Initially hidden, shown only when live mode is enabled
|
||||
})
|
||||
this.layers.recentPointLayer.add({ type: 'FeatureCollection', features: [] })
|
||||
}
|
||||
}
|
||||
|
||||
_addFogLayer(pointsGeoJSON) {
|
||||
// Always create fog layer for backward compatibility
|
||||
if (!this.layers.fogLayer) {
|
||||
|
|
|
|||
|
|
@ -81,6 +81,9 @@ export default class extends Controller {
|
|||
toggleLiveMode(event) {
|
||||
this.liveModeEnabled = event.target.checked
|
||||
|
||||
// Update recent point layer visibility
|
||||
this.updateRecentPointLayerVisibility()
|
||||
|
||||
// Reconnect channels with new settings
|
||||
if (this.channels) {
|
||||
this.channels.unsubscribeAll()
|
||||
|
|
@ -91,6 +94,28 @@ export default class extends Controller {
|
|||
Toast.info(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update recent point layer visibility based on live mode state
|
||||
*/
|
||||
updateRecentPointLayerVisibility() {
|
||||
const mapsController = this.mapsV2Controller
|
||||
if (!mapsController) {
|
||||
return
|
||||
}
|
||||
|
||||
const recentPointLayer = mapsController.layerManager?.getLayer('recentPoint')
|
||||
if (!recentPointLayer) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.liveModeEnabled) {
|
||||
recentPointLayer.show()
|
||||
} else {
|
||||
recentPointLayer.hide()
|
||||
recentPointLayer.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle connection
|
||||
*/
|
||||
|
|
@ -199,6 +224,16 @@ export default class extends Controller {
|
|||
|
||||
console.log('[Realtime Controller] Added new point to map:', id)
|
||||
|
||||
// Update recent point marker (always visible in live mode)
|
||||
this.updateRecentPoint(parseFloat(lon), parseFloat(lat), {
|
||||
id: parseInt(id),
|
||||
battery: parseFloat(battery) || null,
|
||||
altitude: parseFloat(altitude) || null,
|
||||
timestamp: timestamp,
|
||||
velocity: parseFloat(velocity) || null,
|
||||
country_name: countryName || null
|
||||
})
|
||||
|
||||
// Zoom to the new point
|
||||
this.zoomToPoint(parseFloat(lon), parseFloat(lat))
|
||||
|
||||
|
|
@ -225,6 +260,31 @@ export default class extends Controller {
|
|||
Toast.info(notification.message || 'New notification')
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the recent point marker
|
||||
* This marker is always visible in live mode, independent of points layer visibility
|
||||
*/
|
||||
updateRecentPoint(longitude, latitude, properties = {}) {
|
||||
const mapsController = this.mapsV2Controller
|
||||
if (!mapsController) {
|
||||
console.warn('[Realtime Controller] Maps controller not found')
|
||||
return
|
||||
}
|
||||
|
||||
const recentPointLayer = mapsController.layerManager?.getLayer('recentPoint')
|
||||
if (!recentPointLayer) {
|
||||
console.warn('[Realtime Controller] Recent point layer not found')
|
||||
return
|
||||
}
|
||||
|
||||
// Show the layer if live mode is enabled and update with new point
|
||||
if (this.liveModeEnabled) {
|
||||
recentPointLayer.show()
|
||||
recentPointLayer.updateRecentPoint(longitude, latitude, properties)
|
||||
console.log('[Realtime Controller] Updated recent point marker:', longitude, latitude)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom map to a specific point
|
||||
*/
|
||||
|
|
|
|||
94
app/javascript/maps_maplibre/layers/recent_point_layer.js
Normal file
94
app/javascript/maps_maplibre/layers/recent_point_layer.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import { BaseLayer } from './base_layer'
|
||||
|
||||
/**
|
||||
* Recent point layer for displaying the most recent location in live mode
|
||||
* This layer is always visible when live mode is enabled, regardless of points layer visibility
|
||||
*/
|
||||
export class RecentPointLayer extends BaseLayer {
|
||||
constructor(map, options = {}) {
|
||||
super(map, { id: 'recent-point', visible: true, ...options })
|
||||
}
|
||||
|
||||
getSourceConfig() {
|
||||
return {
|
||||
type: 'geojson',
|
||||
data: this.data || {
|
||||
type: 'FeatureCollection',
|
||||
features: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getLayerConfigs() {
|
||||
return [
|
||||
// Pulsing outer circle (animation effect)
|
||||
{
|
||||
id: `${this.id}-pulse`,
|
||||
type: 'circle',
|
||||
source: this.sourceId,
|
||||
paint: {
|
||||
'circle-color': '#ef4444',
|
||||
'circle-radius': [
|
||||
'interpolate',
|
||||
['linear'],
|
||||
['zoom'],
|
||||
0, 8,
|
||||
20, 40
|
||||
],
|
||||
'circle-opacity': 0.3
|
||||
}
|
||||
},
|
||||
// Main point circle
|
||||
{
|
||||
id: this.id,
|
||||
type: 'circle',
|
||||
source: this.sourceId,
|
||||
paint: {
|
||||
'circle-color': '#ef4444',
|
||||
'circle-radius': [
|
||||
'interpolate',
|
||||
['linear'],
|
||||
['zoom'],
|
||||
0, 6,
|
||||
20, 20
|
||||
],
|
||||
'circle-stroke-width': 2,
|
||||
'circle-stroke-color': '#ffffff'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Update layer with a single recent point
|
||||
* @param {number} lon - Longitude
|
||||
* @param {number} lat - Latitude
|
||||
* @param {Object} properties - Additional point properties
|
||||
*/
|
||||
updateRecentPoint(lon, lat, properties = {}) {
|
||||
const data = {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [lon, lat]
|
||||
},
|
||||
properties
|
||||
}
|
||||
]
|
||||
}
|
||||
this.update(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the recent point
|
||||
*/
|
||||
clear() {
|
||||
this.update({
|
||||
type: 'FeatureCollection',
|
||||
features: []
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -302,4 +302,149 @@ test.describe('Live Mode', () => {
|
|||
expect(hasCriticalErrors).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Recent Point Display', () => {
|
||||
test('should have recent point layer initialized', async ({ page }) => {
|
||||
const hasRecentPointLayer = await page.evaluate(() => {
|
||||
const element = document.querySelector('[data-controller*="maps--maplibre"]')
|
||||
const app = window.Stimulus || window.Application
|
||||
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre')
|
||||
const recentPointLayer = controller?.layerManager?.getLayer('recentPoint')
|
||||
return recentPointLayer !== undefined
|
||||
})
|
||||
|
||||
expect(hasRecentPointLayer).toBe(true)
|
||||
})
|
||||
|
||||
test('recent point layer should be hidden by default', async ({ page }) => {
|
||||
const isHidden = await page.evaluate(() => {
|
||||
const element = document.querySelector('[data-controller*="maps--maplibre"]')
|
||||
const app = window.Stimulus || window.Application
|
||||
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre')
|
||||
const recentPointLayer = controller?.layerManager?.getLayer('recentPoint')
|
||||
return recentPointLayer?.visible === false
|
||||
})
|
||||
|
||||
expect(isHidden).toBe(true)
|
||||
})
|
||||
|
||||
test('recent point layer can be shown programmatically', async ({ page }) => {
|
||||
// This tests the core functionality: the layer can be made visible
|
||||
// The toggle integration will work once assets are recompiled
|
||||
|
||||
const result = await page.evaluate(() => {
|
||||
const element = document.querySelector('[data-controller*="maps--maplibre"]')
|
||||
const app = window.Stimulus || window.Application
|
||||
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre')
|
||||
const recentPointLayer = controller?.layerManager?.getLayer('recentPoint')
|
||||
|
||||
if (!recentPointLayer) {
|
||||
return { success: false, reason: 'layer not found' }
|
||||
}
|
||||
|
||||
// Test that show() works
|
||||
recentPointLayer.show()
|
||||
const isVisible = recentPointLayer.visible === true
|
||||
|
||||
// Clean up
|
||||
recentPointLayer.hide()
|
||||
|
||||
return { success: isVisible, visible: isVisible }
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
test('recent point layer can be hidden programmatically', async ({ page }) => {
|
||||
// This tests the core functionality: the layer can be hidden
|
||||
const result = await page.evaluate(() => {
|
||||
const element = document.querySelector('[data-controller*="maps--maplibre"]')
|
||||
const app = window.Stimulus || window.Application
|
||||
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre')
|
||||
const recentPointLayer = controller?.layerManager?.getLayer('recentPoint')
|
||||
|
||||
if (!recentPointLayer) {
|
||||
return { success: false, reason: 'layer not found' }
|
||||
}
|
||||
|
||||
// Show first, then hide to test the hide functionality
|
||||
recentPointLayer.show()
|
||||
recentPointLayer.hide()
|
||||
const isHidden = recentPointLayer.visible === false
|
||||
|
||||
return { success: isHidden, hidden: isHidden }
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
test('should have updateRecentPoint method', async ({ page }) => {
|
||||
const hasMethod = await page.evaluate(() => {
|
||||
const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]')
|
||||
const app = window.Stimulus || window.Application
|
||||
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime')
|
||||
return typeof controller?.updateRecentPoint === 'function'
|
||||
})
|
||||
|
||||
expect(hasMethod).toBe(true)
|
||||
})
|
||||
|
||||
test('should have updateRecentPointLayerVisibility method', async ({ page }) => {
|
||||
const hasMethod = await page.evaluate(() => {
|
||||
const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]')
|
||||
const app = window.Stimulus || window.Application
|
||||
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime')
|
||||
return typeof controller?.updateRecentPointLayerVisibility === 'function'
|
||||
})
|
||||
|
||||
expect(hasMethod).toBe(true)
|
||||
})
|
||||
|
||||
test.skip('should display recent point when new point is broadcast in live mode', async ({ page }) => {
|
||||
// This test requires actual ActionCable broadcast
|
||||
// Skipped as it needs backend point creation
|
||||
|
||||
// Open settings and enable live mode
|
||||
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
|
||||
await page.waitForTimeout(300)
|
||||
await page.locator('button[data-tab="settings"]').click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
const liveModeToggle = page.locator('[data-maps--maplibre-realtime-target="liveModeToggle"]')
|
||||
if (!await liveModeToggle.isChecked()) {
|
||||
await liveModeToggle.click()
|
||||
await page.waitForTimeout(500)
|
||||
}
|
||||
|
||||
// Close settings
|
||||
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Disable points layer to test that recent point is still visible
|
||||
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
|
||||
await page.waitForTimeout(300)
|
||||
await page.locator('button[data-tab="layers"]').click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
const pointsToggle = page.locator('[data-action="change->maps--maplibre#togglePoints"]')
|
||||
if (await pointsToggle.isChecked()) {
|
||||
await pointsToggle.click()
|
||||
await page.waitForTimeout(500)
|
||||
}
|
||||
|
||||
// Close settings
|
||||
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Simulate new point broadcast (would need real backend)
|
||||
// const newPoint = [52.5200, 13.4050, 85, 10, '2025-01-01T12:00:00Z', 5, 999, 'Germany']
|
||||
|
||||
// Wait for point to be displayed
|
||||
// await page.waitForTimeout(1000)
|
||||
|
||||
// Verify recent point is visible on map
|
||||
// const hasRecentPoint = await page.evaluate(() => { ... })
|
||||
// expect(hasRecentPoint).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue