diff --git a/packages/node_modules/@node-red/editor-client/src/js/multiplayer.js b/packages/node_modules/@node-red/editor-client/src/js/multiplayer.js
index 926a118a9..576541a4d 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/multiplayer.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/multiplayer.js
@@ -1,114 +1,92 @@
RED.multiplayer = (function () {
- // sessionId - used to identify sessions across websocket reconnects
- let sessionId
+ // activeSessionId - used to identify sessions across websocket reconnects
+ let activeSessionId
let headerWidget
// Map of session id to { session:'', user:{}, location:{}}
- let connections = {}
- // Map of username to { user:{}, connections:[] }
+ let sessions = {}
+ // Map of username to { user:{}, sessions:[] }
let users = {}
- function addUserConnection (connection) {
- if (connections[connection.session]) {
+ function addUserSession (session) {
+ if (sessions[session.session]) {
// This is an existing connection that has been authenticated
- const existingConnection = connections[connection.session]
- if (existingConnection.user.username !== connection.user.username) {
- removeUserButton(users[existingConnection.user.username])
+ const existingSession = sessions[session.session]
+ if (existingSession.user.username !== session.user.username) {
+ removeUserHeaderButton(users[existingSession.user.username])
}
}
- connections[connection.session] = connection
- const user = users[connection.user.username] = users[connection.user.username] || {
- user: connection.user,
- connections: []
+ sessions[session.session] = session
+ const user = users[session.user.username] = users[session.user.username] || {
+ user: session.user,
+ sessions: []
}
- connection.location = connection.location || {}
- user.connections.push(connection)
+ if (session.user.profileColor === undefined) {
+ 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 ||
- connection.session === sessionId
- ) {
- // This is the current user - do not add a extra button for them
+ if (session.session === activeSessionId) {
+ // This is the current user session - do not add a extra button for them
} else {
- if (user.connections.length === 1) {
+ if (user.sessions.length === 1) {
if (user.button) {
clearTimeout(user.inactiveTimeout)
clearTimeout(user.removeTimeout)
user.button.removeClass('inactive')
} else {
- addUserButton(user)
+ addUserHeaderButton(user)
}
}
+ sessions[session.session].location = session.location
+ updateUserLocation(session.session)
}
}
- function removeUserConnection (session, isDisconnected) {
- const connection = connections[session]
- delete connections[session]
- const user = users[connection.user.username]
- const i = user.connections.indexOf(connection)
- user.connections.splice(i, 1)
+ function removeUserSession (sessionId, isDisconnected) {
+ removeUserLocation(sessionId)
+ const session = sessions[sessionId]
+ delete sessions[sessionId]
+ const user = users[session.user.username]
+ const i = user.sessions.indexOf(session)
+ user.sessions.splice(i, 1)
if (isDisconnected) {
- removeUserButton(user)
+ removeUserHeaderButton(user)
} else {
- if (user.connections.length === 0) {
+ if (user.sessions.length === 0) {
// Give the user 5s to reconnect before marking inactive
user.inactiveTimeout = setTimeout(() => {
user.button.addClass('inactive')
// Give the user further 20 seconds to reconnect before removing them
// from the user toolbar entirely
user.removeTimeout = setTimeout(() => {
- removeUserButton(user)
+ removeUserHeaderButton(user)
}, 20000)
}, 5000)
}
}
}
- function addUserButton (user) {
- user.button = $('
')
+ function addUserHeaderButton (user) {
+ user.button = $('')
.attr('data-username', user.user.username)
.prependTo("#red-ui-multiplayer-user-list");
var button = user.button.find("button")
+ RED.popover.tooltip(button, user.user.username)
button.on('click', function () {
- RED.popover.create({
- target:button,
- trigger: 'modal',
- interactive: true,
- width: "250px",
- direction: 'bottom',
- content: () => {
- const content = $('')
- $('
').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) {
- $('
').text(`${ws.type}: ${ws.label||ws.name||ws.id}`).appendTo(content)
- } else {
- $('
').text(`tab: unknown`).appendTo(content)
- }
- }
- if (location.node) {
- const node = RED.nodes.node(location.node)
- if (node) {
- $('
').text(`node: ${node.id}`).appendTo(content)
- } else {
- $('
').text(`node: unknown`).appendTo(content)
- }
- }
- return content
- },
- }).open()
+ const location = user.sessions[0].location
+ revealUser(location)
})
- if (!user.user.image) {
- $('
').appendTo(button);
- } else {
- $('
').css({
- backgroundImage: "url("+user.user.image+")",
- }).appendTo(button);
- }
+
+ const userProfile = RED.user.generateUserIcon(user.user)
+ userProfile.appendTo(button)
+ }
+
+ function removeUserHeaderButton (user) {
+ user.button.remove()
+ delete user.button
}
function getLocation () {
@@ -124,7 +102,7 @@ RED.multiplayer = (function () {
}
return location
}
- function updateLocation () {
+ function publishLocation () {
const location = getLocation()
if (location.workspace !== 0) {
log('send', 'multiplayer/location', location)
@@ -132,31 +110,314 @@ RED.multiplayer = (function () {
}
}
- function removeUserButton (user) {
- user.button.remove()
- delete user.button
+ function revealUser(location, skipWorkspace) {
+ if (location.node) {
+ // 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) {
- connections[data.session].location = data
- delete data.session
+ const workspaceTrays = {}
+ function getWorkspaceTray(workspaceId) {
+ // console.log('get tray for',workspaceId)
+ if (!workspaceTrays[workspaceId]) {
+ const tray = $('
')
+ const users = []
+ const userIcons = {}
+
+ const userCountIcon = $(`
`)
+ const userCountSpan = userCountIcon.find('span span')
+ userCountIcon.hide()
+ userCountSpan.text('')
+ userCountIcon.appendTo(tray)
+ const userCountTooltip = RED.popover.tooltip(userCountIcon, function () {
+ const content = $('
')
+ users.forEach(sessionId => {
+ $('
').append($('
').text(sessions[sessionId].user.username).on('click', function (evt) {
+ evt.preventDefault()
+ revealUser(sessions[sessionId].location, true)
+ userCountTooltip.close()
+ })).appendTo(content)
+ })
+ return content
+ },
+ null,
+ true
+ )
+
+ function updateUserCount () {
+ 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 = $(``)
+ 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 {
init: function () {
-
- sessionId = RED.settings.getLocal('multiplayer:sessionId')
- if (!sessionId) {
- sessionId = RED.nodes.id()
- RED.settings.setLocal('multiplayer:sessionId', sessionId)
+ 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',
+ 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 = $('').prependTo('.red-ui-header-toolbar')
RED.comms.on('connect', () => {
const location = getLocation()
const connectInfo = {
- session: sessionId
+ session: activeSessionId
}
if (location.workspace !== 0) {
connectInfo.location = location
@@ -168,40 +429,52 @@ RED.multiplayer = (function () {
if (topic === 'multiplayer/init') {
// We have just reconnected, runtime has sent state to
// initialise the world
- connections = {}
+ sessions = {}
users = {}
$('#red-ui-multiplayer-user-list').empty()
- msg.forEach(connection => {
- addUserConnection(connection)
+ msg.sessions.forEach(session => {
+ addUserSession(session)
})
} else if (topic === 'multiplayer/connection-added') {
- addUserConnection(msg)
+ addUserSession(msg)
} else if (topic === 'multiplayer/connection-removed') {
- removeUserConnection(msg.session, msg.disconnected)
+ removeUserSession(msg.session, msg.disconnected)
} else if (topic === 'multiplayer/location') {
- updateUserLocation(msg)
+ const session = msg.session
+ delete msg.session
+ updateUserLocation(session, msg)
}
})
RED.events.on('workspace:change', (event) => {
- updateLocation()
+ getWorkspaceTray(event.workspace)
+ publishLocation()
})
RED.events.on('editor:open', () => {
- updateLocation()
+ publishLocation()
})
RED.events.on('editor:close', () => {
- updateLocation()
+ publishLocation()
})
RED.events.on('editor:change', () => {
- updateLocation()
+ publishLocation()
})
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', () => {
const disconnectInfo = {
- session: sessionId
+ session: activeSessionId
}
RED.comms.send('multiplayer/disconnect', disconnectInfo)
RED.settings.removeLocal('multiplayer:sessionId')
diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js
index f377e7349..f8130a686 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/red.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/red.js
@@ -298,6 +298,7 @@ var RED = (function() {
RED.workspaces.show(workspaces[0]);
}
}
+ RED.events.emit('flows:loaded')
} catch(err) {
console.warn(err);
RED.notify(
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js
index 1a70839ae..381bb9d3a 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js
@@ -445,9 +445,12 @@ RED.popover = (function() {
return {
create: createPopover,
- tooltip: function(target,content, action) {
+ tooltip: function(target,content, action, interactive) {
var label = function() {
var label = content;
+ if (typeof content === 'function') {
+ label = content()
+ }
if (action) {
var shortcut = RED.keyboard.getShortcut(action);
if (shortcut && shortcut.key) {
@@ -463,6 +466,7 @@ RED.popover = (function() {
size: "small",
direction: "bottom",
content: label,
+ interactive,
delay: { show: 750, hide: 50 }
});
popover.setContent = function(newContent) {
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js
index a09fdeb01..83c04d775 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js
@@ -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); });
}
-
+ 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) {
if (state.dirty) {
- window.onbeforeunload = function() {
- return RED._("deploy.confirm.undeployedChanges");
- }
+ // window.onbeforeunload = function() {
+ // return
+ // }
$("#red-ui-header-button-deploy").removeClass("disabled");
} else {
- window.onbeforeunload = null;
+ // window.onbeforeunload = null;
$("#red-ui-header-button-deploy").addClass("disabled");
}
});
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js
index 1df8c4bd1..b08df80ae 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js
@@ -9,14 +9,27 @@ RED.view.annotations = (function() {
addAnnotation(evt.node.__pendingAnnotation__,evt);
delete evt.node.__pendingAnnotation__;
}
- var badgeDX = 0;
- var controlDX = 0;
- for (var i=0,l=evt.el.__annotations__.length;i').appendTo("#red-ui-header-button-user");
} else {
RED.menu.addItem("red-ui-header-button-user",{
id:"usermenu-item-username",
@@ -239,17 +240,9 @@ RED.user = (function() {
RED.user.logout();
}
});
- const userMenu = $("#red-ui-header-button-user")
- userMenu.empty()
- if (RED.settings.user.image) {
- $('').css({
- backgroundImage: "url("+RED.settings.user.image+")",
- }).appendTo(userMenu);
- } else {
- $('').appendTo(userMenu);
- }
}
-
+ const userIcon = generateUserIcon(RED.settings.user)
+ userIcon.appendTo(userMenu);
}
function init() {
@@ -320,12 +313,30 @@ RED.user = (function() {
return false;
}
+ function generateUserIcon(user) {
+ const userIcon = $('')
+ if (user.image) {
+ userIcon.addClass('has_profile_image')
+ userIcon.css({
+ backgroundImage: "url("+user.image+")",
+ })
+ } else if (user.anonymous) {
+ $('').appendTo(userIcon);
+ } else {
+ $('').text(user.username.substring(0,2)).appendTo(userIcon);
+ }
+ if (user.profileColor !== undefined) {
+ userIcon.addClass('red-ui-user-profile-color-' + user.profileColor)
+ }
+ return userIcon
+ }
return {
init: init,
login: login,
logout: logout,
- hasPermission: hasPermission
+ hasPermission: hasPermission,
+ generateUserIcon
}
})();
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss
index b561fde16..f0a69e00a 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss
@@ -314,6 +314,16 @@ $spinner-color: #999;
$tab-icon-color: #dedede;
+// Anonymous User Colors
+
+$user-profile-colors: (
+ 1: #822e81,
+ 2: #955e42,
+ 3: #9c914f,
+ 4: #748e54,
+ 5: #06bcc1
+);
+
// Deprecated
$text-color-green: $text-color-success;
$info-text-code-color: $text-color-code;
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/header.scss b/packages/node_modules/@node-red/editor-client/src/sass/header.scss
index 5ce9e877d..b8db40f81 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/header.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/header.scss
@@ -274,18 +274,44 @@
#usermenu-item-username > .red-ui-menu-label {
color: var(--red-ui-header-menu-heading-color);
}
+}
- .user-profile {
- background-position: center center;
- background-repeat: no-repeat;
- background-size: contain;
- display: inline-block;
- width: 30px;
- height: 30px;
- vertical-align: middle;
+
+.red-ui-user-profile {
+ background-color: var(--red-ui-header-background);
+ border: 2px solid var(--red-ui-header-menu-color);
+ border-radius: 30px;
+ overflow: hidden;
+
+ 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) {
span.red-ui-header-logo > span {
display: none;
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/multiplayer.scss b/packages/node_modules/@node-red/editor-client/src/sass/multiplayer.scss
index 58fb9472f..4aaab86b1 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/multiplayer.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/multiplayer.scss
@@ -5,23 +5,18 @@
li {
display: inline-flex;
align-items: center;
- width: 30px;
margin: 0 2px;
}
}
.red-ui-multiplayer-user-icon {
- background: var(--red-ui-header-background);
- border: 2px solid var(--red-ui-header-menu-color);
- border-radius: 30px;
+ background: none;
+ border: none;
display: inline-flex;
justify-content: center;
align-items: center;
- width: 28px;
- height: 28px;
text-align: center;
- overflow: hidden;
box-sizing: border-box;
text-decoration: none;
color: var(--red-ui-header-menu-color);
@@ -36,13 +31,86 @@
.red-ui-multiplayer-user.inactive & {
opacity: 0.5;
}
- .user-profile {
- background-position: center center;
- background-repeat: no-repeat;
- background-size: contain;
- display: inline-block;
- vertical-align: middle;
- width: 28px;
- height: 28px;
+ .red-ui-user-profile {
+ width: 20px;
+ border-radius: 20px;
+ height: 20px;
+ font-size: 12px
+ }
+}
+.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;
+ }
}
}
\ No newline at end of file
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/variables.scss b/packages/node_modules/@node-red/editor-client/src/sass/variables.scss
index c04c26ff9..bc8f9da17 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/variables.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/variables.scss
@@ -299,4 +299,7 @@
--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)};
+ }
}
diff --git a/packages/node_modules/@node-red/runtime/lib/multiplayer/index.js b/packages/node_modules/@node-red/runtime/lib/multiplayer/index.js
index a4108e51f..adfa63c28 100644
--- a/packages/node_modules/@node-red/runtime/lib/multiplayer/index.js
+++ b/packages/node_modules/@node-red/runtime/lib/multiplayer/index.js
@@ -69,7 +69,7 @@ module.exports = {
// Send init info to new connection
const initPacket = {
topic: "multiplayer/init",
- data: getSessionsList(),
+ data: { sessions: getSessionsList() },
session: opts.session
}
// console.log('<<', initPacket)