From d61167cf45a3f5ad8ef81fe86896566b15d87019 Mon Sep 17 00:00:00 2001 From: James Gatz Date: Tue, 23 Apr 2024 12:20:19 +0200 Subject: [PATCH] feat(event-log): Improve UX of the event log for WS and SSE responses (#7300) * change to tailwind and rac * change selection on navigation * resizable panel for the event view * cleanup styled components * fix preview mode --- .../components/websockets/event-log-view.tsx | 171 ++++++------------ .../ui/components/websockets/event-view.tsx | 10 +- .../websockets/realtime-response-pane.tsx | 120 +++++------- 3 files changed, 97 insertions(+), 204 deletions(-) diff --git a/packages/insomnia/src/ui/components/websockets/event-log-view.tsx b/packages/insomnia/src/ui/components/websockets/event-log-view.tsx index 33a0b75de..7f8426fe4 100644 --- a/packages/insomnia/src/ui/components/websockets/event-log-view.tsx +++ b/packages/insomnia/src/ui/components/websockets/event-log-view.tsx @@ -1,8 +1,7 @@ import { useVirtualizer } from '@tanstack/react-virtual'; import { format } from 'date-fns'; import React, { FC, useRef } from 'react'; -import { useMeasure } from 'react-use'; -import styled from 'styled-components'; +import { Cell, Column, Row, Table, TableBody, TableHeader } from 'react-aria-components'; import { CurlEvent } from '../../../main/network/curl'; import { WebSocketEvent } from '../../../main/network/websocket'; @@ -19,67 +18,6 @@ interface Props { onSelect: (event: WebSocketEvent | CurlEvent) => void; } -const Divider = styled('div')({ - height: '100%', - width: '1px', - backgroundColor: 'var(--hl-md)', -}); - -const AutoSize = styled.div({ - flex: '1 0', - overflow: 'hidden', -}); - -const Scrollable = styled.div({ - overflowY: 'scroll', -}); - -const HeadingRow = styled('div')({ - flex: '0 0 30px', - display: 'flex', - width: '100%', - alignItems: 'center', - borderBottom: '1px solid var(--hl-md)', - paddingRight: 'var(--scrollbar-width)', - boxSizing: 'border-box', -}); - -const Row = styled('div')<{ isActive: boolean }>(({ isActive }) => ({ - position: 'absolute', - top: 0, - left: 0, - height: '30px', - display: 'flex', - width: '100%', - alignItems: 'center', - borderBottom: '1px solid var(--hl-md)', - boxSizing: 'border-box', - backgroundColor: isActive ? 'var(--hl-lg)' : 'transparent', -})); - -const List = styled('div')({ - width: '100%', - position: 'relative', -}); - -const EventLog = styled('div')({ - display: 'flex', - flexDirection: 'column', - width: '100%', - height: '100%', - overflow: 'hidden', - borderTop: '1px solid var(--hl-md)', -}); - -const EventIconCell = styled('div')({ - flex: '0 0 15px', - height: '100%', - display: 'flex', - alignItems: 'center', - boxSizing: 'border-box', - padding: 'var(--padding-xs)', -}); - function getIcon(event: WebSocketEvent | CurlEvent): SvgIconProps['icon'] { switch (event.type) { case 'message': { @@ -104,14 +42,6 @@ function getIcon(event: WebSocketEvent | CurlEvent): SvgIconProps['icon'] { } } -const EventMessageCell = styled('div')({ - flex: '1 0', - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - padding: 'var(--padding-xs)', -}); - const getMessage = (event: WebSocketEvent | CurlEvent): string => { switch (event.type) { case 'message': { @@ -135,13 +65,8 @@ const getMessage = (event: WebSocketEvent | CurlEvent): string => { } }; -const EventTimestampCell = styled('div')({ - flex: '0 0 80px', - padding: 'var(--padding-xs)', -}); - export const EventLogView: FC = ({ events, onSelect, selectionId }) => { - const parentRef = useRef(null); + const parentRef = useRef(null); const virtualizer = useVirtualizer({ getScrollElement: () => parentRef.current, count: events.length, @@ -150,55 +75,63 @@ export const EventLogView: FC = ({ events, onSelect, selectionId }) => { getItemKey: index => events[index]._id, }); - const [autoSizeRef, { height }] = useMeasure(); - return ( - - - -
- - - Data - - Time - - - - +
+ { + if (keys !== 'all') { + const key = keys.values().next().value; + + const event = events.find(e => e._id === key); + + if (event) { + onSelect(event); + } + } + }} + aria-label='Modified objects' + className="border-separate border-spacing-0 w-full" + > + + + + + + Data + + + Time + + + - {virtualizer.getVirtualItems().map(item => { + {item => { const event = events[item.index]; - return ( - onSelect(event)} - isActive={event._id === selectionId} - style={{ - height: `${item.size}px`, - transform: `translateY(${item.start}px)`, - }} - > - + + - - - - {getMessage(event)} - - + + + {getMessage(event)} + + - + ); - })} - - - - + }} + +
+
+ ); }; diff --git a/packages/insomnia/src/ui/components/websockets/event-view.tsx b/packages/insomnia/src/ui/components/websockets/event-view.tsx index aa16e2139..e896539f8 100644 --- a/packages/insomnia/src/ui/components/websockets/event-view.tsx +++ b/packages/insomnia/src/ui/components/websockets/event-view.tsx @@ -3,10 +3,10 @@ import React, { FC, useCallback } from 'react'; import { useParams, useRouteLoaderData } from 'react-router-dom'; import styled from 'styled-components'; -import { PREVIEW_MODE_FRIENDLY, PREVIEW_MODE_RAW, PREVIEW_MODE_SOURCE, PreviewMode } from '../../../common/constants'; +import { PREVIEW_MODE_FRIENDLY, PREVIEW_MODE_RAW, PREVIEW_MODE_SOURCE } from '../../../common/constants'; import { CurlEvent, CurlMessageEvent } from '../../../main/network/curl'; import { WebSocketEvent, WebSocketMessageEvent } from '../../../main/network/websocket'; -import { requestMeta } from '../../../models'; +import { useRequestMetaPatcher } from '../../hooks/use-request'; import { RequestLoaderData } from '../../routes/request'; import { CodeEditor } from '../codemirror/code-editor'; import { showError } from '../modals'; @@ -78,9 +78,7 @@ export const MessageEventView: FC { - return requestMeta.updateOrCreateByParentId(requestId, { previewMode }); - }; + const patchRequestMeta = useRequestMetaPatcher(); let pretty = raw; try { @@ -98,7 +96,7 @@ export const MessageEventView: FC patchRequestMeta(requestId, { previewMode })} /> diff --git a/packages/insomnia/src/ui/components/websockets/realtime-response-pane.tsx b/packages/insomnia/src/ui/components/websockets/realtime-response-pane.tsx index d1a30610d..88083e616 100644 --- a/packages/insomnia/src/ui/components/websockets/realtime-response-pane.tsx +++ b/packages/insomnia/src/ui/components/websockets/realtime-response-pane.tsx @@ -1,5 +1,7 @@ import fs from 'fs'; -import React, { FC, useEffect, useRef, useState } from 'react'; +import React, { FC, useEffect, useState } from 'react'; +import { Button, Input, SearchField } from 'react-aria-components'; +import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; import { useRouteLoaderData } from 'react-router-dom'; import styled from 'styled-components'; @@ -14,6 +16,7 @@ import { RequestLoaderData, WebSocketRequestLoaderData } from '../../routes/requ import { PanelContainer, TabItem, Tabs } from '../base/tabs'; import { ResponseHistoryDropdown } from '../dropdowns/response-history-dropdown'; import { ErrorBoundary } from '../error-boundary'; +import { Icon } from '../icon'; import { Pane, PaneHeader as OriginalPaneHeader } from '../panes/pane'; import { PlaceholderResponsePane } from '../panes/placeholder-response-pane'; import { SvgIcon } from '../svg-icon'; @@ -31,52 +34,6 @@ const PaneHeader = styled(OriginalPaneHeader)({ '&&': { justifyContent: 'unset' }, }); -const EventLogTableWrapper = styled.div({ - width: '100%', - flex: 1, - overflow: 'hidden', - boxSizing: 'border-box', -}); - -const EventViewWrapper = styled.div({ - flex: 1, - borderTop: '1px solid var(--hl-md)', - height: '100%', -}); - -const EventSearchFormControl = styled.div({ - outline: 'none', - width: '100%', - boxSizing: 'border-box', - position: 'relative', - display: 'flex', - border: '1px solid var(--hl-md)', - borderRadius: 'var(--radius-md)', -}); - -const EventSearchInput = styled.input({ - paddingRight: '2em', - padding: 'var(--padding-sm)', - backgroundColor: 'var(--hl-xxs)', - width: '100%', - display: 'block', - boxSizing: 'border-box', - - // Remove the default search input cancel button - '::-webkit-search-cancel-button': { - display: 'none', - }, - - ':focus': { - backgroundColor: 'transparent', - borderColor: 'var(--hl-lg)', - }, -}); - -const PaddedButton = styled('button')({ - padding: 'var(--padding-sm)', -}); - export const RealtimeResponsePane: FC<{ requestId: string }> = () => { const { activeResponse } = useRouteLoaderData('request/:requestId') as RequestLoaderData | WebSocketRequestLoaderData; @@ -96,7 +53,6 @@ const RealtimeActiveResponsePane: FC<{ response: WebSocketResponse | Response }> }) => { const [selectedEvent, setSelectedEvent] = useState(null); const [timeline, setTimeline] = useState([]); - const searchInputRef = useRef(null); const [clearEventsBefore, setClearEventsBefore] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [eventType, setEventType] = useState(); @@ -173,10 +129,10 @@ const RealtimeActiveResponsePane: FC<{ response: WebSocketResponse | Response }> -
+ {response.error ? : <> - +
- - { + setSearchQuery(query); + }} + > + setSearchQuery(e.currentTarget.value)} + className="py-1 w-full pl-2 pr-7 rounded-sm border border-solid border-[--hl-sm] bg-[--color-bg] text-[--color-font] focus:outline-none focus:ring-1 focus:ring-[--hl-md] transition-colors" /> - {searchQuery && ( - { - setSearchQuery(''); - searchInputRef.current?.focus(); - }} - > - - - )} - - { +
+ +
+ +
+ {Boolean(events?.length) && ( selectionId={selectedEvent?._id} /> )} -
+ {selectedEvent && ( - - - + <> + + +
+ +
+
+ )} } -
+