support for resume on multiple devices, fix camera freeze bug when opening setup overlay
All checks were successful
pedestrian-simulator / build (push) Successful in 1m19s

This commit is contained in:
2026-01-18 09:55:53 -07:00
parent 16c6c9c074
commit aebd0f21a3
2 changed files with 110 additions and 9 deletions

View File

@@ -824,6 +824,11 @@ function showSetupOverlay() {
function hideSetupOverlay() { function hideSetupOverlay() {
document.getElementById('setup-overlay').classList.remove('active'); document.getElementById('setup-overlay').classList.remove('active');
// Resume animation if a trip is active
if (localStorage.getItem(LOCATION_STORAGE)) {
startCameraAnimation();
}
} }
function setupEventListeners() { function setupEventListeners() {
@@ -989,6 +994,8 @@ async function calculateAndStartRoute(startAddress, endAddress, isRestoring = fa
// Reset display steps for visual consistency // Reset display steps for visual consistency
displayTripSteps = 0; displayTripSteps = 0;
stateBuffer = []; stateBuffer = [];
window.lastPositionUpdateDistance = 0;
window.lastPanoUpdate = 0;
startFromLocation(leg.start_location, locationData.startAddress); startFromLocation(leg.start_location, locationData.startAddress);
hideSetupOverlay(); hideSetupOverlay();
@@ -1169,7 +1176,7 @@ function resetLocation() {
showSetupOverlay(); showSetupOverlay();
document.getElementById('startLocationInput').value = ''; document.getElementById('startLocationInput').value = '';
document.getElementById('endLocationInput').value = ''; document.getElementById('endLocationInput').value = '';
stopAnimation(); // stopAnimation(); // Don't stop animation just because we opened the setup overlay
} }
// Rate limiting for refresh // Rate limiting for refresh
@@ -1230,14 +1237,64 @@ async function fetchStatus() {
const data = await response.json(); const data = await response.json();
const now = Date.now(); const now = Date.now();
// Update target steps from server // Server-Side Trip Synchronization
if (data.tripSteps !== undefined) { if (data.activeTrip) {
// Add to buffer // A trip is active on the server
stateBuffer.push({ t: now, s: data.tripSteps });
// Keep buffer size manageable (20 points is ~1.5 mins) // Check if we need to sync/resume this trip locally
if (stateBuffer.length > 20) { const localLocation = localStorage.getItem(LOCATION_STORAGE);
stateBuffer.shift(); let needsResync = false;
if (!localLocation) {
needsResync = true;
console.log("[Sync] No local trip found. Syncing from server.");
} else {
// We have a local trip, check if it's the SAME trip
try {
const localData = JSON.parse(localLocation);
if (localData.routeName !== data.activeTrip.route_name) {
needsResync = true;
console.log("[Sync] Local trip route mismatch. Syncing from server.");
}
} catch (e) {
needsResync = true;
}
}
if (needsResync) {
syncActiveTrip(data.activeTrip);
}
// Update target steps from server
if (data.tripSteps !== undefined) {
// Safety: Only accept step updates if they are for the current trip
if (needsResync) {
stateBuffer = [];
displayTripSteps = 0;
}
stateBuffer.push({ t: now, s: data.tripSteps });
// Keep buffer size manageable (20 points is ~1.5 mins)
if (stateBuffer.length > 20) {
stateBuffer.shift();
}
}
} else {
// No trip active on the server
if (localStorage.getItem(LOCATION_STORAGE)) {
console.log("[Sync] No trip on server. Clearing local trip.");
localStorage.removeItem(LOCATION_STORAGE);
if (!document.getElementById('celebration-overlay').classList.contains('active')) {
document.getElementById('routeInfo').textContent = 'Not Started';
document.getElementById('currentSteps').textContent = '0';
document.getElementById('tripMeter').textContent = '0.0 / 0.0 km';
masterPath = [];
routeTotalDistance = 0;
displayTripSteps = 0;
stateBuffer = [];
}
} }
} }
@@ -1254,6 +1311,24 @@ async function fetchStatus() {
} }
} }
async function syncActiveTrip(activeTrip) {
console.log("[Sync] Restoring trip from server:", activeTrip.route_name);
if (activeTrip.trip_type === 'address') {
calculateAndStartRoute(activeTrip.start_address, activeTrip.end_address, true);
} else if (activeTrip.trip_type === 'kml') {
try {
const response = await fetch(`/api/kml/download?owner_id=${activeTrip.kml_owner_id || currentUserID}&filename=${encodeURIComponent(activeTrip.route_name)}`);
if (response.ok) {
const kmlContent = await response.text();
processKML(kmlContent, activeTrip.route_name);
}
} catch (err) {
console.error("[Sync] Failed to download KML for sync:", err);
}
}
}
function updateNextSyncCountdown(nextSyncTime) { function updateNextSyncCountdown(nextSyncTime) {
const now = new Date(); const now = new Date();
const diff = nextSyncTime - now; const diff = nextSyncTime - now;
@@ -2154,6 +2229,8 @@ function startFromCustomRoute(pathData, routeName, isRestoring = false, markers
// Reset display steps // Reset display steps
displayTripSteps = 0; displayTripSteps = 0;
stateBuffer = []; stateBuffer = [];
window.lastPositionUpdateDistance = 0;
window.lastPanoUpdate = 0;
// Initialize Map View // Initialize Map View
startFromLocation(startPoint, routeName); startFromLocation(startPoint, routeName);
@@ -2301,6 +2378,14 @@ async function triggerCelebration(finalDistance) {
currentTripDetails = null; currentTripDetails = null;
masterPath = []; masterPath = [];
routeTotalDistance = 0; routeTotalDistance = 0;
displayTripSteps = 0;
stateBuffer = [];
// Update UI to reflect reset
document.getElementById('routeInfo').textContent = 'Not Started';
document.getElementById('currentSteps').textContent = '0';
document.getElementById('tripMeter').textContent = '0.0 / 0.0 km';
// We keep window.hasCelebrated = true to prevent immediate re-trigger // We keep window.hasCelebrated = true to prevent immediate re-trigger
// but starting a new trip will reset it. // but starting a new trip will reset it.
} }

View File

@@ -360,10 +360,26 @@ func (sm *StepManager) GetStatus() map[string]interface{} {
go sm.Sync() // Async sync go sm.Sync() // Async sync
} }
return map[string]interface{}{ res := map[string]interface{}{
"tripSteps": currentSmoothed, "tripSteps": currentSmoothed,
"nextSyncTime": nextSyncMilli, "nextSyncTime": nextSyncMilli,
} }
sm.mu.Lock()
if sm.tripState.StartDate != "" {
res["activeTrip"] = map[string]interface{}{
"trip_type": sm.tripState.TripType,
"route_name": sm.tripState.RouteName,
"start_address": sm.tripState.StartAddress,
"end_address": sm.tripState.EndAddress,
"kml_id": sm.tripState.KmlID,
"total_distance": sm.tripState.TotalDistance,
"start_time": sm.tripState.StartTime,
}
}
sm.mu.Unlock()
return res
} }
// RecalculateTotalFromState sums up the steps from the DailyCache without making external API calls // RecalculateTotalFromState sums up the steps from the DailyCache without making external API calls