Skip to content

Commit 3f0aaa0

Browse files
Freikartuszik
andauthored
0.36.4 (#2062)
* fix: move foreman to global gems to fix startup crash (#1971) * Update exporting code to stream points data to file in batches to red… (#1980) * Update exporting code to stream points data to file in batches to reduce memory usage * Update changelog * Update changelog * Feature/maplibre frontend (#1953) * Add a plan to use MapLibre GL JS for the frontend map rendering, replacing Leaflet * Implement phase 1 * Phases 1-3 + part of 4 * Fix e2e tests * Phase 6 * Implement fog of war * Phase 7 * Next step: fix specs, phase 7 done * Use our own map tiles * Extract v2 map logic to separate manager classes * Update settings panel on v2 map * Update v2 e2e tests structure * Reimplement location search in maps v2 * Update speed routes * Implement visits and places creation in v2 * Fix last failing test * Implement visits merging * Fix a routes e2e test and simplify the routes layer styling. * Extract js to modules from maps_v2_controller.js * Implement area creation * Fix spec problem * Fix some e2e tests * Implement live mode in v2 map * Update icons and panel * Extract some styles * Remove unused file * Start adding dark theme to popups on MapLibre maps * Make popups respect dark theme * Move v2 maps to maplibre namespace * Update v2 references to maplibre * Put place, area and visit info into side panel * Update API to use safe settings config method * Fix specs * Fix method name to config in SafeSettings and update usages accordingly * Add missing public files * Add handling for real time points * Fix remembering enabled/disabled layers of the v2 map * Fix lots of e2e tests * Add settings to select map version * Use maps/v2 as main path for MapLibre maps * Update routing * Update live mode * Update maplibre controller * Update changelog * Remove some console.log statements * Pull only necessary data for map v2 points * Feature/raw data archive (#2009) * 0.36.2 (#2007) * fix: move foreman to global gems to fix startup crash (#1971) * Update exporting code to stream points data to file in batches to red… (#1980) * Update exporting code to stream points data to file in batches to reduce memory usage * Update changelog * Update changelog * Feature/maplibre frontend (#1953) * Add a plan to use MapLibre GL JS for the frontend map rendering, replacing Leaflet * Implement phase 1 * Phases 1-3 + part of 4 * Fix e2e tests * Phase 6 * Implement fog of war * Phase 7 * Next step: fix specs, phase 7 done * Use our own map tiles * Extract v2 map logic to separate manager classes * Update settings panel on v2 map * Update v2 e2e tests structure * Reimplement location search in maps v2 * Update speed routes * Implement visits and places creation in v2 * Fix last failing test * Implement visits merging * Fix a routes e2e test and simplify the routes layer styling. * Extract js to modules from maps_v2_controller.js * Implement area creation * Fix spec problem * Fix some e2e tests * Implement live mode in v2 map * Update icons and panel * Extract some styles * Remove unused file * Start adding dark theme to popups on MapLibre maps * Make popups respect dark theme * Move v2 maps to maplibre namespace * Update v2 references to maplibre * Put place, area and visit info into side panel * Update API to use safe settings config method * Fix specs * Fix method name to config in SafeSettings and update usages accordingly * Add missing public files * Add handling for real time points * Fix remembering enabled/disabled layers of the v2 map * Fix lots of e2e tests * Add settings to select map version * Use maps/v2 as main path for MapLibre maps * Update routing * Update live mode * Update maplibre controller * Update changelog * Remove some console.log statements --------- Co-authored-by: Robin Tuszik <[email protected]> * Remove esbuild scripts from package.json * Remove sideEffects field from package.json * Raw data archivation * Add tests * Fix tests * Fix tests * Update ExceptionReporter * Add schedule to run raw data archival job monthly * Change file structure for raw data archival feature * Update changelog and version for raw data archival feature --------- Co-authored-by: Robin Tuszik <[email protected]> * Set raw_data to an empty hash instead of nil when archiving * Fix storage configuration and file extraction * Consider MIN_MINUTES_SPENT_IN_CITY during stats calculation (#2018) * Consider MIN_MINUTES_SPENT_IN_CITY during stats calculation * Remove raw data from visited cities api endpoint * Use user timezone to show dates on maps (#2020) * Fix/pre epoch time (#2019) * Use user timezone to show dates on maps * Limit timestamps to valid range to prevent database errors when users enter pre-epoch dates. * Limit timestamps to valid range to prevent database errors when users enter pre-epoch dates. * Fix tests failing due to new index on stats table * Fix failing specs * Update redis client configuration to support unix socket connection * Update changelog * Fix kml kmz import issues (#2023) * Fix kml kmz import issues * Refactor KML importer to improve readability and maintainability * Implement moving points in map v2 and fix route rendering logic to ma… (#2027) * Implement moving points in map v2 and fix route rendering logic to match map v1. * Fix route spec * fix(maplibre): update date format to ISO 8601 (#2029) * Add verification step to raw data archival process (#2028) * Add verification step to raw data archival process * Add actual verification of raw data archives after creation, and only clear raw_data for verified archives. * Fix failing specs * Eliminate zip-bomb risk * Fix potential memory leak in js * Return .keep files * Use Toast instead of alert for notifications * Add help section to navbar dropdown * Update changelog * Remove raw_data_archival_job * Ensure file is being closed properly after reading in Archivable concern * Add composite index to stats table if not exists * Update changelog * Update entrypoint to always sync static assets (not only new ones) * Add family layer to MapLibre maps (#2055) * Add family layer to MapLibre maps * Update migration * Don't show family toggle if feature is disabled * Update changelog * Return changelog * Update changelog * Update tailwind file --------- Co-authored-by: Robin Tuszik <[email protected]>
1 parent 2a1584e commit 3f0aaa0

File tree

11 files changed

+733
-26
lines changed

11 files changed

+733
-26
lines changed

.app_version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.36.3
1+
0.36.4

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/)
55
and this project adheres to [Semantic Versioning](http://semver.org/).
66

7+
# [0.36.4] - Unreleased
8+
9+
## Fixed
10+
11+
- Fixed a bug preventing the app to start if a composite index on stats table already exists. #2034 #2051 #2046
12+
- New compiled assets will override old ones on app start to prevent serving stale assets.
13+
- Number of points in stats should no longer go negative when points are deleted. #2054
14+
- Disable Family::Invitations::CleanupJob no invitations are in the database. #2043
15+
- User can now enable family layer in Maps v2 and center on family members by clicking their emails. #2036
16+
717
# [0.36.3] - 2025-12-14
818

919
## Added

app/assets/builds/tailwind.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/javascript/controllers/maps/maplibre/routes_manager.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,4 +357,28 @@ export class RoutesManager {
357357

358358
SettingsManager.updateSetting('pointsVisible', visible)
359359
}
360+
361+
/**
362+
* Toggle family members layer
363+
*/
364+
async toggleFamily(event) {
365+
const enabled = event.target.checked
366+
SettingsManager.updateSetting('familyEnabled', enabled)
367+
368+
const familyLayer = this.layerManager.getLayer('family')
369+
if (familyLayer) {
370+
if (enabled) {
371+
familyLayer.show()
372+
// Load family members data
373+
await this.controller.loadFamilyMembers()
374+
} else {
375+
familyLayer.hide()
376+
}
377+
}
378+
379+
// Show/hide the family members list
380+
if (this.controller.hasFamilyMembersListTarget) {
381+
this.controller.familyMembersListTarget.style.display = enabled ? 'block' : 'none'
382+
}
383+
}
360384
}

app/javascript/controllers/maps/maplibre/settings_manager.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export class SettingsController {
5353
placesToggle: 'placesEnabled',
5454
fogToggle: 'fogEnabled',
5555
scratchToggle: 'scratchEnabled',
56+
familyToggle: 'familyEnabled',
5657
speedColoredToggle: 'speedColoredRoutesEnabled'
5758
}
5859

@@ -73,6 +74,11 @@ export class SettingsController {
7374
controller.placesFiltersTarget.style.display = controller.placesToggleTarget.checked ? 'block' : 'none'
7475
}
7576

77+
// Show/hide family members list based on initial toggle state
78+
if (controller.hasFamilyToggleTarget && controller.hasFamilyMembersListTarget) {
79+
controller.familyMembersListTarget.style.display = controller.familyToggleTarget.checked ? 'block' : 'none'
80+
}
81+
7682
// Sync route opacity slider
7783
if (controller.hasRouteOpacityRangeTarget) {
7884
controller.routeOpacityRangeTarget.value = (this.settings.routeOpacity || 1.0) * 100

app/javascript/controllers/maps/maplibre_controller.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,15 @@ export default class extends Controller {
5858
'placesToggle',
5959
'fogToggle',
6060
'scratchToggle',
61+
'familyToggle',
6162
// Speed-colored routes
6263
'routesOptions',
6364
'speedColoredToggle',
6465
'speedColorScaleContainer',
6566
'speedColorScaleInput',
67+
// Family members
68+
'familyMembersList',
69+
'familyMembersContainer',
6670
// Area selection
6771
'selectAreaButton',
6872
'selectionActions',
@@ -347,6 +351,103 @@ export default class extends Controller {
347351
toggleSpeedColoredRoutes(event) { return this.routesManager.toggleSpeedColoredRoutes(event) }
348352
openSpeedColorEditor() { return this.routesManager.openSpeedColorEditor() }
349353
handleSpeedColorSave(event) { return this.routesManager.handleSpeedColorSave(event) }
354+
toggleFamily(event) { return this.routesManager.toggleFamily(event) }
355+
356+
// Family Members methods
357+
async loadFamilyMembers() {
358+
try {
359+
const response = await fetch(`/api/v1/families/locations?api_key=${this.apiKeyValue}`, {
360+
headers: {
361+
'Accept': 'application/json',
362+
'Content-Type': 'application/json'
363+
}
364+
})
365+
366+
if (!response.ok) {
367+
if (response.status === 403) {
368+
console.warn('[Maps V2] Family feature not enabled or user not in family')
369+
Toast.info('Family feature not available')
370+
return
371+
}
372+
throw new Error(`HTTP error! status: ${response.status}`)
373+
}
374+
375+
const data = await response.json()
376+
const locations = data.locations || []
377+
378+
// Update family layer with locations
379+
const familyLayer = this.layerManager.getLayer('family')
380+
if (familyLayer) {
381+
familyLayer.loadMembers(locations)
382+
}
383+
384+
// Render family members list
385+
this.renderFamilyMembersList(locations)
386+
387+
Toast.success(`Loaded ${locations.length} family member(s)`)
388+
} catch (error) {
389+
console.error('[Maps V2] Failed to load family members:', error)
390+
Toast.error('Failed to load family members')
391+
}
392+
}
393+
394+
renderFamilyMembersList(locations) {
395+
if (!this.hasFamilyMembersContainerTarget) return
396+
397+
const container = this.familyMembersContainerTarget
398+
399+
if (locations.length === 0) {
400+
container.innerHTML = '<p class="text-xs text-base-content/60">No family members sharing location</p>'
401+
return
402+
}
403+
404+
container.innerHTML = locations.map(location => {
405+
const emailInitial = location.email?.charAt(0)?.toUpperCase() || '?'
406+
const color = this.getFamilyMemberColor(location.user_id)
407+
const lastSeen = new Date(location.updated_at).toLocaleString('en-US', {
408+
timeZone: this.timezoneValue || 'UTC',
409+
month: 'short',
410+
day: 'numeric',
411+
hour: 'numeric',
412+
minute: '2-digit'
413+
})
414+
415+
return `
416+
<div class="flex items-center gap-2 p-2 hover:bg-base-200 rounded-lg cursor-pointer transition-colors"
417+
data-action="click->maps--maplibre#centerOnFamilyMember"
418+
data-member-id="${location.user_id}">
419+
<div style="background-color: ${color}; color: white; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold; flex-shrink: 0;">
420+
${emailInitial}
421+
</div>
422+
<div class="flex-1 min-w-0">
423+
<div class="text-sm font-medium truncate">${location.email || 'Unknown'}</div>
424+
<div class="text-xs text-base-content/60">${lastSeen}</div>
425+
</div>
426+
</div>
427+
`
428+
}).join('')
429+
}
430+
431+
getFamilyMemberColor(userId) {
432+
const colors = [
433+
'#3b82f6', '#10b981', '#f59e0b',
434+
'#ef4444', '#8b5cf6', '#ec4899'
435+
]
436+
// Use user ID to get consistent color
437+
const hash = userId.toString().split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)
438+
return colors[hash % colors.length]
439+
}
440+
441+
centerOnFamilyMember(event) {
442+
const memberId = event.currentTarget.dataset.memberId
443+
if (!memberId) return
444+
445+
const familyLayer = this.layerManager.getLayer('family')
446+
if (familyLayer) {
447+
familyLayer.centerOnMember(parseInt(memberId))
448+
Toast.success('Centered on family member')
449+
}
450+
}
350451

351452
// Info Display methods
352453
showInfo(title, content, actions = []) {

app/javascript/maps_maplibre/layers/family_layer.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,63 @@ export class FamilyLayer extends BaseLayer {
148148
features: filtered
149149
})
150150
}
151+
152+
/**
153+
* Load all family members from API
154+
* @param {Object} locations - Array of family member locations
155+
*/
156+
loadMembers(locations) {
157+
if (!Array.isArray(locations)) {
158+
console.warn('[FamilyLayer] Invalid locations data:', locations)
159+
return
160+
}
161+
162+
const features = locations.map(location => ({
163+
type: 'Feature',
164+
geometry: {
165+
type: 'Point',
166+
coordinates: [location.longitude, location.latitude]
167+
},
168+
properties: {
169+
id: location.user_id,
170+
name: location.email || 'Unknown',
171+
email: location.email,
172+
color: location.color || this.getMemberColor(location.user_id),
173+
lastUpdate: Date.now(),
174+
battery: location.battery,
175+
batteryStatus: location.battery_status,
176+
updatedAt: location.updated_at
177+
}
178+
}))
179+
180+
this.update({
181+
type: 'FeatureCollection',
182+
features
183+
})
184+
}
185+
186+
/**
187+
* Center map on specific family member
188+
* @param {string} memberId - ID of the member to center on
189+
*/
190+
centerOnMember(memberId) {
191+
const features = this.data?.features || []
192+
const member = features.find(f => f.properties.id === memberId)
193+
194+
if (member && this.map) {
195+
this.map.flyTo({
196+
center: member.geometry.coordinates,
197+
zoom: 15,
198+
duration: 1500
199+
})
200+
}
201+
}
202+
203+
/**
204+
* Get all current family members
205+
* @returns {Array} Array of member features
206+
*/
207+
getMembers() {
208+
return this.data?.features || []
209+
}
151210
}

app/views/map/maplibre/_settings_panel.html.erb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,32 @@
317317
<p class="text-sm text-base-content/60 ml-14">Show scratched countries</p>
318318
</div>
319319

320+
<% if DawarichSettings.family_feature_enabled? %>
321+
<div class="divider"></div>
322+
323+
<!-- Family Members Layer -->
324+
<div class="form-control">
325+
<label class="label cursor-pointer justify-start gap-3">
326+
<input type="checkbox"
327+
class="toggle toggle-primary"
328+
data-maps--maplibre-target="familyToggle"
329+
data-action="change->maps--maplibre#toggleFamily" />
330+
<span class="label-text font-medium">Family Members</span>
331+
</label>
332+
<p class="text-sm text-base-content/60 ml-14">Show family member locations</p>
333+
</div>
334+
335+
<!-- Family Members List (conditionally shown) -->
336+
<div class="ml-14 space-y-2" data-maps--maplibre-target="familyMembersList" style="display: none;">
337+
<div class="text-xs text-base-content/60 mb-2">
338+
Click to center on member
339+
</div>
340+
<div data-maps--maplibre-target="familyMembersContainer" class="space-y-1">
341+
<!-- Family members will be dynamically inserted here -->
342+
</div>
343+
</div>
344+
<% end %>
345+
320346
</div>
321347
</div>
322348

docker/web-entrypoint.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ export DATABASE_NAME
3535
rm -f $APP_PATH/tmp/pids/server.pid
3636

3737
# Sync static assets from image to volume
38-
# This ensures new files (like maps_maplibre styles) are copied to the persistent volume
38+
# This ensures new and updated files are copied to the persistent volume
3939
if [ -d "/tmp/public_assets" ]; then
40-
echo "📦 Syncing new static assets to public volume..."
41-
cp -rn /tmp/public_assets/* $APP_PATH/public/ 2>/dev/null || true
40+
echo "📦 Syncing static assets to public volume..."
41+
cp -ru /tmp/public_assets/* $APP_PATH/public/ 2>/dev/null || true
4242
echo "✅ Static assets synced!"
4343
fi
4444

0 commit comments

Comments
 (0)