mirror of https://github.com/langgenius/dify.git
cursor pos transform to canvas
This commit is contained in:
parent
f091868b7c
commit
53ba6aadff
|
|
@ -27,7 +27,7 @@ import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
|||
import { useCollaboration } from '@/app/components/workflow/collaboration'
|
||||
import { collaborationManager } from '@/app/components/workflow/collaboration'
|
||||
import { fetchWorkflowDraft } from '@/service/workflow'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import { useReactFlow, useStoreApi } from 'reactflow'
|
||||
|
||||
type WorkflowMainProps = Pick<WorkflowProps, 'nodes' | 'edges' | 'viewport'>
|
||||
const WorkflowMain = ({
|
||||
|
|
@ -39,6 +39,7 @@ const WorkflowMain = ({
|
|||
const workflowStore = useWorkflowStore()
|
||||
const appId = useStore(s => s.appId)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const reactFlow = useReactFlow()
|
||||
|
||||
const store = useStoreApi()
|
||||
const { startCursorTracking, stopCursorTracking, onlineUsers, cursors, isConnected } = useCollaboration(appId, store)
|
||||
|
|
@ -55,12 +56,12 @@ const WorkflowMain = ({
|
|||
|
||||
useEffect(() => {
|
||||
if (containerRef.current)
|
||||
startCursorTracking(containerRef as React.RefObject<HTMLElement>)
|
||||
startCursorTracking(containerRef as React.RefObject<HTMLElement>, reactFlow)
|
||||
|
||||
return () => {
|
||||
stopCursorTracking()
|
||||
}
|
||||
}, [startCursorTracking, stopCursorTracking])
|
||||
}, [startCursorTracking, stopCursorTracking, reactFlow])
|
||||
|
||||
const handleWorkflowDataUpdate = useCallback((payload: any) => {
|
||||
const {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { FC } from 'react'
|
||||
import { useViewport } from 'reactflow'
|
||||
import type { CursorPosition, OnlineUser } from '@/app/components/workflow/collaboration/types'
|
||||
import { getUserColor } from '../utils/user-color'
|
||||
|
||||
|
|
@ -13,6 +14,15 @@ const UserCursors: FC<UserCursorsProps> = ({
|
|||
myUserId,
|
||||
onlineUsers,
|
||||
}) => {
|
||||
const viewport = useViewport()
|
||||
|
||||
const convertToScreenCoordinates = (cursor: CursorPosition) => {
|
||||
// Convert world coordinates to screen coordinates using current viewport
|
||||
const screenX = cursor.x * viewport.zoom + viewport.x
|
||||
const screenY = cursor.y * viewport.zoom + viewport.y
|
||||
|
||||
return { x: screenX, y: screenY }
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{Object.entries(cursors || {}).map(([userId, cursor]) => {
|
||||
|
|
@ -22,14 +32,15 @@ const UserCursors: FC<UserCursorsProps> = ({
|
|||
const userInfo = onlineUsers.find(user => user.user_id === userId)
|
||||
const userName = userInfo?.username || `User ${userId.slice(-4)}`
|
||||
const userColor = getUserColor(userId)
|
||||
const screenPos = convertToScreenCoordinates(cursor)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={userId}
|
||||
className="pointer-events-none absolute z-[10000] transition-all duration-150 ease-out"
|
||||
style={{
|
||||
left: cursor.x,
|
||||
top: cursor.y,
|
||||
left: screenPos.x,
|
||||
top: screenPos.y,
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useEffect, useRef, useState } from 'react'
|
||||
import type { ReactFlowInstance } from 'reactflow'
|
||||
import { collaborationManager } from '../core/collaboration-manager'
|
||||
import { CursorService } from '../services/cursor-service'
|
||||
import type { CollaborationState } from '../types/collaboration'
|
||||
|
|
@ -62,11 +63,11 @@ export function useCollaboration(appId: string, reactFlowStore?: any) {
|
|||
}
|
||||
}, [appId, reactFlowStore])
|
||||
|
||||
const startCursorTracking = (containerRef: React.RefObject<HTMLElement>) => {
|
||||
const startCursorTracking = (containerRef: React.RefObject<HTMLElement>, reactFlowInstance?: ReactFlowInstance) => {
|
||||
if (cursorServiceRef.current) {
|
||||
cursorServiceRef.current.startTracking(containerRef, (position) => {
|
||||
collaborationManager.emitCursorMove(position)
|
||||
})
|
||||
}, reactFlowInstance)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { RefObject } from 'react'
|
||||
import type { CursorPosition } from '../types/collaboration'
|
||||
import type { ReactFlowInstance } from 'reactflow'
|
||||
|
||||
export type CursorServiceConfig = {
|
||||
minMoveDistance?: number
|
||||
|
|
@ -8,6 +9,7 @@ export type CursorServiceConfig = {
|
|||
|
||||
export class CursorService {
|
||||
private containerRef: RefObject<HTMLElement> | null = null
|
||||
private reactFlowInstance: ReactFlowInstance | null = null
|
||||
private isTracking = false
|
||||
private onCursorUpdate: ((cursors: Record<string, CursorPosition>) => void) | null = null
|
||||
private onEmitPosition: ((position: CursorPosition) => void) | null = null
|
||||
|
|
@ -25,11 +27,13 @@ export class CursorService {
|
|||
startTracking(
|
||||
containerRef: RefObject<HTMLElement>,
|
||||
onEmitPosition: (position: CursorPosition) => void,
|
||||
reactFlowInstance?: ReactFlowInstance,
|
||||
): void {
|
||||
if (this.isTracking) this.stopTracking()
|
||||
|
||||
this.containerRef = containerRef
|
||||
this.onEmitPosition = onEmitPosition
|
||||
this.reactFlowInstance = reactFlowInstance || null
|
||||
this.isTracking = true
|
||||
|
||||
if (containerRef.current)
|
||||
|
|
@ -41,6 +45,7 @@ export class CursorService {
|
|||
this.containerRef.current.removeEventListener('mousemove', this.handleMouseMove)
|
||||
|
||||
this.containerRef = null
|
||||
this.reactFlowInstance = null
|
||||
this.onEmitPosition = null
|
||||
this.isTracking = false
|
||||
this.lastPosition = null
|
||||
|
|
@ -59,26 +64,34 @@ export class CursorService {
|
|||
if (!this.containerRef?.current || !this.onEmitPosition) return
|
||||
|
||||
const rect = this.containerRef.current.getBoundingClientRect()
|
||||
const x = event.clientX - rect.left
|
||||
const y = event.clientY - rect.top
|
||||
let x = event.clientX - rect.left
|
||||
let y = event.clientY - rect.top
|
||||
|
||||
if (x >= 0 && y >= 0 && x <= rect.width && y <= rect.height) {
|
||||
const now = Date.now()
|
||||
const timeThrottled = now - this.lastEmitTime > this.config.throttleMs
|
||||
const distanceThrottled = !this.lastPosition
|
||||
|| (Math.abs(x - this.lastPosition.x) > this.config.minMoveDistance
|
||||
|| Math.abs(y - this.lastPosition.y) > this.config.minMoveDistance)
|
||||
// Transform coordinates to ReactFlow world coordinates if ReactFlow instance is available
|
||||
if (this.reactFlowInstance) {
|
||||
const viewport = this.reactFlowInstance.getViewport()
|
||||
// Convert screen coordinates to world coordinates
|
||||
// World coordinates = (screen coordinates - viewport translation) / zoom
|
||||
x = (x - viewport.x) / viewport.zoom
|
||||
y = (y - viewport.y) / viewport.zoom
|
||||
}
|
||||
|
||||
if (timeThrottled && distanceThrottled) {
|
||||
this.lastPosition = { x, y }
|
||||
this.lastEmitTime = now
|
||||
this.onEmitPosition({
|
||||
x,
|
||||
y,
|
||||
userId: '',
|
||||
timestamp: now,
|
||||
})
|
||||
}
|
||||
// Always emit cursor position (remove boundary check since world coordinates can be negative)
|
||||
const now = Date.now()
|
||||
const timeThrottled = now - this.lastEmitTime > this.config.throttleMs
|
||||
const distanceThrottled = !this.lastPosition
|
||||
|| (Math.abs(x - this.lastPosition.x) > this.config.minMoveDistance / (this.reactFlowInstance?.getZoom() || 1))
|
||||
|| (Math.abs(y - this.lastPosition.y) > this.config.minMoveDistance / (this.reactFlowInstance?.getZoom() || 1))
|
||||
|
||||
if (timeThrottled && distanceThrottled) {
|
||||
this.lastPosition = { x, y }
|
||||
this.lastEmitTime = now
|
||||
this.onEmitPosition({
|
||||
x,
|
||||
y,
|
||||
userId: '',
|
||||
timestamp: now,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue