aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.vue44
-rw-r--r--src/components/BaseLine.vue27
-rw-r--r--src/components/TimeLine.vue38
-rw-r--r--src/lib.ts20
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)
diff --git a/src/lib.ts b/src/lib.ts
index bd81a8d..54dffc5 100644
--- a/src/lib.ts
+++ b/src/lib.ts
@@ -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 = {