From 85166c87efbe50a80c6bf92bc6e74d11420ab677 Mon Sep 17 00:00:00 2001 From: taDuc Date: Wed, 17 Jun 2026 00:51:22 +0700 Subject: [PATCH] refactor: rename map feature types and improve path following animation with flyTo transitions --- src/uhm/components/map/useMapInteraction.ts | 4 +- src/uhm/lib/replay/replayMapEffects.ts | 67 +++++++++++++++++---- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/uhm/components/map/useMapInteraction.ts b/src/uhm/components/map/useMapInteraction.ts index 7fd84db..b93ea9a 100644 --- a/src/uhm/components/map/useMapInteraction.ts +++ b/src/uhm/components/map/useMapInteraction.ts @@ -284,7 +284,7 @@ export function useMapInteraction({ type: "Feature", properties: { id, - type: "attack_route", + type: "military_route", geometry_preset: "line", entity_id: null, entity_ids: [], @@ -306,7 +306,7 @@ export function useMapInteraction({ type: "Feature", properties: { id, - type: "war", + type: "battle", geometry_preset: "circle-area", entity_id: null, entity_ids: [], diff --git a/src/uhm/lib/replay/replayMapEffects.ts b/src/uhm/lib/replay/replayMapEffects.ts index a5409e8..45ee923 100644 --- a/src/uhm/lib/replay/replayMapEffects.ts +++ b/src/uhm/lib/replay/replayMapEffects.ts @@ -259,8 +259,11 @@ export function createReplayMapEffects() { const path = removeDuplicateCoordinates(coordinates); if (path.length === 0) return; if (path.length === 1) { - map.easeTo({ + map.flyTo({ center: path[0], + zoom: typeof zoom === "number" ? zoom : map.getZoom(), + pitch: map.getPitch(), + bearing: map.getBearing(), duration: clampNumber(duration, 250, 60000, 5000), }); return; @@ -272,35 +275,73 @@ export function createReplayMapEffects() { if (totalDistance <= 0) return; const totalDuration = clampNumber(duration, 250, 60000, 5000); - const startedAt = performance.now(); + + // Allocate flyDuration dynamically based on the total step duration + let flyDuration = 1500; + if (totalDuration < 3000) { + flyDuration = Math.round(totalDuration * 0.4); + } else if (totalDuration < 4500) { + flyDuration = 1200; + } + const followDuration = Math.max(100, totalDuration - flyDuration); + let rafId = 0; + let flyTimeoutId: NodeJS.Timeout | null = null; let unregister: Cleanup | null = null; + let onMoveStart: ((e: any) => void) | null = null; const stop = () => { + if (flyTimeoutId) { + clearTimeout(flyTimeoutId); + flyTimeoutId = null; + } if (rafId) { cancelAnimationFrame(rafId); rafId = 0; } + if (onMoveStart) { + map.off("movestart", onMoveStart); + onMoveStart = null; + } unregister?.(); unregister = null; }; - const tick = (now: number) => { - const progress = Math.min(1, (now - startedAt) / totalDuration); - const targetDistance = totalDistance * progress; - const center = interpolateMeasuredPath(measured, targetDistance); - map.jumpTo({ - center, - }); - if (progress >= 1) { - stop(); + onMoveStart = (e: any) => { + if (e && e.isFollowPath) { return; } - rafId = requestAnimationFrame(tick); + stop(); }; + map.on("movestart", onMoveStart); + + map.flyTo({ + center: path[0], + zoom: typeof zoom === "number" ? zoom : map.getZoom(), + pitch: map.getPitch(), + bearing: map.getBearing(), + duration: flyDuration, + }, { isFollowPath: true }); + + flyTimeoutId = setTimeout(() => { + const startedAt = performance.now(); + const tick = (now: number) => { + const progress = Math.min(1, (now - startedAt) / followDuration); + const targetDistance = totalDistance * progress; + const center = interpolateMeasuredPath(measured, targetDistance); + map.jumpTo({ + center, + }, { isFollowPath: true }); + if (progress >= 1) { + stop(); + return; + } + rafId = requestAnimationFrame(tick); + }; + rafId = requestAnimationFrame(tick); + }, flyDuration); unregister = registerCleanup(stop); - rafId = requestAnimationFrame(tick); }, }; }