sync newtimeline
This commit is contained in:
parent
448f71a07c
commit
aafc5078fa
@ -15,7 +15,8 @@ import {
|
||||
} from '@/components/ui/sidebar'
|
||||
import { MapComponent } from '@/components/map-component';
|
||||
import { ThemeToggle } from '@/components/theme-toggle';
|
||||
import { Timeline } from '@/app/timeline';
|
||||
// import { Timeline } from '@/app/timeline';
|
||||
import { Timeline } from '@/app/tl';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useTimeline } from '@/hooks/use-timeline';
|
||||
import { useEffect } from 'react'
|
||||
@ -62,7 +63,7 @@ export default function Page() {
|
||||
</header>
|
||||
<div className="relative h-full w-full flex flex-col">
|
||||
<MapComponent imgBitmap={imgBitmap} />
|
||||
<Timeline
|
||||
{/* <Timeline
|
||||
className={
|
||||
cn(
|
||||
"backdrop-blur-lg border shadow-lg",
|
||||
@ -76,7 +77,8 @@ export default function Page() {
|
||||
console.log('Selected date:', date);
|
||||
setTime(date)
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
<Timeline />
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
|
||||
277
app/tl.tsx
Normal file
277
app/tl.tsx
Normal file
@ -0,0 +1,277 @@
|
||||
import React, { useRef, useEffect, useState, useCallback } from "react";
|
||||
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 { useTimeline } from "@/hooks/use-timeline";
|
||||
import { Timeline as TimelineEngine, ZoomMode, TimelineConfig } from "@/lib/timeline";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
interface Uniforms {
|
||||
startTimestamp: number; // Unix 时间戳开始
|
||||
endTimestamp: number; // Unix 时间戳结束
|
||||
currentTimestamp: number; // 当前时间戳
|
||||
radius: number;
|
||||
d: number;
|
||||
timelineStartX: number; // 时间轴在屏幕上的开始X坐标
|
||||
timelineEndX: number; // 时间轴在屏幕上的结束X坐标
|
||||
viewportSize: [number, number];
|
||||
zoomLevel: number; // 当前缩放级别
|
||||
panOffset: number; // 当前平移偏移
|
||||
}
|
||||
|
||||
interface Instants {
|
||||
position: Float32Array;
|
||||
color: Float32Array;
|
||||
}
|
||||
|
||||
interface VesicaDataPoint {
|
||||
timestamp: number; // Unix 时间戳
|
||||
color?: [number, number, number, number]; // RGBA 颜色,默认为白色
|
||||
}
|
||||
|
||||
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||
boxSize?: [number, number];
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
currentDate?: Date;
|
||||
onDateChange?: (date: Date) => void;
|
||||
onPlay?: () => void;
|
||||
onPause?: () => void;
|
||||
minZoom?: number; // 最小缩放级别
|
||||
maxZoom?: number; // 最大缩放级别
|
||||
initialZoom?: number; // 初始缩放级别
|
||||
vesicaData?: VesicaDataPoint[]; // vesica 实例数据
|
||||
dateFormat?: (timestamp: number) => string; // 自定义时间格式化函数
|
||||
timelineConfig?: TimelineConfig; // 时间轴配置
|
||||
}
|
||||
|
||||
export const Timeline: React.FC<Props> = React.memo(({
|
||||
startDate,
|
||||
endDate,
|
||||
currentDate,
|
||||
onDateChange,
|
||||
onPlay,
|
||||
onPause,
|
||||
boxSize = [4, 8],
|
||||
minZoom = 0.5,
|
||||
maxZoom = 8,
|
||||
initialZoom = 1,
|
||||
vesicaData,
|
||||
dateFormat,
|
||||
timelineConfig,
|
||||
...props
|
||||
}) => {
|
||||
const { isPlaying, togglePlay } = useTimeline({})
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const ticksCanvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const timelineEngineRef = useRef<TimelineEngine | null>(null);
|
||||
const [timeStep, setTimeStep] = useState(360000);
|
||||
const [viewportInfo, setViewportInfo] = useState({
|
||||
centerTime: 0,
|
||||
timeRange: 0,
|
||||
visibleRange: [0, 0] as [number, number],
|
||||
currentLevel: null as any
|
||||
});
|
||||
|
||||
// 定时器效果 - 当播放时每隔指定时间执行操作
|
||||
useEffect(() => {
|
||||
let intervalId: NodeJS.Timeout | null = null;
|
||||
|
||||
if (isPlaying) {
|
||||
intervalId = setInterval(() => {
|
||||
// 执行时间前进操作
|
||||
if (timelineEngineRef.current) {
|
||||
// timelineEngineRef.current.forwardTimeMark(timeStep);
|
||||
timelineEngineRef.current.playAndEnsureMarkInView(timeStep)
|
||||
}
|
||||
}, 1000); // 每秒执行一次,你可以根据需要调整这个间隔
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
};
|
||||
}, [isPlaying, timeStep]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ticksCanvasRef.current) return;
|
||||
|
||||
const canvas = ticksCanvasRef.current;
|
||||
|
||||
// 计算初始时间范围
|
||||
const now = Date.now();
|
||||
const defaultStartTime = startDate ? startDate.getTime() : now - 24 * 60 * 60 * 1000; // 默认24小时前
|
||||
const defaultEndTime = endDate ? endDate.getTime() : now + 24 * 60 * 60 * 1000; // 默认24小时后
|
||||
const defaultCenterTime = currentDate ? currentDate.getTime() : now;
|
||||
|
||||
// 合并配置
|
||||
const config: TimelineConfig = {
|
||||
initialCenterTime: defaultCenterTime,
|
||||
initialTimeRange: (defaultEndTime - defaultStartTime) / 2, // 使用时间范围的一半作为初始范围
|
||||
highlightWeekends: false,
|
||||
zoomMode: ZoomMode.MousePosition,
|
||||
zoomSensitivity: 0.001,
|
||||
colors: {
|
||||
background: 'transparent', // 使用透明背景,让父容器背景显示
|
||||
grid: '#333333',
|
||||
majorTick: '#cccccc',
|
||||
minorTick: '#cccccc',
|
||||
primaryLabel: '#b4b4b4',
|
||||
secondaryLabel: '#b4b4b4',
|
||||
currentTime: '#ff0000'
|
||||
},
|
||||
sizes: {
|
||||
majorTickHeight: 8,
|
||||
minorTickHeight: 4,
|
||||
labelOffset: 3,
|
||||
primaryFontSize: 10,
|
||||
secondaryFontSize: 10
|
||||
},
|
||||
...timelineConfig
|
||||
};
|
||||
|
||||
try {
|
||||
timelineEngineRef.current = new TimelineEngine(canvas, config);
|
||||
const updateViewportInfo = () => {
|
||||
if (timelineEngineRef.current) {
|
||||
const info = timelineEngineRef.current.getViewportInfo();
|
||||
setViewportInfo(info);
|
||||
|
||||
if (onDateChange) {
|
||||
onDateChange(new Date(info.centerTime));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const interval = setInterval(updateViewportInfo, 100);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
if (timelineEngineRef.current) {
|
||||
timelineEngineRef.current.destroy();
|
||||
timelineEngineRef.current = null;
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize timeline engine:', error);
|
||||
}
|
||||
}, [startDate, endDate, currentDate, initialZoom, timelineConfig, onDateChange]);
|
||||
|
||||
// 处理画布大小变化
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (timelineEngineRef.current && ticksCanvasRef.current) {
|
||||
timelineEngineRef.current.render();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
const handleHome = useCallback(() => {
|
||||
if (timelineEngineRef.current) {
|
||||
timelineEngineRef.current.goToTime(new Date().getTime())
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 控制按钮功能
|
||||
const handlePrevious = useCallback(() => {
|
||||
if (timelineEngineRef.current) {
|
||||
timelineEngineRef.current.playBackwardAndEnsureMarkInView(timeStep)
|
||||
}
|
||||
}, [timeStep]);
|
||||
|
||||
const handleNext = useCallback(() => {
|
||||
if (timelineEngineRef.current) {
|
||||
timelineEngineRef.current.playAndEnsureMarkInView(timeStep)
|
||||
}
|
||||
}, [timeStep]);
|
||||
|
||||
return (
|
||||
<div className={cn(props.className, "w-full h-10 flex flex-row")}>
|
||||
<div className="h-full flex flex-row items-center px-3 gap-2 bg-black/60" style={{ boxShadow: '8px 0 24px rgba(0, 0, 0, 0.15), 4px 0 12px rgba(0, 0, 0, 0.1)' }}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="size-5"
|
||||
onClick={handlePrevious}
|
||||
title="上一个时间段"
|
||||
>
|
||||
<ChevronLeft size={10} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="size-5"
|
||||
onClick={() => togglePlay()}
|
||||
title={isPlaying ? "暂停" : "播放"}
|
||||
>
|
||||
{isPlaying ? <Pause size={10} /> : <Play size={10} />}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="size-5"
|
||||
onClick={handleNext}
|
||||
title="下一个时间段"
|
||||
>
|
||||
<ChevronRight size={10} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="size-5"
|
||||
onClick={handleHome}
|
||||
title="上一个时间段"
|
||||
>
|
||||
<HomeIcon size={10} />
|
||||
</Button>
|
||||
|
||||
<Separator orientation="vertical" className="h-4" />
|
||||
|
||||
<div className="ml-2">
|
||||
<select
|
||||
defaultValue="360000"
|
||||
className="w-20 h-6 text-xs bg-background border border-input rounded px-2 focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setTimeStep(parseInt(value));
|
||||
}}
|
||||
style={{ fontSize: '10px' }}
|
||||
>
|
||||
<option value="60000">1分钟</option>
|
||||
<option value="300000">5分钟</option>
|
||||
<option value="360000">6分钟</option>
|
||||
<option value="600000">10分钟</option>
|
||||
<option value="900000">15分钟</option>
|
||||
<option value="1800000">30分钟</option>
|
||||
<option value="3600000">1小时</option>
|
||||
<option value="7200000">2小时</option>
|
||||
<option value="86400000">1天</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div className={cn("relative", "w-full h-full bg-gray-800/20")}>
|
||||
<canvas ref={canvasRef} className="w-full h-full absolute inset-0" />
|
||||
<canvas
|
||||
ref={ticksCanvasRef}
|
||||
className="w-full h-full absolute inset-0 select-none"
|
||||
style={{ touchAction: 'manipulation' }}
|
||||
/>
|
||||
</div>
|
||||
</div >
|
||||
);
|
||||
});
|
||||
@ -19,6 +19,8 @@ interface MapComponentProps {
|
||||
|
||||
export function MapComponent({
|
||||
style = 'https://api.maptiler.com/maps/019817f1-82a8-7f37-901d-4bedf68b27fb/style.json?key=hj3fxRdwF9KjEsBq8sYI',
|
||||
// style = 'https://api.maptiler.com/maps/landscape/style.json?key=hj3fxRdwF9KjEsBq8sYI',
|
||||
// style = 'https://api.maptiler.com/tiles/land-gradient-dark/tiles.json?key=hj3fxRdwF9KjEsBq8sYI',
|
||||
// center = [103.851959, 1.290270],
|
||||
// zoom = 11
|
||||
imgBitmap: propImgBitmap,
|
||||
|
||||
1211
lib/timeline.ts
Normal file
1211
lib/timeline.ts
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user