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