diff --git a/packages/insomnia/package-lock.json b/packages/insomnia/package-lock.json
index 31b8a794ce0..68d9d509039 100644
--- a/packages/insomnia/package-lock.json
+++ b/packages/insomnia/package-lock.json
@@ -141,6 +141,7 @@
"react-sortable-hoc": "^2.0.0",
"react-tabs": "^3.2.3",
"react-use": "^17.2.4",
+ "react-virtual": "2.10.4",
"redux": "^4.1.2",
"redux-mock-store": "^1.5.4",
"redux-thunk": "^2.4.1",
@@ -3164,6 +3165,12 @@
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
},
+ "node_modules/@reach/observe-rect": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@reach/observe-rect/-/observe-rect-1.2.0.tgz",
+ "integrity": "sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==",
+ "dev": true
+ },
"node_modules/@repeaterjs/repeater": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.4.tgz",
@@ -17606,6 +17613,21 @@
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"dev": true
},
+ "node_modules/react-virtual": {
+ "version": "2.10.4",
+ "resolved": "https://registry.npmjs.org/react-virtual/-/react-virtual-2.10.4.tgz",
+ "integrity": "sha512-Ir6+oPQZTVHfa6+JL9M7cvMILstFZH/H3jqeYeKI4MSUX+rIruVwFC6nGVXw9wqAw8L0Kg2KvfXxI85OvYQdpQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/tannerlinsley"
+ ],
+ "dependencies": {
+ "@reach/observe-rect": "^1.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.6.3 || ^17.0.0"
+ }
+ },
"node_modules/read-config-file": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.2.0.tgz",
@@ -22981,6 +23003,12 @@
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
},
+ "@reach/observe-rect": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@reach/observe-rect/-/observe-rect-1.2.0.tgz",
+ "integrity": "sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==",
+ "dev": true
+ },
"@repeaterjs/repeater": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.4.tgz",
@@ -34250,6 +34278,15 @@
}
}
},
+ "react-virtual": {
+ "version": "2.10.4",
+ "resolved": "https://registry.npmjs.org/react-virtual/-/react-virtual-2.10.4.tgz",
+ "integrity": "sha512-Ir6+oPQZTVHfa6+JL9M7cvMILstFZH/H3jqeYeKI4MSUX+rIruVwFC6nGVXw9wqAw8L0Kg2KvfXxI85OvYQdpQ==",
+ "dev": true,
+ "requires": {
+ "@reach/observe-rect": "^1.1.0"
+ }
+ },
"read-config-file": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.2.0.tgz",
diff --git a/packages/insomnia/package.json b/packages/insomnia/package.json
index da17daf9998..a373e8ec7fb 100644
--- a/packages/insomnia/package.json
+++ b/packages/insomnia/package.json
@@ -196,6 +196,7 @@
"react-sortable-hoc": "^2.0.0",
"react-tabs": "^3.2.3",
"react-use": "^17.2.4",
+ "react-virtual": "2.10.4",
"redux": "^4.1.2",
"redux-mock-store": "^1.5.4",
"redux-thunk": "^2.4.1",
diff --git a/packages/insomnia/src/ui/components/websockets/event-log-table.tsx b/packages/insomnia/src/ui/components/websockets/event-log-table.tsx
deleted file mode 100644
index 5f67fcf0c24..00000000000
--- a/packages/insomnia/src/ui/components/websockets/event-log-table.tsx
+++ /dev/null
@@ -1,242 +0,0 @@
-import { format } from 'date-fns';
-import { SvgIcon } from 'insomnia-components';
-import React, { FC, memo, useCallback } from 'react';
-import styled from 'styled-components';
-
-import {
- WebSocketCloseEvent,
- WebSocketErrorEvent,
- WebSocketEvent,
- WebSocketMessageEvent,
- WebSocketOpenEvent,
-} from '../../../main/network/websocket';
-
-const Table = styled.table({
- borderCollapse: 'collapse',
- tableLayout: 'fixed',
- width: '100%',
- boxSizing: 'border-box',
-});
-const TableCell = styled('td')({
- border: '1px solid var(--hl-md)',
-});
-const TableRow = styled('tr')<{ isActive: boolean }>(
- ({ isActive }) => ({
- zIndex: 1,
- backgroundColor: isActive ? 'var(--hl-md)' : 'transparent',
- })
-);
-const TableCellTextWrapper = styled.div({
- width: 'inherit',
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
-});
-const TableCellIconWrapper = styled.div({
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
-});
-
-const Timestamp: FC<{ time: Date | number }> = ({ time }) => {
- const date = format(time, 'HH:mm:ss');
- return <>{date}>;
-};
-
-export const MessageEventTableRow = memo(
- (props: {
- event: WebSocketMessageEvent;
- isActive: boolean;
- onClick: () => void;
- }) => {
- const { event, isActive, onClick } = props;
- return (
-
-
-
- {event.direction === 'OUTGOING' ? : }
-
-
-
-
- {event.data}
-
-
-
-
-
-
- );
- }
-);
-MessageEventTableRow.displayName = 'MessageEventTableRow';
-
-export const CloseEventTableRow = memo(
- (props: {
- event: WebSocketCloseEvent;
- isActive: boolean;
- onClick: () => void;
- }) => {
- const { event, isActive, onClick } = props;
- return (
-
-
-
-
-
-
-
-
- Connection closed. {event.reason && `Reason: ${event.reason}`}{' '}
- {event.code && `Code: ${event.code}`}
-
-
-
-
-
-
- );
- }
-);
-CloseEventTableRow.displayName = 'CloseEventTableRow';
-
-export const OpenEventTableRow = memo(
- (props: {
- event: WebSocketOpenEvent;
- isActive: boolean;
- onClick: () => void;
- }) => {
- const { isActive, onClick } = props;
- return (
-
-
-
-
-
-
-
-
- Connected successfully
-
-
-
-
-
-
- );
- }
-);
-OpenEventTableRow.displayName = 'OpenEventTableRow';
-
-export const ErrorEventTableRow = memo(
- (props: {
- event: WebSocketErrorEvent;
- isActive: boolean;
- onClick: () => void;
- }) => {
- const { event, isActive, onClick } = props;
- return (
-
-
-
-
-
-
- {event.message.slice(0, 50)}
-
-
-
-
- );
- }
-);
-ErrorEventTableRow.displayName = 'ErrorEventTableRow';
-
-export const EventTableRow = memo(
- (props: {
- event: WebSocketEvent;
- isActive: boolean;
- onClick: (event: WebSocketEvent) => void;
- }) => {
- const { event, isActive, onClick } = props;
- const _onClick = useCallback(() => onClick(event), [event, onClick]);
-
- switch (event.type) {
- case 'message': {
- return (
-
- );
- }
- case 'open': {
- return (
-
- );
- }
- case 'close': {
- return (
-
- );
- }
- case 'error': {
- return (
-
- );
- }
- default: {
- return null;
- }
- }
- }
-);
-EventTableRow.displayName = 'EventTableRow';
-
-const TableHeadRow = styled('tr')({
- position: 'sticky',
- backgroundColor: 'var(--color-bg)',
- top: 'calc(-0.5rem - 1px)',
-});
-
-interface Props {
- events: WebSocketEvent[];
- selectionId?: string;
- onSelect: (event: WebSocketEvent) => void;
-}
-export const EventLogTable: FC = ({ events, onSelect, selectionId }) => {
- return (
-
-
-
- |
- Data |
- Time |
-
-
-
- {events.map(event => (
-
- ))}
-
-
- );
-};
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 db70d4d809b..bc5ba78b280 100644
--- a/packages/insomnia/src/ui/components/websockets/event-log-view.tsx
+++ b/packages/insomnia/src/ui/components/websockets/event-log-view.tsx
@@ -1,18 +1,200 @@
-import React, { FC } from 'react';
+import { format } from 'date-fns';
+import { SvgIcon, SvgIconProps } from 'insomnia-components';
+import React, { FC, useRef } from 'react';
+import { useMeasure } from 'react-use';
+import { useVirtual } from 'react-virtual';
+import styled from 'styled-components';
import { WebSocketEvent } from '../../../main/network/websocket';
-import { CodeEditor } from '../codemirror/code-editor';
+
+const Timestamp: FC<{ time: Date | number }> = ({ time }) => {
+ const date = format(time, 'HH:mm:ss');
+ return <>{date}>;
+};
+
interface Props {
- event: WebSocketEvent;
+ events: WebSocketEvent[];
+ selectionId?: string;
+ onSelect: (event: WebSocketEvent) => 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',
+ border: '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): SvgIconProps['icon'] {
+ switch (event.type) {
+ case 'message': {
+ if (event.direction === 'OUTGOING') {
+ return 'sent';
+ } else {
+ return 'receive';
+ }
+ }
+ case 'open': {
+ return 'ellipsis-circle2';
+ }
+ case 'close': {
+ return 'disconnected';
+ }
+ case 'error': {
+ return 'error';
+ }
+ default: {
+ return 'bug';
+ }
+ }
}
-export const EventLogView: FC = ({ event }) => {
+
+const EventMessageCell = styled('div')({
+ flex: '1 0',
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ padding: 'var(--padding-xs)',
+});
+
+const getMessage = (event: WebSocketEvent): string => {
+ switch (event.type) {
+ case 'message': {
+ return event.data.toString();
+ }
+ case 'open': {
+ return 'Connected successfully';
+ }
+ case 'close': {
+ return 'Disconnected';
+ }
+ case 'error': {
+ return event.message;
+ }
+ default: {
+ return 'Unknown event';
+ }
+ }
+};
+
+const EventTimestampCell = styled('div')({
+ flex: '0 0 80px',
+ padding: 'var(--padding-xs)',
+});
+
+export const EventLogView: FC = ({ events, onSelect, selectionId }) => {
+ const parentRef = useRef(null);
+ const virtualizer = useVirtual({
+ parentRef,
+ size: events.length,
+ estimateSize: React.useCallback(() => 30, []),
+ overscan: 30,
+ keyExtractor: index => events[index]._id,
+ });
+
+ const [autoSizeRef, { height }] = useMeasure();
+
return (
-
+
+
+
+
+
+
+ Data
+
+ Time
+
+
+
+
+ {virtualizer.virtualItems.map(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)}
+
+
+
+
+
+ );
+ })}
+
+
+
+
);
};
diff --git a/packages/insomnia/src/ui/components/websockets/event-view.tsx b/packages/insomnia/src/ui/components/websockets/event-view.tsx
new file mode 100644
index 00000000000..7a486976949
--- /dev/null
+++ b/packages/insomnia/src/ui/components/websockets/event-view.tsx
@@ -0,0 +1,18 @@
+import React, { FC } from 'react';
+
+import { WebSocketEvent } from '../../../main/network/websocket';
+import { CodeEditor } from '../codemirror/code-editor';
+interface Props {
+ event: WebSocketEvent;
+}
+export const EventView: FC = ({ event }) => {
+ return (
+
+ );
+};
diff --git a/packages/insomnia/src/ui/components/websockets/websocket-response-pane.tsx b/packages/insomnia/src/ui/components/websockets/websocket-response-pane.tsx
index 2336c49cd5c..821fb51d31b 100644
--- a/packages/insomnia/src/ui/components/websockets/websocket-response-pane.tsx
+++ b/packages/insomnia/src/ui/components/websockets/websocket-response-pane.tsx
@@ -18,32 +18,35 @@ import { ResponseCookiesViewer } from '../viewers/response-cookies-viewer';
import { ResponseErrorViewer } from '../viewers/response-error-viewer';
import { ResponseHeadersViewer } from '../viewers/response-headers-viewer';
import { ResponseTimelineViewer } from '../viewers/response-timeline-viewer';
-import { EventLogTable } from './event-log-table';
import { EventLogView } from './event-log-view';
+import { EventView } from './event-view';
const PaneHeader = styled(OriginalPaneHeader)({
'&&': { justifyContent: 'unset' },
});
+
const EventLogTableWrapper = styled.div({
width: '100%',
flex: 1,
- overflowY: 'scroll',
+ overflow: 'hidden',
padding: 'var(--padding-sm)',
boxSizing: 'border-box',
});
-const EventLogViewWrapper = styled.div({
+
+const EventViewWrapper = styled.div({
flex: 1,
borderTop: '1px solid var(--hl-md)',
height: '100%',
- boxSizing: 'content-box',
padding: 'var(--padding-sm)',
});
+
const PaneBodyContent = styled.div({
height: '100%',
- display: 'flex',
- flexDirection: 'column',
- flex: 1,
+ width: '100%',
+ display: 'grid',
+ gridTemplateRows: 'repeat(auto-fit, minmax(0, 1fr))',
});
+
export const WebSocketResponsePane: FC<{ requestId: string; response: Response | null; handleSetActiveResponse: (requestId: string, activeResponse: Response | null) => void }> =
({
requestId,
@@ -144,7 +147,7 @@ const WebSocketActiveResponsePane: FC<{ requestId: string; response: Response; h
: <>
{Boolean(events?.length) && (
-
)}
{selectedEvent && (
-
-
-
+
+
+
)}
>}