fix: Replay controls
This commit is contained in:
@@ -1,30 +1,137 @@
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
export function useReplayControls() {
|
||||
const isPaused = ref(false)
|
||||
const speed = ref(60)
|
||||
const currentOffsetMinutes = ref(0)
|
||||
const scenarioDurationMinutes = ref(0)
|
||||
const TICK_MS = 100
|
||||
|
||||
const progress = computed(() =>
|
||||
scenarioDurationMinutes.value > 0
|
||||
? (currentOffsetMinutes.value / scenarioDurationMinutes.value) * 100
|
||||
: 0
|
||||
)
|
||||
export function useReplayControls() {
|
||||
const isPaused = ref(true)
|
||||
const speed = ref(60)
|
||||
const scenarioStartMs = ref(0)
|
||||
const scenarioEndMs = ref(0)
|
||||
const currentMs = ref(0)
|
||||
let timer = null
|
||||
|
||||
const scenarioDurationMinutes = computed(() => {
|
||||
const span = scenarioEndMs.value - scenarioStartMs.value
|
||||
return span > 0 ? span / 60_000 : 0
|
||||
})
|
||||
|
||||
const currentOffsetMinutes = computed(() => {
|
||||
const span = currentMs.value - scenarioStartMs.value
|
||||
return span > 0 ? span / 60_000 : 0
|
||||
})
|
||||
|
||||
const progress = computed(() => {
|
||||
const span = scenarioEndMs.value - scenarioStartMs.value
|
||||
if (span <= 0) return 0
|
||||
return Math.min(100, ((currentMs.value - scenarioStartMs.value) / span) * 100)
|
||||
})
|
||||
|
||||
const formattedTime = computed(() => {
|
||||
const h = Math.floor(currentOffsetMinutes.value / 60)
|
||||
const m = Math.floor(currentOffsetMinutes.value % 60)
|
||||
const mins = currentOffsetMinutes.value
|
||||
const h = Math.floor(mins / 60)
|
||||
const m = Math.floor(mins % 60)
|
||||
return `${h}:${String(m).padStart(2, '0')}`
|
||||
})
|
||||
|
||||
function pause() { isPaused.value = true }
|
||||
function resume() { isPaused.value = false }
|
||||
function setSpeed(s) { speed.value = s }
|
||||
|
||||
function jumpToOffset(offset) {
|
||||
currentOffsetMinutes.value = offset
|
||||
function setScenarioBounds(startMs, endMs) {
|
||||
if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs <= startMs) return
|
||||
scenarioStartMs.value = startMs
|
||||
scenarioEndMs.value = endMs
|
||||
if (currentMs.value < startMs || currentMs.value > endMs) {
|
||||
currentMs.value = endMs
|
||||
}
|
||||
}
|
||||
|
||||
return { isPaused, speed, currentOffsetMinutes, scenarioDurationMinutes, progress, formattedTime, pause, resume, setSpeed, jumpToOffset }
|
||||
}
|
||||
function syncToEnd() {
|
||||
if (scenarioEndMs.value > scenarioStartMs.value) {
|
||||
currentMs.value = scenarioEndMs.value
|
||||
}
|
||||
}
|
||||
|
||||
function pause() {
|
||||
isPaused.value = true
|
||||
stopTick()
|
||||
}
|
||||
|
||||
function resume() {
|
||||
if (scenarioEndMs.value <= scenarioStartMs.value) return
|
||||
if (currentMs.value >= scenarioEndMs.value) {
|
||||
currentMs.value = scenarioStartMs.value
|
||||
}
|
||||
isPaused.value = false
|
||||
startTick()
|
||||
}
|
||||
|
||||
function setSpeed(s) {
|
||||
speed.value = s
|
||||
if (s === 0) {
|
||||
currentMs.value = scenarioEndMs.value
|
||||
pause()
|
||||
}
|
||||
}
|
||||
|
||||
function jumpToOffset(minutes) {
|
||||
if (scenarioEndMs.value <= scenarioStartMs.value) return
|
||||
const target = scenarioStartMs.value + minutes * 60_000
|
||||
currentMs.value = Math.min(Math.max(target, scenarioStartMs.value), scenarioEndMs.value)
|
||||
}
|
||||
|
||||
function jumpToTimestamp(iso) {
|
||||
if (!iso || scenarioEndMs.value <= scenarioStartMs.value) return
|
||||
const ms = new Date(iso).getTime()
|
||||
currentMs.value = Math.min(Math.max(ms, scenarioStartMs.value), scenarioEndMs.value)
|
||||
}
|
||||
|
||||
function isAtOrBefore(iso) {
|
||||
if (!iso) return true
|
||||
return new Date(iso).getTime() <= currentMs.value
|
||||
}
|
||||
|
||||
function tick() {
|
||||
if (isPaused.value || scenarioEndMs.value <= scenarioStartMs.value) return
|
||||
|
||||
if (speed.value === 0) {
|
||||
currentMs.value = scenarioEndMs.value
|
||||
pause()
|
||||
return
|
||||
}
|
||||
|
||||
currentMs.value = Math.min(currentMs.value + TICK_MS * speed.value, scenarioEndMs.value)
|
||||
if (currentMs.value >= scenarioEndMs.value) pause()
|
||||
}
|
||||
|
||||
function startTick() {
|
||||
stopTick()
|
||||
timer = setInterval(tick, TICK_MS)
|
||||
}
|
||||
|
||||
function stopTick() {
|
||||
if (timer) clearInterval(timer)
|
||||
timer = null
|
||||
}
|
||||
|
||||
function stopPlayback() {
|
||||
stopTick()
|
||||
}
|
||||
|
||||
return {
|
||||
isPaused,
|
||||
speed,
|
||||
scenarioStartMs,
|
||||
scenarioEndMs,
|
||||
currentMs,
|
||||
scenarioDurationMinutes,
|
||||
currentOffsetMinutes,
|
||||
progress,
|
||||
formattedTime,
|
||||
setScenarioBounds,
|
||||
syncToEnd,
|
||||
pause,
|
||||
resume,
|
||||
setSpeed,
|
||||
jumpToOffset,
|
||||
jumpToTimestamp,
|
||||
isAtOrBefore,
|
||||
stopPlayback,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user