From 356ffa2250c66c442cfa8c4e638ca53926396a65 Mon Sep 17 00:00:00 2001 From: Terry Truong Date: Sun, 15 Jan 2023 12:25:08 +1100 Subject: Add visual indication of pan/zoom failure Add tick display data to Tick objects Add 'movement fail' divs Add animateWithClass() utility function Add animate-show-then-fade class --- src/components/TimeLine.vue | 91 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 15 deletions(-) (limited to 'src/components/TimeLine.vue') diff --git a/src/components/TimeLine.vue b/src/components/TimeLine.vue index c72fbff..14f9d16 100644 --- a/src/components/TimeLine.vue +++ b/src/components/TimeLine.vue @@ -25,18 +25,11 @@ - + + +
+
+
@@ -80,13 +77,16 @@ import CloseIcon from './icon/CloseIcon.vue'; import {WRITING_MODE_HORZ, MIN_DATE, MAX_DATE, MONTH_SCALE, DAY_SCALE, SCALES, MONTH_NAMES, MIN_CAL_DATE, getDaysInMonth, HistDate, stepDate, getScaleRatio, getNumSubUnits, getUnitDiff, getEventPrecision, dateToUnit, dateToScaleDate, - moduloPositive, TimelineState, HistEvent, getImagePath} from '../lib'; + moduloPositive, TimelineState, HistEvent, getImagePath, animateWithClass} from '../lib'; import {useStore} from '../store'; import {RBTree} from '../rbtree'; // Refs const rootRef: Ref = ref(null); const svgRef: Ref = ref(null); +const minFailRef: Ref = ref(null); +const maxFailRef: Ref = ref(null); +const bgFailRef: Ref = ref(null); // Global store const store = useStore(); @@ -239,10 +239,32 @@ class Tick { date: HistDate; major: boolean; // False if tick is on the minor scale offset: number; // Distance from start of visible timeline, in major units - constructor(date: HistDate, major: boolean, offset: number){ + bound: null | 'min' | 'max'; // Indicates MIN_DATE or MAX_DATE tick + // SVG attributes + x1: number; + y1: number; + x2: number; + y2: number; + width: number; + // + constructor(date: HistDate, major: boolean, offset: number, bound=null as null | 'min' | 'max'){ this.date = date; this.major = major; this.offset = offset; + this.bound = bound; + if (this.bound == null){ + this.x1 = props.vert ? -store.tickLen / 2 : 0; + this.y1 = props.vert ? 0 : -store.tickLen / 2; + this.x2 = props.vert ? store.tickLen / 2 : 0; + this.y2 = props.vert ? 0 : store.tickLen / 2; + this.width = 1; + } else { + this.x1 = props.vert ? -store.endTickSz / 2 : 0; + this.y1 = props.vert ? 0 : -store.endTickSz / 2; + this.x2 = props.vert ? store.endTickSz / 2 : 0; + this.y2 = props.vert ? 0 : store.endTickSz / 2; + this.width = store.endTickSz; + } } } function getNumDisplayUnits({inclOffsets=true} = {}): number { // Get num major units in display range @@ -303,7 +325,15 @@ const ticks = computed((): Tick[] => { date = startDate.value.clone(); let numMajorUnits = getNumDisplayUnits({inclOffsets: false}); for (let i = 0; i <= numMajorUnits; i++){ - ticks.push(new Tick(date, true, startOffset.value + i)); + // Check for MIN_DATE or MAX_DATE + let minOrMax = null as null | 'min' | 'max'; + if (i == 0 && date.equals(MIN_DATE, scale.value)){ + minOrMax = 'min'; + } else if (i == numMajorUnits && date.equals(MAX_DATE, scale.value)){ + minOrMax = 'max'; + } + // Add ticks + ticks.push(new Tick(date, true, startOffset.value + i, minOrMax)); if (i == numMajorUnits){ break; } @@ -777,6 +807,7 @@ function panTimeline(scrollRatio: number){ } else { // Pan up to an offset of store.defaultEndTickOffset if (store.defaultEndTickOffset == endOffset.value){ + animateFailDiv('max'); console.log('Reached maximum date limit'); newStartOffset = startOffset.value; newEndOffset = endOffset.value; @@ -813,6 +844,7 @@ function panTimeline(scrollRatio: number){ } else { // Pan up to an offset of store.defaultEndTickOffset if (store.defaultEndTickOffset == startOffset.value){ + animateFailDiv('min'); console.log('Reached minimum date limit'); newStartOffset = startOffset.value; newEndOffset = endOffset.value; @@ -850,6 +882,7 @@ function zoomTimeline(zoomRatio: number, ignorePointer=false){ if (zoomRatio > 1 && startDate.value.equals(MIN_DATE, scale.value) && endDate.value.equals(MAX_DATE, scale.value)){ + animateFailDiv('both'); console.log('Reached upper scale limit'); return; } @@ -914,6 +947,7 @@ function zoomTimeline(zoomRatio: number, ignorePointer=false){ let tickDiff = availLen.value / newNumUnits; if (tickDiff < store.minTickSep){ // Zoom out into new scale if (scaleIdx.value == 0){ + animateFailDiv('both'); console.log('Reached zoom out limit'); return; } else { @@ -978,6 +1012,7 @@ function zoomTimeline(zoomRatio: number, ignorePointer=false){ } else { // If trying to zoom in if (scaleIdx.value == SCALES.length - 1){ if (newNumUnits < store.minLastTicks){ + animateFailDiv('bg'); console.log('Reached zoom in limit'); return; } @@ -1009,6 +1044,7 @@ function zoomTimeline(zoomRatio: number, ignorePointer=false){ } // Account for zooming into sub-year dates before MIN_CAL_DATE if (newStart.isEarlier(MIN_CAL_DATE, newScale) && (newScale == MONTH_SCALE || newScale == DAY_SCALE)){ + animateFailDiv('bg'); console.log('Unable to zoom into range where month/day scale is invalid'); return; } @@ -1330,4 +1366,29 @@ function countDivStyles(tickIdx: number, count: number): Record { transitionTimingFunction: 'linear', } } +function animateFailDiv(which: 'min' | 'max' | 'both' | 'bg'){ + if (which == 'min'){ + animateWithClass(minFailRef.value!, 'animate-show-then-fade'); + } else if (which == 'max'){ + animateWithClass(maxFailRef.value!, 'animate-show-then-fade'); + } else if (which == 'both'){ + animateWithClass(minFailRef.value!, 'animate-show-then-fade'); + animateWithClass(maxFailRef.value!, 'animate-show-then-fade'); + } else { + animateWithClass(bgFailRef.value!, 'animate-red-then-fade'); + } +} +function failDivStyles(minDiv: boolean){ + const gradientDir = props.vert ? (minDiv ? 'top' : 'bottom') : (minDiv ? 'left' : 'right'); + return { + top: props.vert ? (minDiv ? 0 : 'auto') : 0, + bottom: props.vert ? (minDiv ? 'auto' : 0) : 'auto', + left: props.vert ? 0 : (minDiv ? 0 : 'auto'), + right: props.vert ? 'auto' : (minDiv ? 'auto' : 0), + width: props.vert ? '100%' : '2cm', + height: props.vert ? '2cm' : '100%', + backgroundImage: `linear-gradient(to ${gradientDir}, rgba(255,0,0,0), rgba(255,0,0,0.3))`, + opacity: 0, + }; +} -- cgit v1.2.3