event hover effect
This commit is contained in:
48
src/App.vue
48
src/App.vue
@@ -53,6 +53,20 @@ const dragEndMin = ref<number | null>(null);
|
||||
|
||||
const editingEvent = ref<DBEvent>({ id: 0, date: "", start_minute: 0, end_minute: 0, main_tag_id: 0, sub_tag_id: null, content: "" });
|
||||
const hoveredTime = ref<string | null>(null);
|
||||
const hoveredEventDetails = ref<{ event: DBEvent; x: number; y: number } | null>(null);
|
||||
|
||||
const handleEventMouseEnter = (ev: DBEvent, e: MouseEvent) => {
|
||||
hoveredEventDetails.value = { event: ev, x: e.clientX, y: e.clientY };
|
||||
};
|
||||
const handleEventMouseMove = (e: MouseEvent) => {
|
||||
if (hoveredEventDetails.value) {
|
||||
hoveredEventDetails.value.x = e.clientX;
|
||||
hoveredEventDetails.value.y = e.clientY;
|
||||
}
|
||||
};
|
||||
const handleEventMouseLeave = () => {
|
||||
hoveredEventDetails.value = null;
|
||||
};
|
||||
const timelineRef = ref<HTMLElement | null>(null);
|
||||
|
||||
const rulerHeight = computed(() => TOTAL_MINUTES * timelineZoom.value);
|
||||
@@ -265,6 +279,14 @@ const eventDurationFormatted = computed(() => {
|
||||
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
||||
});
|
||||
|
||||
const formatDuration = (start: number, end: number) => {
|
||||
let diff = end - start;
|
||||
if (diff < 0) diff += 1440;
|
||||
const h = Math.floor(diff / 60);
|
||||
const m = diff % 60;
|
||||
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const updatePreview = async (img: TimelineItem | null) => {
|
||||
if (!img || (selectedImage.value?.path === img.path && previewSrc.value)) return;
|
||||
selectedImage.value = img;
|
||||
@@ -506,7 +528,14 @@ const handleExport = async () => {
|
||||
<div v-for="h in 24" :key="h" class="absolute left-0 w-full border-t border-[#E5E5E7]/60" :style="{ top: (h-1) * 60 * timelineZoom + 'px' }">
|
||||
<span v-if="h > 1" class="absolute -left-11 -top-2.5 text-[10px] font-bold text-[#86868B]">{{ String((h - 1 + 3) % 24).padStart(2, '0') }}:00</span>
|
||||
</div>
|
||||
<div v-for="ev in dayEvents" :key="ev.id" class="absolute left-0 w-[45%] opacity-80 border-l-4" :style="{ top: ev.start_minute * timelineZoom + 'px', height: (ev.end_minute - ev.start_minute) * timelineZoom + 'px', backgroundColor: getTagColor(ev.main_tag_id) + '22', borderColor: getTagColor(ev.main_tag_id) }" @click.stop="editingEvent = { ...ev }; isEventModalOpen = true"></div>
|
||||
<div v-for="ev in dayEvents" :key="ev.id"
|
||||
class="absolute left-0 w-[45%] opacity-80 border-l-4 transition-all duration-200 cursor-pointer hover:opacity-100 hover:border-l-[6px] hover:z-50 hover:brightness-110"
|
||||
:style="{ top: ev.start_minute * timelineZoom + 'px', height: (ev.end_minute - ev.start_minute) * timelineZoom + 'px', backgroundColor: getTagColor(ev.main_tag_id) + '22', borderColor: getTagColor(ev.main_tag_id) }"
|
||||
@mouseenter="handleEventMouseEnter(ev, $event)"
|
||||
@mousemove="handleEventMouseMove"
|
||||
@mouseleave="handleEventMouseLeave"
|
||||
@click.stop="editingEvent = { ...ev }; isEventModalOpen = true">
|
||||
</div>
|
||||
<div v-for="img in timelineImages" :key="img.path" class="absolute left-[50%] right-2 h-0.5 bg-[#007AFF]/20 rounded-full" :class="[selectedImage?.path === img.path ? 'bg-[#007AFF]/60 h-1 z-10' : '', lockedImage?.path === img.path ? 'bg-[#007AFF] h-1.5 ring-2 ring-[#007AFF]/20 z-20' : '']" :style="{ top: timeToLogicalMinutes(img.time, img.isNextDay) * timelineZoom + 'px' }"></div>
|
||||
<div v-if="dragStartMin !== null && dragEndMin !== null" class="absolute left-0 w-full bg-[#007AFF]/10 border-y-2 border-[#007AFF] pointer-events-none z-30" :style="{ top: Math.min(dragStartMin, dragEndMin) * timelineZoom + 'px', height: Math.abs(dragEndMin - dragStartMin) * timelineZoom + 'px' }"></div>
|
||||
<div v-if="hoveredTime" class="absolute left-0 right-0 border-t-2 border-[#007AFF] z-40 pointer-events-none" :style="{ top: timeToLogicalMinutes(hoveredTime, hoveredTime < '03:00') * timelineZoom + 'px' }"><div class="absolute -left-12 -top-3 bg-[#007AFF] text-white text-[9px] px-1 py-0.5 rounded font-bold">{{ hoveredTime }}</div></div>
|
||||
@@ -741,6 +770,23 @@ const handleExport = async () => {
|
||||
|
||||
<div v-if="isFullscreen && previewSrc" class="fixed inset-0 z-200 bg-black/95 flex items-center justify-center p-6 backdrop-blur-xl"><button @click="isFullscreen = false" class="absolute top-10 right-10 w-12 h-12 bg-white/10 rounded-full flex items-center justify-center"><X :size="32" class="text-white" /></button><img :src="previewSrc" class="max-w-full max-h-full object-contain shadow-2xl" /></div>
|
||||
|
||||
<div v-if="hoveredEventDetails"
|
||||
class="fixed z-300 pointer-events-none bg-white/90 backdrop-blur-xl border border-white/20 shadow-2xl rounded-2xl p-4 w-64 animate-in fade-in zoom-in-95 duration-150"
|
||||
:style="{ left: hoveredEventDetails.x + 15 + 'px', top: hoveredEventDetails.y + 15 + 'px' }">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<div class="w-3 h-3 rounded-full" :style="{ backgroundColor: getTagColor(hoveredEventDetails.event.main_tag_id) }"></div>
|
||||
<span class="font-bold text-sm text-[#1D1D1F]">{{ getTagName(hoveredEventDetails.event.main_tag_id) }}</span>
|
||||
<span v-if="hoveredEventDetails.event.sub_tag_id" class="text-[10px] font-bold text-[#86868B] bg-[#F2F2F7] px-2 py-0.5 rounded-md border border-[#E5E5E7]/50">{{ getTagName(hoveredEventDetails.event.sub_tag_id) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-[11px] font-bold text-[#86868B] mb-2.5">
|
||||
<span>{{ logicalMinutesToTime(hoveredEventDetails.event.start_minute) }} - {{ logicalMinutesToTime(hoveredEventDetails.event.end_minute) }}</span>
|
||||
<span class="bg-[#F2F2F7] px-1.5 py-0.5 rounded-lg border border-[#E5E5E7]/30 text-[#007AFF]">{{ formatDuration(hoveredEventDetails.event.start_minute, hoveredEventDetails.event.end_minute) }}</span>
|
||||
</div>
|
||||
<div v-if="hoveredEventDetails.event.content" class="text-xs text-[#1D1D1F] leading-relaxed wrap-break-words whitespace-pre-wrap">
|
||||
{{ hoveredEventDetails.event.content }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="toast.visible" class="fixed bottom-10 left-1/2 -translate-x-1/2 z-300 animate-in fade-in slide-in-from-bottom-4 duration-300">
|
||||
<div class="px-6 py-3 rounded-2xl shadow-2xl backdrop-blur-md flex items-center gap-3 border border-white/20" :class="toast.type === 'error' ? 'bg-[#FF3B30] text-white' : 'bg-white/90 text-[#1D1D1F]'">
|
||||
<div v-if="toast.type === 'error'" class="w-5 h-5 rounded-full border-2 border-white flex items-center justify-center text-[12px] font-black">!</div>
|
||||
|
||||
Reference in New Issue
Block a user