From 3a66879b889dce85d7498e216980a2c08288b36f Mon Sep 17 00:00:00 2001
From: Terry Truong <terry06890@gmail.com>
Date: Fri, 6 Jan 2023 23:58:25 +1100
Subject: Keep track of a 'current' timeline

Make timeline addition and searching use the current timeline.
Add keyboard controls for timeline panning, zooming, switching, opening, and closing.
Fix zoomTimeline() bug when not centering zoom on pointer.
---
 src/App.vue | 68 +++++++++++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 55 insertions(+), 13 deletions(-)

(limited to 'src/App.vue')

diff --git a/src/App.vue b/src/App.vue
index 960e648..803b53a 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -2,7 +2,7 @@
 <div class="absolute left-0 top-0 w-screen h-screen overflow-hidden flex flex-col">
 	<!-- Title bar -->
 	<div class="flex gap-2 p-2" :style="{backgroundColor: store.color.bgDark2}">
-		<h1 class="my-auto ml-2 text-4xl" :style="{color: store.color.altDark}">Histplorer</h1>
+		<h1 class="my-auto sm:ml-2 text-3xl sm:text-4xl" :style="{color: store.color.altDark}">Histplorer</h1>
 		<div class="mx-auto"/> <!-- Spacer -->
 		<!-- Icons -->
 		<icon-button :size="45" :style="buttonStyles" @click="helpOpen = true" title="Show help info">
@@ -23,10 +23,11 @@
 			:style="{backgroundColor: store.color.bg}" ref="contentAreaRef">
 		<time-line v-for="(state, idx) in timelines" :key="state.id"
 			:vert="vert" :initialState="state" :closeable="timelines.length > 1"
-			:eventTree="eventTree" :unitCountMaps="unitCountMaps" :searchTarget="timelineTargets[idx]"
+			:eventTree="eventTree" :unitCountMaps="unitCountMaps"
+			:current="idx == currentTimelineIdx && !modalOpen" :searchTarget="timelineTargets[idx]"
 			class="grow basis-full min-h-0 outline outline-1"
 			@close="onTimelineClose(idx)" @state-chg="onTimelineChg($event, idx)" @event-display="onEventDisplay"
-			@info-click="onInfoClick"/>
+			@info-click="onInfoClick" @pointerenter="currentTimelineIdx = idx"/>
 		<base-line :vert="vert" :timelines="timelines" class='m-1 sm:m-2'/>
 	</div>
 	<!-- Modals -->
@@ -87,23 +88,24 @@ onMounted(updateAreaDims);
 
 // Timeline data
 const timelines: Ref<TimelineState[]> = ref([]);
+const currentTimelineIdx = ref(0);
 let nextTimelineId = 1;
 function addTimeline(){
 	if (timelines.value.length == 0){
 		timelines.value.push(new TimelineState(nextTimelineId, store.initialStartDate, store.initialEndDate));
 	} else {
-		let last = timelines.value[timelines.value.length - 1];
-		timelines.value.push(new TimelineState(
-			nextTimelineId, last.startDate,
-			last.endDate, last.startOffset, last.endOffset, last.scaleIdx
-		));
+		let state = timelines.value[currentTimelineIdx.value];
+		timelines.value.splice(currentTimelineIdx.value, 0, new TimelineState(
+			nextTimelineId, state.startDate, state.endDate, state.startOffset, state.endOffset, state.scaleIdx));
 	}
-	timelineTargets.value.push([null, false]);
-	nextTimelineId++;
+	timelineTargets.value.splice(currentTimelineIdx.value, 0, [null, false]);
+	currentTimelineIdx.value += 1;
+	nextTimelineId += 1;
 }
 onMounted(addTimeline);
 function onTimelineChg(state: TimelineState, idx: number){
 	timelines.value[idx] = state;
+	currentTimelineIdx.value = idx;
 }
 
 // For timeline addition/removal
@@ -123,6 +125,9 @@ function onTimelineClose(idx: number){
 	}
 	timelines.value.splice(idx, 1);
 	timelineTargets.value.splice(idx, 1);
+	if (currentTimelineIdx.value >= idx){
+		currentTimelineIdx.value = Math.max(0, idx - 1);
+	}
 }
 
 // For storing and looking up events
@@ -319,9 +324,8 @@ const timelineTargets = ref([] as [HistEvent | null, boolean][]); // For communi
 function onSearch(event: HistEvent){
 	searchOpen.value = false;
 	// Trigger jump in endmost timeline
-	let timelineIdx = timelineTargets.value.length - 1;
-	let oldFlag = timelineTargets.value[timelineIdx];
-	timelineTargets.value.splice(timelineIdx, 1, [event, !oldFlag[1]]);
+	let oldVal = timelineTargets.value[currentTimelineIdx.value];
+	timelineTargets.value.splice(currentTimelineIdx.value, 1, [event, !oldVal[1]]);
 }
 
 // For settings modal
@@ -330,6 +334,10 @@ const settingsOpen = ref(false);
 // For help modal
 const helpOpen = ref(false);
 
+//
+const modalOpen = computed(() =>
+	(infoModalData.value != null || searchOpen.value || settingsOpen.value || helpOpen.value));
+
 // For resize handling
 let lastResizeHdlrTime = 0; // Used to throttle resize handling
 let afterResizeHdlr = 0; // Used to trigger handler after ending a run of resize events
@@ -366,6 +374,10 @@ function onKeyDown(evt: KeyboardEvent){
 			infoModalData.value = null;
 		} else if (searchOpen.value){
 			searchOpen.value = false;
+		} else if (settingsOpen.value){
+			settingsOpen.value = false;
+		} else if (helpOpen.value){
+			helpOpen.value = false;
 		}
 	} else if (evt.key == 'f' && evt.ctrlKey){
 		evt.preventDefault();
@@ -373,6 +385,36 @@ function onKeyDown(evt: KeyboardEvent){
 		if (!searchOpen.value){
 			searchOpen.value = true;
 		}
+	} else if (evt.key.startsWith('Arrow') && !modalOpen.value && !evt.shiftKey){
+		if (evt.key == 'ArrowUp'){
+			if (!vert.value){
+				if (currentTimelineIdx.value > 0){
+					currentTimelineIdx.value -= 1;
+				}
+			}
+		} else if (evt.key == 'ArrowDown'){
+			if (!vert.value){
+				if (currentTimelineIdx.value < timelines.value.length - 1){
+					currentTimelineIdx.value += 1;
+				}
+			}
+		} else if (evt.key == 'ArrowLeft'){
+			if (vert.value){
+				if (currentTimelineIdx.value > 0){
+					currentTimelineIdx.value -= 1;
+				}
+			}
+		} else if (evt.key == 'ArrowRight'){
+			if (vert.value){
+				if (currentTimelineIdx.value < timelines.value.length - 1){
+					currentTimelineIdx.value += 1;
+				}
+			}
+		}
+	} else if (evt.key == '+' && !modalOpen.value){
+		onTimelineAdd();
+	} else if (evt.key == 'Delete' && !modalOpen.value){
+		onTimelineClose(currentTimelineIdx.value);
 	}
 }
 onMounted(() => {
-- 
cgit v1.2.3

