support lock image
This commit is contained in:
66
src/App.vue
66
src/App.vue
@@ -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,8 +281,7 @@ 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">
|
||||
<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"
|
||||
@@ -260,7 +293,6 @@ const handleTimelineMouseLeave = () => {
|
||||
</div>
|
||||
<p class="font-medium">Hover over the timeline to preview</p>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user