mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
restyling andalytics
This commit is contained in:
@@ -63,6 +63,7 @@ export function trackAnalyticsEvent(name, payload = {}) {
|
||||
if (!eventName) return;
|
||||
const adapter = getAdapter();
|
||||
if (!adapter || typeof adapter.track !== 'function') return;
|
||||
const cleanPayload = normalizePayload(payload);
|
||||
|
||||
try {
|
||||
/*
|
||||
@@ -70,7 +71,16 @@ export function trackAnalyticsEvent(name, payload = {}) {
|
||||
broken or blocked third-party script from interfering with rover driving,
|
||||
scanner input, chat, or any other live-control UI.
|
||||
*/
|
||||
adapter.track(eventName, normalizePayload(payload));
|
||||
/*
|
||||
Umami accepts an event name without a data object. Avoid forwarding `{}` so
|
||||
events that only need counting do not create noisy empty-property requests
|
||||
in the dashboard or the network payload.
|
||||
*/
|
||||
if (Object.keys(cleanPayload).length) {
|
||||
adapter.track(eventName, cleanPayload);
|
||||
} else {
|
||||
adapter.track(eventName);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Analytics event failed', error);
|
||||
}
|
||||
|
||||
@@ -47,10 +47,11 @@ function getGameTheme(themeColor) {
|
||||
selectedButtonStyle: {
|
||||
borderColor: rgba(rgb, 0.86),
|
||||
borderLeftColor: rgba(rgb, 1),
|
||||
backgroundColor: rgba(rgb, 0.16),
|
||||
backgroundColor: rgba(rgb, 0.14),
|
||||
},
|
||||
titleBoxStyle: {
|
||||
borderLeftColor: rgba(rgb, 0.88),
|
||||
backgroundColor: rgba(rgb, 0.06),
|
||||
},
|
||||
boxStyle: {
|
||||
borderColor: rgba(rgb, 0.62),
|
||||
@@ -88,7 +89,7 @@ function GameChoice({ game, disabled, onVote }) {
|
||||
type="button"
|
||||
disabled={disabled || game.active}
|
||||
onClick={() => onVote(game.id)}
|
||||
className="button-dark flex min-h-[4.25rem] flex-col items-start justify-between border-l-4 px-1.5 py-1 text-left disabled:opacity-70"
|
||||
className="button-dark flex min-h-[3.5rem] flex-col items-start justify-between border-l-4 border-neutral-600 bg-neutral-800 px-1 py-0.75 text-left hover:bg-neutral-700 disabled:opacity-70"
|
||||
style={style}
|
||||
>
|
||||
<span className="flex w-full items-start justify-between gap-1">
|
||||
@@ -106,9 +107,9 @@ function GameChoice({ game, disabled, onVote }) {
|
||||
function StatGrid({ stats, theme }) {
|
||||
if (!Array.isArray(stats) || !stats.length) return null;
|
||||
return (
|
||||
<div className="grid gap-1 sm:grid-cols-3">
|
||||
<div className="grid gap-0.5 sm:grid-cols-3">
|
||||
{stats.slice(0, 6).map((stat) => (
|
||||
<div key={stat.label} className="border border-neutral-700 px-1.5 py-1" style={theme.boxStyle}>
|
||||
<div key={stat.label} className="surface border border-neutral-700 px-1 py-0.75" style={theme.boxStyle}>
|
||||
<p className="truncate text-[0.68rem] text-neutral-400">{stat.label}</p>
|
||||
<p className="truncate text-sm font-semibold text-neutral-100">{stat.value}</p>
|
||||
</div>
|
||||
@@ -127,9 +128,9 @@ function ParticipantsBlock({ participants, theme }) {
|
||||
.filter(Boolean);
|
||||
|
||||
return (
|
||||
<div className="min-w-0 border border-neutral-700 px-2 py-1.5" style={theme.boxStyle}>
|
||||
<div className="surface min-w-0 border border-neutral-700 px-1 py-0.75" style={theme.boxStyle}>
|
||||
<p className="text-sm font-semibold text-neutral-400">Participants</p>
|
||||
<p className="font-mono text-2xl font-semibold leading-tight text-neutral-50">
|
||||
<p className="font-mono text-xl font-semibold leading-tight text-neutral-50">
|
||||
{knownParticipants.length}
|
||||
</p>
|
||||
<p className="truncate text-sm leading-tight text-neutral-300">
|
||||
@@ -160,15 +161,15 @@ function Leaderboard({ players, ownPlayer }) {
|
||||
const hasOwnPoints = ownPlayer && Number.isFinite(ownPlayer.totalPoints);
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<div className="grid gap-1 sm:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]">
|
||||
<div className="border border-neutral-700 px-1.5 py-1">
|
||||
<div className="grid gap-0.5 sm:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]">
|
||||
<div className="surface border border-neutral-700 px-1 py-0.75">
|
||||
<p className="text-xs text-neutral-400">Your points</p>
|
||||
<p className="text-lg font-semibold leading-tight text-neutral-50">
|
||||
{hasOwnPoints ? ownPlayer.totalPoints : 0}
|
||||
</p>
|
||||
{ownPlayer?.rank ? <p className="text-[0.7rem] text-neutral-400">Rank {ownPlayer.rank}</p> : null}
|
||||
</div>
|
||||
<div className="border border-neutral-700 px-1.5 py-1">
|
||||
<div className="surface border border-neutral-700 px-1 py-0.75">
|
||||
<p className="mb-0.5 text-xs font-semibold text-neutral-300">Leaderboard</p>
|
||||
{Array.isArray(players) && players.length ? (
|
||||
<div className="space-y-0.5">
|
||||
@@ -284,8 +285,8 @@ export default function BarcodeGamesPanel() {
|
||||
};
|
||||
|
||||
return (
|
||||
<CardFrame title="Barcode games" bodyClassName="space-y-2 p-1.5 text-sm">
|
||||
<div className="grid gap-1 sm:grid-cols-2">
|
||||
<CardFrame title="Barcode games" bodyClassName="space-y-1 p-0.5 text-sm">
|
||||
<div className="grid gap-0.5 sm:grid-cols-2">
|
||||
{games.map((game) => (
|
||||
<GameChoice
|
||||
key={game.id}
|
||||
@@ -296,25 +297,25 @@ export default function BarcodeGamesPanel() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<section className="space-y-2 border-t border-neutral-700 pt-2">
|
||||
<div className="grid items-start gap-2 lg:grid-cols-[minmax(0,1fr)_15rem_15rem]">
|
||||
<div className="min-w-0 border-l-4 border-neutral-700 pl-2" style={activeTheme.titleBoxStyle}>
|
||||
<p className="break-words text-2xl font-bold leading-tight text-neutral-50" style={activeTheme.textStyle}>
|
||||
<section className="space-y-1 border-t border-neutral-700 pt-1">
|
||||
<div className="grid items-stretch gap-0.5 lg:grid-cols-[minmax(0,1fr)_9rem_10rem]">
|
||||
<div className="surface min-w-0 border-l-4 border-neutral-700 px-1 py-0.75" style={activeTheme.titleBoxStyle}>
|
||||
<p className="break-words text-xl font-bold leading-tight text-neutral-50" style={activeTheme.textStyle}>
|
||||
{display.title || activeGame?.title || 'No game active'}
|
||||
</p>
|
||||
<p className="mt-0.5 break-words text-lg font-semibold leading-tight text-neutral-200">
|
||||
<p className="mt-0.5 break-words text-sm font-semibold leading-tight text-neutral-200">
|
||||
{display.primary || 'Vote for a game to start'}
|
||||
</p>
|
||||
{display.secondary ? (
|
||||
<p className="mt-1 break-words text-base leading-snug text-neutral-300">{display.secondary}</p>
|
||||
<p className="mt-0.5 break-words text-xs leading-snug text-neutral-300">{display.secondary}</p>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="border border-neutral-700 px-2 py-1.5 text-left lg:text-right" style={activeTheme.boxStyle}>
|
||||
<p className="text-sm font-semibold text-neutral-400">{display.timer?.label || 'Time'}</p>
|
||||
<p className="font-mono text-2xl font-semibold leading-tight text-neutral-50">
|
||||
<div className="surface border border-neutral-700 px-1 py-0.75 text-left lg:text-right" style={activeTheme.boxStyle}>
|
||||
<p className="text-xs font-semibold text-neutral-400">{display.timer?.label || 'Time'}</p>
|
||||
<p className="font-mono text-xl font-semibold leading-tight text-neutral-50">
|
||||
{timerText || '--'}
|
||||
</p>
|
||||
<p className="text-sm leading-tight text-neutral-500">
|
||||
<p className="text-xs leading-tight text-neutral-500">
|
||||
{timerText ? 'active timer' : 'no timer'}
|
||||
</p>
|
||||
</div>
|
||||
@@ -325,9 +326,13 @@ export default function BarcodeGamesPanel() {
|
||||
|
||||
<Leaderboard players={state.leaderboard} ownPlayer={state.ownPlayer} />
|
||||
|
||||
<div className="grid gap-2 border-t border-neutral-700 pt-1.5 sm:grid-cols-2">
|
||||
<CounterList title="Most scanned objects" entries={state.counters?.objects} />
|
||||
<CounterList title="Most scanned rovers" entries={state.counters?.rovers} />
|
||||
<div className="grid gap-0.5 border-t border-neutral-700 pt-1 sm:grid-cols-2">
|
||||
<div className="surface px-1 py-0.75">
|
||||
<CounterList title="Most scanned objects" entries={state.counters?.objects} />
|
||||
</div>
|
||||
<div className="surface px-1 py-0.75">
|
||||
<CounterList title="Most scanned rovers" entries={state.counters?.rovers} />
|
||||
</div>
|
||||
</div>
|
||||
</CardFrame>
|
||||
);
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function SocketConnectionPill() {
|
||||
setLastReason('');
|
||||
show();
|
||||
scheduleFade();
|
||||
trackAnalyticsEvent('socket_connect');
|
||||
trackAnalyticsEvent('socket_connect', { connected: true });
|
||||
};
|
||||
|
||||
const onDisconnect = (reason) => {
|
||||
@@ -70,7 +70,11 @@ export default function SocketConnectionPill() {
|
||||
const onReconnectAttempt = () => {
|
||||
setConnected(false);
|
||||
show();
|
||||
trackAnalyticsEventThrottled('socket_reconnect_attempt', {}, { key: 'socket_reconnect_attempt', throttleMs: 30 * 1000 });
|
||||
trackAnalyticsEventThrottled(
|
||||
'socket_reconnect_attempt',
|
||||
{ connected: false },
|
||||
{ key: 'socket_reconnect_attempt', throttleMs: 30 * 1000 },
|
||||
);
|
||||
};
|
||||
|
||||
socket.on('connect', onConnect);
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
window.roverAnalytics = {
|
||||
track: function (name, data) {
|
||||
enqueue('track', [name, data || {}]);
|
||||
enqueue('track', typeof data === 'undefined' ? [name] : [name, data]);
|
||||
},
|
||||
identify: function (data) {
|
||||
enqueue('identify', [data || {}]);
|
||||
|
||||
Reference in New Issue
Block a user