remove log && increate motion

This commit is contained in:
tsuki 2025-08-19 22:49:28 +08:00
parent 4eab292e1b
commit 77416f635a
13 changed files with 129 additions and 106 deletions

View File

@ -183,9 +183,9 @@ const columns: ColumnDef<z.infer<typeof blogSchema>>[] = [
published: { label: "已发布", variant: "default" }, published: { label: "已发布", variant: "default" },
archived: { label: "已归档", variant: "outline" }, archived: { label: "已归档", variant: "outline" },
}; };
const statusInfo = statusMap[status] || { label: status, variant: "outline" }; const statusInfo = statusMap[status] || { label: status, variant: "outline" };
return ( return (
<div className="w-20"> <div className="w-20">
<Badge variant={statusInfo.variant} className="text-xs"> <Badge variant={statusInfo.variant} className="text-xs">
@ -349,7 +349,7 @@ const columns: ColumnDef<z.infer<typeof blogSchema>>[] = [
cell: ({ row, table }) => { cell: ({ row, table }) => {
const updateBlogMutation = (table.options.meta as any)?.updateBlog const updateBlogMutation = (table.options.meta as any)?.updateBlog
const deleteBlogMutation = (table.options.meta as any)?.deleteBlog const deleteBlogMutation = (table.options.meta as any)?.deleteBlog
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@ -424,7 +424,7 @@ const columns: ColumnDef<z.infer<typeof blogSchema>>[] = [
{row.original.isActive ? "停用" : "启用"} {row.original.isActive ? "停用" : "启用"}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem <DropdownMenuItem
variant="destructive" variant="destructive"
onClick={() => { onClick={() => {
if (confirm('确定要删除这篇博客吗?此操作不可撤销。')) { if (confirm('确定要删除这篇博客吗?此操作不可撤销。')) {
@ -604,7 +604,7 @@ export function BlogTable() {
const { active, over } = event const { active, over } = event
if (active && over && active.id !== over.id) { if (active && over && active.id !== over.id) {
// Handle drag end for reordering // Handle drag end for reordering
console.log('Reordering blogs:', active.id, 'to', over.id) // console.log('Reordering blogs:', active.id, 'to', over.id)
// You can implement actual reordering API call here // You can implement actual reordering API call here
} }
} }
@ -657,11 +657,11 @@ export function BlogTable() {
) )
} }
function BlogTabs({ function BlogTabs({
blogs, blogs,
loading, loading,
info, info,
handleDragEnd, handleDragEnd,
refetch, refetch,
pagination, pagination,
setPagination, setPagination,
@ -672,7 +672,7 @@ function BlogTabs({
totalCount, totalCount,
updateBlog, updateBlog,
deleteBlog deleteBlog
}: { }: {
blogs: any[] blogs: any[]
loading: boolean loading: boolean
info: any info: any
@ -766,8 +766,8 @@ function BlogTabs({
<TabsTrigger value="archived_blogs"> <Badge variant="secondary">{info?.totalArchived}</Badge></TabsTrigger> <TabsTrigger value="archived_blogs"> <Badge variant="secondary">{info?.totalArchived}</Badge></TabsTrigger>
</TabsList> </TabsList>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => window.location.href = '/admin/editor'} onClick={() => window.location.href = '/admin/editor'}
> >
@ -1149,7 +1149,7 @@ function BlogCellViewer({ item }: { item: z.infer<typeof blogSchema> }) {
</form> </form>
</div> </div>
<DrawerFooter> <DrawerFooter>
<Button <Button
onClick={() => { onClick={() => {
window.location.href = `/admin/editor?id=${item.id}` window.location.href = `/admin/editor?id=${item.id}`
}} }}

View File

@ -271,7 +271,7 @@ const columns: ColumnDef<z.infer<typeof categorySchema>>[] = [
cell: ({ row, table }) => { cell: ({ row, table }) => {
const updateCategoryMutation = (table.options.meta as any)?.updateCategory const updateCategoryMutation = (table.options.meta as any)?.updateCategory
const deleteCategoryMutation = (table.options.meta as any)?.deleteCategory const deleteCategoryMutation = (table.options.meta as any)?.deleteCategory
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@ -299,7 +299,7 @@ const columns: ColumnDef<z.infer<typeof categorySchema>>[] = [
{row.original.isActive ? "禁用" : "启用"} {row.original.isActive ? "禁用" : "启用"}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem <DropdownMenuItem
variant="destructive" variant="destructive"
onClick={() => { onClick={() => {
if (confirm('确定要删除这个分类吗?此操作不可撤销。')) { if (confirm('确定要删除这个分类吗?此操作不可撤销。')) {
@ -451,7 +451,7 @@ export function CategoryTable() {
function handleDragEnd(event: DragEndEvent) { function handleDragEnd(event: DragEndEvent) {
const { active, over } = event const { active, over } = event
if (active && over && active.id !== over.id) { if (active && over && active.id !== over.id) {
console.log('Reordering categories:', active.id, 'to', over.id) // console.log('Reordering categories:', active.id, 'to', over.id)
} }
} }
@ -495,11 +495,11 @@ export function CategoryTable() {
) )
} }
function CategoryTabs({ function CategoryTabs({
categories, categories,
loading, loading,
info, info,
handleDragEnd, handleDragEnd,
refetch, refetch,
pagination, pagination,
setPagination, setPagination,
@ -510,7 +510,7 @@ function CategoryTabs({
totalCount, totalCount,
updateCategory, updateCategory,
deleteCategory deleteCategory
}: { }: {
categories: any[] categories: any[]
loading: boolean loading: boolean
info: any info: any
@ -584,8 +584,8 @@ function CategoryTabs({
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => window.location.href = '/admin/categories/create'} onClick={() => window.location.href = '/admin/categories/create'}
> >
@ -873,8 +873,8 @@ function CategoryCellViewer({ item }: { item: z.infer<typeof categorySchema> })
<Button variant="link" className="text-foreground w-fit px-0 text-left justify-start"> <Button variant="link" className="text-foreground w-fit px-0 text-left justify-start">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{item.color && ( {item.color && (
<div <div
className="w-3 h-3 rounded-full" className="w-3 h-3 rounded-full"
style={{ backgroundColor: item.color }} style={{ backgroundColor: item.color }}
/> />
)} )}

View File

@ -559,7 +559,7 @@ export default function Control() {
<CardContent> <CardContent>
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-3">
<Button className="flex items-center gap-2" onClick={() => { <Button className="flex items-center gap-2" onClick={() => {
console.log('Applying configuration:', config) // console.log('Applying configuration:', config)
alert('配置应用成功!') alert('配置应用成功!')
}}> }}>
<Zap className="h-4 w-4" /> <Zap className="h-4 w-4" />

View File

@ -269,7 +269,7 @@ const columns: ColumnDef<z.infer<typeof tagSchema>>[] = [
cell: ({ row, table }) => { cell: ({ row, table }) => {
const updateTagMutation = (table.options.meta as any)?.updateTag const updateTagMutation = (table.options.meta as any)?.updateTag
const deleteTagMutation = (table.options.meta as any)?.deleteTag const deleteTagMutation = (table.options.meta as any)?.deleteTag
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@ -297,7 +297,7 @@ const columns: ColumnDef<z.infer<typeof tagSchema>>[] = [
{row.original.isActive ? "禁用" : "启用"} {row.original.isActive ? "禁用" : "启用"}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem <DropdownMenuItem
variant="destructive" variant="destructive"
onClick={() => { onClick={() => {
if (confirm('确定要删除这个标签吗?此操作不可撤销。')) { if (confirm('确定要删除这个标签吗?此操作不可撤销。')) {
@ -447,7 +447,7 @@ export function TagTable() {
function handleDragEnd(event: DragEndEvent) { function handleDragEnd(event: DragEndEvent) {
const { active, over } = event const { active, over } = event
if (active && over && active.id !== over.id) { if (active && over && active.id !== over.id) {
console.log('Reordering tags:', active.id, 'to', over.id) // console.log('Reordering tags:', active.id, 'to', over.id)
} }
} }
@ -491,11 +491,11 @@ export function TagTable() {
) )
} }
function TagTabs({ function TagTabs({
tags, tags,
loading, loading,
info, info,
handleDragEnd, handleDragEnd,
refetch, refetch,
pagination, pagination,
setPagination, setPagination,
@ -506,7 +506,7 @@ function TagTabs({
totalCount, totalCount,
updateTag, updateTag,
deleteTag deleteTag
}: { }: {
tags: any[] tags: any[]
loading: boolean loading: boolean
info: any info: any
@ -580,8 +580,8 @@ function TagTabs({
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => window.location.href = '/admin/tags/create'} onClick={() => window.location.href = '/admin/tags/create'}
> >
@ -869,8 +869,8 @@ function TagCellViewer({ item }: { item: z.infer<typeof tagSchema> }) {
<Button variant="link" className="text-foreground w-fit px-0 text-left justify-start"> <Button variant="link" className="text-foreground w-fit px-0 text-left justify-start">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{item.color ? ( {item.color ? (
<div <div
className="w-3 h-3 rounded-full" className="w-3 h-3 rounded-full"
style={{ backgroundColor: item.color }} style={{ backgroundColor: item.color }}
/> />
) : ( ) : (

View File

@ -31,7 +31,7 @@ export default function Page({ params }: { params: Promise<{ slug: string }> })
if (!data?.blogBySlug) return <div className="flex items-center justify-center min-h-[400px]">Blog not found</div>; if (!data?.blogBySlug) return <div className="flex items-center justify-center min-h-[400px]">Blog not found</div>;
const content = data.blogBySlug.content; const content = data.blogBySlug.content;
console.log('Blog content:', content); // console.log('Blog content:', content);
return ( return (
<div className="relative"> <div className="relative">

View File

@ -51,7 +51,7 @@ export function MapProvider({ children }: MapProviderProps) {
const setMap = (map: Map, layers: any[]) => { const setMap = (map: Map, layers: any[]) => {
// 如果已经有地图实例,先清理旧的 // 如果已经有地图实例,先清理旧的
if (mapRef.current) { if (mapRef.current) {
console.log('Cleaning up previous map instance...'); // console.log('Cleaning up previous map instance...');
mapRef.current = null; mapRef.current = null;
} }
@ -122,7 +122,7 @@ export function MapProvider({ children }: MapProviderProps) {
const clearMap = () => { const clearMap = () => {
if (mapRef.current) { if (mapRef.current) {
console.log('Clearing map instance...'); // console.log('Clearing map instance...');
mapRef.current.remove(); mapRef.current.remove();
mapRef.current = null; mapRef.current = null;
layersRef.current = []; layersRef.current = [];

View File

@ -13,7 +13,7 @@ export default async function Layout({ children }: { children: React.ReactNode }
const isLoggedIn = (await cookies()).get('jwt')?.value const isLoggedIn = (await cookies()).get('jwt')?.value
console.log(isLoggedIn) // console.log(isLoggedIn)
if (!isLoggedIn) { if (!isLoggedIn) {
redirect('/login') redirect('/login')

View File

@ -401,9 +401,9 @@ export default function MePage() {
}; };
const handleSave = () => { const handleSave = () => {
console.log("Settings saved:", settings); // console.log("Settings saved:", settings);
console.log("Profile saved:", profile); // console.log("Profile saved:", profile);
console.log("Tags saved:", tags); // console.log("Tags saved:", tags);
// TODO: 实际保存到后端 // TODO: 实际保存到后端
}; };

View File

@ -26,6 +26,7 @@ import { Separator } from "@/components/ui/separator";
import { useWS } from "./ws-context"; import { useWS } from "./ws-context";
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
import { useIsMobile } from "@/hooks/use-mobile"; import { useIsMobile } from "@/hooks/use-mobile";
import { Label } from "@/components/ui/label";
interface Uniforms { interface Uniforms {
@ -154,7 +155,6 @@ export const Timeline: React.FC<Props> = React.memo(({
// 合并配置 // 合并配置
const config: TimelineConfig = { const config: TimelineConfig = {
initialCenterTime: defaultCenterTime, initialCenterTime: defaultCenterTime,
initialTimeRange: (defaultEndTime - defaultStartTime) / 2, // 使用时间范围的一半作为初始范围
highlightWeekends: false, highlightWeekends: false,
zoomMode: ZoomMode.MousePosition, zoomMode: ZoomMode.MousePosition,
zoomSensitivity: 0.001, zoomSensitivity: 0.001,
@ -174,6 +174,15 @@ export const Timeline: React.FC<Props> = React.memo(({
primaryFontSize: 10, primaryFontSize: 10,
secondaryFontSize: 10 secondaryFontSize: 10
}, },
discreteZoomLevels: [
604123.3009862272,
383869.5109421124,
167314.88060646105,
64314.3439546868,
24625.670894409748,
4964.7447283082365,
],
initialZoomLevel: 4964.7447283082365,
onDateChange: async (date: Date) => { onDateChange: async (date: Date) => {
const datestr = formatInTimeZone(date, 'UTC', 'yyyyMMddHHmmss') const datestr = formatInTimeZone(date, 'UTC', 'yyyyMMddHHmmss')
const url_base = process.env.NEXT_PUBLIC_GRAPHQL_BACKEND_URL?.replace('/graphql', '') || 'http://localhost:3050' const url_base = process.env.NEXT_PUBLIC_GRAPHQL_BACKEND_URL?.replace('/graphql', '') || 'http://localhost:3050'

View File

@ -356,7 +356,7 @@ export function UserProvider({ children }: UserProviderProps) {
// 监听用户数据变化,定期更新用户信息 // 监听用户数据变化,定期更新用户信息
useEffect(() => { useEffect(() => {
if (userData && authState.isAuthenticated) { if (userData && authState.isAuthenticated) {
console.log('userData', userData) // console.log('userData', userData)
updateUserInfo(userData) updateUserInfo(userData)
} }
}, [userData, authState.isAuthenticated]) }, [userData, authState.isAuthenticated])

View File

@ -72,7 +72,7 @@ export function MapComponent({
op: "ui.component.load", op: "ui.component.load",
name: "Map Component Initialization", name: "Map Component Initialization",
}); });
span.setAttribute("map.style", style); span.setAttribute("map.style", style);
span.setAttribute("map.center", `${location.center[0]},${location.center[1]}`); span.setAttribute("map.center", `${location.center[0]},${location.center[1]}`);
span.setAttribute("map.zoom", location.zoom); span.setAttribute("map.zoom", location.zoom);
@ -92,10 +92,10 @@ export function MapComponent({
map.on('style.load', () => { map.on('style.load', () => {
logMapEvent('style.load', { logMapEvent('style.load', {
style: style, style: style,
center: location.center, center: location.center,
zoom: location.zoom zoom: location.zoom
}); });
logger.info('Map style loaded successfully', { logger.info('Map style loaded successfully', {
@ -285,9 +285,9 @@ export function MapComponent({
// 只在缩放级别变化时更新网格数据 // 只在缩放级别变化时更新网格数据
const currentZoom = Math.floor(map.getZoom()); const currentZoom = Math.floor(map.getZoom());
if (currentZoom !== this.lastZoom) { if (currentZoom !== this.lastZoom) {
logMapEvent('zoom_change', { logMapEvent('zoom_change', {
previousZoom: this.lastZoom, previousZoom: this.lastZoom,
currentZoom: currentZoom currentZoom: currentZoom
}); });
// 智能计算最佳细分数量 // 智能计算最佳细分数量
@ -300,20 +300,20 @@ export function MapComponent({
// 获取细分建议信息 // 获取细分建议信息
const recommendation = getSubdivisionRecommendation(currentZoom, performanceLevel); const recommendation = getSubdivisionRecommendation(currentZoom, performanceLevel);
logger.debug(logger.fmt`Zoom level: ${currentZoom}, Performance level: ${performanceLevel}`, { logger.debug(logger.fmt`Zoom level: ${currentZoom}, Performance level: ${performanceLevel}`, {
currentZoom, currentZoom,
performanceLevel, performanceLevel,
component: 'MapComponent' component: 'MapComponent'
}); });
logger.debug(logger.fmt`Subdivision recommendation: ${recommendation.subdivisions} (${recommendation.description})`, { logger.debug(logger.fmt`Subdivision recommendation: ${recommendation.subdivisions} (${recommendation.description})`, {
subdivisions: recommendation.subdivisions, subdivisions: recommendation.subdivisions,
description: recommendation.description, description: recommendation.description,
triangleCount: recommendation.triangleCount, triangleCount: recommendation.triangleCount,
estimatedMemoryMB: recommendation.estimatedMemoryMB estimatedMemoryMB: recommendation.estimatedMemoryMB
}); });
logPerformanceMetric('triangles', recommendation.triangleCount, 'count'); logPerformanceMetric('triangles', recommendation.triangleCount, 'count');
logPerformanceMetric('memory_estimate', recommendation.estimatedMemoryMB, 'MB'); logPerformanceMetric('memory_estimate', recommendation.estimatedMemoryMB, 'MB');
@ -463,7 +463,7 @@ export function MapComponent({
// 清理函数:当组件卸载或重新初始化时清理资源 // 清理函数:当组件卸载或重新初始化时清理资源
return () => { return () => {
console.log('Cleaning up map resources...'); // console.log('Cleaning up map resources...');
// 清理自定义图层引用 // 清理自定义图层引用
customLayerRef.current = null; customLayerRef.current = null;
@ -480,7 +480,7 @@ export function MapComponent({
if (map) { if (map) {
map.remove(); map.remove();
} }
// 结束Sentry span // 结束Sentry span
span.end(); span.end();
} }
@ -492,7 +492,7 @@ export function MapComponent({
const gl = glRef.current const gl = glRef.current
if (!gl) return; if (!gl) return;
console.log('Updating texture with imgBitmap:', imgBitmap); // console.log('Updating texture with imgBitmap:', imgBitmap);
gl.bindTexture(gl.TEXTURE_2D, texRef.current) gl.bindTexture(gl.TEXTURE_2D, texRef.current)
@ -557,7 +557,7 @@ export function MapComponent({
(span) => { (span) => {
span.setAttribute("colorbar.position.x", colorbarPosition.x); span.setAttribute("colorbar.position.x", colorbarPosition.x);
span.setAttribute("colorbar.position.y", colorbarPosition.y); span.setAttribute("colorbar.position.y", colorbarPosition.y);
e.preventDefault() e.preventDefault()
setIsDragging(true) setIsDragging(true)

View File

@ -39,7 +39,7 @@ export function useRadarTile({
radarTileRef.current.url = url radarTileRef.current.url = url
const blob = await resp.blob() const blob = await resp.blob()
const newImgBitmap = await createImageBitmap(blob) const newImgBitmap = await createImageBitmap(blob)
console.log('Created new ImageBitmap:', newImgBitmap); // console.log('Created new ImageBitmap:', newImgBitmap);
setImgBitmap(newImgBitmap) // 使用 setState 更新状态 setImgBitmap(newImgBitmap) // 使用 setState 更新状态
}).catch((err) => { }).catch((err) => {
radarTileRef.current.isError = true radarTileRef.current.isError = true

View File

@ -75,6 +75,8 @@ interface TimelineConfig {
initialCenterTime?: number; initialCenterTime?: number;
/** 初始显示范围毫秒默认1小时 */ /** 初始显示范围毫秒默认1小时 */
initialTimeRange?: number; initialTimeRange?: number;
/** Initial Zoom Level */
initialZoomLevel?: number | null;
/** 缩放模式 */ /** 缩放模式 */
zoomMode?: ZoomMode; zoomMode?: ZoomMode;
/** 缩放灵敏度 */ /** 缩放灵敏度 */
@ -226,7 +228,7 @@ class Viewport {
/** 获取缩放级别(像素/毫秒) */ /** 获取缩放级别(像素/毫秒) */
getZoomLevel(): number { getZoomLevel(): number {
return this.width / this.timeRange; return this.timeRange / this.width;
} }
/** 将时间转换为屏幕坐标 */ /** 将时间转换为屏幕坐标 */
@ -291,11 +293,12 @@ class Viewport {
const newTimeAtMouse = this.screenToTime(screenX); const newTimeAtMouse = this.screenToTime(screenX);
const timeCorrection = timeAtMouse - newTimeAtMouse; const timeCorrection = timeAtMouse - newTimeAtMouse;
this.centerTime += timeCorrection; this.centerTime += timeCorrection;
console.log(this.getZoomLevel())
} }
/** 动画缩放到指定时间范围 */ /** 动画缩放到指定时间范围 */
animateToTimeRange( animateToTimeRange(
targetRange: number, targetRange: number,
duration: number = 300, duration: number = 300,
targetCenterTime?: number, targetCenterTime?: number,
onComplete?: () => void onComplete?: () => void
@ -308,10 +311,10 @@ class Viewport {
const startTime = Date.now(); const startTime = Date.now();
const startTimeRange = this.timeRange; const startTimeRange = this.timeRange;
const startCenterTime = this.centerTime; const startCenterTime = this.centerTime;
// 限制目标范围 // 限制目标范围
targetRange = Math.max(this.MIN_RANGE, Math.min(this.MAX_RANGE, targetRange)); targetRange = Math.max(this.MIN_RANGE, Math.min(this.MAX_RANGE, targetRange));
this.animationState = { this.animationState = {
isAnimating: true, isAnimating: true,
startTime, startTime,
@ -328,16 +331,16 @@ class Viewport {
const elapsed = Date.now() - this.animationState.startTime; const elapsed = Date.now() - this.animationState.startTime;
const progress = Math.min(elapsed / this.animationState.duration, 1); const progress = Math.min(elapsed / this.animationState.duration, 1);
// 使用缓动函数 (easeInOutCubic) // 使用缓动函数 (easeInOutCubic)
const easedProgress = progress < 0.5 const easedProgress = progress < 0.5
? 4 * progress * progress * progress ? 4 * progress * progress * progress
: 1 - Math.pow(-2 * progress + 2, 3) / 2; : 1 - Math.pow(-2 * progress + 2, 3) / 2;
// 插值计算当前值 // 插值计算当前值
this.timeRange = this.animationState.startTimeRange + this.timeRange = this.animationState.startTimeRange +
(this.animationState.targetTimeRange - this.animationState.startTimeRange) * easedProgress; (this.animationState.targetTimeRange - this.animationState.startTimeRange) * easedProgress;
this.centerTime = this.animationState.startCenterTime + this.centerTime = this.animationState.startCenterTime +
(this.animationState.targetCenterTime - this.animationState.startCenterTime) * easedProgress; (this.animationState.targetCenterTime - this.animationState.startCenterTime) * easedProgress;
@ -365,11 +368,11 @@ class Viewport {
onComplete?: () => void onComplete?: () => void
): void { ): void {
const timeAtMouse = this.screenToTime(screenX); const timeAtMouse = this.screenToTime(screenX);
// 计算缩放后的中心时间,保持鼠标位置下的时间不变 // 计算缩放后的中心时间,保持鼠标位置下的时间不变
const currentRatio = (screenX - this.width / 2) / this.width; const currentRatio = (screenX - this.width / 2) / this.width;
const targetCenterTime = timeAtMouse - currentRatio * targetRange; const targetCenterTime = timeAtMouse - currentRatio * targetRange;
this.animateToTimeRange(targetRange, duration, targetCenterTime, onComplete); this.animateToTimeRange(targetRange, duration, targetCenterTime, onComplete);
} }
@ -429,8 +432,8 @@ class ScaleManager {
}, },
{ {
level: TimeFormatLevel.FiveMinutes, level: TimeFormatLevel.FiveMinutes,
majorInterval: 5 * 60 * 1000, // 5分钟 majorInterval: 6 * 60 * 1000, // 5分钟
minorTicks: 5, // 每分钟一个次刻度 minorTicks: 6, // 每分钟一个次刻度
minPixelDistance: 60, minPixelDistance: 60,
maxPixelDistance: 150, maxPixelDistance: 150,
formatter: (date: Date) => ({ formatter: (date: Date) => ({
@ -440,8 +443,8 @@ class ScaleManager {
}, },
{ {
level: TimeFormatLevel.TenMinutes, level: TimeFormatLevel.TenMinutes,
majorInterval: 10 * 60 * 1000, // 10分钟 majorInterval: 12 * 60 * 1000, // 12分钟
minorTicks: 2, // 每5分钟一个次刻度 minorTicks: 2, // 每6分钟一个次刻度
minPixelDistance: 50, minPixelDistance: 50,
maxPixelDistance: 120, maxPixelDistance: 120,
formatter: (date: Date) => ({ formatter: (date: Date) => ({
@ -501,7 +504,7 @@ class ScaleManager {
{ {
level: TimeFormatLevel.Day, level: TimeFormatLevel.Day,
majorInterval: 24 * 60 * 60 * 1000, // 1天 majorInterval: 24 * 60 * 60 * 1000, // 1天
minorTicks: 4, // 每6小时一个次刻度 minorTicks: 12, // 每6小时一个次刻度
minPixelDistance: 80, minPixelDistance: 80,
maxPixelDistance: 200, maxPixelDistance: 200,
formatter: (date: Date) => ({ formatter: (date: Date) => ({
@ -523,7 +526,7 @@ class ScaleManager {
const zoomLevel = viewport.getZoomLevel(); const zoomLevel = viewport.getZoomLevel();
for (const level of this.scaleLevels) { for (const level of this.scaleLevels) {
const pixelDistance = level.majorInterval * zoomLevel; const pixelDistance = level.majorInterval / zoomLevel;
if (pixelDistance >= level.minPixelDistance && pixelDistance <= level.maxPixelDistance) { if (pixelDistance >= level.minPixelDistance && pixelDistance <= level.maxPixelDistance) {
this.currentLevel = level; this.currentLevel = level;
return level; return level;
@ -531,7 +534,7 @@ class ScaleManager {
} }
// 如果没有找到合适的级别,选择最接近的 // 如果没有找到合适的级别,选择最接近的
const pixelDistances = this.scaleLevels.map(level => level.majorInterval * zoomLevel); const pixelDistances = this.scaleLevels.map(level => level.majorInterval / zoomLevel);
const targetDistance = 100; // 目标像素距离 const targetDistance = 100; // 目标像素距离
const closestIndex = pixelDistances.reduce((bestIdx, dist, idx) => { const closestIndex = pixelDistances.reduce((bestIdx, dist, idx) => {
const bestDist = pixelDistances[bestIdx]; const bestDist = pixelDistances[bestIdx];
@ -610,14 +613,14 @@ class InteractionHandler {
private discreteZoomLevels: number[]; private discreteZoomLevels: number[];
private enableSmoothZoom: boolean; private enableSmoothZoom: boolean;
private zoomAnimationDuration: number; private zoomAnimationDuration: number;
// 触摸相关状态 // 触摸相关状态
private lastTouchDistance: number | null = null; private lastTouchDistance: number | null = null;
private lastTouchCenter: { x: number; y: number } | null = null; private lastTouchCenter: { x: number; y: number } | null = null;
private isTouchZooming: boolean = false; private isTouchZooming: boolean = false;
constructor( constructor(
zoomMode: ZoomMode = ZoomMode.MousePosition, zoomMode: ZoomMode = ZoomMode.MousePosition,
sensitivity: number = 0.001, sensitivity: number = 0.001,
discreteZoomLevels: number[] = [], discreteZoomLevels: number[] = [],
enableSmoothZoom: boolean = true, enableSmoothZoom: boolean = true,
@ -705,14 +708,14 @@ class InteractionHandler {
/** 处理离散缩放 */ /** 处理离散缩放 */
private handleDiscreteZoom(deltaY: number, mouseX: number, viewport: Viewport, markX?: number): void { private handleDiscreteZoom(deltaY: number, mouseX: number, viewport: Viewport, markX?: number): void {
const currentRange = viewport.getTimeRange(); const currentLevel = viewport.getZoomLevel();
// 找到当前最接近的缩放级别 // 找到当前最接近的缩放级别
let currentLevelIndex = 0; let currentLevelIndex = 0;
let minDiff = Math.abs(this.discreteZoomLevels[0] - currentRange); let minDiff = Math.abs(this.discreteZoomLevels[0] - currentLevel);
for (let i = 1; i < this.discreteZoomLevels.length; i++) { for (let i = 1; i < this.discreteZoomLevels.length; i++) {
const diff = Math.abs(this.discreteZoomLevels[i] - currentRange); const diff = Math.abs(this.discreteZoomLevels[i] - currentLevel);
if (diff < minDiff) { if (diff < minDiff) {
minDiff = diff; minDiff = diff;
currentLevelIndex = i; currentLevelIndex = i;
@ -721,7 +724,7 @@ class InteractionHandler {
// 根据滚轮方向确定目标级别 // 根据滚轮方向确定目标级别
let targetLevelIndex: number; let targetLevelIndex: number;
if (deltaY > 0) { if (deltaY < 0) {
// 向上滚动,缩小(增加时间范围) // 向上滚动,缩小(增加时间范围)
targetLevelIndex = Math.min(currentLevelIndex + 1, this.discreteZoomLevels.length - 1); targetLevelIndex = Math.min(currentLevelIndex + 1, this.discreteZoomLevels.length - 1);
} else { } else {
@ -734,7 +737,7 @@ class InteractionHandler {
return; return;
} }
const targetRange = this.discreteZoomLevels[targetLevelIndex]; const targetRange = viewport.getWidth() * this.discreteZoomLevels[targetLevelIndex];
const zoomX = (this.zoomMode === ZoomMode.MarkMode && markX) ? markX : mouseX; const zoomX = (this.zoomMode === ZoomMode.MarkMode && markX) ? markX : mouseX;
if (this.enableSmoothZoom) { if (this.enableSmoothZoom) {
@ -764,15 +767,15 @@ class InteractionHandler {
// 双指触摸,准备缩放 // 双指触摸,准备缩放
this.isTouchZooming = true; this.isTouchZooming = true;
this.isDragging = false; this.isDragging = false;
const touch1 = touches[0]; const touch1 = touches[0];
const touch2 = touches[1]; const touch2 = touches[1];
this.lastTouchDistance = Math.sqrt( this.lastTouchDistance = Math.sqrt(
Math.pow(touch2.clientX - touch1.clientX, 2) + Math.pow(touch2.clientX - touch1.clientX, 2) +
Math.pow(touch2.clientY - touch1.clientY, 2) Math.pow(touch2.clientY - touch1.clientY, 2)
); );
this.lastTouchCenter = { this.lastTouchCenter = {
x: (touch1.clientX + touch2.clientX) / 2, x: (touch1.clientX + touch2.clientX) / 2,
y: (touch1.clientY + touch2.clientY) / 2 y: (touch1.clientY + touch2.clientY) / 2
@ -790,24 +793,24 @@ class InteractionHandler {
// 双指缩放 // 双指缩放
const touch1 = touches[0]; const touch1 = touches[0];
const touch2 = touches[1]; const touch2 = touches[1];
const currentDistance = Math.sqrt( const currentDistance = Math.sqrt(
Math.pow(touch2.clientX - touch1.clientX, 2) + Math.pow(touch2.clientX - touch1.clientX, 2) +
Math.pow(touch2.clientY - touch1.clientY, 2) Math.pow(touch2.clientY - touch1.clientY, 2)
); );
const currentCenter = { const currentCenter = {
x: (touch1.clientX + touch2.clientX) / 2, x: (touch1.clientX + touch2.clientX) / 2,
y: (touch1.clientY + touch2.clientY) / 2 y: (touch1.clientY + touch2.clientY) / 2
}; };
if (this.lastTouchDistance && this.lastTouchCenter) { if (this.lastTouchDistance && this.lastTouchCenter) {
const scaleChange = currentDistance / this.lastTouchDistance; const scaleChange = currentDistance / this.lastTouchDistance;
// 转换为画布坐标 // 转换为画布坐标
const canvasRect = canvas.getBoundingClientRect(); const canvasRect = canvas.getBoundingClientRect();
const canvasX = currentCenter.x - canvasRect.left; const canvasX = currentCenter.x - canvasRect.left;
if (this.discreteZoomLevels.length > 0) { if (this.discreteZoomLevels.length > 0) {
// 对于离散缩放,当缩放变化超过阈值时触发 // 对于离散缩放,当缩放变化超过阈值时触发
const threshold = 1.1; // 10% 的变化阈值 const threshold = 1.1; // 10% 的变化阈值
@ -824,9 +827,9 @@ class InteractionHandler {
this.lastTouchDistance = currentDistance; this.lastTouchDistance = currentDistance;
} }
} }
this.lastTouchCenter = currentCenter; this.lastTouchCenter = currentCenter;
} else if (touches.length === 1 && !this.isTouchZooming) { } else if (touches.length === 1 && !this.isTouchZooming) {
// 单指拖拽 - 转换为画布坐标 // 单指拖拽 - 转换为画布坐标
const touch = touches[0]; const touch = touches[0];
@ -882,10 +885,14 @@ class RealTimeTimeline {
// 初始化组件 // 初始化组件
this.viewport = new Viewport(canvas.width, canvas.height); this.viewport = new Viewport(canvas.width, canvas.height);
this.viewport.goToTime(this.config.initialCenterTime); this.viewport.goToTime(this.config.initialCenterTime);
if (this.config.initialTimeRange) { if (this.config.initialTimeRange && !this.config.initialZoomLevel) {
this.viewport.setTimeRange(this.config.initialTimeRange); this.viewport.setTimeRange(this.config.initialTimeRange);
} }
if (this.config.initialZoomLevel && !this.config.initialTimeRange) {
this.viewport.setTimeRange(this.viewport.getWidth() * this.config.initialZoomLevel);
}
this.scaleManager = new ScaleManager(); this.scaleManager = new ScaleManager();
this.interaction = new InteractionHandler( this.interaction = new InteractionHandler(
this.config.zoomMode, this.config.zoomMode,
@ -931,6 +938,7 @@ class RealTimeTimeline {
const defaultConfig: Required<TimelineConfig> = { const defaultConfig: Required<TimelineConfig> = {
initialCenterTime: Date.now(), initialCenterTime: Date.now(),
initialTimeRange: 60 * 60 * 1000, // 1小时 initialTimeRange: 60 * 60 * 1000, // 1小时
initialZoomLevel: null,
zoomMode: ZoomMode.MousePosition, zoomMode: ZoomMode.MousePosition,
zoomSensitivity: 0.001, zoomSensitivity: 0.001,
discreteZoomLevels: defaultDiscreteZoomLevels, discreteZoomLevels: defaultDiscreteZoomLevels,
@ -969,6 +977,7 @@ class RealTimeTimeline {
return { return {
initialCenterTime: config.initialCenterTime ?? defaultConfig.initialCenterTime, initialCenterTime: config.initialCenterTime ?? defaultConfig.initialCenterTime,
initialTimeRange: config.initialTimeRange ?? defaultConfig.initialTimeRange, initialTimeRange: config.initialTimeRange ?? defaultConfig.initialTimeRange,
initialZoomLevel: config.initialZoomLevel ?? defaultConfig.initialZoomLevel,
zoomMode: config.zoomMode ?? defaultConfig.zoomMode, zoomMode: config.zoomMode ?? defaultConfig.zoomMode,
zoomSensitivity: config.zoomSensitivity ?? defaultConfig.zoomSensitivity, zoomSensitivity: config.zoomSensitivity ?? defaultConfig.zoomSensitivity,
discreteZoomLevels: config.discreteZoomLevels ?? defaultConfig.discreteZoomLevels, discreteZoomLevels: config.discreteZoomLevels ?? defaultConfig.discreteZoomLevels,
@ -1037,7 +1046,7 @@ class RealTimeTimeline {
// 统一的结束拖拽处理(鼠标) // 统一的结束拖拽处理(鼠标)
const handleMouseEnd = (e: MouseEvent) => { const handleMouseEnd = (e: MouseEvent) => {
const { x, y } = getEventCoordinates(e); const { x, y } = getEventCoordinates(e);
// 检查是否为点击(而非拖拽) // 检查是否为点击(而非拖拽)
if (this.interaction.isClick()) { if (this.interaction.isClick()) {
// 获取当前刻度信息用于吸附 // 获取当前刻度信息用于吸附
@ -1059,14 +1068,14 @@ class RealTimeTimeline {
const handleTouchMove = (e: TouchEvent) => { const handleTouchMove = (e: TouchEvent) => {
e.preventDefault(); e.preventDefault();
// 对于单指触摸,更新显示位置 // 对于单指触摸,更新显示位置
if (e.touches.length === 1 && !this.interaction.getIsDragging()) { if (e.touches.length === 1 && !this.interaction.getIsDragging()) {
const { x, y } = getEventCoordinates(e); const { x, y } = getEventCoordinates(e);
this.mousePosition = { x, y }; this.mousePosition = { x, y };
this.showMouseIndicator = true; this.showMouseIndicator = true;
} }
this.interaction.handleTouchMove(e.touches, this.viewport, this.canvas); this.interaction.handleTouchMove(e.touches, this.viewport, this.canvas);
this.requestRender(); this.requestRender();
}; };
@ -1485,6 +1494,11 @@ class RealTimeTimeline {
currentLevel(): number {
return this.viewport.getZoomLevel()
}
/** 获取当前视口信息 */ /** 获取当前视口信息 */
getViewportInfo(): { getViewportInfo(): {
centerTime: number; centerTime: number;