sync
This commit is contained in:
parent
520efbbb0d
commit
9646e5d0e1
@ -1,15 +1,14 @@
|
||||
'use client'
|
||||
|
||||
import React, { createContext, useContext, useRef, useState, ReactNode } from 'react'
|
||||
// import Map from 'ol/Map';
|
||||
import { Map } from 'maplibre-gl';
|
||||
import { fromLonLat } from 'ol/proj';
|
||||
|
||||
// 定义MapContext的类型
|
||||
interface MapContextType {
|
||||
mapRef: React.RefObject<Map | null>
|
||||
layers: React.RefObject<any[]>
|
||||
mapState: MapState
|
||||
currentDatetime: Date | null
|
||||
setMap: (map: Map, layers: any[]) => void
|
||||
flyTo: (options: { center: [number, number]; zoom: number; duration?: number }) => void
|
||||
zoomIn: () => void
|
||||
@ -21,6 +20,8 @@ interface MapContextType {
|
||||
isMapReady: boolean
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 创建Context
|
||||
const MapContext = createContext<MapContextType | undefined>(undefined)
|
||||
|
||||
@ -42,7 +43,7 @@ export function MapProvider({ children }: MapProviderProps) {
|
||||
});
|
||||
|
||||
const layersRef = useRef<any[]>([])
|
||||
|
||||
const [currentDatetime, setCurrentDatetime] = useState<Date | null>(null)
|
||||
|
||||
const setMap = (map: Map, layers: any[]) => {
|
||||
// 如果已经有地图实例,先清理旧的
|
||||
@ -51,9 +52,6 @@ export function MapProvider({ children }: MapProviderProps) {
|
||||
mapRef.current = null;
|
||||
}
|
||||
|
||||
// 监听视图变化事件
|
||||
// const view = map.getView();
|
||||
|
||||
// 监听视图的缩放变化
|
||||
map.on('zoom', () => {
|
||||
setMapState(prevState => ({
|
||||
@ -78,12 +76,6 @@ export function MapProvider({ children }: MapProviderProps) {
|
||||
|
||||
const flyTo = (options: { center: [number, number]; zoom: number; duration?: number }) => {
|
||||
if (mapRef.current) {
|
||||
// mapRef.current.getView().animate({
|
||||
// center: fromLonLat(options.center),
|
||||
// zoom: options.zoom,
|
||||
// duration: options.duration || 1000
|
||||
// })
|
||||
|
||||
mapRef.current.flyTo({
|
||||
center: options.center,
|
||||
zoom: options.zoom,
|
||||
@ -95,42 +87,28 @@ export function MapProvider({ children }: MapProviderProps) {
|
||||
|
||||
const zoomIn = () => {
|
||||
if (mapRef.current) {
|
||||
// mapRef.current.getView().setZoom(mapRef.current.getView().getZoom()! + 1)
|
||||
mapRef.current.zoomIn()
|
||||
}
|
||||
}
|
||||
|
||||
const zoomOut = () => {
|
||||
if (mapRef.current) {
|
||||
// mapRef.current.getView().setZoom(mapRef.current.getView().getZoom()! - 1)
|
||||
mapRef.current.zoomOut()
|
||||
}
|
||||
}
|
||||
|
||||
const zoomTo = (zoom: number) => {
|
||||
if (mapRef.current) {
|
||||
// mapRef.current.getView().setZoom(zoom)
|
||||
mapRef.current.zoomTo(zoom)
|
||||
}
|
||||
}
|
||||
|
||||
const setTime = (date: Date) => {
|
||||
if (mapRef.current) {
|
||||
layersRef.current.forEach(layer => {
|
||||
const source = layer.getSource()
|
||||
if (source) {
|
||||
source.updateParams({
|
||||
'TIME': date.toISOString()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
setCurrentDatetime(date)
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
if (mapRef.current) {
|
||||
// mapRef.current.getView().setCenter([103.851959, 1.290270])
|
||||
// mapRef.current.getView().setZoom(11)
|
||||
mapRef.current.flyTo({
|
||||
center: [103.851959, 1.290270],
|
||||
zoom: 11,
|
||||
@ -154,6 +132,7 @@ export function MapProvider({ children }: MapProviderProps) {
|
||||
|
||||
const value: MapContextType = {
|
||||
setTime,
|
||||
currentDatetime,
|
||||
mapRef,
|
||||
layers: layersRef,
|
||||
mapState,
|
||||
|
||||
@ -133,7 +133,6 @@ function SelectDemo() {
|
||||
export const Timeline: React.FC<Props> = ({
|
||||
startDate,
|
||||
endDate,
|
||||
currentDate,
|
||||
onDateChange,
|
||||
onPlay,
|
||||
onPause,
|
||||
@ -148,19 +147,12 @@ export const Timeline: React.FC<Props> = ({
|
||||
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const ticksCanvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
const { isPlaying, togglePlay } = useTimeline({
|
||||
initialDate: currentDate ?? new Date(),
|
||||
onDateChange(date) {
|
||||
onDateChange?.(date);
|
||||
}
|
||||
})
|
||||
|
||||
const { isPlaying, togglePlay, loading, setTime, currentDatetime: currentDate } = useTimeline({})
|
||||
const [state, setState] = useState<Status>({
|
||||
isDragging: false,
|
||||
isLongPress: false,
|
||||
isPanningTimeline: false,
|
||||
customLineTimestamp: currentDate?.getTime() ?? new Date().getTime(),
|
||||
customLineTimestamp: currentDate?.getTime() ?? null,
|
||||
panOffset: 0,
|
||||
zoomLevel: initialZoom,
|
||||
});
|
||||
@ -188,6 +180,7 @@ export const Timeline: React.FC<Props> = ({
|
||||
const newDate = new Date(state.customLineTimestamp! + 360000);
|
||||
setState({ ...state, customLineTimestamp: newDate.getTime() });
|
||||
onDateChange?.(newDate);
|
||||
setTime(newDate);
|
||||
}, [state.customLineTimestamp])
|
||||
|
||||
// 缩放处理函数
|
||||
@ -282,7 +275,7 @@ export const Timeline: React.FC<Props> = ({
|
||||
dpr,
|
||||
overrides.startDate ?? currentProps.startDate,
|
||||
overrides.endDate ?? currentProps.endDate,
|
||||
overrides.currentDate ?? currentProps.currentDate,
|
||||
overrides.currentDate ?? currentProps.currentDate ?? undefined,
|
||||
overrides.zoomLevel ?? currentState.zoomLevel,
|
||||
overrides.panOffset ?? currentState.panOffset,
|
||||
overrides.customLineTimestamp ?? currentState.customLineTimestamp,
|
||||
@ -386,17 +379,24 @@ export const Timeline: React.FC<Props> = ({
|
||||
|
||||
const selectedTimestamp = visibleStartTime + progress * visibleTimeRange;
|
||||
|
||||
// 规整到最近的6分钟整数时间
|
||||
const sixMinutesInMs = 6 * 60 * 1000; // 6分钟 = 360000毫秒
|
||||
const roundedTimestamp = Math.round(selectedTimestamp / sixMinutesInMs) * sixMinutesInMs;
|
||||
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
customLineTimestamp: selectedTimestamp,
|
||||
customLineTimestamp: roundedTimestamp,
|
||||
isLongPress: false,
|
||||
isDragging: false
|
||||
}));
|
||||
|
||||
// 通知父组件
|
||||
if (onDateChange) {
|
||||
onDateChange(new Date(selectedTimestamp));
|
||||
onDateChange(new Date(roundedTimestamp));
|
||||
}
|
||||
|
||||
// 使用setTime更新时间轴状态
|
||||
setTime(new Date(roundedTimestamp));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -601,12 +601,19 @@ export const Timeline: React.FC<Props> = ({
|
||||
|
||||
const finalTimestamp = visibleStartTime + progress * visibleTimeRange;
|
||||
|
||||
// 规整到最近的6分钟整数时间
|
||||
const sixMinutesInMs = 6 * 60 * 1000; // 6分钟 = 360000毫秒
|
||||
const roundedFinalTimestamp = Math.round(finalTimestamp / sixMinutesInMs) * sixMinutesInMs;
|
||||
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
customLineTimestamp: finalTimestamp,
|
||||
customLineTimestamp: roundedFinalTimestamp,
|
||||
isDragging: false,
|
||||
isLongPress: false
|
||||
}));
|
||||
|
||||
// 使用setTime更新时间轴状态
|
||||
setTime(new Date(roundedFinalTimestamp));
|
||||
} else {
|
||||
setState(prevState => ({ ...prevState, isDragging: false, isLongPress: false }));
|
||||
}
|
||||
@ -706,9 +713,6 @@ export const Timeline: React.FC<Props> = ({
|
||||
current_uniforms.current.currentTimestamp = currentDate.getTime();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 鼠标滚轮缩放和平移
|
||||
const handleWheel = (e: WheelEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
72
app/tl.tsx
72
app/tl.tsx
@ -3,12 +3,26 @@ import vsSource from './glsl/timeline/vert.glsl';
|
||||
import fsSource from './glsl/timeline/frag.glsl';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight, HomeIcon, Pause, Play } from "lucide-react";
|
||||
|
||||
import { ChevronLeft, ChevronRight, HomeIcon, LockIcon, Pause, Play, UnlockIcon } from "lucide-react";
|
||||
import { formatInTimeZone } from "date-fns-tz";
|
||||
import { parse } from "date-fns"
|
||||
|
||||
import { useTimeline } from "@/hooks/use-timeline";
|
||||
import { Timeline as TimelineEngine, ZoomMode, TimelineConfig } from "@/lib/timeline";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { gql, useSubscription } from "@apollo/client";
|
||||
|
||||
const SUBSCRIPTION_QUERY = gql`
|
||||
subscription {
|
||||
statusUpdates {
|
||||
id
|
||||
message
|
||||
status
|
||||
timestamp
|
||||
newestDt
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
interface Uniforms {
|
||||
startTimestamp: number; // Unix 时间戳开始
|
||||
@ -65,7 +79,8 @@ export const Timeline: React.FC<Props> = React.memo(({
|
||||
timelineConfig,
|
||||
...props
|
||||
}) => {
|
||||
const { isPlaying, togglePlay } = useTimeline({})
|
||||
const { isPlaying, togglePlay, currentDatetime, setTime } = useTimeline({})
|
||||
const [lock, setLock] = useState(false)
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const ticksCanvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const timelineEngineRef = useRef<TimelineEngine | null>(null);
|
||||
@ -77,6 +92,21 @@ export const Timeline: React.FC<Props> = React.memo(({
|
||||
currentLevel: null as any
|
||||
});
|
||||
|
||||
|
||||
const { data, loading, error } = useSubscription(SUBSCRIPTION_QUERY)
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
if (data.statusUpdates) {
|
||||
if (!lock && data.statusUpdates.newestDt) {
|
||||
const newDt = parse(data.statusUpdates.newestDt + 'Z', 'yyyyMMddHHmmssX', new Date())
|
||||
setTime(newDt)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}, [data, lock])
|
||||
|
||||
// 定时器效果 - 当播放时每隔指定时间执行操作
|
||||
useEffect(() => {
|
||||
let intervalId: NodeJS.Timeout | null = null;
|
||||
@ -85,7 +115,6 @@ export const Timeline: React.FC<Props> = React.memo(({
|
||||
intervalId = setInterval(() => {
|
||||
// 执行时间前进操作
|
||||
if (timelineEngineRef.current) {
|
||||
// timelineEngineRef.current.forwardTimeMark(timeStep);
|
||||
timelineEngineRef.current.playAndEnsureMarkInView(timeStep)
|
||||
}
|
||||
}, 1000); // 每秒执行一次,你可以根据需要调整这个间隔
|
||||
@ -98,6 +127,12 @@ export const Timeline: React.FC<Props> = React.memo(({
|
||||
};
|
||||
}, [isPlaying, timeStep]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentDatetime && !lock) {
|
||||
timelineEngineRef.current?.replaceTimeMarkByTimestamp(currentDatetime.getTime())
|
||||
}
|
||||
}, [currentDatetime, lock])
|
||||
|
||||
useEffect(() => {
|
||||
if (!ticksCanvasRef.current) return;
|
||||
|
||||
@ -132,6 +167,20 @@ export const Timeline: React.FC<Props> = React.memo(({
|
||||
primaryFontSize: 10,
|
||||
secondaryFontSize: 10
|
||||
},
|
||||
onDateChange: async (date: Date) => {
|
||||
const datestr = formatInTimeZone(date, 'UTC', 'yyyyMMddHHmmss')
|
||||
const response = await fetch(`http://localhost:3050/api/v1/data/nearest?datetime=${datestr}&area=cn`)
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
const nearestDatetime = data.nearest_data_time
|
||||
const nearestDate = new Date(Date.parse(nearestDatetime))
|
||||
|
||||
setTime(nearestDate)
|
||||
} else {
|
||||
console.error('Failed to fetch data:', response.status)
|
||||
}
|
||||
},
|
||||
...timelineConfig
|
||||
};
|
||||
|
||||
@ -210,7 +259,10 @@ export const Timeline: React.FC<Props> = React.memo(({
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="size-5"
|
||||
onClick={() => togglePlay()}
|
||||
onClick={() => {
|
||||
togglePlay()
|
||||
setLock(true)
|
||||
}}
|
||||
title={isPlaying ? "暂停" : "播放"}
|
||||
>
|
||||
{isPlaying ? <Pause size={10} /> : <Play size={10} />}
|
||||
@ -260,6 +312,16 @@ export const Timeline: React.FC<Props> = React.memo(({
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="size-5"
|
||||
onClick={() => setLock(!lock)}
|
||||
title="锁定时间"
|
||||
>
|
||||
{lock ? <LockIcon size={10} /> : <UnlockIcon size={10} />}
|
||||
</Button>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@ -26,7 +26,7 @@ export function MapComponent({
|
||||
// center = [103.851959, 1.290270],
|
||||
// zoom = 11
|
||||
imgBitmap: propImgBitmap,
|
||||
colorMapType = 'heatmap',
|
||||
colorMapType = 'meteorological',
|
||||
onColorMapChange
|
||||
}: MapComponentProps) {
|
||||
|
||||
|
||||
@ -69,7 +69,8 @@ export function Timeline({
|
||||
const newDays = Math.round((newProgress / 100) * totalDays)
|
||||
const newDate = addDays(startDate, newDays)
|
||||
onDateChange?.(newDate)
|
||||
}, [startDate, totalDays, onDateChange])
|
||||
timeline.setTime(newDate)
|
||||
}, [startDate, totalDays, onDateChange, timeline])
|
||||
|
||||
const handlePlayPause = useCallback(() => {
|
||||
if (isPlaying) {
|
||||
@ -82,12 +83,14 @@ export function Timeline({
|
||||
const handleSkipBack = useCallback(() => {
|
||||
const newDate = subDays(currentDate, 1)
|
||||
onDateChange?.(newDate)
|
||||
}, [currentDate, onDateChange])
|
||||
timeline.setTime(newDate)
|
||||
}, [currentDate, onDateChange, timeline])
|
||||
|
||||
const handleSkipForward = useCallback(() => {
|
||||
const newDate = addDays(currentDate, 1)
|
||||
onDateChange?.(newDate)
|
||||
}, [currentDate, onDateChange])
|
||||
timeline.setTime(newDate)
|
||||
}, [currentDate, onDateChange, timeline])
|
||||
|
||||
const speedOptions = [
|
||||
{ value: 'slow', label: '慢速', interval: 2000 },
|
||||
|
||||
@ -1,28 +1,11 @@
|
||||
import { useState, useEffect, useRef, useCallback } from 'react'
|
||||
import { addDays, subDays } from 'date-fns'
|
||||
import { useMap } from '@/app/map-context'
|
||||
import { useSubscription, gql } from '@apollo/client'
|
||||
import { parse } from 'date-fns'
|
||||
import { UTCDate } from "@date-fns/utc";
|
||||
import { toZonedTime } from 'date-fns-tz';
|
||||
|
||||
interface UseTimelineOptions {
|
||||
startDate?: Date
|
||||
endDate?: Date
|
||||
initialDate?: Date
|
||||
onDateChange?: (date: Date) => void
|
||||
autoPlay?: boolean
|
||||
}
|
||||
|
||||
export function useTimeline({
|
||||
startDate = subDays(new Date(), 30),
|
||||
endDate = new Date(),
|
||||
initialDate = new Date(),
|
||||
onDateChange,
|
||||
autoPlay = false
|
||||
}: UseTimelineOptions = {}) {
|
||||
const [currentDate, setCurrentDate] = useState(initialDate)
|
||||
const [isPlaying, setIsPlaying] = useState(autoPlay)
|
||||
const [speed, setSpeed] = useState<'slow' | 'normal' | 'fast'>('normal')
|
||||
const intervalRef = useRef<NodeJS.Timeout | null>(null)
|
||||
|
||||
const { setTime } = useMap()
|
||||
|
||||
const speedIntervals = {
|
||||
slow: 2000,
|
||||
@ -30,11 +13,35 @@ export function useTimeline({
|
||||
fast: 500
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface UseTimelineOptions {
|
||||
startDate?: Date
|
||||
endDate?: Date
|
||||
initialDate?: Date
|
||||
onDateChange?: (date: Date) => void
|
||||
autoPlay?: boolean
|
||||
autoUpdate?: boolean
|
||||
}
|
||||
|
||||
export function useTimeline({
|
||||
startDate = subDays(new Date(), 30),
|
||||
endDate = new Date(),
|
||||
onDateChange,
|
||||
autoPlay = false
|
||||
}: UseTimelineOptions = {}) {
|
||||
|
||||
const [isPlaying, setIsPlaying] = useState(autoPlay)
|
||||
const [speed, setSpeed] = useState<'slow' | 'normal' | 'fast'>('normal')
|
||||
const intervalRef = useRef<NodeJS.Timeout | null>(null)
|
||||
const { setTime, currentDatetime } = useMap()
|
||||
|
||||
const updateDate = useCallback((newDate: Date) => {
|
||||
setCurrentDate(newDate)
|
||||
setTime(newDate)
|
||||
onDateChange?.(newDate)
|
||||
}, [onDateChange])
|
||||
|
||||
|
||||
const play = useCallback(() => {
|
||||
setIsPlaying(true)
|
||||
}, [])
|
||||
@ -48,18 +55,20 @@ export function useTimeline({
|
||||
}, [])
|
||||
|
||||
const skipForward = useCallback(() => {
|
||||
const newDate = addDays(currentDate, 1)
|
||||
if (!currentDatetime) return;
|
||||
const newDate = addDays(currentDatetime, 1)
|
||||
if (newDate <= endDate) {
|
||||
updateDate(newDate)
|
||||
}
|
||||
}, [currentDate, endDate, updateDate])
|
||||
}, [currentDatetime, endDate, updateDate])
|
||||
|
||||
const skipBackward = useCallback(() => {
|
||||
const newDate = subDays(currentDate, 1)
|
||||
if (!currentDatetime) return;
|
||||
const newDate = subDays(currentDatetime, 1)
|
||||
if (newDate >= startDate) {
|
||||
updateDate(newDate)
|
||||
}
|
||||
}, [currentDate, startDate, updateDate])
|
||||
}, [currentDatetime, startDate, updateDate])
|
||||
|
||||
const changeSpeed = useCallback((newSpeed: 'slow' | 'normal' | 'fast') => {
|
||||
setSpeed(newSpeed)
|
||||
@ -75,7 +84,8 @@ export function useTimeline({
|
||||
useEffect(() => {
|
||||
if (isPlaying) {
|
||||
intervalRef.current = setInterval(() => {
|
||||
const nextDate = addDays(currentDate, 1)
|
||||
if (!currentDatetime) return;
|
||||
const nextDate = addDays(currentDatetime, 1)
|
||||
if (nextDate <= endDate) {
|
||||
updateDate(nextDate)
|
||||
} else {
|
||||
@ -95,7 +105,7 @@ export function useTimeline({
|
||||
clearInterval(intervalRef.current)
|
||||
}
|
||||
}
|
||||
}, [isPlaying, currentDate, endDate, speed, updateDate])
|
||||
}, [isPlaying, currentDatetime, endDate, speed, updateDate])
|
||||
|
||||
// 清理定时器
|
||||
useEffect(() => {
|
||||
@ -107,7 +117,7 @@ export function useTimeline({
|
||||
}, [])
|
||||
|
||||
return {
|
||||
currentDate,
|
||||
currentDatetime,
|
||||
isPlaying,
|
||||
speed,
|
||||
startDate,
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
* 用于globe模式下的球面渲染
|
||||
*/
|
||||
|
||||
import { MercatorCoordinate } from 'maplibre-gl'
|
||||
|
||||
export interface TileMeshOptions {
|
||||
/** 经纬度边界 [west, south, east, north] */
|
||||
bounds: [number, number, number, number];
|
||||
@ -163,10 +165,12 @@ export function detectPerformanceLevel(): PerformanceLevel {
|
||||
* 将经纬度转换为Web Mercator坐标 (归一化到0-1范围)
|
||||
*/
|
||||
function lonLatToMercator(lon: number, lat: number): [number, number] {
|
||||
const x = (lon + 180) / 360;
|
||||
const latRad = (lat * Math.PI) / 180;
|
||||
const y = (1 - Math.log(Math.tan(latRad / 2 + Math.PI / 4)) / Math.PI) / 2;
|
||||
return [x, y];
|
||||
const mercator = MercatorCoordinate.fromLngLat({ lng: lon, lat: lat })
|
||||
return [mercator.x, mercator.y]
|
||||
// const x = (lon + 180) / 360;
|
||||
// const latRad = (lat * Math.PI) / 180;
|
||||
// const y = (1 - Math.log(Math.tan(latRad / 2 + Math.PI / 4)) / Math.PI) / 2;
|
||||
// return [x, y];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -110,6 +110,8 @@ interface TimelineConfig {
|
||||
highlightWeekends?: boolean;
|
||||
/** 时间标记列表 */
|
||||
timeMarks?: TimeMark[];
|
||||
|
||||
onDateChange?: (date: Date) => void;
|
||||
}
|
||||
|
||||
/** 时间工具类 */
|
||||
@ -649,7 +651,8 @@ class RealTimeTimeline {
|
||||
},
|
||||
showCurrentTime: true,
|
||||
highlightWeekends: true,
|
||||
timeMarks: []
|
||||
timeMarks: [],
|
||||
onDateChange: () => { }
|
||||
};
|
||||
|
||||
if (!config) return defaultConfig;
|
||||
@ -664,7 +667,8 @@ class RealTimeTimeline {
|
||||
sizes: { ...defaultConfig.sizes, ...config.sizes },
|
||||
showCurrentTime: config.showCurrentTime ?? defaultConfig.showCurrentTime,
|
||||
highlightWeekends: config.highlightWeekends ?? defaultConfig.highlightWeekends,
|
||||
timeMarks: config.timeMarks ?? defaultConfig.timeMarks
|
||||
timeMarks: config.timeMarks ?? defaultConfig.timeMarks,
|
||||
onDateChange: config.onDateChange ?? defaultConfig.onDateChange
|
||||
};
|
||||
}
|
||||
|
||||
@ -723,13 +727,7 @@ class RealTimeTimeline {
|
||||
// 获取当前刻度信息用于吸附
|
||||
const ticks = this.scaleManager.calculateTicks(this.viewport);
|
||||
const date = this.viewport.screenToTime(x, true, ticks);
|
||||
this.interaction.setZoomMode(ZoomMode.MarkMode);
|
||||
this.replaceTimeMark({
|
||||
timestamp: date,
|
||||
color: '#ff6b6b',
|
||||
label: format(date, 'HH:mm:ss'),
|
||||
type: 'custom'
|
||||
});
|
||||
this.changeTime(new Date(date))
|
||||
}
|
||||
});
|
||||
|
||||
@ -814,6 +812,17 @@ class RealTimeTimeline {
|
||||
this.drawTimeMarks();
|
||||
}
|
||||
|
||||
private changeTime(date: Date): void {
|
||||
this.interaction.setZoomMode(ZoomMode.MarkMode);
|
||||
this.replaceTimeMark({
|
||||
timestamp: date.getTime(),
|
||||
color: '#ff6b6b',
|
||||
label: format(date, 'HH:mm:ss'),
|
||||
type: 'custom'
|
||||
});
|
||||
this.config.onDateChange?.(date)
|
||||
}
|
||||
|
||||
/** 绘制周末高亮 */
|
||||
private drawWeekends(): void {
|
||||
const [startTime, endTime] = this.viewport.getVisibleRange();
|
||||
@ -1038,19 +1047,25 @@ class RealTimeTimeline {
|
||||
this.render();
|
||||
}
|
||||
|
||||
replaceTimeMarkByTimestamp(timestamp: number): void {
|
||||
|
||||
this.replaceTimeMark({
|
||||
timestamp: timestamp,
|
||||
color: '#ff6b6b',
|
||||
label: format(timestamp, 'HH:mm:ss'),
|
||||
type: 'custom'
|
||||
});
|
||||
|
||||
this.interaction.setZoomMode(ZoomMode.MarkMode)
|
||||
}
|
||||
|
||||
forwardTimeMark(delta: number, timestamp?: number): void {
|
||||
if (!timestamp) {
|
||||
timestamp = this.config.timeMarks[0].timestamp;
|
||||
}
|
||||
const index = this.config.timeMarks.findIndex(mark => mark.timestamp === timestamp);
|
||||
if (index !== -1) {
|
||||
this.config.timeMarks.splice(index, 1);
|
||||
this.replaceTimeMark({
|
||||
timestamp: timestamp + delta,
|
||||
color: '#ff6b6b',
|
||||
label: format(timestamp + delta, 'HH:mm:ss'),
|
||||
type: 'custom'
|
||||
});
|
||||
this.changeTime(new Date(timestamp + delta))
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
@ -1061,13 +1076,7 @@ class RealTimeTimeline {
|
||||
}
|
||||
const index = this.config.timeMarks.findIndex(mark => mark.timestamp === timestamp);
|
||||
if (index !== -1) {
|
||||
this.config.timeMarks.splice(index, 1);
|
||||
this.replaceTimeMark({
|
||||
timestamp: timestamp - delta,
|
||||
color: '#ff6b6b',
|
||||
label: format(timestamp - delta, 'HH:mm:ss'),
|
||||
type: 'custom'
|
||||
});
|
||||
this.changeTime(new Date(timestamp - delta))
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
33
package-lock.json
generated
33
package-lock.json
generated
@ -11,6 +11,7 @@
|
||||
"@21st-extension/react": "^0.5.14",
|
||||
"@21st-extension/toolbar-next": "^0.5.14",
|
||||
"@apollo/client": "^3.13.9",
|
||||
"@date-fns/utc": "^2.1.1",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/modifiers": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
@ -37,6 +38,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"dnd-kit": "^0.0.2",
|
||||
"framer-motion": "^12.23.6",
|
||||
"graphql": "^16.11.0",
|
||||
@ -196,6 +198,12 @@
|
||||
"resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.2.0.tgz",
|
||||
"integrity": "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg=="
|
||||
},
|
||||
"node_modules/@date-fns/utc": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "http://mirrors.cloud.tencent.com/npm/@date-fns/utc/-/utc-2.1.1.tgz",
|
||||
"integrity": "sha512-SlJDfG6RPeEX8wEVv6ZB3kak4MmbtyiI2qX/5zuKdordbrhB/iaJ58GVMZgJ6P1sJaM1gMgENFYYeg1JWrCFrA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@dnd-kit/accessibility": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "http://mirrors.cloud.tencent.com/npm/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
|
||||
@ -3254,8 +3262,9 @@
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"resolved": "http://mirrors.cloud.tencent.com/npm/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
@ -3266,6 +3275,15 @@
|
||||
"resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz",
|
||||
"integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg=="
|
||||
},
|
||||
"node_modules/date-fns-tz": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "http://mirrors.cloud.tencent.com/npm/date-fns-tz/-/date-fns-tz-3.2.0.tgz",
|
||||
"integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"date-fns": "^3.0.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/decimal.js-light": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "http://mirrors.cloud.tencent.com/npm/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||
@ -5659,6 +5677,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.2.0.tgz",
|
||||
"integrity": "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg=="
|
||||
},
|
||||
"@date-fns/utc": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "http://mirrors.cloud.tencent.com/npm/@date-fns/utc/-/utc-2.1.1.tgz",
|
||||
"integrity": "sha512-SlJDfG6RPeEX8wEVv6ZB3kak4MmbtyiI2qX/5zuKdordbrhB/iaJ58GVMZgJ6P1sJaM1gMgENFYYeg1JWrCFrA=="
|
||||
},
|
||||
"@dnd-kit/accessibility": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "http://mirrors.cloud.tencent.com/npm/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
|
||||
@ -7508,7 +7531,7 @@
|
||||
},
|
||||
"date-fns": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"resolved": "http://mirrors.cloud.tencent.com/npm/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="
|
||||
},
|
||||
"date-fns-jalali": {
|
||||
@ -7516,6 +7539,12 @@
|
||||
"resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz",
|
||||
"integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg=="
|
||||
},
|
||||
"date-fns-tz": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "http://mirrors.cloud.tencent.com/npm/date-fns-tz/-/date-fns-tz-3.2.0.tgz",
|
||||
"integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"decimal.js-light": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "http://mirrors.cloud.tencent.com/npm/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
"@21st-extension/react": "^0.5.14",
|
||||
"@21st-extension/toolbar-next": "^0.5.14",
|
||||
"@apollo/client": "^3.13.9",
|
||||
"@date-fns/utc": "^2.1.1",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/modifiers": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
@ -38,6 +39,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"dnd-kit": "^0.0.2",
|
||||
"framer-motion": "^12.23.6",
|
||||
"graphql": "^16.11.0",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user