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() {
document.getElementById('setup-overlay').classList.remove('active');
// Resume animation if a trip is active
if (localStorage.getItem(LOCATION_STORAGE)) {
startCameraAnimation();
}
}
function setupEventListeners() {
@@ -989,6 +994,8 @@ async function calculateAndStartRoute(startAddress, endAddress, isRestoring = fa
// Reset display steps for visual consistency
displayTripSteps = 0;
stateBuffer = [];
window.lastPositionUpdateDistance = 0;
window.lastPanoUpdate = 0;
startFromLocation(leg.start_location, locationData.startAddress);
hideSetupOverlay();
@@ -1169,7 +1176,7 @@ function resetLocation() {
showSetupOverlay();
document.getElementById('startLocationInput').value = '';
document.getElementById('endLocationInput').value = '';
stopAnimation();
// stopAnimation(); // Don't stop animation just because we opened the setup overlay
}
// Rate limiting for refresh
@@ -1230,14 +1237,64 @@ async function fetchStatus() {
const data = await response.json();
const now = Date.now();
// Update target steps from server
if (data.tripSteps !== undefined) {
// Add to buffer
stateBuffer.push({ t: now, s: data.tripSteps });
// Server-Side Trip Synchronization
if (data.activeTrip) {
// A trip is active on the server
// Keep buffer size manageable (20 points is ~1.5 mins)
if (stateBuffer.length > 20) {
stateBuffer.shift();
// Check if we need to sync/resume this trip locally
const localLocation = localStorage.getItem(LOCATION_STORAGE);
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) {
const now = new Date();
const diff = nextSyncTime - now;
@@ -2154,6 +2229,8 @@ function startFromCustomRoute(pathData, routeName, isRestoring = false, markers
// Reset display steps
displayTripSteps = 0;
stateBuffer = [];
window.lastPositionUpdateDistance = 0;
window.lastPanoUpdate = 0;
// Initialize Map View
startFromLocation(startPoint, routeName);
@@ -2301,6 +2378,14 @@ async function triggerCelebration(finalDistance) {
currentTripDetails = null;
masterPath = [];
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
// 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
}
return map[string]interface{}{
res := map[string]interface{}{
"tripSteps": currentSmoothed,
"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