M frontend/src/locale/en/translation.json => frontend/src/locale/en/translation.json +1 -0
@@ 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": "<n>{{name}}</n> followed you",
"redemption": "<n>{{name}}</n> redeemed <r>{{reward}}</r>",
M frontend/src/locale/it/translation.json => frontend/src/locale/it/translation.json +1 -0
@@ 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": "<n>{{name}}</n> ti ha tifato con <b>{{bits}} bit</b>",
M frontend/src/ui/components/LogViewer.tsx => frontend/src/ui/components/LogViewer.tsx +5 -4
@@ 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 (
<LogEntryContainer level={levelStyle}>
<LogTime level={levelStyle}>{formatTime(data.time)}</LogTime>
@@ 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<LogLevel>(null);
const count = logEntries.reduce(
M frontend/src/ui/pages/Dashboard.tsx => frontend/src/ui/pages/Dashboard.tsx +95 -14
@@ 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 (
<>
<HoverCard.Root>
@@ 383,17 443,23 @@ function TwitchEventLog({ events }: { events: EventSubNotification[] }) {
</HoverCard.Root>
<Scrollbar vertical={true} viewport={{ maxHeight: '250px' }}>
<EventListContainer>
- {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) => (
- <TwitchEvent key={eventSubKeyFunction(ev)} data={ev} />
- ))}
+ {orderedEvents.map((ev) => {
+ switch (ev.type) {
+ case 'marker':
+ return (
+ <SessionMarker key="marker">
+ {t('pages.dashboard.twitch-events.marker')}
+ </SessionMarker>
+ );
+ default:
+ return (
+ <TwitchEvent
+ key={eventSubKeyFunction(ev.event)}
+ data={ev.event}
+ />
+ );
+ }
+ })}
</EventListContainer>
</Scrollbar>
</>
@@ 439,6 505,19 @@ function TwitchSection() {
const twitchInfo = useLiveKey<StreamInfo[]>('twitch/stream-info');
const kv = useAppSelector((state) => state.api.client);
const [twitchEvents, setTwitchEvents] = useState<EventSubNotification[]>([]);
+ const [oldestLog, setOldestLog] = useState(Date.now());
+
+ useEffect(() => {
+ GetLastLogs().then((res) => {
+ // Get oldest log entry
+ const oldest = res.reduce<number>((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() {
) : (
<TextBlock>{t('pages.dashboard.not-live')}</TextBlock>
)}
- {twitchEvents ? <TwitchEventLog events={twitchEvents} /> : null}
+ {twitchEvents ? (
+ <TwitchEventLog events={twitchEvents} dateMarker={oldestLog} />
+ ) : null}
</>
);
}