diff options
| -rw-r--r-- | src/App.vue | 44 | ||||
| -rw-r--r-- | src/components/BaseLine.vue | 27 | ||||
| -rw-r--r-- | src/components/TimeLine.vue | 38 | ||||
| -rw-r--r-- | src/lib.ts | 20 |
4 files changed, 79 insertions, 50 deletions
diff --git a/src/App.vue b/src/App.vue index 5970449..1cd00f5 100644 --- a/src/App.vue +++ b/src/App.vue @@ -18,11 +18,10 @@ <!-- Content area --> <div class="grow min-h-0 flex" :class="{'flex-col': !vert}" :style="{backgroundColor: store.color.bg}" ref="contentAreaRef"> - <time-line v-for="(range, idx) in timelineRanges" :key="range.id" - :vert="vert" :initialStart="range.start" :initialEnd="range.end" - class="grow basis-full min-h-0 outline outline-1" - @remove="onTimelineRemove(idx)" @range-chg="onRangeChg($event, idx)"/> - <base-line :vert="vert" :timelineRanges="timelineRanges"/> + <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)"/> + <base-line :vert="vert" :timelines="timelines"/> </div> </div> </template> @@ -38,7 +37,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, TimelineRange} from './lib'; +import {HistDate, TimelineState} from './lib'; import {useStore} from './store'; // Refs @@ -59,42 +58,43 @@ function updateAreaDims(){ onMounted(updateAreaDims) // Timeline data -const timelineRanges: Ref<TimelineRange[]> = ref([]); +const timelines: Ref<TimelineState[]> = ref([]); const INITIAL_START_DATE = new HistDate(1900, 1, 1); const INITIAL_END_DATE = new HistDate(2000, 1, 1); let nextTimelineId = 1; -function addNewTimelineRange(){ - if (timelineRanges.value.length == 0){ - timelineRanges.value.push({id: nextTimelineId, start: INITIAL_START_DATE, end: INITIAL_END_DATE}); +function addTimeline(){ + if (timelines.value.length == 0){ + timelines.value.push(new TimelineState(nextTimelineId, INITIAL_START_DATE, INITIAL_END_DATE)); } else { - let lastRange = timelineRanges.value[timelineRanges.value.length - 1]; - timelineRanges.value.push({id: nextTimelineId, start: lastRange.start, end: lastRange.end}); + let last = timelines.value[timelines.value.length - 1]; + timelines.value.push(new TimelineState( + nextTimelineId, last.startDate, + last.endDate, last.startOffset, last.endOffset, last.scaleIdx + )); } nextTimelineId++; } -addNewTimelineRange(); -function onRangeChg(newBounds: [HistDate, HistDate], idx: number){ - let range = timelineRanges.value[idx]; - range.start = newBounds[0]; - range.end = newBounds[1]; +addTimeline(); +function onTimelineChg(state: TimelineState, idx: number){ + timelines.value[idx] = state; } // For timeline addition/removal const MIN_TIMELINE_BREADTH = 150; function onTimelineAdd(){ - if (vert.value && contentWidth.value / (timelineRanges.value.length + 1) < MIN_TIMELINE_BREADTH || - !vert.value && contentHeight.value / (timelineRanges.value.length + 1) < MIN_TIMELINE_BREADTH){ + if (vert.value && contentWidth.value / (timelines.value.length + 1) < MIN_TIMELINE_BREADTH || + !vert.value && contentHeight.value / (timelines.value.length + 1) < MIN_TIMELINE_BREADTH){ console.log('Reached timeline minimum breadth'); return; } - addNewTimelineRange(); + addTimeline(); } function onTimelineRemove(idx: number){ - if (timelineRanges.value.length == 1){ + if (timelines.value.length == 1){ console.log('Ignored removal of last timeline') return; } - timelineRanges.value.splice(idx, 1); + timelines.value.splice(idx, 1); } // For resize handling diff --git a/src/components/BaseLine.vue b/src/components/BaseLine.vue index 82507c1..33d8a88 100644 --- a/src/components/BaseLine.vue +++ b/src/components/BaseLine.vue @@ -5,8 +5,8 @@ <div :style="labelStyles">{{p.label}}</div> </div> <TransitionGroup name="fade" v-if="mounted"> - <div v-for="range in timelineRanges" :key="range.id" class="absolute" :style="spanStyles(range)"> - {{range.id}} + <div v-for="state in timelines" :key="state.id" class="absolute" :style="spanStyles(state)"> + {{state.id}} </div> </TransitionGroup> </div> @@ -14,7 +14,7 @@ <script setup lang="ts"> import {ref, computed, onMounted, PropType, Ref} from 'vue'; -import {MIN_DATE, MAX_DATE, WRITING_MODE_HORZ, TimelineRange} from '../lib'; +import {MIN_DATE, MAX_DATE, SCALES, MONTH_SCALE, DAY_SCALE, WRITING_MODE_HORZ, TimelineState, stepDate} from '../lib'; import {useStore} from '../store'; // Refs @@ -26,7 +26,7 @@ const store = useStore(); // Props const props = defineProps({ vert: {type: Boolean, required: true}, - timelineRanges: {type: Object as PropType<TimelineRange[]>, required: true}, + timelines: {type: Object as PropType<TimelineState[]>, required: true}, }); // Static time periods @@ -77,12 +77,23 @@ const labelStyles = computed((): Record<string, string> => ({ width: props.vert ? '40px' : 'auto', padding: props.vert ? '0' : '4px', })); -function spanStyles(range: TimelineRange){ +function spanStyles(state: TimelineState){ let styles: Record<string,string>; let availLen = props.vert ? height.value : width.value; - // Determine positions in full timeline - let startFrac = (range.start.year - MIN_DATE.year) / (MAX_DATE.year - MIN_DATE.year); - let lenFrac = (range.end.year - range.start.year) / (MAX_DATE.year - MIN_DATE.year); + // Determine start/end date + if (state.startOffset == null || state.endOffset == null || state.scaleIdx == null){ + return {display: 'none'}; + } + let start = state.startDate.clone(); + let end = state.endDate.clone(); + let scale = SCALES[state.scaleIdx]; + if (scale != MONTH_SCALE && scale != DAY_SCALE){ // Possibly incorporate offsets + stepDate(start, 1, {forward: false, count: Math.floor(state.startOffset * scale), inplace: true}); + stepDate(end, 1, {count: Math.floor(state.endOffset * scale), inplace: true}); + } + // Determine positions in full timeline (only uses year information) + let startFrac = (start.year - MIN_DATE.year) / (MAX_DATE.year - MIN_DATE.year); + let lenFrac = (end.year - start.year) / (MAX_DATE.year - MIN_DATE.year); let startPx = Math.max(0, availLen * startFrac); // Prevent negatives due to end-padding let lenPx = Math.min(availLen - startPx, availLen * lenFrac); lenPx = Math.max(1, lenPx); // Prevent zero length diff --git a/src/components/TimeLine.vue b/src/components/TimeLine.vue index a99b05e..add7ce8 100644 --- a/src/components/TimeLine.vue +++ b/src/components/TimeLine.vue @@ -44,7 +44,7 @@ import IconButton from './IconButton.vue'; 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} from '../lib'; + HistDate, stepDate, inDateScale, getScaleRatio, getDaysInMonth, moduloPositive, TimelineState} from '../lib'; import {useStore} from '../store'; // Refs @@ -56,10 +56,9 @@ const store = useStore(); // Props + events const props = defineProps({ vert: {type: Boolean, required: true}, - initialStart: {type: Object as PropType<HistDate>, required: true}, - initialEnd: {type: Object as PropType<HistDate>, required: true}, + initialState: {type: Object as PropType<TimelineState>, required: true}, }); -const emit = defineEmits(['remove', 'range-chg']); +const emit = defineEmits(['remove', 'state-chg']); // For size tracking const width = ref(0); @@ -92,17 +91,29 @@ const resizeObserver = new ResizeObserver((entries) => { onMounted(() => resizeObserver.observe(rootRef.value as HTMLElement)); // Timeline data -const startDate = ref(props.initialStart); // Earliest date to display -const endDate = ref(props.initialEnd); +const ID = props.initialState.id; +const startDate = ref(props.initialState.startDate); // Earliest date to display +const endDate = ref(props.initialState.endDate); const INITIAL_EXTRA_OFFSET = 0.5; const startOffset = ref(INITIAL_EXTRA_OFFSET); // Fraction of a scale unit before startDate to show // Note: Without this, the timeline can only move if the distance is over one unit, which makes dragging awkward, // can cause unexpected jumps when zooming, and limits display when a unit has many ticks on the next scale +if (props.initialState.startOffset != null){ + startOffset.value = props.initialState.startOffset as number; +} const endOffset = ref(INITIAL_EXTRA_OFFSET); +if (props.initialState.endOffset != null){ + endOffset.value = props.initialState.endOffset as number; +} const scaleIdx = ref(0); // Index of current scale in SCALES +if (props.initialState.scaleIdx != null){ + scaleIdx.value = props.initialState.scaleIdx as number; +} else { + onMounted(initScale); +} const scale = computed(() => SCALES[scaleIdx.value]) -// Initialise to smallest usable scale -function initScale(){ +// +function initScale(){ // Initialises to smallest usable scale if (startDate.value.isEarlier(MIN_CAL_DATE)){ // If unable to use JDNs, use a yearly scale scaleIdx.value = getYearlyScale(startDate.value, endDate.value, availLen.value); } else { @@ -139,7 +150,6 @@ function getYearlyScale(startDate: HistDate, endDate: HistDate, availLen: number } return idx; } -onMounted(initScale); // Tick data const TICK_LEN = 8; @@ -634,13 +644,9 @@ function onShiftWheel(evt: WheelEvent){ // For bound-change signalling watch(startDate, () => { - let start = startDate.value.clone(); - let end = endDate.value.clone(); - if (scale.value != MONTH_SCALE && scale.value != DAY_SCALE){ // Possibly incorporate offsets - stepDate(start, 1, {forward: false, count: Math.floor(startOffset.value * scale.value)}); - stepDate(end, 1, {count: Math.floor(endOffset.value * scale.value)}); - } - emit('range-chg', [start, end]); + emit('state-chg', new TimelineState( + ID, startDate.value, endDate.value, startOffset.value, endOffset.value, scaleIdx.value + )); }); // For skipping transitions on startup (and on horz/vert swap) @@ -268,10 +268,22 @@ export function getScaleRatio(scale: number, scale2: number){ } // For sending timeline-bound data to BaseLine -export type TimelineRange = { - id: number, - start: HistDate, - end: HistDate, +export class TimelineState { + id: number; + startDate: HistDate; + endDate: HistDate; + startOffset: number | null; + endOffset: number | null; + scaleIdx: number | null; + constructor(id: number, startDate: HistDate, endDate: HistDate, + startOffset: number | null = null, endOffset: number | null = null, scaleIdx: number | null = null){ + this.id = id; + this.startDate = startDate; + this.endDate = endDate; + this.startOffset = startOffset; + this.endOffset = endOffset; + this.scaleIdx = scaleIdx; + } }; export type HistEvent = { |
