diff --git a/src/views/map-bases-view.ts b/src/views/map-bases-view.ts index c6126b7..12b96eb 100644 --- a/src/views/map-bases-view.ts +++ b/src/views/map-bases-view.ts @@ -26,16 +26,16 @@ export class MapBasesView extends BasesView { mapEl: HTMLElement; plugin: MapPlugin; - private deck: Deck | null = null; + protected deck: Deck | 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); diff --git a/src/views/map-timeline-bases-view.ts b/src/views/map-timeline-bases-view.ts index eb5c044..3df86f3 100644 --- a/src/views/map-timeline-bases-view.ts +++ b/src/views/map-timeline-bases-view.ts @@ -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(); @@ -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, { diff --git a/styles.css b/styles.css index 6912bb4..ce29b4b 100644 --- a/styles.css +++ b/styles.css @@ -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; }