support lock image

This commit is contained in:
Julian Freeman
2026-03-22 16:37:36 -04:00
parent 15d84dace4
commit 858cf85281

View File

@@ -23,6 +23,7 @@ const isPaused = ref(false);
const currentDate = ref(new Date().toISOString().split("T")[0]);
const timelineImages = ref<TimelineItem[]>([]);
const selectedImage = ref<TimelineItem | null>(null);
const lockedImage = ref<TimelineItem | null>(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);
}
};
</script>
@@ -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"
>
<!-- Ruler Canvas -->
<div :style="{ height: RULER_HEIGHT + 'px' }" class="relative ml-16">
@@ -203,8 +233,11 @@ const handleTimelineMouseLeave = () => {
<div
v-for="img in timelineImages"
:key="img.path"
class="absolute left-1 right-8 h-1 bg-[#007AFF]/40 rounded-full transition-all"
:class="selectedImage?.path === img.path ? 'bg-[#007AFF] h-1.5 shadow-sm z-10' : ''"
class="absolute left-1 right-8 h-1 bg-[#007AFF]/30 rounded-full transition-all"
:class="[
selectedImage?.path === img.path ? 'bg-[#007AFF]/60 h-2 shadow-sm z-10' : '',
lockedImage?.path === img.path ? 'bg-[#007AFF] h-2.5 ring-2 ring-[#007AFF]/20 z-20' : ''
]"
:style="{ top: timeToMinutes(img.time) * PIXELS_PER_MINUTE + 'px' }"
></div>
@@ -237,7 +270,8 @@ const handleTimelineMouseLeave = () => {
<div class="p-6 flex items-center justify-between border-b border-[#E5E5E7]/50 bg-white/80 backdrop-blur-md z-10">
<div v-if="selectedImage" class="flex items-center gap-3">
<span class="text-lg font-bold">{{ selectedImage.time }}</span>
<span class="text-xs font-medium px-2 py-0.5 bg-[#F2F2F7] text-[#86868B] rounded-full uppercase tracking-wider">Screenshot</span>
<span v-if="lockedImage?.path === selectedImage.path" class="text-xs font-bold px-2 py-0.5 bg-[#007AFF] text-white rounded-full uppercase tracking-wider">Locked</span>
<span v-else class="text-xs font-medium px-2 py-0.5 bg-[#F2F2F7] text-[#86868B] rounded-full uppercase tracking-wider">Previewing</span>
</div>
<div v-else class="text-[#86868B] font-medium">No activity selected</div>
@@ -247,20 +281,18 @@ const handleTimelineMouseLeave = () => {
</div>
<div class="flex-1 flex items-center justify-center p-12 overflow-hidden bg-[#F2F2F7]/30">
<transition name="fade" mode="out-in">
<div v-if="previewSrc" :key="previewSrc" class="relative group max-w-full max-h-full">
<img
:src="previewSrc"
class="max-w-full max-h-full object-contain rounded-3xl shadow-2xl border border-white/50"
/>
<div v-if="previewSrc" class="relative group max-w-full max-h-full">
<img
:src="previewSrc"
class="max-w-full max-h-full object-contain rounded-3xl shadow-2xl border border-white/50"
/>
</div>
<div v-else class="flex flex-col items-center gap-4 text-[#86868B]">
<div class="w-16 h-16 bg-[#E5E5E7] rounded-full flex items-center justify-center opacity-50">
<Maximize2 :size="30" />
</div>
<div v-else class="flex flex-col items-center gap-4 text-[#86868B]">
<div class="w-16 h-16 bg-[#E5E5E7] rounded-full flex items-center justify-center opacity-50">
<Maximize2 :size="30" />
</div>
<p class="font-medium">Hover over the timeline to preview</p>
</div>
</transition>
<p class="font-medium">Hover over the timeline to preview</p>
</div>
</div>
</div>
</div>