aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/App.vue39
-rw-r--r--src/components/TimeLine.vue66
-rw-r--r--src/lib.ts14
3 files changed, 100 insertions, 19 deletions
diff --git a/src/App.vue b/src/App.vue
index 1cd00f5..24da714 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -19,8 +19,9 @@
<div class="grow min-h-0 flex" :class="{'flex-col': !vert}"
:style="{backgroundColor: store.color.bg}" ref="contentAreaRef">
<time-line v-for="(state, idx) in timelines" :key="state.id"
- :vert="vert" :initialState="state" class="grow basis-full min-h-0 outline outline-1"
- @remove="onTimelineRemove(idx)" @state-chg="onTimelineChg($event, idx)"/>
+ :vert="vert" :initialState="state" :eventMap="eventMap"
+ class="grow basis-full min-h-0 outline outline-1"
+ @remove="onTimelineRemove(idx)" @state-chg="onTimelineChg($event, idx)" @event-req="onEventReq"/>
<base-line :vert="vert" :timelines="timelines"/>
</div>
</div>
@@ -37,7 +38,7 @@ import PlusIcon from './components/icon/PlusIcon.vue';
import SettingsIcon from './components/icon/SettingsIcon.vue';
import HelpIcon from './components/icon/HelpIcon.vue';
// Other
-import {HistDate, TimelineState} from './lib';
+import {HistDate, TimelineState, HistEvent, getUnitDiff, MONTH_SCALE, DAY_SCALE, stepDate} from './lib';
import {useStore} from './store';
// Refs
@@ -97,6 +98,38 @@ function onTimelineRemove(idx: number){
timelines.value.splice(idx, 1);
}
+// Event data
+const eventMap: Ref<Map<number, HistEvent>> = ref(new Map()); // Maps event IDs to HistEvents
+let nextEventId = 0; // For generating placeholder events
+function onEventReq(startDate: HistDate, endDate: HistDate){
+ // Get number of existing events in range
+ let numExisting = 0;
+ for (let event of eventMap.value.values()){
+ if (!event.start.isEarlier(startDate) && !endDate.isEarlier(event.start)){
+ numExisting += 1;
+ }
+ }
+ // Possibly add new events
+ let tempScale = 1;
+ let numUnits = getUnitDiff(startDate, endDate, tempScale);
+ if (numUnits < 2){
+ tempScale = MONTH_SCALE;
+ numUnits = getUnitDiff(startDate, endDate, tempScale);
+ if (numUnits < 2){
+ tempScale = DAY_SCALE;
+ numUnits = getUnitDiff(startDate, endDate, tempScale);
+ }
+ }
+ for (let i = 0; i < 3 - numExisting; i++){
+ let start = startDate.clone();
+ let steps = Math.floor(Math.random() * (numUnits + 1));
+ stepDate(start, tempScale, {count: steps, inplace: true});
+ let event = {id: nextEventId, title: `Event ${nextEventId}`, start, startUpper: null, end: null, endUpper: null};
+ eventMap.value.set(event.id, event);
+ nextEventId += 1;
+ }
+}
+
// 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
diff --git a/src/components/TimeLine.vue b/src/components/TimeLine.vue
index add7ce8..d71176f 100644
--- a/src/components/TimeLine.vue
+++ b/src/components/TimeLine.vue
@@ -27,6 +27,12 @@
{{date}}
</text>
</svg>
+ <!-- Events -->
+ <div v-for="id in eventIdToPos.keys()" :key="id"
+ class="absolute bg-black text-white border border-white rounded animate-fadein"
+ :style="eventStyles(id)">
+ {{eventMap.get(id)!.id}}
+ </div>
<!-- Buttons -->
<icon-button :size="30" class="absolute top-2 right-2"
:style="{color: store.color.text, backgroundColor: store.color.altDark2}"
@@ -37,18 +43,19 @@
</template>
<script setup lang="ts">
-import {ref, onMounted, computed, watch, PropType} from 'vue';
+import {ref, onMounted, computed, watch, PropType, Ref, nextTick} from 'vue';
// Components
import IconButton from './IconButton.vue';
// Icons
import MinusIcon from './icon/MinusIcon.vue';
// Other
import {WRITING_MODE_HORZ, MIN_DATE, MAX_DATE, MONTH_SCALE, DAY_SCALE, SCALES, MIN_CAL_DATE,
- HistDate, stepDate, inDateScale, getScaleRatio, getDaysInMonth, moduloPositive, TimelineState} from '../lib';
+ HistDate, stepDate, inDateScale, getScaleRatio, getUnitDiff, getDaysInMonth, moduloPositive, TimelineState,
+ HistEvent} from '../lib';
import {useStore} from '../store';
// Refs
-const rootRef = ref(null as HTMLElement | null);
+const rootRef: Ref<HTMLElement | null> = ref(null);
// Global store
const store = useStore();
@@ -57,8 +64,9 @@ const store = useStore();
const props = defineProps({
vert: {type: Boolean, required: true},
initialState: {type: Object as PropType<TimelineState>, required: true},
+ eventMap: {type: Object as PropType<Map<number, HistEvent>>, required: true},
});
-const emit = defineEmits(['remove', 'state-chg']);
+const emit = defineEmits(['remove', 'state-chg', 'event-req']);
// For size tracking
const width = ref(0);
@@ -91,7 +99,7 @@ const resizeObserver = new ResizeObserver((entries) => {
onMounted(() => resizeObserver.observe(rootRef.value as HTMLElement));
// Timeline data
-const ID = props.initialState.id;
+const ID = props.initialState.id as number;
const startDate = ref(props.initialState.startDate); // Earliest date to display
const endDate = ref(props.initialState.endDate);
const INITIAL_EXTRA_OFFSET = 0.5;
@@ -164,15 +172,8 @@ type Ticks = {
endIdx: number, // Index of last visible tick
};
function getNumVisibleUnits(): number {
- let numUnits: number;
- if (scale.value == DAY_SCALE){
- numUnits = startDate.value.getDayDiff(endDate.value);
- } else if (scale.value == MONTH_SCALE){
- numUnits = startDate.value.getMonthDiff(endDate.value);
- } else {
- numUnits = startDate.value.getYearDiff(endDate.value) / scale.value;
- }
- return numUnits + startOffset.value + endOffset.value;
+ let unitDiff = getUnitDiff(startDate.value, endDate.value, scale.value);
+ return unitDiff + startOffset.value + endOffset.value;
}
const ticks = computed((): Ticks => {
if (!mounted.value){
@@ -251,6 +252,31 @@ const ticks = computed((): Ticks => {
return {dates, startIdx, endIdx};
});
+// For displayed events
+const eventIdToPos = computed(() => { // Maps visible event IDs to x-pos, y-pos, width, and height
+ let idToPos: Map<number, [number, number, number, number]> = new Map();
+ // Find events to display, and do basic layouting
+ let numUnits = getNumVisibleUnits();
+ let minorAxisStep = 0;
+ for (let event of props.eventMap.values()){
+ if (event.start.isEarlier(startDate.value) || endDate.value.isEarlier(event.start)){
+ continue;
+ }
+ 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, [posX, posY, 50, 50]);
+ minorAxisStep += 5;
+ }
+ // If more events could be displayed, notify parent
+ if (idToPos.size < 3){
+ nextTick(() => emit('event-req', startDate.value, endDate.value));
+ }
+ //
+ return idToPos;
+});
+
// For panning/zooming
function panTimeline(scrollRatio: number){
let numUnits = getNumVisibleUnits();
@@ -698,4 +724,16 @@ function tickLabelStyles(idx: number){
opacity: (offset >= labelSz && offset <= availLen.value - labelSz) ? 1 : 0,
}
}
+function eventStyles(eventId: number){
+ const [x, y, w, h] = eventIdToPos.value.get(eventId)!;
+ return {
+ left: x + 'px',
+ top: y + 'px',
+ width: w + 'px',
+ height: h + 'px',
+ transitionProperty: skipTransition.value ? 'none' : 'all',
+ transitionDuration,
+ transitionTimingFunction,
+ };
+}
</script>
diff --git a/src/lib.ts b/src/lib.ts
index 54dffc5..ad3686d 100644
--- a/src/lib.ts
+++ b/src/lib.ts
@@ -266,6 +266,15 @@ export function getScaleRatio(scale: number, scale2: number){
}
return scale2 / scale;
}
+export function getUnitDiff(date: HistDate, date2: HistDate, scale: number): number {
+ if (scale == DAY_SCALE){
+ return date.getDayDiff(date2);
+ } else if (scale == MONTH_SCALE){
+ return date.getMonthDiff(date2);
+ } else {
+ return date.getYearDiff(date2) / scale;
+ }
+}
// For sending timeline-bound data to BaseLine
export class TimelineState {
@@ -284,12 +293,13 @@ export class TimelineState {
this.endOffset = endOffset;
this.scaleIdx = scaleIdx;
}
-};
+}
export type HistEvent = {
+ id: number,
title: string,
start: HistDate,
startUpper: HistDate | null,
- end: HistDate,
+ end: HistDate | null,
endUpper: HistDate | null,
};