aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.vue92
-rw-r--r--src/components/TimeLine.vue55
-rw-r--r--src/lib.ts18
3 files changed, 133 insertions, 32 deletions
diff --git a/src/App.vue b/src/App.vue
index 334d75e..cc71cfe 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -99,11 +99,95 @@ function onTimelineRemove(idx: number){
timelines.value.splice(idx, 1);
}
-// Event data
-const eventTree: ShallowRef<RBTree<HistEvent>> = shallowRef(new RBTree(cmpHistEvent)); // Used to look up events by date range
+// For storing and looking up events
+const eventTree: ShallowRef<RBTree<HistEvent>> = shallowRef(new RBTree(cmpHistEvent));
+// For tracking ranges for which the server has no more events
+let exhaustedRanges = new RBTree(cmpDatePairs);
+function cmpDatePairs(datePair1: [HistDate, HistDate], datePair2: [HistDate, HistDate]){
+ return datePair1[0].cmp(datePair2[0]);
+}
+function isExhaustedRange(startDate: HistDate, endDate: HistDate): boolean {
+ // Check if input range is contained in a stored exhausted range
+ let itr = exhaustedRanges.lowerBound([startDate, new HistDate(1)]);
+ let datePair = itr.data();
+ if (datePair == null){
+ datePair = itr.prev();
+ if (datePair == null){
+ return false;
+ } else {
+ return !datePair[1].isEarlier(endDate);
+ }
+ } else {
+ if (startDate.isEarlier(datePair[0])){
+ return false;
+ } else {
+ return !datePair[1].isEarlier(endDate);
+ }
+ }
+}
+function addExhaustedRange(startDate: HistDate, endDate: HistDate){
+ let rangesToRemove: HistDate[] = []; // Holds starts of ranges to remove
+ // Find ranges to remove
+ let itr = exhaustedRanges.lowerBound([startDate, new HistDate(1)]);
+ let prevRange = itr.prev();
+ if (prevRange != null){ // Check for start-overlapping range
+ if (prevRange[1].isEarlier(startDate)){
+ prevRange = null;
+ } else {
+ rangesToRemove.push(prevRange[0]);
+ }
+ }
+ let datePair = itr.next();
+ while (datePair != null && !endDate.isEarlier(datePair[1])){ // Check for included ranges
+ rangesToRemove.push(datePair[0]);
+ datePair = itr.next();
+ }
+ let nextRange = itr.data();
+ if (nextRange != null){ // Check for end-overlapping range
+ if (endDate.isEarlier(nextRange[0])){
+ nextRange = null;
+ } else {
+ rangesToRemove.push(nextRange[0])
+ }
+ }
+ // Remove included/overlapping ranges
+ for (let start of rangesToRemove){
+ exhaustedRanges.remove([start, new HistDate(1)]);
+ }
+ // Add possibly-merged range
+ if (prevRange != null){
+ startDate = prevRange[0];
+ }
+ if (nextRange != null){
+ endDate = nextRange[1];
+ }
+ exhaustedRanges.insert([startDate, endDate]);
+}
+// For getting events from server
+const EVENT_REQ_LIMIT = 10;
async function onEventReq(startDate: HistDate, endDate: HistDate){
+ // Exclude exhausted range
+ if (isExhaustedRange(startDate, endDate)){
+ return;
+ }
+ // Get existing events in range
+ let existingEventIds: number[] = [];
+ let itr = eventTree.value.lowerBound(new HistEvent(0, '', startDate));
+ while (itr.data() != null){
+ let event = itr.data()!;
+ itr.next();
+ if (endDate.isEarlier(event.start)){
+ break;
+ }
+ existingEventIds.push(event.id);
+ }
// Get events from server
- let urlParams = new URLSearchParams({type: 'events', range: `${startDate}.${endDate}`, limit: '10'});
+ let urlParams = new URLSearchParams({
+ type: 'events',
+ range: `${startDate}.${endDate}`,
+ limit: String(EVENT_REQ_LIMIT),
+ excl: existingEventIds.join('.'),
+ });
let responseObj: HistEventJson[] = await queryServer(urlParams);
if (responseObj == null){
return;
@@ -118,6 +202,8 @@ async function onEventReq(startDate: HistDate, endDate: HistDate){
// Notify components if new events were added
if (added){
eventTree.value = rbtree_shallow_copy(eventTree.value); // Note: triggerRef(eventTree) does not work here
+ } else {
+ addExhaustedRange(startDate, endDate); // Mark as exhausted range
}
}
diff --git a/src/components/TimeLine.vue b/src/components/TimeLine.vue
index 62780c8..6c6b6fc 100644
--- a/src/components/TimeLine.vue
+++ b/src/components/TimeLine.vue
@@ -28,10 +28,10 @@
</text>
</svg>
<!-- Events -->
- <div v-for="id in idToEvent.keys()" :key="id"
+ <div v-for="id in idToPos.keys()" :key="id"
class="absolute bg-black text-white border border-white rounded animate-fadein"
:style="eventStyles(id)">
- {{idToEvent.get(id)!.event.title}}
+ {{idToEvent.get(id)!.title}}
</div>
<!-- Buttons -->
<icon-button :size="30" class="absolute top-2 right-2"
@@ -43,7 +43,7 @@
</template>
<script setup lang="ts">
-import {ref, onMounted, computed, watch, PropType, Ref, nextTick} from 'vue';
+import {ref, onMounted, computed, watch, PropType, Ref} from 'vue';
// Components
import IconButton from './IconButton.vue';
// Icons
@@ -254,31 +254,41 @@ const ticks = computed((): Ticks => {
});
// For displayed events
-const idToEvent = computed(() => { // Maps visible event IDs to HistEvent, x-pos, y-pos, width, and height
- let idToPos: Map<number, {event: HistEvent, x: number, y: number, w: number, h: number}> = new Map();
- let numUnits = getNumVisibleUnits();
- let minorAxisStep = 0;
- // Find events to display, and do basic layouting
- let iter = props.eventTree.lowerBound(new HistEvent(0, '', startDate.value));
- while (iter.data() != null){
- let event = iter.data()!;
- iter.next();
+let pendingReq = false;
+const idToEvent = computed(() => { // Maps visible event IDs to HistEvents
+ let map: Map<number, HistEvent> = new Map();
+ // Find events to display
+ let itr = props.eventTree.lowerBound(new HistEvent(0, '', startDate.value));
+ while (itr.data() != null){
+ let event = itr.data()!;
+ itr.next();
if (endDate.value.isEarlier(event.start)){
break;
}
+ map.set(event.id, event);
+ }
+ pendingReq = false;
+ return map;
+});
+const idToPos = computed(() => {
+ let map: Map<number, [number, number, number, number]> = new Map(); // Maps visible event IDs to x/y/w/h
+ let numUnits = getNumVisibleUnits();
+ let minorAxisStep = 0;
+ for (let event of idToEvent.value.values()){
let unitOffset = getUnitDiff(event.start, startDate.value, scale.value) + startOffset.value;
let posFrac = unitOffset / numUnits;
let posX = props.vert ? minorAxisStep : availLen.value * posFrac;
let posY = props.vert ? availLen.value * posFrac : minorAxisStep;
- idToPos.set(event.id, {event, x: posX, y: posY, w: 100, h: 100});
+ map.set(event.id, [posX, posY, 100, 100]);
minorAxisStep += 10;
}
// If more events could be displayed, notify parent
- if (idToPos.size < 3){
- nextTick(() => emit('event-req', startDate.value, endDate.value));
+ if (map.size < 3 && !pendingReq){
+ emit('event-req', startDate.value, endDate.value);
}
+ pendingReq = true;
//
- return idToPos;
+ return map;
});
// For panning/zooming
@@ -729,13 +739,14 @@ function tickLabelStyles(idx: number){
}
}
function eventStyles(eventId: number){
- const evt = idToEvent.value.get(eventId)!;
+ const event = idToEvent.value.get(eventId)!;
+ const [x, y, w, h] = idToPos.value.get(eventId)!;
return {
- left: evt.x + 'px',
- top: evt.y + 'px',
- width: evt.w + 'px',
- height: evt.h + 'px',
- backgroundImage: `url(${getImagePath(evt.event.imgId)})`,
+ left: x + 'px',
+ top: y + 'px',
+ width: w + 'px',
+ height: h + 'px',
+ backgroundImage: `url(${getImagePath(event.imgId)})`,
backgroundSize: 'cover',
transitionProperty: skipTransition.value ? 'none' : 'all',
transitionDuration,
diff --git a/src/lib.ts b/src/lib.ts
index b635d28..4edf2f8 100644
--- a/src/lib.ts
+++ b/src/lib.ts
@@ -89,6 +89,15 @@ export class HistDate {
return Math.floor(this.year / scale) == Math.floor(other.year / scale);
}
}
+ cmp(other: HistDate, scale=DAY_SCALE){
+ if (this.isEarlier(other)){
+ return -1;
+ } else if (!this.equals(other)){
+ return 1;
+ } else {
+ return 0;
+ }
+ }
isEarlier(other: HistDate, scale=DAY_SCALE){
const yearlyScale = scale != DAY_SCALE && scale != MONTH_SCALE;
const thisYear = yearlyScale ? Math.floor(this.year / scale) : this.year;
@@ -324,13 +333,8 @@ export class HistEvent {
}
}
export function cmpHistEvent(event: HistEvent, event2: HistEvent){
- if (event.start.isEarlier(event2.start)){
- return -1;
- } else if (!event.start.equals(event2.start)){
- return 1;
- } else {
- return event.id - event2.id;
- }
+ let cmp = event.start.cmp(event2.start);
+ return cmp != 0 ? cmp : event.id - event2.id;;
}
// For server requests