From a1fab34a703b4744af531d0d588424a3a71b1f1a Mon Sep 17 00:00:00 2001 From: Ash Keel Date: Thu, 25 Apr 2024 22:03:41 +0200 Subject: [PATCH] feat: add marker in recent events to show which events are new --- frontend/src/locale/en/translation.json | 1 + frontend/src/locale/it/translation.json | 1 + frontend/src/ui/components/LogViewer.tsx | 9 +- frontend/src/ui/pages/Dashboard.tsx | 109 ++++++++++++++++++++--- 4 files changed, 102 insertions(+), 18 deletions(-) diff --git a/frontend/src/locale/en/translation.json b/frontend/src/locale/en/translation.json index e6f40ea..65be9ea 100644 --- a/frontend/src/locale/en/translation.json +++ b/frontend/src/locale/en/translation.json @@ -267,6 +267,7 @@ "header": "Recent events", "warning": "This section only contains events that happened while {{APPNAME}} was open, so only use it for recent stuff", "anonymous": "An anonymous viewer", + "marker": "Events from previous sessions", "events": { "follow": "{{name}} followed you", "redemption": "{{name}} redeemed {{reward}}", diff --git a/frontend/src/locale/it/translation.json b/frontend/src/locale/it/translation.json index a95c08d..db38a4d 100644 --- a/frontend/src/locale/it/translation.json +++ b/frontend/src/locale/it/translation.json @@ -152,6 +152,7 @@ "anonymous": "Uno spettatore anonimo", "header": "Eventi recenti", "warning": "Questa sezione contiene solo gli eventi accaduti mentre {{APPNAME}} era aperto, quindi utilizzala solo per cose recenti", + "marker": "Eventi delle sessioni precedenti", "events": { "channel-updated": "Informazioni stream modificate", "cheered": "{{name}} ti ha tifato con {{bits}} bit", diff --git a/frontend/src/ui/components/LogViewer.tsx b/frontend/src/ui/components/LogViewer.tsx index b49f595..0317810 100644 --- a/frontend/src/ui/components/LogViewer.tsx +++ b/frontend/src/ui/components/LogViewer.tsx @@ -1,8 +1,7 @@ import { ClipboardCopyIcon, Cross2Icon, SizeIcon } from '@radix-ui/react-icons'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useSelector } from 'react-redux'; -import { RootState } from 'src/store'; +import { useAppSelector } from 'src/store'; import * as DialogPrimitive from '@radix-ui/react-dialog'; import { delay } from '~/lib/time'; import { ProcessedLogEntry } from '~/store/logging/reducer'; @@ -294,12 +293,14 @@ export function LogItem({ data, expandDefault }: LogItemProps) { const details = Object.entries(data.data).filter(([key]) => key.length > 1); const [copied, setCopied] = useState(false); const [showDetails, setShowDetails] = useState(expandDefault ?? false); + const copyToClipboard = async () => { await navigator.clipboard.writeText(JSON.stringify(data.data)); setCopied(true); await delay(2000); setCopied(false); }; + return ( {formatTime(data.time)} @@ -355,7 +356,7 @@ interface LogDialogProps { } function LogDialog({ initialFilter }: LogDialogProps) { - const logEntries = useSelector((state: RootState) => state.logging.messages); + const logEntries = useAppSelector((state) => state.logging.messages); const [filter, setFilter] = useState({ ...emptyFilter, ...Object.fromEntries(initialFilter.map((f) => [f, true])), @@ -440,7 +441,7 @@ function LogDialog({ initialFilter }: LogDialogProps) { } function LogViewer() { - const logEntries = useSelector((state: RootState) => state.logging.messages); + const logEntries = useAppSelector((state) => state.logging.messages); const [activeDialog, setActiveDialog] = useState(null); const count = logEntries.reduce( diff --git a/frontend/src/ui/pages/Dashboard.tsx b/frontend/src/ui/pages/Dashboard.tsx index f04917f..cc92e70 100644 --- a/frontend/src/ui/pages/Dashboard.tsx +++ b/frontend/src/ui/pages/Dashboard.tsx @@ -16,7 +16,11 @@ import { modules } from '~/store/api/reducer'; import * as HoverCard from '@radix-ui/react-hover-card'; import { useEffect, useState } from 'react'; import { main } from '@wailsapp/go/models'; -import { GetProblems, GetTwitchAuthURL } from '@wailsapp/go/main/App'; +import { + GetLastLogs, + GetProblems, + GetTwitchAuthURL, +} from '@wailsapp/go/main/App'; import { BrowserOpenURL } from '@wailsapp/runtime/runtime'; import { PageContainer, @@ -362,8 +366,64 @@ function TwitchEvent({ data }: { data: EventSubNotification }) { ); } -function TwitchEventLog({ events }: { events: EventSubNotification[] }) { +const block = { + content: '""', + display: 'inline-block', + width: '1rem', + height: '0.1rem', + margin: '0 0.5rem', + backgroundColor: '$gray9', +}; + +const SessionMarker = styled('div', { + textTransform: 'uppercase', + fontWeight: '600', + fontSize: '0.75rem', + color: '$gray11', + margin: '0.25rem', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + '&::before': block, + '&::after': block, +}); + +interface TwitchEventLogProps { + events: EventSubNotification[]; + dateMarker: number; +} + +type EventItem = + | { type: 'marker' } + | { type: 'event'; event: EventSubNotification }; + +function TwitchEventLog({ events, dateMarker }: TwitchEventLogProps) { const { t } = useTranslation(); + + // Filter only supported events and order them by date + const orderedEvents: EventItem[] = events + .filter((ev) => supportedMessages.includes(ev.subscription.type)) + .sort((a, b) => + a.date && b.date + ? Date.parse(b.date) - Date.parse(a.date) + : Date.parse(b.subscription.created_at) - + Date.parse(a.subscription.created_at), + ) + .map((ev) => ({ type: 'event', event: ev })); + + // Add marker + if (dateMarker) { + const index = orderedEvents.findIndex((ev) => { + if (ev.type !== 'event') { + return false; + } + return Date.parse(ev.event.date) < dateMarker; + }); + if (index >= 0) { + orderedEvents.splice(index, 0, { type: 'marker' }); + } + } + return ( <> @@ -383,17 +443,23 @@ function TwitchEventLog({ events }: { events: EventSubNotification[] }) { - {events - .filter((ev) => supportedMessages.includes(ev.subscription.type)) - .sort((a, b) => - a.date && b.date - ? Date.parse(b.date) - Date.parse(a.date) - : Date.parse(b.subscription.created_at) - - Date.parse(a.subscription.created_at), - ) - .map((ev) => ( - - ))} + {orderedEvents.map((ev) => { + switch (ev.type) { + case 'marker': + return ( + + {t('pages.dashboard.twitch-events.marker')} + + ); + default: + return ( + + ); + } + })} @@ -439,6 +505,19 @@ function TwitchSection() { const twitchInfo = useLiveKey('twitch/stream-info'); const kv = useAppSelector((state) => state.api.client); const [twitchEvents, setTwitchEvents] = useState([]); + const [oldestLog, setOldestLog] = useState(Date.now()); + + useEffect(() => { + GetLastLogs().then((res) => { + // Get oldest log entry + const oldest = res.reduce((acc, log) => { + const parsedDate = Date.parse(log.time); + return parsedDate < acc ? parsedDate : acc; + }, Date.now()); + + setOldestLog(oldest); + }); + }, []); const keyfn = (ev: EventSubNotification) => JSON.stringify(ev); @@ -493,7 +572,9 @@ function TwitchSection() { ) : ( {t('pages.dashboard.not-live')} )} - {twitchEvents ? : null} + {twitchEvents ? ( + + ) : null} ); }