mirror of
https://github.com/node-red/node-red
synced 2024-11-21 23:48:30 +00:00
Merge pull request #4845 from node-red/multiplayer-cursor
Multiplayer cursor tracking
This commit is contained in:
commit
380d3be819
@ -100,16 +100,36 @@ RED.multiplayer = (function () {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (isInWorkspace) {
|
||||
const chart = $('#red-ui-workspace-chart')
|
||||
const chartOffset = chart.offset()
|
||||
const scaleFactor = RED.view.scale()
|
||||
location.cursor = {
|
||||
x: (lastPosition[0] - chartOffset.left + chart.scrollLeft()) / scaleFactor,
|
||||
y: (lastPosition[1] - chartOffset.top + chart.scrollTop()) / scaleFactor
|
||||
}
|
||||
}
|
||||
return location
|
||||
}
|
||||
|
||||
let publishLocationTimeout
|
||||
let lastPosition = [0,0]
|
||||
let isInWorkspace = false
|
||||
|
||||
function publishLocation () {
|
||||
const location = getLocation()
|
||||
if (location.workspace !== 0) {
|
||||
log('send', 'multiplayer/location', location)
|
||||
RED.comms.send('multiplayer/location', location)
|
||||
if (!publishLocationTimeout) {
|
||||
publishLocationTimeout = setTimeout(() => {
|
||||
const location = getLocation()
|
||||
if (location.workspace !== 0) {
|
||||
log('send', 'multiplayer/location', location)
|
||||
RED.comms.send('multiplayer/location', location)
|
||||
}
|
||||
publishLocationTimeout = null
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function revealUser(location, skipWorkspace) {
|
||||
if (location.node) {
|
||||
// Need to check if this is a known node, so we can fall back to revealing
|
||||
@ -271,7 +291,16 @@ RED.multiplayer = (function () {
|
||||
|
||||
function removeUserLocation (sessionId) {
|
||||
updateUserLocation(sessionId, {})
|
||||
removeUserCursor(sessionId)
|
||||
}
|
||||
function removeUserCursor (sessionId) {
|
||||
// return
|
||||
if (sessions[sessionId]?.cursor) {
|
||||
sessions[sessionId].cursor.parentNode.removeChild(sessions[sessionId].cursor)
|
||||
delete sessions[sessionId].cursor
|
||||
}
|
||||
}
|
||||
|
||||
function updateUserLocation (sessionId, location) {
|
||||
let viewTouched = false
|
||||
const oldLocation = sessions[sessionId].location
|
||||
@ -291,6 +320,28 @@ RED.multiplayer = (function () {
|
||||
// console.log(`updateUserLocation sessionId:${sessionId} oldWS:${oldLocation?.workspace} newWS:${location.workspace}`)
|
||||
if (location.workspace) {
|
||||
getWorkspaceTray(location.workspace).addUser(sessionId)
|
||||
if (location.cursor && location.workspace === RED.workspaces.active()) {
|
||||
if (!sessions[sessionId].cursor) {
|
||||
const user = sessions[sessionId].user
|
||||
const cursorIcon = document.createElementNS("http://www.w3.org/2000/svg","g");
|
||||
cursorIcon.setAttribute("class", "red-ui-multiplayer-annotation")
|
||||
cursorIcon.appendChild(createAnnotationUser(user, true))
|
||||
$(cursorIcon).css({
|
||||
transform: `translate( ${location.cursor.x}px, ${location.cursor.y}px)`,
|
||||
transition: 'transform 0.1s linear'
|
||||
})
|
||||
$("#red-ui-workspace-chart svg").append(cursorIcon)
|
||||
sessions[sessionId].cursor = cursorIcon
|
||||
} else {
|
||||
const cursorIcon = sessions[sessionId].cursor
|
||||
$(cursorIcon).css({
|
||||
transform: `translate( ${location.cursor.x}px, ${location.cursor.y}px)`
|
||||
})
|
||||
|
||||
}
|
||||
} else if (sessions[sessionId].cursor) {
|
||||
removeUserCursor(sessionId)
|
||||
}
|
||||
}
|
||||
if (location.node) {
|
||||
addUserToNode(sessionId, location.node)
|
||||
@ -309,67 +360,69 @@ RED.multiplayer = (function () {
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
function createAnnotationUser(user, pointer = false) {
|
||||
const radius = 20
|
||||
const halfRadius = radius/2
|
||||
const group = document.createElementNS("http://www.w3.org/2000/svg","g");
|
||||
const badge = document.createElementNS("http://www.w3.org/2000/svg","path");
|
||||
let shapePath
|
||||
if (!pointer) {
|
||||
shapePath = `M 0 ${halfRadius} a ${halfRadius} ${halfRadius} 0 1 1 ${radius} 0 a ${halfRadius} ${halfRadius} 0 1 1 -${radius} 0 z`
|
||||
} else {
|
||||
shapePath = `M 0 0 h ${halfRadius} a ${halfRadius} ${halfRadius} 0 1 1 -${halfRadius} ${halfRadius} z`
|
||||
}
|
||||
badge.setAttribute('d', shapePath)
|
||||
badge.setAttribute("class", "red-ui-multiplayer-annotation-background")
|
||||
group.appendChild(badge)
|
||||
if (user && user.profileColor !== undefined) {
|
||||
badge.setAttribute("class", "red-ui-multiplayer-annotation-background red-ui-user-profile-color-" + user.profileColor)
|
||||
}
|
||||
if (user && user.image) {
|
||||
const image = document.createElementNS("http://www.w3.org/2000/svg","image");
|
||||
image.setAttribute("width", radius)
|
||||
image.setAttribute("height", radius)
|
||||
image.setAttribute("href", user.image)
|
||||
image.setAttribute("clip-path", "circle("+Math.floor(radius/2)+")")
|
||||
group.appendChild(image)
|
||||
} else if (user && user.anonymous) {
|
||||
const anonIconHead = document.createElementNS("http://www.w3.org/2000/svg","circle");
|
||||
anonIconHead.setAttribute("cx", radius/2)
|
||||
anonIconHead.setAttribute("cy", radius/2 - 2)
|
||||
anonIconHead.setAttribute("r", 2.4)
|
||||
anonIconHead.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
|
||||
group.appendChild(anonIconHead)
|
||||
const anonIconBody = document.createElementNS("http://www.w3.org/2000/svg","path");
|
||||
anonIconBody.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
|
||||
// anonIconBody.setAttribute("d",`M ${radius/2 - 4} ${radius/2 + 1} h 8 v4 h -8 z`);
|
||||
anonIconBody.setAttribute("d",`M ${radius/2} ${radius/2 + 5} h -2.5 c -2 1 -2 -5 0.5 -4.5 c 2 1 2 1 4 0 c 2.5 -0.5 2.5 5.5 0 4.5 z`);
|
||||
group.appendChild(anonIconBody)
|
||||
} else {
|
||||
const labelText = user.username ? user.username.substring(0,2) : user
|
||||
const label = document.createElementNS("http://www.w3.org/2000/svg","text");
|
||||
if (user.username) {
|
||||
label.setAttribute("class","red-ui-multiplayer-annotation-label");
|
||||
label.textContent = user.username.substring(0,2)
|
||||
} else {
|
||||
label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count")
|
||||
label.textContent = user
|
||||
}
|
||||
label.setAttribute("text-anchor", "middle")
|
||||
label.setAttribute("x",radius/2);
|
||||
label.setAttribute("y",radius/2 + 3);
|
||||
group.appendChild(label)
|
||||
}
|
||||
const border = document.createElementNS("http://www.w3.org/2000/svg","path");
|
||||
border.setAttribute('d', shapePath)
|
||||
border.setAttribute("class", "red-ui-multiplayer-annotation-border")
|
||||
group.appendChild(border)
|
||||
return group
|
||||
}
|
||||
|
||||
return {
|
||||
init: function () {
|
||||
|
||||
function createAnnotationUser(user) {
|
||||
|
||||
const group = document.createElementNS("http://www.w3.org/2000/svg","g");
|
||||
const badge = document.createElementNS("http://www.w3.org/2000/svg","circle");
|
||||
const radius = 20
|
||||
badge.setAttribute("cx",radius/2);
|
||||
badge.setAttribute("cy",radius/2);
|
||||
badge.setAttribute("r",radius/2);
|
||||
badge.setAttribute("class", "red-ui-multiplayer-annotation-background")
|
||||
group.appendChild(badge)
|
||||
if (user && user.profileColor !== undefined) {
|
||||
badge.setAttribute("class", "red-ui-multiplayer-annotation-background red-ui-user-profile-color-" + user.profileColor)
|
||||
}
|
||||
if (user && user.image) {
|
||||
const image = document.createElementNS("http://www.w3.org/2000/svg","image");
|
||||
image.setAttribute("width", radius)
|
||||
image.setAttribute("height", radius)
|
||||
image.setAttribute("href", user.image)
|
||||
image.setAttribute("clip-path", "circle("+Math.floor(radius/2)+")")
|
||||
group.appendChild(image)
|
||||
} else if (user && user.anonymous) {
|
||||
const anonIconHead = document.createElementNS("http://www.w3.org/2000/svg","circle");
|
||||
anonIconHead.setAttribute("cx", radius/2)
|
||||
anonIconHead.setAttribute("cy", radius/2 - 2)
|
||||
anonIconHead.setAttribute("r", 2.4)
|
||||
anonIconHead.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
|
||||
group.appendChild(anonIconHead)
|
||||
const anonIconBody = document.createElementNS("http://www.w3.org/2000/svg","path");
|
||||
anonIconBody.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
|
||||
// anonIconBody.setAttribute("d",`M ${radius/2 - 4} ${radius/2 + 1} h 8 v4 h -8 z`);
|
||||
anonIconBody.setAttribute("d",`M ${radius/2} ${radius/2 + 5} h -2.5 c -2 1 -2 -5 0.5 -4.5 c 2 1 2 1 4 0 c 2.5 -0.5 2.5 5.5 0 4.5 z`);
|
||||
group.appendChild(anonIconBody)
|
||||
} else {
|
||||
const labelText = user.username ? user.username.substring(0,2) : user
|
||||
const label = document.createElementNS("http://www.w3.org/2000/svg","text");
|
||||
if (user.username) {
|
||||
label.setAttribute("class","red-ui-multiplayer-annotation-label");
|
||||
label.textContent = user.username.substring(0,2)
|
||||
} else {
|
||||
label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count")
|
||||
label.textContent = user
|
||||
}
|
||||
label.setAttribute("text-anchor", "middle")
|
||||
label.setAttribute("x",radius/2);
|
||||
label.setAttribute("y",radius/2 + 3);
|
||||
group.appendChild(label)
|
||||
}
|
||||
const border = document.createElementNS("http://www.w3.org/2000/svg","circle");
|
||||
border.setAttribute("cx",radius/2);
|
||||
border.setAttribute("cy",radius/2);
|
||||
border.setAttribute("r",radius/2);
|
||||
border.setAttribute("class", "red-ui-multiplayer-annotation-border")
|
||||
group.appendChild(border)
|
||||
|
||||
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
|
||||
RED.view.annotations.register("red-ui-multiplayer",{
|
||||
type: 'badge',
|
||||
@ -479,6 +532,24 @@ RED.multiplayer = (function () {
|
||||
RED.comms.send('multiplayer/disconnect', disconnectInfo)
|
||||
RED.settings.removeLocal('multiplayer:sessionId')
|
||||
})
|
||||
|
||||
const chart = $('#red-ui-workspace-chart')
|
||||
chart.on('mousemove', function (evt) {
|
||||
lastPosition[0] = evt.clientX
|
||||
lastPosition[1] = evt.clientY
|
||||
publishLocation()
|
||||
})
|
||||
chart.on('scroll', function (evt) {
|
||||
publishLocation()
|
||||
})
|
||||
chart.on('mouseenter', function () {
|
||||
isInWorkspace = true
|
||||
publishLocation()
|
||||
})
|
||||
chart.on('mouseleave', function () {
|
||||
isInWorkspace = false
|
||||
publishLocation()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,8 @@ module.exports = {
|
||||
const payload = {
|
||||
session: sessionId,
|
||||
workspace: opts.data.workspace,
|
||||
node: opts.data.node
|
||||
node: opts.data.node,
|
||||
cursor: opts.data.cursor
|
||||
}
|
||||
runtime.events.emit('comms', {
|
||||
topic: 'multiplayer/location',
|
||||
|
Loading…
Reference in New Issue
Block a user