diff options
| author | Terry Truong <terry06890@gmail.com> | 2022-10-10 19:24:56 +1100 |
|---|---|---|
| committer | Terry Truong <terry06890@gmail.com> | 2022-10-10 19:27:00 +1100 |
| commit | bb4f7f6dd4054de2c29113f72bc43777337aed9b (patch) | |
| tree | 381ad22f0fbaf366082ea6f55cf037574bf1e0ce | |
| parent | 151e77296fbc80b36649c89604adde071f6139a3 (diff) | |
Add timeline bound indicators to baseline
| -rw-r--r-- | src/App.vue | 22 | ||||
| -rw-r--r-- | src/components/BaseLine.vue | 39 | ||||
| -rw-r--r-- | src/components/TimeLine.vue | 44 | ||||
| -rw-r--r-- | src/index.css | 23 | ||||
| -rw-r--r-- | src/lib.ts | 6 |
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; |
