From 858cf852814219223f47a86ecd1ca728525f4aa2 Mon Sep 17 00:00:00 2001 From: Julian Freeman Date: Sun, 22 Mar 2026 16:37:36 -0400 Subject: [PATCH] support lock image --- src/App.vue | 86 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 27 deletions(-) diff --git a/src/App.vue b/src/App.vue index a5186b3..a1ef76b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -23,6 +23,7 @@ const isPaused = ref(false); const currentDate = ref(new Date().toISOString().split("T")[0]); const timelineImages = ref([]); const selectedImage = ref(null); +const lockedImage = ref(null); // State for the clicked/locked point const previewSrc = ref(""); const isFullscreen = ref(false); const isSettingsOpen = ref(false); @@ -91,7 +92,16 @@ const loadTimeline = async () => { }); }; -const selectImage = async (img: TimelineItem) => { +// Internal function to update preview with caching/optimization +const updatePreview = async (img: TimelineItem | null) => { + if (!img) { + selectedImage.value = null; + previewSrc.value = ""; + return; + } + + if (selectedImage.value?.path === img.path && previewSrc.value) return; + selectedImage.value = img; try { const base64 = await invoke("get_image_base64", { path: img.path }); @@ -113,6 +123,15 @@ const minutesToTime = (totalMinutes: number) => { return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`; }; +const findClosestImage = (minutes: number) => { + if (timelineImages.value.length === 0) return null; + return timelineImages.value.reduce((prev, curr) => { + const prevDiff = Math.abs(timeToMinutes(prev.time) - minutes); + const currDiff = Math.abs(timeToMinutes(curr.time) - minutes); + return currDiff < prevDiff ? curr : prev; + }, timelineImages.value[0]); +}; + const handleTimelineMouseMove = (e: MouseEvent) => { if (!timelineRef.value) return; const rect = timelineRef.value.getBoundingClientRect(); @@ -121,22 +140,32 @@ const handleTimelineMouseMove = (e: MouseEvent) => { if (minutes >= 0 && minutes < TOTAL_MINUTES) { hoveredTime.value = minutesToTime(minutes); - - // Find closest image - const closest = timelineImages.value.reduce((prev, curr) => { - const prevDiff = Math.abs(timeToMinutes(prev.time) - minutes); - const currDiff = Math.abs(timeToMinutes(curr.time) - minutes); - return currDiff < prevDiff ? curr : prev; - }, timelineImages.value[0]); - - if (closest && closest !== selectedImage.value) { - selectImage(closest); + const closest = findClosestImage(minutes); + if (closest) { + updatePreview(closest); } } }; const handleTimelineMouseLeave = () => { hoveredTime.value = null; + // Revert to locked image if exists, otherwise keep current + if (lockedImage.value) { + updatePreview(lockedImage.value); + } +}; + +const handleTimelineClick = (e: MouseEvent) => { + if (!timelineRef.value) return; + const rect = timelineRef.value.getBoundingClientRect(); + const y = e.clientY - rect.top + timelineRef.value.scrollTop; + const minutes = Math.floor(y / PIXELS_PER_MINUTE); + + const closest = findClosestImage(minutes); + if (closest) { + lockedImage.value = closest; + updatePreview(closest); + } }; @@ -186,6 +215,7 @@ const handleTimelineMouseLeave = () => { class="flex-1 overflow-y-auto relative no-scrollbar hover:cursor-crosshair group" @mousemove="handleTimelineMouseMove" @mouseleave="handleTimelineMouseLeave" + @click="handleTimelineClick" >
@@ -203,8 +233,11 @@ const handleTimelineMouseLeave = () => {
@@ -237,7 +270,8 @@ const handleTimelineMouseLeave = () => {
{{ selectedImage.time }} - Screenshot + Locked + Previewing
No activity selected
@@ -247,20 +281,18 @@ const handleTimelineMouseLeave = () => {
- -
- +
+ +
+
+
+
-
-
- -
-

Hover over the timeline to preview

-
- +

Hover over the timeline to preview

+