feat: ext base view for timeline

This commit is contained in:
ccmdi 2025-11-15 11:54:19 -05:00
parent abe91b400d
commit e02267f54e
No known key found for this signature in database
3 changed files with 79 additions and 36 deletions

View file

@ -26,16 +26,16 @@ export class MapBasesView extends BasesView {
mapEl: HTMLElement;
plugin: MapPlugin;
private deck: Deck<MapViewType[]> | null = null;
protected deck: Deck<MapViewType[]> | null = null;
private coordinatesProp: BasesPropertyId | null = null;
private coverProp: BasesPropertyId | null = null;
private mapHeight: number = DEFAULT_MAP_HEIGHT;
private defaultZoom: number = DEFAULT_MAP_ZOOM;
private center: [number, number] = [0, 0];
private markerType: 'pins' | 'dots' = 'pins';
protected defaultZoom: number = DEFAULT_MAP_ZOOM;
protected center: [number, number] = [0, 0];
protected markerType: 'pins' | 'dots' = 'pins';
private tileLayer: string = 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png';
private savedViewState: { latitude: number; longitude: number; zoom: number } | null = null;
private lastPoints: MapPoint[] = [];
protected lastPoints: MapPoint[] = [];
constructor(controller: QueryController, scrollEl: HTMLElement, plugin: MapPlugin) {
super(controller);
@ -102,7 +102,7 @@ export class MapBasesView extends BasesView {
}
}
private loadConfig(): void {
protected loadConfig(): void {
this.coordinatesProp = this.config.getAsPropertyId('coordinates');
this.coverProp = this.config.getAsPropertyId('coverImage');
@ -147,7 +147,7 @@ export class MapBasesView extends BasesView {
}
}
private renderMap(): void {
protected renderMap(): void {
if (!this.data) {
this.containerEl.removeClass('is-loading');
return;
@ -358,7 +358,7 @@ export class MapBasesView extends BasesView {
return points;
}
private extractCoordinates(entry: BasesEntry): [number, number] | null {
protected extractCoordinates(entry: BasesEntry): [number, number] | null {
if (this.coordinatesProp) {
try {
const value = entry.getValue(this.coordinatesProp);

View file

@ -4,11 +4,10 @@ import {
QueryController,
ViewOption,
} from 'obsidian';
import { Deck } from '@deck.gl/core';
import { MapView as MapViewType } from '@deck.gl/core';
import { MapPoint } from '../map-renderer';
import MapPlugin from '../main';
import { MapBasesView } from './map-bases-view';
import { updateMapPoints } from '../map-renderer';
export const MapTimelineBasesViewType = 'map-timeline';
@ -40,7 +39,6 @@ export class MapTimelineBasesView extends MapBasesView {
onload(): void {
super.onload();
this.createSlider();
}
onunload() {
@ -49,7 +47,22 @@ export class MapTimelineBasesView extends MapBasesView {
}
private createSlider(): void {
const sliderContainer = this.containerEl.createDiv({ cls: 'bases-timeline-slider' });
const sliderContainer = this.mapEl.createDiv({ cls: 'bases-timeline-slider' });
const dateInputEl = sliderContainer.createEl('input', {
type: 'date',
cls: 'timeline-date-input',
});
dateInputEl.addEventListener('change', () => {
const selectedDate = new Date(dateInputEl.value);
if (!isNaN(selectedDate.getTime())) {
this.dateRangeEnd = selectedDate.getTime();
this.updateSliderFromDate();
this.updateDateDisplay();
this.applyTimelineFilter();
}
});
this.dateDisplayEl = sliderContainer.createDiv({ cls: 'timeline-date-display' });
this.dateDisplayEl.textContent = this.getDateText();
@ -65,8 +78,34 @@ export class MapTimelineBasesView extends MapBasesView {
this.sliderEl.addEventListener('input', () => {
this.updateDateRange();
this.updateDateDisplay();
this.updateDateInput(dateInputEl);
this.applyTimelineFilter();
});
this.updateDateInput(dateInputEl);
}
private updateSliderFromDate(): void {
if (!this.sliderEl || this.allTimelineEntries.length === 0) return;
const minDate = Math.min(...this.allTimelineEntries.map(e => e.date));
const maxDate = Math.max(...this.allTimelineEntries.map(e => e.date));
const range = maxDate - minDate;
if (range === 0) {
this.sliderEl.value = '100';
} else {
const percentage = ((this.dateRangeEnd - minDate) / range) * 100;
this.sliderEl.value = Math.min(100, Math.max(0, percentage)).toString();
}
}
private updateDateInput(dateInputEl: HTMLInputElement): void {
const date = new Date(this.dateRangeEnd);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
dateInputEl.value = `${year}-${month}-${day}`;
}
private getDateText(): string {
@ -96,22 +135,24 @@ export class MapTimelineBasesView extends MapBasesView {
if (this.dateProperty) {
this.updateTimelineData();
// Don't call super - we'll handle rendering ourselves
if (this.deck) {
this.applyTimelineFilter();
} else {
// First render - need to call parent's loadConfig and renderMap
super['loadConfig']();
super['renderMap']();
super.loadConfig();
super.renderMap();
this.createSlider();
this.applyTimelineFilter();
}
} else {
// No date property set, just render normally
super.onDataUpdated();
}
}
private loadConfig(): void {
protected loadConfig(): void {
super.loadConfig();
this.dateProperty = this.config.getAsPropertyId('dateProperty');
this.uniquenessProperty = this.config.getAsPropertyId('uniquenessProperty');
@ -178,7 +219,6 @@ export class MapTimelineBasesView extends MapBasesView {
const date = new Date(stringValue);
if (isNaN(date.getTime())) {
// Try parsing as timestamp
const timestamp = parseInt(stringValue);
if (!isNaN(timestamp)) {
return timestamp;
@ -227,12 +267,10 @@ export class MapTimelineBasesView extends MapBasesView {
return;
}
// Filter by date range
let filteredEntries = this.allTimelineEntries.filter(
entry => entry.date >= this.dateRangeStart && entry.date <= this.dateRangeEnd
);
// Apply uniqueness constraint
if (this.uniquenessProperty && this.uniquenessMode !== 'all') {
const grouped = new Map<string, TimelineEntry[]>();
@ -248,10 +286,8 @@ export class MapTimelineBasesView extends MapBasesView {
for (const group of grouped.values()) {
if (group.length === 0) continue;
// Sort by date
group.sort((a, b) => a.date - b.date);
// Pick based on mode
if (this.uniquenessMode === 'most-recent') {
filteredEntries.push(group[group.length - 1]);
} else if (this.uniquenessMode === 'least-recent') {
@ -260,16 +296,11 @@ export class MapTimelineBasesView extends MapBasesView {
}
}
// Update the view with filtered points
this.updateMapWithFilteredPoints(filteredEntries.map(e => e.point));
}
private updateMapWithFilteredPoints(points: MapPoint[]): void {
if (!this.deck || !this.data) return;
// Directly update using updateMapPoints
const { updateMapPoints } = require('../map-renderer');
const hasConfiguredCenter = this.center[0] !== 0 || this.center[1] !== 0;
updateMapPoints(this.deck, points, {

View file

@ -362,21 +362,33 @@
.bases-timeline-slider {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 16px;
right: 16px;
min-width: 280px;
padding: 12px 16px;
background: var(--background-secondary);
border-bottom: 1px solid var(--background-modifier-border);
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 10;
display: flex;
flex-direction: column;
gap: 8px;
gap: 10px;
}
.timeline-date-input {
width: 100%;
padding: 6px 8px;
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
background: var(--background-secondary);
color: var(--text-normal);
font-size: 13px;
}
.timeline-date-display {
font-size: 13px;
color: var(--text-normal);
font-size: 12px;
color: var(--text-muted);
font-weight: 500;
text-align: center;
}