@datalayer/core
Version:
**Datalayer Core**
101 lines (100 loc) • 4.82 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
/*
* Copyright (c) 2023-2025 Datalayer, Inc.
* Distributed under the terms of the Modified BSD License.
*/
import { useEffect, useState } from 'react';
import { Avatar, AvatarStack } from '@primer/react';
import { getAvatarURL, getRelativeTime } from '../../utils';
const AVATAR_SIZE = 28;
const SPACER_USER_AGENT = 'DatalayerSpacer';
const AWARENESS_NOTIFICATION_TIMEOUT_MS = 30_000;
var NotificationType;
(function (NotificationType) {
NotificationType["PROGRESS"] = "var(--borderColor-accent-emphasis)";
NotificationType["ERROR"] = "var(--borderColor-danger-emphasis)";
NotificationType["SUCCESS"] = "var(--borderColor-success-emphasis)";
})(NotificationType || (NotificationType = {}));
const MSG_TO_NOTIFICATION = {
'-1': NotificationType.ERROR,
'0': NotificationType.PROGRESS,
'1': NotificationType.SUCCESS
};
function isExpired(timestamp) {
// Timestamp is expired if older than 10 seconds ago
return timestamp + AWARENESS_NOTIFICATION_TIMEOUT_MS < Date.now();
}
function defaultAvatarSrc(color, text) {
const svg = `<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="12" fill="${color}" stroke="none" /><text x="12" y="12" fill="black">${text}</text></svg>`;
return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
}
/**
* Connected peers indicator
*/
export function PeersIndicator({ awareness, currentUserHandle }) {
const [peers, setPeers] = useState([]);
useEffect(() => {
const onAwarenessChange = () => {
const peers = awareness.getStates();
setPeers(Array.from(peers.values())
.filter(p => p.user &&
p.user?.name !== currentUserHandle &&
!(p.user?.agent ?? '').startsWith(SPACER_USER_AGENT))
.reduce((agg, peer) => {
const user = peer.user;
const notExpiredNotification = !isExpired(peer.notification?.timestamp ?? 0);
const newPeer = user.agent
? {
// TODO pick something better
avatarUrl: 'https://static.vecteezy.com/system/resources/previews/006/662/139/large_2x/artificial-intelligence-ai-processor-chip-icon-symbol-for-graphic-design-logo-web-site-social-media-mobile-app-ui-illustration-free-vector.jpg',
color: user.color,
displayName: user.agent,
handle: 'agent',
initials: 'AI',
username: user.agent,
notification: notExpiredNotification
? `${peer.notification?.message} ${getRelativeTime(new Date(peer.notification.timestamp))}`
: undefined,
notificationType: notExpiredNotification &&
peer.notification?.message_type !== undefined
? MSG_TO_NOTIFICATION[peer.notification?.message_type.toString()]
: undefined
}
: {
avatarUrl: user.avatar_url,
color: user.color,
displayName: user.display_name,
handle: user.name,
initials: user.initials,
username: user.username
};
// Ensure to display a peer only once
if (!agg.map(p => p.username).includes(newPeer.username)) {
agg.push(newPeer);
}
return agg;
}, []));
};
onAwarenessChange();
awareness.on('change', onAwarenessChange);
// Force regular update to update agent notification
const updateInterval = setInterval(onAwarenessChange, AWARENESS_NOTIFICATION_TIMEOUT_MS / 3);
return () => {
clearInterval(updateInterval);
awareness.off('change', onAwarenessChange);
};
}, [awareness, currentUserHandle]);
return (_jsx(AvatarStack, { children: peers.map(peer => {
let title = `${peer.displayName}\n${peer.handle}`;
if (peer.notification) {
title += `\n${peer.notification}`;
}
return (_jsx(Avatar, { sx: {
border: peer.notificationType
? `var(--borderWidth-thick) solid ${peer.notificationType}`
: 'none'
}, title: title, src: peer.avatarUrl
? getAvatarURL(peer.avatarUrl)
: defaultAvatarSrc(peer.color, peer.initials), size: AVATAR_SIZE }, peer.displayName));
}) }));
}