smarter time labels
This commit is contained in:
		
							parent
							
								
									2ef039da12
								
							
						
					
					
						commit
						0da9524f87
					
				
							
								
								
									
										137
									
								
								src/index.html
									
									
									
									
									
								
							
							
						
						
									
										137
									
								
								src/index.html
									
									
									
									
									
								
							| @ -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"> | ||||||
|                  |                  | ||||||
| 
 | 
 | ||||||
| @ -292,13 +302,13 @@ | |||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|                 // 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() { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user