smarter time labels

This commit is contained in:
Jose Aquino Jr 2025-05-27 16:53:47 +01:00
parent 2ef039da12
commit 0da9524f87

View File

@ -107,7 +107,16 @@
border-radius: 2px; border-radius: 2px;
transition: width 0.3s ease; transition: width 0.3s ease;
width: 0%; width: 0%;
margin: 0 0 0.5rem; margin: 0 0 1rem;
}
.timeline-labels {
display: flex;
justify-content: space-between;
font-size: 0.7rem;
color: rgba(255,255,255,0.8);
margin-bottom: 0.5rem;
height: 1.2rem;
} }
.control-group { .control-group {
@ -174,6 +183,7 @@
<article class="controls-card"> <article class="controls-card">
<div class="timeline-progress" id="timelineProgress"></div> <div class="timeline-progress" id="timelineProgress"></div>
<div class="timeline-labels" id="timelineLabels" style="display: flex; justify-content: space-between; font-size: 0.8rem; color: #fff; margin-bottom: 0.5rem;"></div>
<div class="control-group"> <div class="control-group">
@ -291,14 +301,14 @@
thumb.classList.toggle('active', index === currentIndex); thumb.classList.toggle('active', index === currentIndex);
}); });
// Scroll active thumbnail into view // Scroll active thumbnail into view
// if (thumbnails[currentIndex]) { if (thumbnails[currentIndex]) {
// thumbnails[currentIndex].scrollIntoView({ thumbnails[currentIndex].scrollIntoView({
// behavior: 'smooth', behavior: 'smooth',
// block: 'nearest', block: 'nearest',
// inline: 'center' inline: 'center'
// }); });
// } }
} }
function goToFirst() { function goToFirst() {
@ -350,6 +360,117 @@
function updateTimeline() { function updateTimeline() {
const progress = images.length > 0 ? ((currentIndex + 1) / images.length) * 100 : 0; const progress = images.length > 0 ? ((currentIndex + 1) / images.length) * 100 : 0;
document.getElementById('timelineProgress').style.width = progress + '%'; document.getElementById('timelineProgress').style.width = progress + '%';
updateTimelineLabels();
}
function updateTimelineLabels() {
const labelsContainer = document.getElementById('timelineLabels');
labelsContainer.innerHTML = '';
if (images.length === 0) return;
const firstDate = images[0].createdDate;
const lastDate = images[images.length - 1].createdDate;
const totalDuration = lastDate.getTime() - firstDate.getTime();
// Aim for approximately 6-8 labels depending on the container width
const desiredLabels = Math.min(8, Math.max(6, Math.floor(window.innerWidth / 200)));
const labels = [];
// Always add the first timestamp
labels.push({
time: firstDate,
pos: 0
});
// Add evenly spaced timestamps
for (let i = 1; i < desiredLabels - 1; i++) {
const time = new Date(firstDate.getTime() + (totalDuration * i / (desiredLabels - 1)));
// Find closest image to this time
let closestIdx = 0;
let minDiff = Math.abs(images[0].createdDate - time);
for (let j = 1; j < images.length; j++) {
const diff = Math.abs(images[j].createdDate - time);
if (diff < minDiff) {
minDiff = diff;
closestIdx = j;
}
}
const pos = (closestIdx / (images.length - 1)) * 100;
labels.push({
time: images[closestIdx].createdDate,
pos: pos
});
}
// Add the last timestamp
labels.push({
time: lastDate,
pos: 100
});
// Remove any labels that are too close to each other
const filteredLabels = labels.filter((label, index) => {
if (index === 0) return true;
return Math.abs(label.pos - labels[index - 1].pos) >= 5;
});
// Calculate if we need to show seconds (if total duration is less than 5 minutes)
const showSeconds = totalDuration < 5 * 60 * 1000;
// Calculate optimal spacing between labels
const containerWidth = labelsContainer.offsetWidth;
const labelWidth = 70; // Approximate width of a time label
const minSpacing = labelWidth * 1.5; // Minimum space needed between labels
const maxLabels = Math.floor(containerWidth / minSpacing);
const targetLabels = Math.min(filteredLabels.length, maxLabels);
// Select evenly spaced labels
const finalLabels = [];
if (filteredLabels.length > 0) {
const step = (filteredLabels.length - 1) / (targetLabels - 1);
for (let i = 0; i < targetLabels; i++) {
const index = Math.round(i * step);
finalLabels.push(filteredLabels[index]);
}
}
// Round the timestamps
const roundedLabels = finalLabels.map(label => {
const newTime = new Date(label.time);
if (showSeconds) {
// Round seconds to nearest 5
const seconds = newTime.getSeconds();
const roundedSeconds = Math.round(seconds / 5) * 5;
newTime.setSeconds(roundedSeconds);
} else {
// Round minutes to nearest 5 and set seconds to 0
const minutes = newTime.getMinutes();
const roundedMinutes = Math.round(minutes / 5) * 5;
newTime.setMinutes(roundedMinutes);
newTime.setSeconds(0);
}
return {
...label,
time: newTime
};
});
// Render labels
labelsContainer.style.position = 'relative';
labelsContainer.innerHTML = roundedLabels.map(l => {
const options = showSeconds
? {hour: '2-digit', minute:'2-digit', second:'2-digit'}
: {hour: '2-digit', minute:'2-digit'};
return `<span style="position: absolute; left: ${l.pos}%; transform: translateX(-50%); white-space: nowrap;">
${l.time.toLocaleTimeString([], options)}
</span>`;
}).join('');
} }
function togglePlay() { function togglePlay() {