aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/App.vue22
-rw-r--r--src/components/BaseLine.vue39
-rw-r--r--src/components/TimeLine.vue44
-rw-r--r--src/index.css23
-rw-r--r--src/lib.ts6
5 files changed, 97 insertions, 37 deletions
diff --git a/src/App.vue b/src/App.vue
index 2555d3f..95009e7 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -18,8 +18,10 @@
<!-- Content area -->
<div class="grow min-h-0 bg-stone-800 flex" :class="{'flex-col': !vert}" ref="contentAreaRef">
<time-line v-for="(data, idx) in timelineData" :key="data"
- class="grow basis-full min-h-0 outline outline-1" :vert="vert" @close="onTimelineClose(idx)"/>
- <base-line :vert="vert" :timelineData="[]"/>
+ :vert="vert" :initialStart="data.start" :initialEnd="data.end"
+ class="grow basis-full min-h-0 outline outline-1"
+ @close="onTimelineClose(idx)" @bound-chg="onBoundChg($event, idx)"/>
+ <base-line :vert="vert" :timelineData="timelineData"/>
</div>
</div>
</template>
@@ -50,14 +52,21 @@ onMounted(updateAreaDims)
// For multiple timelines
const vert = computed(() => contentHeight.value > contentWidth.value);
-const timelineData = ref([{}]);
+const timelineData = ref([]);
+let nextTimelineId = 1;
+function genTimelineData(){
+ let data = {id: nextTimelineId, start: -500, end: 500};
+ nextTimelineId++;
+ return data;
+}
+timelineData.value.push(genTimelineData());
function onTimelineAdd(){
if (vert.value && contentWidth.value / (timelineData.value.length + 1) < 150 ||
!vert.value && contentHeight.value / (timelineData.value.length + 1) < 150){
console.log('Reached timeline min size');
return;
}
- timelineData.value.push({});
+ timelineData.value.push(genTimelineData());
}
function onTimelineClose(idx: number){
if (timelineData.value.length == 1){
@@ -66,6 +75,11 @@ function onTimelineClose(idx: number){
}
timelineData.value.splice(idx, 1);
}
+function onBoundChg(newBounds: [number, number], idx: number){
+ let data = timelineData.value[idx];
+ data.start = newBounds[0];
+ data.end = newBounds[1];
+}
// For resize handling
let lastResizeHdlrTime = 0; // Used to throttle resize handling
diff --git a/src/components/BaseLine.vue b/src/components/BaseLine.vue
index f0008f9..bde2e56 100644
--- a/src/components/BaseLine.vue
+++ b/src/components/BaseLine.vue
@@ -1,21 +1,28 @@
<template>
-<div class="bg-stone-900 text-stone-50 flex" :class="{'flex-col': vert}">
+<div class="bg-stone-900 text-stone-50 flex relative" :class="{'flex-col': vert}">
<div v-for="p in periods" :key="p.label" :style="periodStyles(p)">
<div :style="labelStyles">{{p.label}}</div>
</div>
+ <TransitionGroup name="fade">
+ <div v-for="d in timelineData" :key="d.id"
+ class="absolute bg-yellow-200/50" :style="spanStyles(d)">
+ {{d.id}}
+ </div>
+ </TransitionGroup>
</div>
</template>
<script setup lang="ts">
-import {ref, computed, PropType} from 'vue';
+import {ref, computed} from 'vue';
+import {MIN_DATE, MAX_DATE} from '../lib';
// Props
const props = defineProps({
vert: {type: Boolean, required: true},
- timelineData: {type: Object as PropType<number[][]>, required: true},
+ timelineData: {type: Object, required: true},
});
-// Time periods to represent
+// Static time periods to represent
const periods = ref([
{label: 'One', len: 1},
{label: 'Two', len: 2},
@@ -35,4 +42,28 @@ const labelStyles = computed(() => ({
width: props.vert ? '40px' : 'auto',
padding: props.vert ? '0' : '4px',
}));
+function spanStyles(d){
+ let styles: Record<string,string>;
+ let beforeFrac = Math.max(0, (d.start - MIN_DATE) / (MAX_DATE - MIN_DATE)); // Clip at zero due to end-padding
+ let lenFrac = Math.min(1 - beforeFrac, (d.end - d.start) / (MAX_DATE - MIN_DATE));
+ if (props.vert){
+ styles = {
+ top: beforeFrac * 100 + '%',
+ left: 0,
+ height: lenFrac * 100 + '%',
+ width: '100%',
+ }
+ } else {
+ styles = {
+ top: 0,
+ left: beforeFrac * 100 + '%',
+ height: '100%',
+ width: lenFrac * 100 + '%',
+ }
+ }
+ return {
+ ...styles,
+ transition: 'all 300ms ease-out',
+ };
+}
</script>
diff --git a/src/components/TimeLine.vue b/src/components/TimeLine.vue
index 796f5c7..2cdf31f 100644
--- a/src/components/TimeLine.vue
+++ b/src/components/TimeLine.vue
@@ -32,7 +32,8 @@
</template>
<script setup lang="ts">
-import {ref, onMounted, computed, nextTick} from 'vue';
+import {ref, onMounted, computed, watch, nextTick} from 'vue';
+import {MIN_DATE, MAX_DATE} from '../lib';
// Components
import IconButton from './IconButton.vue';
// Icons
@@ -44,8 +45,10 @@ const rootRef = ref(null as HTMLElement | null);
// Props + events
const props = defineProps({
vert: {type: Boolean, required: true},
+ initialStart: {type: Number, required: true},
+ initialEnd: {type: Number, required: true},
});
-const emit = defineEmits(['close']);
+const emit = defineEmits(['close', 'bound-chg']);
// For skipping transitions on horz/vert swap
const skipTransition = ref(false);
@@ -72,10 +75,8 @@ const resizeObserver = new ResizeObserver((entries) => {
onMounted(() => resizeObserver.observe(rootRef.value));
// Vars
-const MIN_DATE = -1000; // Lowest date that gets marked
-const MAX_DATE = 1000;
-const startDate = ref(0); // Lowest date on displayed timeline
-const endDate = ref(0);
+const startDate = ref(props.initialStart); // Lowest date on displayed timeline
+const endDate = ref(props.initialEnd);
const SCALES = [200, 50, 10, 1, 0.2]; // The timeline get divided into units of SCALES[0], then SCALES[1], etc
let scaleIdx = 0; // Index of current scale in SCALES
const ticks = ref(null); // Holds date value for each tick
@@ -92,21 +93,17 @@ const availLen = computed(() => props.vert ? height.value : width.value);
function initTicks(): number[] {
// Find smallest usable scale
for (let i = 0; i < SCALES.length; i++){
- let dateLen = MAX_DATE - MIN_DATE + (padUnits.value * SCALES[i]) * 2;
+ let dateLen = endDate.value - startDate.value;
if (availLen.value * (SCALES[i] / dateLen) > MIN_TICK_SEP){
scaleIdx = i;
} else {
break;
}
}
- // Set start/end date
- let extraPad = padUnits.value * SCALES[scaleIdx];
- startDate.value = MIN_DATE - extraPad;
- endDate.value = MAX_DATE + extraPad;
// Get tick values
let newTicks = [];
- let next = MIN_DATE;
- while (next <= MAX_DATE){
+ let next = startDate.value;
+ while (next <= endDate.value){
newTicks.push(next);
next += SCALES[scaleIdx];
}
@@ -362,6 +359,11 @@ function onClose(){
emit('close');
}
+// For bound-change signalling
+watch(startDate, () => {
+ emit('bound-chg', [startDate.value, endDate.value]);
+});
+
// Styles
const mainlineStyles = computed(() => ({
transform: props.vert ?
@@ -401,19 +403,3 @@ function tickLabelStyles(tick: number){
}
}
</script>
-
-<style>
-.animate-fadein {
- animation-name: fadein;
- animation-duration: 300ms;
- animation-timing-function: ease-in;
-}
-@keyframes fadein {
- from {
- opacity: 0;
- }
- to {
- opacity: 1;
- }
-}
-</style>
diff --git a/src/index.css b/src/index.css
index 44996b5..e050c82 100644
--- a/src/index.css
+++ b/src/index.css
@@ -3,6 +3,29 @@
@tailwind components;
@tailwind utilities;
+/* For transitions/animations */
+.fade-enter-from, .fade-leave-to {
+ opacity: 0;
+}
+.fade-enter-active, .fade-leave-active {
+ transition-property: opacity;
+ transition-duration: 300ms;
+ transition-timing-function: ease-out;
+}
+.animate-fadein {
+ animation-name: fadein;
+ animation-duration: 300ms;
+ animation-timing-function: ease-in;
+}
+@keyframes fadein {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
/* Other */
@font-face {
font-family: Ubuntu;
diff --git a/src/lib.ts b/src/lib.ts
new file mode 100644
index 0000000..e482f72
--- /dev/null
+++ b/src/lib.ts
@@ -0,0 +1,6 @@
+/*
+ * Project-wide globals
+ */
+
+export const MIN_DATE = -1000;
+export const MAX_DATE = 1000;