Skip to content

feat(reactotron-app): Network tab with api responses #1471

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/reactotron-app/src/renderer/App.tsx
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ import Overlay from "./pages/reactNative/Overlay"
import Storybook from "./pages/reactNative/Storybook"
import CustomCommands from "./pages/customCommands"
import Help from "./pages/help"
import NetworkPage from "./pages/network"

const AppContainer = styled.div`
position: absolute;
@@ -56,6 +57,8 @@ function App() {

{/* Timeline */}
<Route path="/timeline" element={<Timeline />} />
{/* Network */}
<Route path="/network" element={<NetworkPage />} />

{/* State */}
<Route path="/state/subscriptions" element={<Subscriptions />} />
24 changes: 15 additions & 9 deletions apps/reactotron-app/src/renderer/ReactotronBrain.tsx
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import {
CustomCommandsProvider,
ReactNativeProvider,
TimelineProvider,
NetworkProvider,
StateProvider,
} from "reactotron-core-ui"

@@ -15,6 +16,7 @@ interface Props {
commands: Command[]
sendCommand: (type: string, payload: any, clientId?: string) => void
clearCommands: () => void
clearNetworkCommands: () => void
addCommandListener: (callback: (command: Command) => void) => void
}

@@ -23,6 +25,7 @@ const ReactotronBrain: FunctionComponent<PropsWithChildren<Props>> = ({
commands,
sendCommand,
clearCommands,
clearNetworkCommands,
addCommandListener,
children,
}) => {
@@ -31,17 +34,20 @@ const ReactotronBrain: FunctionComponent<PropsWithChildren<Props>> = ({
commands={commands}
sendCommand={sendCommand}
clearCommands={clearCommands}
clearNetworkCommands={clearNetworkCommands}
addCommandListener={addCommandListener}
>
<TimelineProvider>
<StateProvider>
<CustomCommandsProvider>
<ReactNativeProvider>
<KeybindHandler>{children}</KeybindHandler>
</ReactNativeProvider>
</CustomCommandsProvider>
</StateProvider>
</TimelineProvider>
<NetworkProvider>
<TimelineProvider>
<StateProvider>
<CustomCommandsProvider>
<ReactNativeProvider>
<KeybindHandler>{children}</KeybindHandler>
</ReactNativeProvider>
</CustomCommandsProvider>
</StateProvider>
</TimelineProvider>
</NetworkProvider>
</ReactotronProvider>
)
}
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import {
MdWarning,
MdOutlineMobileFriendly,
MdMobiledataOff,
MdNetworkCheck,
} from "react-icons/md"
import { FaMagic } from "react-icons/fa"
import styled from "styled-components"
@@ -58,6 +59,7 @@ function SideBar({ isOpen, serverStatus }: { isOpen: boolean; serverStatus: Serv
<SideBarContainer $isOpen={isOpen}>
<SideBarButton image={reactotronLogo} path="/" text="Home" hideTopBar />
<SideBarButton icon={MdReorder} path="/timeline" text="Timeline" />
<SideBarButton icon={MdNetworkCheck} path="/network" text="Network" />
<SideBarButton
icon={MdAssignment}
path="/state/subscriptions"
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@ const Provider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
selectedConnection,
selectConnection,
clearSelectedConnectionCommands,
clearNetworkCommands,
serverStarted,
serverStopped,
connectionEstablished,
@@ -89,6 +90,7 @@ const Provider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
commands={(selectedConnection || { commands: [] }).commands}
sendCommand={sendCommand}
clearCommands={clearSelectedConnectionCommands}
clearNetworkCommands={clearNetworkCommands}
addCommandListener={addCommandListener}
>
{children}
Original file line number Diff line number Diff line change
@@ -286,7 +286,7 @@ describe("contexts/Standalone/useStandalone", () => {
expect(result.current.orphanedCommands[0]).toEqual({ connectionId: 1, payload: true })
})

it("should clear commands from a connection", () => {
it("should clear commands from a connection but keeping the api ones", () => {
const { result } = renderHook(() => useStandalone())

act(() => {
@@ -299,15 +299,43 @@ describe("contexts/Standalone/useStandalone", () => {

act(() => {
result.current.commandReceived({ clientId: "1234", payload: true })
result.current.commandReceived({ clientId: "1234", payload: true, type: "api.response" })
})

expect(result.current.connections[0].commands.length).toEqual(1)
expect(result.current.connections[0].commands[0]).toEqual({ clientId: "1234", payload: true })
expect(result.current.connections[0].commands.length).toEqual(2)
expect(result.current.connections[0].commands[0]).toEqual({ clientId: "1234", payload: true, type: "api.response"})
expect(result.current.connections[0].commands[1]).toEqual({ clientId: "1234", payload: true })

act(() => {
result.current.clearSelectedConnectionCommands()
})

expect(result.current.connections[0].commands.length).toEqual(1)
expect(result.current.connections[0].commands[0]).toEqual({ clientId: "1234", payload: true, type: "api.response"})
})

it("should clear network commands from a connection", () => {
const { result } = renderHook(() => useStandalone())

act(() => {
result.current.connectionEstablished({
clientId: "1234",
id: 0,
platform: "ios",
})
})

act(() => {
result.current.commandReceived({ clientId: "1234", payload: true, type: "api.response" })
})

expect(result.current.connections[0].commands.length).toEqual(1)
expect(result.current.connections[0].commands[0]).toEqual({ clientId: "1234", payload: true, type: "api.response" })

act(() => {
result.current.clearNetworkCommands()
})

expect(result.current.connections[0].commands.length).toEqual(0)
})

Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useCallback, useReducer } from "react"
import { produce } from "immer"
import { CommandType } from "reactotron-core-contract"

export enum ActionTypes {
ServerStarted = "SERVER_STARTED",
ServerStopped = "SERVER_STOPPED",
AddConnection = "ADD_CONNECTION",
RemoveConnection = "REMOVE_CONNECTION",
ClearConnectionCommands = "CLEAR_CONNECTION_COMMANDS",
ClearNetworkCommands = "CLEAR_NETWORK_COMMANDS",
CommandReceived = "COMMAND_RECEIVED",
ChangeSelectedClientId = "CHANGE_SELECTED_CLIENT_ID",
AddCommandHandler = "ADD_COMMAND_HANDLER",
@@ -52,6 +54,7 @@ type Action =
| { type: ActionTypes.ChangeSelectedClientId; payload: string }
| { type: ActionTypes.CommandReceived; payload: any } // TODO: Type this better!
| { type: ActionTypes.ClearConnectionCommands }
| { type: ActionTypes.ClearNetworkCommands }
| { type: ActionTypes.AddCommandHandler; payload: (command: any) => void }
| { type: ActionTypes.PortUnavailable; payload: undefined }

@@ -153,7 +156,25 @@ export function reducer(state: State, action: Action) {

if (!selectedConnection) return

selectedConnection.commands = []
// for now we are keeping the api responses until we separate them out
// into their own list
selectedConnection.commands = selectedConnection.commands.filter(
(c) => c.type === CommandType.ApiResponse
)
})
case ActionTypes.ClearNetworkCommands:
return produce(state, (draftState) => {
if (!draftState.selectedClientId) return

const selectedConnection = draftState.connections.find(
(c) => c.clientId === draftState.selectedClientId
)

if (!selectedConnection) return

selectedConnection.commands = selectedConnection.commands.filter(
(c) => c.type !== CommandType.ApiResponse
)
})
case ActionTypes.ChangeSelectedClientId:
return produce(state, (draftState) => {
@@ -218,6 +239,10 @@ function useStandalone() {
dispatch({ type: ActionTypes.ClearConnectionCommands })
}, [])

const clearNetworkCommands = useCallback(() => {
dispatch({ type: ActionTypes.ClearNetworkCommands })
}, [])

const selectConnection = useCallback((clientId: string) => {
dispatch({ type: ActionTypes.ChangeSelectedClientId, payload: clientId })
}, [])
@@ -240,6 +265,7 @@ function useStandalone() {
connectionDisconnected,
commandReceived,
clearSelectedConnectionCommands,
clearNetworkCommands,
addCommandListener,
portUnavailable,
}
219 changes: 219 additions & 0 deletions apps/reactotron-app/src/renderer/pages/network/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import React, { useContext, useMemo } from "react"
import { clipboard } from "electron"
import fs from "fs"
import {
Header,
filterCommands,
EmptyState,
ReactotronContext,
NetworkContext,
timelineCommandResolver,
} from "reactotron-core-ui"
import { MdSearch, MdDeleteSweep, MdSwapVert, MdReorder } from "react-icons/md"
import { FaTimes } from "react-icons/fa"
import styled from "styled-components"
import { CommandType } from "reactotron-core-contract"
const Container = styled.div`
display: flex;
flex-direction: column;
width: 100%;
`

const NetworkPageContainer = styled.div`
height: 100%;
overflow-y: auto;
overflow-x: hidden;
`

const SearchContainer = styled.div`
display: flex;
align-items: center;
padding-bottom: 10px;
padding-top: 4px;
padding-right: 10px;
`
const SearchLabel = styled.p`
padding: 0 10px;
font-size: 14px;
color: ${(props) => props.theme.foregroundDark};
`
const SearchInput = styled.input`
border-radius: 4px;
padding: 10px;
flex: 1;
background-color: ${(props) => props.theme.backgroundSubtleDark};
border: none;
color: ${(props) => props.theme.foregroundDark};
font-size: 14px;
`
export const ButtonContainer = styled.div`
padding: 10px;
cursor: pointer;
`

export const NetworkTable = styled.table`
width: 100%;
border-collapse: collapse;
border-spacing: 0;
border: 1px solid ${(props) => props.theme.chromeLine};
border-radius: 4px;
margin-bottom: 10px;
color: ${(props) => props.theme.foregroundDark};
`

export const NetworkTableHeader = styled.thead`
background-color: ${(props) => props.theme.backgroundSubtleDark};
color: ${(props) => props.theme.foregroundDark};
font-size: 14px;
font-weight: bold;
text-align: left;
`
export const NetworkTableHeaderCell = styled.th`
padding: 0 10px;
`

export const NetworkTableHeaderRow = styled.tr`
height: 30px;
"& th": {
padding: 0 10px;
}
`
export const NetworkTableBody = styled.tbody`
font-size: 14px;
color: ${(props) => props.theme.foregroundDark};
font-weight: normal;
text-align: left;
`
export const NetworkTableRow = styled.tr`
height: 30px;
`

export const NetworkTableCell = styled.td`
padding: 0 10px;
`

export const NetworkContainer = styled.div`
height: 100%;
display: flex;
flex-direction: row;
width: 100%;
`
export const NetworkInspector = styled.div`
height: 100%;
overflow-y: auto;
overflow-x: hidden;
width: 400px;
resize: horizontal;
max-width: 600px;
`

function NetworkPage() {
const { sendCommand,clearNetworkCommands, commands, openDispatchModal } = useContext(ReactotronContext)
const {
isSearchOpen,
toggleSearch,
closeSearch,
setSearch,
search,
isReversed,
toggleReverse,
} = useContext(NetworkContext)

let filteredCommands = useMemo(() => {
const cmds = filterCommands(commands, search, []).filter((a) =>
a.type === CommandType.ApiResponse
)
return cmds;
}, [commands, search])

if (isReversed) {
filteredCommands = filteredCommands.reverse()
}

return (
<Container>
<Header
title="Network Inspect"
isDraggable
actions={[
{
tip: "Search",
icon: MdSearch,
onClick: () => {
toggleSearch()
},
},
{
tip: "Reverse Order",
icon: MdSwapVert,
onClick: () => {
toggleReverse()
},
},
{
tip: "Clear",
icon: MdDeleteSweep,
onClick: () => {
clearNetworkCommands()
},
},
]}
>
{isSearchOpen && (
<SearchContainer>
<SearchLabel>Search</SearchLabel>
<SearchInput autoFocus value={search} onChange={(e) => setSearch(e.target.value)} />
<ButtonContainer
onClick={() => {
if (search === "") {
closeSearch()
} else {
setSearch("")
}
}}
>
<FaTimes size={24} />
</ButtonContainer>
</SearchContainer>
)}
</Header>
<NetworkPageContainer>
{filteredCommands.length === 0 ? (
<EmptyState icon={MdReorder} title="No Activity">
Once your app connects and starts sending events, they will appear here.
</EmptyState>
) : (

filteredCommands.map((command) => {
const CommandComponent = timelineCommandResolver(command.type)

if (CommandComponent) {
return (
<CommandComponent
key={command.messageId}
command={command}
copyToClipboard={clipboard.writeText}
readFile={(path) => {
return new Promise((resolve, reject) => {
fs.readFile(path, "utf-8", (err, data) => {
if (err || !data) reject(new Error("Something failed"))
else resolve(data)
})
})
}}
sendCommand={sendCommand}
openDispatchDialog={openDispatchModal}
/>
)
}

return null
})

)}
</NetworkPageContainer>
</Container>
)
}

export default NetworkPage
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ function buildContextValues({ addCommandListener = null } = {}) {
commands: [],
sendCommand: jest.fn(),
clearCommands: jest.fn(),
clearNetworkCommands: jest.fn(),
addCommandListener: addCommandListener || jest.fn(),
isDispatchModalOpen: false,
dispatchModalInitialAction: "",
58 changes: 58 additions & 0 deletions lib/reactotron-core-ui/src/contexts/Network/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { FunctionComponent } from "react"

import useNetwork from "./useNetwork"

interface Context {
isSearchOpen: boolean
toggleSearch: () => void
openSearch: () => void
closeSearch: () => void
search: string
setSearch: (search: string) => void
isReversed: boolean
toggleReverse: () => void
}

const NetworkContext = React.createContext<Context>({
isSearchOpen: false,
toggleSearch: null,
openSearch: null,
closeSearch: null,
search: "",
setSearch: null,
isReversed: false,
toggleReverse: null,
})

const Provider: FunctionComponent<any> = ({ children }) => {
const {
isSearchOpen,
toggleSearch,
openSearch,
closeSearch,
search,
setSearch,
isReversed,
toggleReverse,
} = useNetwork()

return (
<NetworkContext.Provider
value={{
isSearchOpen,
toggleSearch,
openSearch,
closeSearch,
search,
setSearch,
isReversed,
toggleReverse,
}}
>
{children}
</NetworkContext.Provider>
)
}

export default NetworkContext
export const NetworkProvider = Provider
84 changes: 84 additions & 0 deletions lib/reactotron-core-ui/src/contexts/Network/useNetwork.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { act, renderHook } from "@testing-library/react"

import useNetwork, { NetworkStorageKey } from "./useNetwork"

describe("contexts/Network/useNetwork", () => {
beforeEach(() => {
localStorage.removeItem(NetworkStorageKey.ReversedOrder)
localStorage.removeItem(NetworkStorageKey.HiddenCommands)
})

describe("Initial Settings", () => {
it("should default to regular order", () => {
const { result } = renderHook(() => useNetwork())

expect(result.current.isReversed).toBeFalsy()
})

it("should load if user had the timeline reversed", () => {
localStorage.setItem(NetworkStorageKey.ReversedOrder, "reversed")

const { result } = renderHook(() => useNetwork())

expect(result.current.isReversed).toBeTruthy()
})

it("should load if user had the timeline regular order", () => {
localStorage.setItem(NetworkStorageKey.ReversedOrder, "regular")

const { result } = renderHook(() => useNetwork())

expect(result.current.isReversed).toBeFalsy()
})
})

describe("actions", () => {
it("should toggle search", () => {
const { result } = renderHook(() => useNetwork())

expect(result.current.isSearchOpen).toBeFalsy()
act(() => {
result.current.toggleSearch()
})
expect(result.current.isSearchOpen).toBeTruthy()
act(() => {
result.current.toggleSearch()
})
expect(result.current.isSearchOpen).toBeFalsy()
})

it("should set the search string", () => {
const { result } = renderHook(() => useNetwork())

expect(result.current.search).toEqual("")
act(() => {
result.current.setSearch("H")
})
expect(result.current.search).toEqual("H")
act(() => {
result.current.setSearch("L")
})
expect(result.current.search).toEqual("L")
act(() => {
result.current.setSearch("")
})
expect(result.current.search).toEqual("")
})

it("should toggle reverse", () => {
const { result } = renderHook(() => useNetwork())

expect(result.current.isReversed).toBeFalsy()
act(() => {
result.current.toggleReverse()
})
expect(localStorage.getItem(NetworkStorageKey.ReversedOrder)).toEqual("reversed")
expect(result.current.isReversed).toBeTruthy()
act(() => {
result.current.toggleReverse()
})
expect(localStorage.getItem(NetworkStorageKey.ReversedOrder)).toEqual("regular")
expect(result.current.isReversed).toBeFalsy()
})
})
})
121 changes: 121 additions & 0 deletions lib/reactotron-core-ui/src/contexts/Network/useNetwork.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { useReducer, useEffect } from "react"

import type { CommandTypeKey } from "reactotron-core-contract"

export enum NetworkStorageKey {
ReversedOrder = "ReactotronNetworkReversedOrder",
HiddenCommands = "ReactotronNetworkHiddenCommands",
}

interface NetworkState {
isSearchOpen: boolean
search: string
isFilterOpen: boolean
isReversed: boolean
hiddenCommands: CommandTypeKey[]
}

enum NetworkActionType {
SearchOpen = "SEARCH_OPEN",
SearchClose = "SEARCH_CLOSE",
SearchSet = "SEARCH_SET",
OrderReverse = "ORDER_REVERSE",
OrderRegular = "ORDER_REGULAR",
}

type Action =
| {
type:
| NetworkActionType.SearchOpen
| NetworkActionType.SearchClose
| NetworkActionType.OrderReverse
| NetworkActionType.OrderRegular
}
| {
type: NetworkActionType.SearchSet
payload: string
}

function networkReducer(state: NetworkState, action: Action) {
switch (action.type) {
case NetworkActionType.SearchOpen:
return { ...state, isSearchOpen: true }
case NetworkActionType.SearchClose:
return { ...state, isSearchOpen: false }
case NetworkActionType.SearchSet:
return { ...state, search: action.payload }
case NetworkActionType.OrderReverse:
return { ...state, isReversed: true }
case NetworkActionType.OrderRegular:
return { ...state, isReversed: false }
default:
return state
}
}

function useNetwork() {
const [state, dispatch] = useReducer(networkReducer, {
isSearchOpen: false,
search: "",
isFilterOpen: false,
isReversed: false,
hiddenCommands: [],
})

// Load some values
useEffect(() => {
const isReversed = localStorage.getItem(NetworkStorageKey.ReversedOrder) === "reversed"
dispatch({
type: isReversed ? NetworkActionType.OrderReverse : NetworkActionType.OrderRegular,
})
}, [])

// Setup event handlers
const toggleSearch = () => {
dispatch({
type: state.isSearchOpen ? NetworkActionType.SearchClose : NetworkActionType.SearchOpen,
})
}

const openSearch = () => {
dispatch({
type: NetworkActionType.SearchOpen,
})
}

const closeSearch = () => {
dispatch({
type: NetworkActionType.SearchClose,
})
}

const setSearch = (search: string) => {
dispatch({
type: NetworkActionType.SearchSet,
payload: search,
})
}

const toggleReverse = () => {
const isReversed = !state.isReversed

localStorage.setItem(NetworkStorageKey.ReversedOrder, isReversed ? "reversed" : "regular")

dispatch({
type: isReversed ? NetworkActionType.OrderReverse : NetworkActionType.OrderRegular,
})
}

return {
isSearchOpen: state.isSearchOpen,
toggleSearch,
openSearch,
closeSearch,
search: state.search,
setSearch,
isReversed: state.isReversed,
toggleReverse,
}
}

export default useNetwork
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ function buildContextValues({ addCommandListener = null } = {}) {
commands: [],
sendCommand: jest.fn(),
clearCommands: jest.fn(),
clearNetworkCommands: jest.fn(),
addCommandListener: addCommandListener || jest.fn(),
isDispatchModalOpen: false,
dispatchModalInitialAction: "",
4 changes: 4 additions & 0 deletions lib/reactotron-core-ui/src/contexts/Reactotron/index.tsx
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ interface Props {
commands: Command[]
sendCommand: (type: string, payload: any, clientId?: string) => void
clearCommands: () => void
clearNetworkCommands: () => void
addCommandListener: (callback: (command: Command) => void) => void
}

@@ -31,6 +32,7 @@ const ReactotronContext = React.createContext<ContextProps>({
commands: [],
sendCommand: null,
clearCommands: null,
clearNetworkCommands: null,
addCommandListener: null,
isDispatchModalOpen: false,
dispatchModalInitialAction: "",
@@ -45,6 +47,7 @@ const Provider: FunctionComponent<React.PropsWithChildren<Props>> = ({
commands,
sendCommand,
clearCommands,
clearNetworkCommands,
addCommandListener,
children,
}) => {
@@ -64,6 +67,7 @@ const Provider: FunctionComponent<React.PropsWithChildren<Props>> = ({
commands,
sendCommand,
clearCommands,
clearNetworkCommands,
addCommandListener,
isDispatchModalOpen,
dispatchModalInitialAction,
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ function buildContextValues({ addCommandListener = null } = {}) {
commands: [],
sendCommand: jest.fn(),
clearCommands: jest.fn(),
clearNetworkCommands: jest.fn(),
addCommandListener: addCommandListener || jest.fn(),
isDispatchModalOpen: false,
dispatchModalInitialAction: "",
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ function buildContextValues({ addCommandListener = null } = {}) {
commands: [],
sendCommand: jest.fn(),
clearCommands: jest.fn(),
clearNetworkCommands: jest.fn(),
addCommandListener: addCommandListener || jest.fn(),
isDispatchModalOpen: false,
dispatchModalInitialAction: "",
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@ describe("contexts/Timline/useTimeline", () => {
it("should default to no hidden commands", () => {
const { result } = renderHook(() => useTimline())

expect(result.current.hiddenCommands).toEqual([])
expect(result.current.hiddenCommands).toEqual([CommandType.ApiResponse])
})

it("should have saved hidden commands", () => {
@@ -123,7 +123,7 @@ describe("contexts/Timline/useTimeline", () => {
it("should set hidden commands", () => {
const { result } = renderHook(() => useTimline())

expect(result.current.hiddenCommands).toEqual([])
expect(result.current.hiddenCommands).toEqual([CommandType.ApiResponse])
act(() => {
result.current.setHiddenCommands([CommandType.ClientIntro])
})
4 changes: 2 additions & 2 deletions lib/reactotron-core-ui/src/contexts/Timeline/useTimeline.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useReducer, useEffect } from "react"

import type { CommandTypeKey } from "reactotron-core-contract"
import { CommandType, type CommandTypeKey } from "reactotron-core-contract"

export enum StorageKey {
ReversedOrder = "ReactotronTimelineReversedOrder",
@@ -80,7 +80,7 @@ function useTimeline() {
// Load some values
useEffect(() => {
const isReversed = localStorage.getItem(StorageKey.ReversedOrder) === "reversed"
const hiddenCommands = JSON.parse(localStorage.getItem(StorageKey.HiddenCommands) || "[]")
const hiddenCommands = JSON.parse(localStorage.getItem(StorageKey.HiddenCommands) || JSON.stringify([CommandType.ApiResponse]))

dispatch({
type: isReversed ? TimelineActionType.OrderReverse : TimelineActionType.OrderRegular,
3 changes: 3 additions & 0 deletions lib/reactotron-core-ui/src/index.ts
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ import CustomCommandsContext, { CustomCommandsProvider } from "./contexts/Custom
import ReactNativeContext, { ReactNativeProvider } from "./contexts/ReactNative"
import StateContext, { StateProvider } from "./contexts/State"
import TimelineContext, { TimelineProvider } from "./contexts/Timeline"
import NetworkContext, { NetworkProvider } from "./contexts/Network"

// Modals
import DispatchActionModal from "./modals/DispatchActionModal"
@@ -65,6 +66,8 @@ export {
StateProvider,
TimelineContext,
TimelineProvider,
NetworkContext,
NetworkProvider,
}

export type { CustomCommand } from "./contexts/CustomCommands/useCustomCommands"
Original file line number Diff line number Diff line change
@@ -20,7 +20,6 @@ const GROUPS = [
items: [
{ value: CommandType.ClientIntro, text: "Connection" },
{ value: CommandType.Benchmark, text: "Benchmark" },
{ value: CommandType.ApiResponse, text: "API" },
],
},
{