mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge pull request #4666 from node-red/multiplayer-2
[multiplayer] Add user presence indicators
This commit is contained in:
commit
a70618cdef
@ -1,114 +1,92 @@
|
|||||||
RED.multiplayer = (function () {
|
RED.multiplayer = (function () {
|
||||||
|
|
||||||
// sessionId - used to identify sessions across websocket reconnects
|
// activeSessionId - used to identify sessions across websocket reconnects
|
||||||
let sessionId
|
let activeSessionId
|
||||||
|
|
||||||
let headerWidget
|
let headerWidget
|
||||||
// Map of session id to { session:'', user:{}, location:{}}
|
// Map of session id to { session:'', user:{}, location:{}}
|
||||||
let connections = {}
|
let sessions = {}
|
||||||
// Map of username to { user:{}, connections:[] }
|
// Map of username to { user:{}, sessions:[] }
|
||||||
let users = {}
|
let users = {}
|
||||||
|
|
||||||
function addUserConnection (connection) {
|
function addUserSession (session) {
|
||||||
if (connections[connection.session]) {
|
if (sessions[session.session]) {
|
||||||
// This is an existing connection that has been authenticated
|
// This is an existing connection that has been authenticated
|
||||||
const existingConnection = connections[connection.session]
|
const existingSession = sessions[session.session]
|
||||||
if (existingConnection.user.username !== connection.user.username) {
|
if (existingSession.user.username !== session.user.username) {
|
||||||
removeUserButton(users[existingConnection.user.username])
|
removeUserHeaderButton(users[existingSession.user.username])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
connections[connection.session] = connection
|
sessions[session.session] = session
|
||||||
const user = users[connection.user.username] = users[connection.user.username] || {
|
const user = users[session.user.username] = users[session.user.username] || {
|
||||||
user: connection.user,
|
user: session.user,
|
||||||
connections: []
|
sessions: []
|
||||||
}
|
}
|
||||||
connection.location = connection.location || {}
|
if (session.user.profileColor === undefined) {
|
||||||
user.connections.push(connection)
|
session.user.profileColor = (1 + Math.floor(Math.random() * 5))
|
||||||
|
}
|
||||||
|
session.location = session.location || {}
|
||||||
|
user.sessions.push(session)
|
||||||
|
|
||||||
if (connection.user.username === RED.settings.user?.username ||
|
if (session.session === activeSessionId) {
|
||||||
connection.session === sessionId
|
// This is the current user session - do not add a extra button for them
|
||||||
) {
|
|
||||||
// This is the current user - do not add a extra button for them
|
|
||||||
} else {
|
} else {
|
||||||
if (user.connections.length === 1) {
|
if (user.sessions.length === 1) {
|
||||||
if (user.button) {
|
if (user.button) {
|
||||||
clearTimeout(user.inactiveTimeout)
|
clearTimeout(user.inactiveTimeout)
|
||||||
clearTimeout(user.removeTimeout)
|
clearTimeout(user.removeTimeout)
|
||||||
user.button.removeClass('inactive')
|
user.button.removeClass('inactive')
|
||||||
} else {
|
} else {
|
||||||
addUserButton(user)
|
addUserHeaderButton(user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sessions[session.session].location = session.location
|
||||||
|
updateUserLocation(session.session)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeUserConnection (session, isDisconnected) {
|
function removeUserSession (sessionId, isDisconnected) {
|
||||||
const connection = connections[session]
|
removeUserLocation(sessionId)
|
||||||
delete connections[session]
|
const session = sessions[sessionId]
|
||||||
const user = users[connection.user.username]
|
delete sessions[sessionId]
|
||||||
const i = user.connections.indexOf(connection)
|
const user = users[session.user.username]
|
||||||
user.connections.splice(i, 1)
|
const i = user.sessions.indexOf(session)
|
||||||
|
user.sessions.splice(i, 1)
|
||||||
if (isDisconnected) {
|
if (isDisconnected) {
|
||||||
removeUserButton(user)
|
removeUserHeaderButton(user)
|
||||||
} else {
|
} else {
|
||||||
if (user.connections.length === 0) {
|
if (user.sessions.length === 0) {
|
||||||
// Give the user 5s to reconnect before marking inactive
|
// Give the user 5s to reconnect before marking inactive
|
||||||
user.inactiveTimeout = setTimeout(() => {
|
user.inactiveTimeout = setTimeout(() => {
|
||||||
user.button.addClass('inactive')
|
user.button.addClass('inactive')
|
||||||
// Give the user further 20 seconds to reconnect before removing them
|
// Give the user further 20 seconds to reconnect before removing them
|
||||||
// from the user toolbar entirely
|
// from the user toolbar entirely
|
||||||
user.removeTimeout = setTimeout(() => {
|
user.removeTimeout = setTimeout(() => {
|
||||||
removeUserButton(user)
|
removeUserHeaderButton(user)
|
||||||
}, 20000)
|
}, 20000)
|
||||||
}, 5000)
|
}, 5000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addUserButton (user) {
|
function addUserHeaderButton (user) {
|
||||||
user.button = $('<li class="red-ui-multiplayer-user"><button type="button" class="red-ui-multiplayer-user-icon" href="#"></button></li>')
|
user.button = $('<li class="red-ui-multiplayer-user"><button type="button" class="red-ui-multiplayer-user-icon"></button></li>')
|
||||||
.attr('data-username', user.user.username)
|
.attr('data-username', user.user.username)
|
||||||
.prependTo("#red-ui-multiplayer-user-list");
|
.prependTo("#red-ui-multiplayer-user-list");
|
||||||
var button = user.button.find("button")
|
var button = user.button.find("button")
|
||||||
|
RED.popover.tooltip(button, user.user.username)
|
||||||
button.on('click', function () {
|
button.on('click', function () {
|
||||||
RED.popover.create({
|
const location = user.sessions[0].location
|
||||||
target:button,
|
revealUser(location)
|
||||||
trigger: 'modal',
|
|
||||||
interactive: true,
|
|
||||||
width: "250px",
|
|
||||||
direction: 'bottom',
|
|
||||||
content: () => {
|
|
||||||
const content = $('<div>')
|
|
||||||
$('<div style="text-align: center">').text(user.user.username).appendTo(content)
|
|
||||||
|
|
||||||
const location = user.connections[0].location
|
|
||||||
if (location.workspace) {
|
|
||||||
const ws = RED.nodes.workspace(location.workspace) || RED.nodes.subflow(location.workspace)
|
|
||||||
if (ws) {
|
|
||||||
$('<div>').text(`${ws.type}: ${ws.label||ws.name||ws.id}`).appendTo(content)
|
|
||||||
} else {
|
|
||||||
$('<div>').text(`tab: unknown`).appendTo(content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (location.node) {
|
|
||||||
const node = RED.nodes.node(location.node)
|
|
||||||
if (node) {
|
|
||||||
$('<div>').text(`node: ${node.id}`).appendTo(content)
|
|
||||||
} else {
|
|
||||||
$('<div>').text(`node: unknown`).appendTo(content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return content
|
|
||||||
},
|
|
||||||
}).open()
|
|
||||||
})
|
})
|
||||||
if (!user.user.image) {
|
|
||||||
$('<i class="fa fa-user"></i>').appendTo(button);
|
const userProfile = RED.user.generateUserIcon(user.user)
|
||||||
} else {
|
userProfile.appendTo(button)
|
||||||
$('<span class="user-profile"></span>').css({
|
}
|
||||||
backgroundImage: "url("+user.user.image+")",
|
|
||||||
}).appendTo(button);
|
function removeUserHeaderButton (user) {
|
||||||
}
|
user.button.remove()
|
||||||
|
delete user.button
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLocation () {
|
function getLocation () {
|
||||||
@ -124,7 +102,7 @@ RED.multiplayer = (function () {
|
|||||||
}
|
}
|
||||||
return location
|
return location
|
||||||
}
|
}
|
||||||
function updateLocation () {
|
function publishLocation () {
|
||||||
const location = getLocation()
|
const location = getLocation()
|
||||||
if (location.workspace !== 0) {
|
if (location.workspace !== 0) {
|
||||||
log('send', 'multiplayer/location', location)
|
log('send', 'multiplayer/location', location)
|
||||||
@ -132,31 +110,314 @@ RED.multiplayer = (function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeUserButton (user) {
|
function revealUser(location, skipWorkspace) {
|
||||||
user.button.remove()
|
if (location.node) {
|
||||||
delete user.button
|
// Need to check if this is a known node, so we can fall back to revealing
|
||||||
|
// the workspace instead
|
||||||
|
const node = RED.nodes.node(location.node)
|
||||||
|
if (node) {
|
||||||
|
RED.view.reveal(location.node)
|
||||||
|
} else if (!skipWorkspace && location.workspace) {
|
||||||
|
RED.view.reveal(location.workspace)
|
||||||
|
}
|
||||||
|
} else if (!skipWorkspace && location.workspace) {
|
||||||
|
RED.view.reveal(location.workspace)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateUserLocation (data) {
|
const workspaceTrays = {}
|
||||||
connections[data.session].location = data
|
function getWorkspaceTray(workspaceId) {
|
||||||
delete data.session
|
// console.log('get tray for',workspaceId)
|
||||||
|
if (!workspaceTrays[workspaceId]) {
|
||||||
|
const tray = $('<div class="red-ui-multiplayer-users-tray"></div>')
|
||||||
|
const users = []
|
||||||
|
const userIcons = {}
|
||||||
|
|
||||||
|
const userCountIcon = $(`<div class="red-ui-multiplayer-user-location"><span class="red-ui-user-profile red-ui-multiplayer-user-count"><span></span></span></div>`)
|
||||||
|
const userCountSpan = userCountIcon.find('span span')
|
||||||
|
userCountIcon.hide()
|
||||||
|
userCountSpan.text('')
|
||||||
|
userCountIcon.appendTo(tray)
|
||||||
|
const userCountTooltip = RED.popover.tooltip(userCountIcon, function () {
|
||||||
|
const content = $('<div>')
|
||||||
|
users.forEach(sessionId => {
|
||||||
|
$('<div>').append($('<a href="#">').text(sessions[sessionId].user.username).on('click', function (evt) {
|
||||||
|
evt.preventDefault()
|
||||||
|
revealUser(sessions[sessionId].location, true)
|
||||||
|
userCountTooltip.close()
|
||||||
|
})).appendTo(content)
|
||||||
|
})
|
||||||
|
return content
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateUserCount = function () {
|
||||||
|
const maxShown = 2
|
||||||
|
const children = tray.children()
|
||||||
|
children.each(function (index, element) {
|
||||||
|
const i = users.length - index
|
||||||
|
if (i > maxShown) {
|
||||||
|
$(this).hide()
|
||||||
|
} else if (i >= 0) {
|
||||||
|
$(this).show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (users.length < maxShown + 1) {
|
||||||
|
userCountIcon.hide()
|
||||||
|
} else {
|
||||||
|
userCountSpan.text('+'+(users.length - maxShown))
|
||||||
|
userCountIcon.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
workspaceTrays[workspaceId] = {
|
||||||
|
attached: false,
|
||||||
|
tray,
|
||||||
|
users,
|
||||||
|
userIcons,
|
||||||
|
addUser: function (sessionId) {
|
||||||
|
if (users.indexOf(sessionId) === -1) {
|
||||||
|
// console.log(`addUser ws:${workspaceId} session:${sessionId}`)
|
||||||
|
users.push(sessionId)
|
||||||
|
const userLocationId = `red-ui-multiplayer-user-location-${sessionId}`
|
||||||
|
const userLocationIcon = $(`<div class="red-ui-multiplayer-user-location" id="${userLocationId}"></div>`)
|
||||||
|
RED.user.generateUserIcon(sessions[sessionId].user).appendTo(userLocationIcon)
|
||||||
|
userLocationIcon.prependTo(tray)
|
||||||
|
RED.popover.tooltip(userLocationIcon, sessions[sessionId].user.username)
|
||||||
|
userIcons[sessionId] = userLocationIcon
|
||||||
|
updateUserCount()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeUser: function (sessionId) {
|
||||||
|
// console.log(`removeUser ws:${workspaceId} session:${sessionId}`)
|
||||||
|
const userLocationId = `red-ui-multiplayer-user-location-${sessionId}`
|
||||||
|
const index = users.indexOf(sessionId)
|
||||||
|
if (index > -1) {
|
||||||
|
users.splice(index, 1)
|
||||||
|
userIcons[sessionId].remove()
|
||||||
|
delete userIcons[sessionId]
|
||||||
|
}
|
||||||
|
updateUserCount()
|
||||||
|
},
|
||||||
|
updateUserCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const trayDef = workspaceTrays[workspaceId]
|
||||||
|
if (!trayDef.attached) {
|
||||||
|
const workspaceTab = $(`#red-ui-tab-${workspaceId}`)
|
||||||
|
if (workspaceTab.length > 0) {
|
||||||
|
trayDef.attached = true
|
||||||
|
trayDef.tray.appendTo(workspaceTab)
|
||||||
|
trayDef.users.forEach(sessionId => {
|
||||||
|
trayDef.userIcons[sessionId].on('click', function (evt) {
|
||||||
|
revealUser(sessions[sessionId].location, true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return workspaceTrays[workspaceId]
|
||||||
}
|
}
|
||||||
|
function attachWorkspaceTrays () {
|
||||||
|
let viewTouched = false
|
||||||
|
for (let sessionId of Object.keys(sessions)) {
|
||||||
|
const location = sessions[sessionId].location
|
||||||
|
if (location) {
|
||||||
|
if (location.workspace) {
|
||||||
|
getWorkspaceTray(location.workspace).updateUserCount()
|
||||||
|
}
|
||||||
|
if (location.node) {
|
||||||
|
addUserToNode(sessionId, location.node)
|
||||||
|
viewTouched = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (viewTouched) {
|
||||||
|
RED.view.redraw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addUserToNode(sessionId, nodeId) {
|
||||||
|
const node = RED.nodes.node(nodeId)
|
||||||
|
if (node) {
|
||||||
|
if (!node._multiplayer) {
|
||||||
|
node._multiplayer = {
|
||||||
|
users: [sessionId]
|
||||||
|
}
|
||||||
|
node._multiplayer_refresh = true
|
||||||
|
} else {
|
||||||
|
if (node._multiplayer.users.indexOf(sessionId) === -1) {
|
||||||
|
node._multiplayer.users.push(sessionId)
|
||||||
|
node._multiplayer_refresh = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function removeUserFromNode(sessionId, nodeId) {
|
||||||
|
const node = RED.nodes.node(nodeId)
|
||||||
|
if (node && node._multiplayer) {
|
||||||
|
const i = node._multiplayer.users.indexOf(sessionId)
|
||||||
|
if (i > -1) {
|
||||||
|
node._multiplayer.users.splice(i, 1)
|
||||||
|
}
|
||||||
|
if (node._multiplayer.users.length === 0) {
|
||||||
|
delete node._multiplayer
|
||||||
|
} else {
|
||||||
|
node._multiplayer_refresh = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeUserLocation (sessionId) {
|
||||||
|
updateUserLocation(sessionId, {})
|
||||||
|
}
|
||||||
|
function updateUserLocation (sessionId, location) {
|
||||||
|
let viewTouched = false
|
||||||
|
const oldLocation = sessions[sessionId].location
|
||||||
|
if (location) {
|
||||||
|
if (oldLocation.workspace !== location.workspace) {
|
||||||
|
// console.log('removing', sessionId, oldLocation.workspace)
|
||||||
|
workspaceTrays[oldLocation.workspace]?.removeUser(sessionId)
|
||||||
|
}
|
||||||
|
if (oldLocation.node !== location.node) {
|
||||||
|
removeUserFromNode(sessionId, oldLocation.node)
|
||||||
|
viewTouched = true
|
||||||
|
}
|
||||||
|
sessions[sessionId].location = location
|
||||||
|
} else {
|
||||||
|
location = sessions[sessionId].location
|
||||||
|
}
|
||||||
|
// console.log(`updateUserLocation sessionId:${sessionId} oldWS:${oldLocation?.workspace} newWS:${location.workspace}`)
|
||||||
|
if (location.workspace) {
|
||||||
|
getWorkspaceTray(location.workspace).addUser(sessionId)
|
||||||
|
}
|
||||||
|
if (location.node) {
|
||||||
|
addUserToNode(sessionId, location.node)
|
||||||
|
viewTouched = true
|
||||||
|
}
|
||||||
|
if (viewTouched) {
|
||||||
|
RED.view.redraw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// function refreshUserLocations () {
|
||||||
|
// for (const session of Object.keys(sessions)) {
|
||||||
|
// if (session !== activeSessionId) {
|
||||||
|
// updateUserLocation(session)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: function () {
|
init: function () {
|
||||||
|
|
||||||
|
|
||||||
sessionId = RED.settings.getLocal('multiplayer:sessionId')
|
function createAnnotationUser(user) {
|
||||||
if (!sessionId) {
|
|
||||||
sessionId = RED.nodes.id()
|
const group = document.createElementNS("http://www.w3.org/2000/svg","g");
|
||||||
RED.settings.setLocal('multiplayer:sessionId', sessionId)
|
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',
|
||||||
|
align: 'left',
|
||||||
|
class: "red-ui-multiplayer-annotation",
|
||||||
|
show: "_multiplayer",
|
||||||
|
refresh: "_multiplayer_refresh",
|
||||||
|
element: function(node) {
|
||||||
|
const containerGroup = document.createElementNS("http://www.w3.org/2000/svg","g");
|
||||||
|
containerGroup.setAttribute("transform","translate(0,-4)")
|
||||||
|
if (node._multiplayer) {
|
||||||
|
let y = 0
|
||||||
|
for (let i = Math.min(1, node._multiplayer.users.length - 1); i >= 0; i--) {
|
||||||
|
const user = sessions[node._multiplayer.users[i]].user
|
||||||
|
const group = createAnnotationUser(user)
|
||||||
|
group.setAttribute("transform","translate("+y+",0)")
|
||||||
|
y += 15
|
||||||
|
containerGroup.appendChild(group)
|
||||||
|
}
|
||||||
|
if (node._multiplayer.users.length > 2) {
|
||||||
|
const group = createAnnotationUser('+'+(node._multiplayer.users.length - 2))
|
||||||
|
group.setAttribute("transform","translate("+y+",0)")
|
||||||
|
y += 12
|
||||||
|
containerGroup.appendChild(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return containerGroup;
|
||||||
|
},
|
||||||
|
tooltip: node => { return node._multiplayer.users.map(u => sessions[u].user.username).join('\n') }
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// activeSessionId = RED.settings.getLocal('multiplayer:sessionId')
|
||||||
|
// if (!activeSessionId) {
|
||||||
|
activeSessionId = RED.nodes.id()
|
||||||
|
// RED.settings.setLocal('multiplayer:sessionId', activeSessionId)
|
||||||
|
// log('Session ID (new)', activeSessionId)
|
||||||
|
// } else {
|
||||||
|
log('Session ID', activeSessionId)
|
||||||
|
// }
|
||||||
|
|
||||||
headerWidget = $('<li><ul id="red-ui-multiplayer-user-list"></ul></li>').prependTo('.red-ui-header-toolbar')
|
headerWidget = $('<li><ul id="red-ui-multiplayer-user-list"></ul></li>').prependTo('.red-ui-header-toolbar')
|
||||||
|
|
||||||
RED.comms.on('connect', () => {
|
RED.comms.on('connect', () => {
|
||||||
const location = getLocation()
|
const location = getLocation()
|
||||||
const connectInfo = {
|
const connectInfo = {
|
||||||
session: sessionId
|
session: activeSessionId
|
||||||
}
|
}
|
||||||
if (location.workspace !== 0) {
|
if (location.workspace !== 0) {
|
||||||
connectInfo.location = location
|
connectInfo.location = location
|
||||||
@ -168,40 +429,52 @@ RED.multiplayer = (function () {
|
|||||||
if (topic === 'multiplayer/init') {
|
if (topic === 'multiplayer/init') {
|
||||||
// We have just reconnected, runtime has sent state to
|
// We have just reconnected, runtime has sent state to
|
||||||
// initialise the world
|
// initialise the world
|
||||||
connections = {}
|
sessions = {}
|
||||||
users = {}
|
users = {}
|
||||||
$('#red-ui-multiplayer-user-list').empty()
|
$('#red-ui-multiplayer-user-list').empty()
|
||||||
|
|
||||||
msg.forEach(connection => {
|
msg.sessions.forEach(session => {
|
||||||
addUserConnection(connection)
|
addUserSession(session)
|
||||||
})
|
})
|
||||||
} else if (topic === 'multiplayer/connection-added') {
|
} else if (topic === 'multiplayer/connection-added') {
|
||||||
addUserConnection(msg)
|
addUserSession(msg)
|
||||||
} else if (topic === 'multiplayer/connection-removed') {
|
} else if (topic === 'multiplayer/connection-removed') {
|
||||||
removeUserConnection(msg.session, msg.disconnected)
|
removeUserSession(msg.session, msg.disconnected)
|
||||||
} else if (topic === 'multiplayer/location') {
|
} else if (topic === 'multiplayer/location') {
|
||||||
updateUserLocation(msg)
|
const session = msg.session
|
||||||
|
delete msg.session
|
||||||
|
updateUserLocation(session, msg)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
RED.events.on('workspace:change', (event) => {
|
RED.events.on('workspace:change', (event) => {
|
||||||
updateLocation()
|
getWorkspaceTray(event.workspace)
|
||||||
|
publishLocation()
|
||||||
})
|
})
|
||||||
RED.events.on('editor:open', () => {
|
RED.events.on('editor:open', () => {
|
||||||
updateLocation()
|
publishLocation()
|
||||||
})
|
})
|
||||||
RED.events.on('editor:close', () => {
|
RED.events.on('editor:close', () => {
|
||||||
updateLocation()
|
publishLocation()
|
||||||
})
|
})
|
||||||
RED.events.on('editor:change', () => {
|
RED.events.on('editor:change', () => {
|
||||||
updateLocation()
|
publishLocation()
|
||||||
})
|
})
|
||||||
RED.events.on('login', () => {
|
RED.events.on('login', () => {
|
||||||
updateLocation()
|
publishLocation()
|
||||||
|
})
|
||||||
|
RED.events.on('flows:loaded', () => {
|
||||||
|
attachWorkspaceTrays()
|
||||||
|
})
|
||||||
|
RED.events.on('workspace:close', (event) => {
|
||||||
|
// A subflow tab has been closed. Need to mark its tray as detached
|
||||||
|
if (workspaceTrays[event.workspace]) {
|
||||||
|
workspaceTrays[event.workspace].attached = false
|
||||||
|
}
|
||||||
})
|
})
|
||||||
RED.events.on('logout', () => {
|
RED.events.on('logout', () => {
|
||||||
const disconnectInfo = {
|
const disconnectInfo = {
|
||||||
session: sessionId
|
session: activeSessionId
|
||||||
}
|
}
|
||||||
RED.comms.send('multiplayer/disconnect', disconnectInfo)
|
RED.comms.send('multiplayer/disconnect', disconnectInfo)
|
||||||
RED.settings.removeLocal('multiplayer:sessionId')
|
RED.settings.removeLocal('multiplayer:sessionId')
|
||||||
|
@ -298,6 +298,7 @@ var RED = (function() {
|
|||||||
RED.workspaces.show(workspaces[0]);
|
RED.workspaces.show(workspaces[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RED.events.emit('flows:loaded')
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.warn(err);
|
console.warn(err);
|
||||||
RED.notify(
|
RED.notify(
|
||||||
|
@ -445,9 +445,12 @@ RED.popover = (function() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
create: createPopover,
|
create: createPopover,
|
||||||
tooltip: function(target,content, action) {
|
tooltip: function(target,content, action, interactive) {
|
||||||
var label = function() {
|
var label = function() {
|
||||||
var label = content;
|
var label = content;
|
||||||
|
if (typeof content === 'function') {
|
||||||
|
label = content()
|
||||||
|
}
|
||||||
if (action) {
|
if (action) {
|
||||||
var shortcut = RED.keyboard.getShortcut(action);
|
var shortcut = RED.keyboard.getShortcut(action);
|
||||||
if (shortcut && shortcut.key) {
|
if (shortcut && shortcut.key) {
|
||||||
@ -463,6 +466,7 @@ RED.popover = (function() {
|
|||||||
size: "small",
|
size: "small",
|
||||||
direction: "bottom",
|
direction: "bottom",
|
||||||
content: label,
|
content: label,
|
||||||
|
interactive,
|
||||||
delay: { show: 750, hide: 50 }
|
delay: { show: 750, hide: 50 }
|
||||||
});
|
});
|
||||||
popover.setContent = function(newContent) {
|
popover.setContent = function(newContent) {
|
||||||
|
@ -112,16 +112,23 @@ RED.deploy = (function() {
|
|||||||
RED.actions.add("core:set-deploy-type-to-modified-nodes",function() { RED.menu.setSelected("deploymenu-item-node",true); });
|
RED.actions.add("core:set-deploy-type-to-modified-nodes",function() { RED.menu.setSelected("deploymenu-item-node",true); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', function (event) {
|
||||||
|
if (RED.nodes.dirty()) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopImmediatePropagation()
|
||||||
|
event.returnValue = RED._("deploy.confirm.undeployedChanges");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
RED.events.on('workspace:dirty',function(state) {
|
RED.events.on('workspace:dirty',function(state) {
|
||||||
if (state.dirty) {
|
if (state.dirty) {
|
||||||
window.onbeforeunload = function() {
|
// window.onbeforeunload = function() {
|
||||||
return RED._("deploy.confirm.undeployedChanges");
|
// return
|
||||||
}
|
// }
|
||||||
$("#red-ui-header-button-deploy").removeClass("disabled");
|
$("#red-ui-header-button-deploy").removeClass("disabled");
|
||||||
} else {
|
} else {
|
||||||
window.onbeforeunload = null;
|
// window.onbeforeunload = null;
|
||||||
$("#red-ui-header-button-deploy").addClass("disabled");
|
$("#red-ui-header-button-deploy").addClass("disabled");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -9,14 +9,27 @@ RED.view.annotations = (function() {
|
|||||||
addAnnotation(evt.node.__pendingAnnotation__,evt);
|
addAnnotation(evt.node.__pendingAnnotation__,evt);
|
||||||
delete evt.node.__pendingAnnotation__;
|
delete evt.node.__pendingAnnotation__;
|
||||||
}
|
}
|
||||||
var badgeDX = 0;
|
let badgeRDX = 0;
|
||||||
var controlDX = 0;
|
let badgeLDX = 0;
|
||||||
for (var i=0,l=evt.el.__annotations__.length;i<l;i++) {
|
|
||||||
var annotation = evt.el.__annotations__[i];
|
for (let i=0,l=evt.el.__annotations__.length;i<l;i++) {
|
||||||
|
const annotation = evt.el.__annotations__[i];
|
||||||
if (annotations.hasOwnProperty(annotation.id)) {
|
if (annotations.hasOwnProperty(annotation.id)) {
|
||||||
var opts = annotations[annotation.id];
|
const opts = annotations[annotation.id];
|
||||||
var showAnnotation = true;
|
let showAnnotation = true;
|
||||||
var isBadge = opts.type === 'badge';
|
const isBadge = opts.type === 'badge';
|
||||||
|
if (opts.refresh !== undefined) {
|
||||||
|
let refreshAnnotation = false
|
||||||
|
if (typeof opts.refresh === "string") {
|
||||||
|
refreshAnnotation = !!evt.node[opts.refresh]
|
||||||
|
delete evt.node[opts.refresh]
|
||||||
|
} else if (typeof opts.refresh === "function") {
|
||||||
|
refreshAnnotation = opts.refresh(evnt.node)
|
||||||
|
}
|
||||||
|
if (refreshAnnotation) {
|
||||||
|
refreshAnnotationElement(annotation.id, annotation.node, annotation.element)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (opts.show !== undefined) {
|
if (opts.show !== undefined) {
|
||||||
if (typeof opts.show === "string") {
|
if (typeof opts.show === "string") {
|
||||||
showAnnotation = !!evt.node[opts.show]
|
showAnnotation = !!evt.node[opts.show]
|
||||||
@ -29,17 +42,24 @@ RED.view.annotations = (function() {
|
|||||||
}
|
}
|
||||||
if (isBadge) {
|
if (isBadge) {
|
||||||
if (showAnnotation) {
|
if (showAnnotation) {
|
||||||
var rect = annotation.element.getBoundingClientRect();
|
const rect = annotation.element.getBoundingClientRect();
|
||||||
badgeDX += rect.width;
|
let annotationX
|
||||||
annotation.element.setAttribute("transform", "translate("+(evt.node.w-3-badgeDX)+", -8)");
|
if (!opts.align || opts.align === 'right') {
|
||||||
badgeDX += 4;
|
annotationX = evt.node.w - 3 - badgeRDX - rect.width
|
||||||
}
|
badgeRDX += rect.width + 4;
|
||||||
} else {
|
|
||||||
if (showAnnotation) {
|
} else if (opts.align === 'left') {
|
||||||
var rect = annotation.element.getBoundingClientRect();
|
annotationX = 3 + badgeLDX
|
||||||
annotation.element.setAttribute("transform", "translate("+(3+controlDX)+", -12)");
|
badgeLDX += rect.width + 4;
|
||||||
controlDX += rect.width + 4;
|
}
|
||||||
|
annotation.element.setAttribute("transform", "translate("+annotationX+", -8)");
|
||||||
}
|
}
|
||||||
|
// } else {
|
||||||
|
// if (showAnnotation) {
|
||||||
|
// var rect = annotation.element.getBoundingClientRect();
|
||||||
|
// annotation.element.setAttribute("transform", "translate("+(3+controlDX)+", -12)");
|
||||||
|
// controlDX += rect.width + 4;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
annotation.element.parentNode.removeChild(annotation.element);
|
annotation.element.parentNode.removeChild(annotation.element);
|
||||||
@ -95,15 +115,25 @@ RED.view.annotations = (function() {
|
|||||||
annotationGroup.setAttribute("class",opts.class || "");
|
annotationGroup.setAttribute("class",opts.class || "");
|
||||||
evt.el.__annotations__.push({
|
evt.el.__annotations__.push({
|
||||||
id:id,
|
id:id,
|
||||||
|
node: evt.node,
|
||||||
element: annotationGroup
|
element: annotationGroup
|
||||||
});
|
});
|
||||||
var annotation = opts.element(evt.node);
|
refreshAnnotationElement(id, evt.node, annotationGroup)
|
||||||
|
evt.el.appendChild(annotationGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshAnnotationElement(id, node, annotationGroup) {
|
||||||
|
const opts = annotations[id];
|
||||||
|
const annotation = opts.element(node);
|
||||||
if (opts.tooltip) {
|
if (opts.tooltip) {
|
||||||
annotation.addEventListener("mouseenter", getAnnotationMouseEnter(annotation,evt.node,opts.tooltip));
|
annotation.addEventListener("mouseenter", getAnnotationMouseEnter(annotation, node, opts.tooltip));
|
||||||
annotation.addEventListener("mouseleave", annotationMouseLeave);
|
annotation.addEventListener("mouseleave", annotationMouseLeave);
|
||||||
}
|
}
|
||||||
|
if (annotationGroup.hasChildNodes()) {
|
||||||
|
annotationGroup.removeChild(annotationGroup.firstChild)
|
||||||
|
}
|
||||||
annotationGroup.appendChild(annotation);
|
annotationGroup.appendChild(annotation);
|
||||||
evt.el.appendChild(annotationGroup);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -401,6 +401,7 @@ RED.workspaces = (function() {
|
|||||||
if (tab.type === "tab") {
|
if (tab.type === "tab") {
|
||||||
workspaceTabCount--;
|
workspaceTabCount--;
|
||||||
} else {
|
} else {
|
||||||
|
RED.events.emit("workspace:close",{workspace: tab.id})
|
||||||
hideStack.push(tab.id);
|
hideStack.push(tab.id);
|
||||||
}
|
}
|
||||||
RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1);
|
RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1);
|
||||||
|
@ -212,6 +212,8 @@ RED.user = (function() {
|
|||||||
|
|
||||||
function updateUserMenu() {
|
function updateUserMenu() {
|
||||||
$("#red-ui-header-button-user-submenu li").remove();
|
$("#red-ui-header-button-user-submenu li").remove();
|
||||||
|
const userMenu = $("#red-ui-header-button-user")
|
||||||
|
userMenu.empty()
|
||||||
if (RED.settings.user.anonymous) {
|
if (RED.settings.user.anonymous) {
|
||||||
RED.menu.addItem("red-ui-header-button-user",{
|
RED.menu.addItem("red-ui-header-button-user",{
|
||||||
id:"usermenu-item-login",
|
id:"usermenu-item-login",
|
||||||
@ -226,7 +228,6 @@ RED.user = (function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$('<i class="fa fa-user"></i>').appendTo("#red-ui-header-button-user");
|
|
||||||
} else {
|
} else {
|
||||||
RED.menu.addItem("red-ui-header-button-user",{
|
RED.menu.addItem("red-ui-header-button-user",{
|
||||||
id:"usermenu-item-username",
|
id:"usermenu-item-username",
|
||||||
@ -239,17 +240,9 @@ RED.user = (function() {
|
|||||||
RED.user.logout();
|
RED.user.logout();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const userMenu = $("#red-ui-header-button-user")
|
|
||||||
userMenu.empty()
|
|
||||||
if (RED.settings.user.image) {
|
|
||||||
$('<span class="user-profile"></span>').css({
|
|
||||||
backgroundImage: "url("+RED.settings.user.image+")",
|
|
||||||
}).appendTo(userMenu);
|
|
||||||
} else {
|
|
||||||
$('<i class="fa fa-user"></i>').appendTo(userMenu);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const userIcon = generateUserIcon(RED.settings.user)
|
||||||
|
userIcon.appendTo(userMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
@ -320,12 +313,30 @@ RED.user = (function() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateUserIcon(user) {
|
||||||
|
const userIcon = $('<span class="red-ui-user-profile"></span>')
|
||||||
|
if (user.image) {
|
||||||
|
userIcon.addClass('has_profile_image')
|
||||||
|
userIcon.css({
|
||||||
|
backgroundImage: "url("+user.image+")",
|
||||||
|
})
|
||||||
|
} else if (user.anonymous) {
|
||||||
|
$('<i class="fa fa-user"></i>').appendTo(userIcon);
|
||||||
|
} else {
|
||||||
|
$('<span>').text(user.username.substring(0,2)).appendTo(userIcon);
|
||||||
|
}
|
||||||
|
if (user.profileColor !== undefined) {
|
||||||
|
userIcon.addClass('red-ui-user-profile-color-' + user.profileColor)
|
||||||
|
}
|
||||||
|
return userIcon
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
login: login,
|
login: login,
|
||||||
logout: logout,
|
logout: logout,
|
||||||
hasPermission: hasPermission
|
hasPermission: hasPermission,
|
||||||
|
generateUserIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
@ -314,6 +314,16 @@ $spinner-color: #999;
|
|||||||
|
|
||||||
$tab-icon-color: #dedede;
|
$tab-icon-color: #dedede;
|
||||||
|
|
||||||
|
// Anonymous User Colors
|
||||||
|
|
||||||
|
$user-profile-colors: (
|
||||||
|
1: #822e81,
|
||||||
|
2: #955e42,
|
||||||
|
3: #9c914f,
|
||||||
|
4: #748e54,
|
||||||
|
5: #06bcc1
|
||||||
|
);
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
$text-color-green: $text-color-success;
|
$text-color-green: $text-color-success;
|
||||||
$info-text-code-color: $text-color-code;
|
$info-text-code-color: $text-color-code;
|
||||||
|
@ -274,18 +274,44 @@
|
|||||||
#usermenu-item-username > .red-ui-menu-label {
|
#usermenu-item-username > .red-ui-menu-label {
|
||||||
color: var(--red-ui-header-menu-heading-color);
|
color: var(--red-ui-header-menu-heading-color);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.user-profile {
|
|
||||||
background-position: center center;
|
.red-ui-user-profile {
|
||||||
background-repeat: no-repeat;
|
background-color: var(--red-ui-header-background);
|
||||||
background-size: contain;
|
border: 2px solid var(--red-ui-header-menu-color);
|
||||||
display: inline-block;
|
border-radius: 30px;
|
||||||
width: 30px;
|
overflow: hidden;
|
||||||
height: 30px;
|
|
||||||
vertical-align: middle;
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
font-size: 20px;
|
||||||
|
|
||||||
|
&.red-ui-user-profile-color-1 {
|
||||||
|
background-color: var(--red-ui-user-profile-colors-1);
|
||||||
|
}
|
||||||
|
&.red-ui-user-profile-color-2 {
|
||||||
|
background-color: var(--red-ui-user-profile-colors-2);
|
||||||
|
}
|
||||||
|
&.red-ui-user-profile-color-3 {
|
||||||
|
background-color: var(--red-ui-user-profile-colors-3);
|
||||||
|
}
|
||||||
|
&.red-ui-user-profile-color-4 {
|
||||||
|
background-color: var(--red-ui-user-profile-colors-4);
|
||||||
|
}
|
||||||
|
&.red-ui-user-profile-color-5 {
|
||||||
|
background-color: var(--red-ui-user-profile-colors-5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@media only screen and (max-width: 450px) {
|
@media only screen and (max-width: 450px) {
|
||||||
span.red-ui-header-logo > span {
|
span.red-ui-header-logo > span {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -5,23 +5,18 @@
|
|||||||
li {
|
li {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 30px;
|
|
||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.red-ui-multiplayer-user-icon {
|
.red-ui-multiplayer-user-icon {
|
||||||
background: var(--red-ui-header-background);
|
background: none;
|
||||||
border: 2px solid var(--red-ui-header-menu-color);
|
border: none;
|
||||||
border-radius: 30px;
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
overflow: hidden;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--red-ui-header-menu-color);
|
color: var(--red-ui-header-menu-color);
|
||||||
@ -36,13 +31,86 @@
|
|||||||
.red-ui-multiplayer-user.inactive & {
|
.red-ui-multiplayer-user.inactive & {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
.user-profile {
|
.red-ui-user-profile {
|
||||||
background-position: center center;
|
width: 20px;
|
||||||
background-repeat: no-repeat;
|
border-radius: 20px;
|
||||||
background-size: contain;
|
height: 20px;
|
||||||
display: inline-block;
|
font-size: 12px
|
||||||
vertical-align: middle;
|
}
|
||||||
width: 28px;
|
}
|
||||||
height: 28px;
|
.red-ui-multiplayer-users-tray {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 20px;
|
||||||
|
line-height: normal;
|
||||||
|
cursor: pointer;
|
||||||
|
// &:hover {
|
||||||
|
// .red-ui-multiplayer-user-location {
|
||||||
|
// margin-left: 1px;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
$multiplayer-user-icon-background: var(--red-ui-primary-background);
|
||||||
|
$multiplayer-user-icon-border: var(--red-ui-view-background);
|
||||||
|
$multiplayer-user-icon-text-color: var(--red-ui-header-menu-color);
|
||||||
|
$multiplayer-user-icon-count-text-color: var(--red-ui-primary-color);
|
||||||
|
$multiplayer-user-icon-shadow: 0px 0px 4px var(--red-ui-shadow);
|
||||||
|
.red-ui-multiplayer-user-location {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: -6px;
|
||||||
|
transition: margin-left 0.2s;
|
||||||
|
.red-ui-user-profile {
|
||||||
|
border: 1px solid $multiplayer-user-icon-border;
|
||||||
|
color: $multiplayer-user-icon-text-color;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 18px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: normal;
|
||||||
|
box-shadow: $multiplayer-user-icon-shadow;
|
||||||
|
&.red-ui-multiplayer-user-count {
|
||||||
|
color: $multiplayer-user-icon-count-text-color;
|
||||||
|
background-color: $multiplayer-user-icon-background;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-ui-multiplayer-annotation {
|
||||||
|
.red-ui-multiplayer-annotation-background {
|
||||||
|
filter: drop-shadow($multiplayer-user-icon-shadow);
|
||||||
|
fill: $multiplayer-user-icon-background;
|
||||||
|
&.red-ui-user-profile-color-1 {
|
||||||
|
fill: var(--red-ui-user-profile-colors-1);
|
||||||
|
}
|
||||||
|
&.red-ui-user-profile-color-2 {
|
||||||
|
fill: var(--red-ui-user-profile-colors-2);
|
||||||
|
}
|
||||||
|
&.red-ui-user-profile-color-3 {
|
||||||
|
fill: var(--red-ui-user-profile-colors-3);
|
||||||
|
}
|
||||||
|
&.red-ui-user-profile-color-4 {
|
||||||
|
fill: var(--red-ui-user-profile-colors-4);
|
||||||
|
}
|
||||||
|
&.red-ui-user-profile-color-5 {
|
||||||
|
fill: var(--red-ui-user-profile-colors-5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.red-ui-multiplayer-annotation-border {
|
||||||
|
stroke: $multiplayer-user-icon-border;
|
||||||
|
stroke-width: 1px;
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
.red-ui-multiplayer-annotation-anon-label {
|
||||||
|
fill: $multiplayer-user-icon-text-color;
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
text {
|
||||||
|
user-select: none;
|
||||||
|
fill: $multiplayer-user-icon-text-color;
|
||||||
|
stroke: none;
|
||||||
|
font-size: 10px;
|
||||||
|
&.red-ui-multiplayer-user-count {
|
||||||
|
fill: $multiplayer-user-icon-count-text-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -299,4 +299,7 @@
|
|||||||
|
|
||||||
--red-ui-tab-icon-color: #{$tab-icon-color};
|
--red-ui-tab-icon-color: #{$tab-icon-color};
|
||||||
|
|
||||||
|
@each $current-color in 1 2 3 4 5 {
|
||||||
|
--red-ui-user-profile-colors-#{"" + $current-color}: #{map-get($user-profile-colors, $current-color)};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ module.exports = {
|
|||||||
// Send init info to new connection
|
// Send init info to new connection
|
||||||
const initPacket = {
|
const initPacket = {
|
||||||
topic: "multiplayer/init",
|
topic: "multiplayer/init",
|
||||||
data: getSessionsList(),
|
data: { sessions: getSessionsList() },
|
||||||
session: opts.session
|
session: opts.session
|
||||||
}
|
}
|
||||||
// console.log('<<', initPacket)
|
// console.log('<<', initPacket)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user