diff --git a/apps/reactotron-app/src/renderer/RootModals.tsx b/apps/reactotron-app/src/renderer/RootModals.tsx index 7c5194318..aae2cd870 100644 --- a/apps/reactotron-app/src/renderer/RootModals.tsx +++ b/apps/reactotron-app/src/renderer/RootModals.tsx @@ -4,7 +4,11 @@ import { SubscriptionAddModal, ReactotronContext, StateContext, + DiagnosticModal, } from "reactotron-core-ui" +import configStore from "./config" +import StandaloneContext from "./contexts/Standalone" +import { getServerStatusData } from "./components/SideBar/Sidebar" function RootModals() { const { @@ -17,8 +21,14 @@ function RootModals() { // Subscription Modal isSubscriptionModalOpen, closeSubscriptionModal, + // Diagnostic Modal + isDiagnosticModalOpen, + closeDiagnosticModal, } = useContext(ReactotronContext) const { addSubscription } = useContext(StateContext) + const { serverStatus } = useContext(StandaloneContext) + const { serverStatusText } = getServerStatusData(serverStatus) + const dispatchAction = (action: any) => { sendCommand("state.action.dispatch", { action }) @@ -46,6 +56,17 @@ function RootModals() { addSubscription(path) }} /> + { + closeDiagnosticModal() + }} + port={configStore.get("serverPort") as string} + serverStatusText={serverStatusText} + onRefresh={() => { + window.location.reload() + }} + /> ) } diff --git a/apps/reactotron-app/src/renderer/components/SideBar/Sidebar.tsx b/apps/reactotron-app/src/renderer/components/SideBar/Sidebar.tsx index 6ce196397..26c7d9431 100644 --- a/apps/reactotron-app/src/renderer/components/SideBar/Sidebar.tsx +++ b/apps/reactotron-app/src/renderer/components/SideBar/Sidebar.tsx @@ -1,4 +1,4 @@ -import React from "react" +import React, { useContext } from "react" import { MdReorder, MdAssignment, @@ -14,6 +14,7 @@ import styled from "styled-components" import SideBarButton from "../SideBarButton" import { reactotronLogo } from "../../images" import { ServerStatus } from "../../contexts/Standalone/useStandalone" +import { ReactotronContext } from "reactotron-core-ui" interface SideBarContainerProps { $isOpen: boolean @@ -33,26 +34,27 @@ const Spacer = styled.div` flex: 1; ` -function SideBar({ isOpen, serverStatus }: { isOpen: boolean; serverStatus: ServerStatus }) { - let serverIcon = MdMobiledataOff - let iconColor - let serverText = "Stopped" +export function getServerStatusData(serverStatus: ServerStatus) { + let serverStatusIcon = MdMobiledataOff + let serverStatusColor + let serverStatusText = "Stopped" if (serverStatus === "started") { - serverIcon = MdOutlineMobileFriendly - serverText = "Running" + serverStatusIcon = MdOutlineMobileFriendly + serverStatusText = "Running" } if (serverStatus === "portUnavailable") { - serverIcon = MdWarning - iconColor = "yellow" - serverText = "Port 9090 unavailable" + serverStatusIcon = MdWarning + serverStatusColor = "yellow" + serverStatusText = "Port 9090 unavailable" } - const retryConnection = () => { - if (serverStatus === "portUnavailable") { - // TODO: Reconnect more elegantly than forcing a reload - window.location.reload() - } - } + return { serverStatusIcon, serverStatusText,serverStatusColor } +} + +function SideBar({ isOpen, serverStatus }: { isOpen: boolean; serverStatus: ServerStatus }) { + const {openDiagnosticModal} = useContext(ReactotronContext) + + const { serverStatusColor, serverStatusIcon, serverStatusText } = getServerStatusData(serverStatus) return ( @@ -75,11 +77,11 @@ function SideBar({ isOpen, serverStatus }: { isOpen: boolean; serverStatus: Serv diff --git a/lib/reactotron-core-ui/src/contexts/CustomCommands/useCustomCommands.test.tsx b/lib/reactotron-core-ui/src/contexts/CustomCommands/useCustomCommands.test.tsx index c48c5454d..6bbe88bb0 100644 --- a/lib/reactotron-core-ui/src/contexts/CustomCommands/useCustomCommands.test.tsx +++ b/lib/reactotron-core-ui/src/contexts/CustomCommands/useCustomCommands.test.tsx @@ -20,6 +20,9 @@ function buildContextValues({ addCommandListener = null } = {}) { isSubscriptionModalOpen: false, openSubscriptionModal: jest.fn(), closeSubscriptionModal: jest.fn(), + isDiagnosticModalOpen: false, + openDiagnosticModal: jest.fn(), + closeDiagnosticModal: jest.fn(), } } diff --git a/lib/reactotron-core-ui/src/contexts/ReactNative/useStorybook.test.tsx b/lib/reactotron-core-ui/src/contexts/ReactNative/useStorybook.test.tsx index 07d8df2f4..8088e2b3d 100644 --- a/lib/reactotron-core-ui/src/contexts/ReactNative/useStorybook.test.tsx +++ b/lib/reactotron-core-ui/src/contexts/ReactNative/useStorybook.test.tsx @@ -20,6 +20,9 @@ function buildContextValues({ addCommandListener = null } = {}) { isSubscriptionModalOpen: false, openSubscriptionModal: jest.fn(), closeSubscriptionModal: jest.fn(), + isDiagnosticModalOpen: false, + openDiagnosticModal: jest.fn(), + closeDiagnosticModal: jest.fn(), } } diff --git a/lib/reactotron-core-ui/src/contexts/Reactotron/index.tsx b/lib/reactotron-core-ui/src/contexts/Reactotron/index.tsx index b13291ba7..ecdc2836f 100644 --- a/lib/reactotron-core-ui/src/contexts/Reactotron/index.tsx +++ b/lib/reactotron-core-ui/src/contexts/Reactotron/index.tsx @@ -25,6 +25,12 @@ interface ContextProps extends Props { isSubscriptionModalOpen: boolean openSubscriptionModal: () => void closeSubscriptionModal: () => void + + // Diagnostic Modal + isDiagnosticModalOpen: boolean + openDiagnosticModal: () => void + closeDiagnosticModal: () => void + } const ReactotronContext = React.createContext({ @@ -39,6 +45,9 @@ const ReactotronContext = React.createContext({ isSubscriptionModalOpen: false, openSubscriptionModal: null, closeSubscriptionModal: null, + isDiagnosticModalOpen: false, + openDiagnosticModal: null, + closeDiagnosticModal: null, }) const Provider: FunctionComponent> = ({ @@ -56,6 +65,9 @@ const Provider: FunctionComponent> = ({ isSubscriptionModalOpen, openSubscriptionModal, closeSubscriptionModal, + isDiagnosticModalOpen, + openDiagnosticModal, + closeDiagnosticModal, } = useReactotron() return ( @@ -72,6 +84,9 @@ const Provider: FunctionComponent> = ({ isSubscriptionModalOpen, openSubscriptionModal, closeSubscriptionModal, + isDiagnosticModalOpen, + openDiagnosticModal, + closeDiagnosticModal, }} > {children} diff --git a/lib/reactotron-core-ui/src/contexts/Reactotron/useReactotron.ts b/lib/reactotron-core-ui/src/contexts/Reactotron/useReactotron.ts index 90c58c29b..111f8b7be 100644 --- a/lib/reactotron-core-ui/src/contexts/Reactotron/useReactotron.ts +++ b/lib/reactotron-core-ui/src/contexts/Reactotron/useReactotron.ts @@ -4,6 +4,7 @@ interface ReactotronState { isDispatchModalOpen: boolean dispatchModalInitialAction: string isSubscriptionModalOpen: boolean + isDiagnosticModalOpen: boolean } enum ReactotronActionType { @@ -11,6 +12,8 @@ enum ReactotronActionType { DispatchModalClose = "DISPATCH_CLOSE", SubscriptionModalOpen = "SUBSCRIPTION_OPEN", SubscriptionModalClose = "SUBSCRIPTION_CLOSE", + DiagnosticModalOpen = "DIAGNOSTIC_OPEN", + DiagnosticModalClose = "DIAGNOSTIC_CLOSE", } interface ReactotronAction { @@ -41,6 +44,16 @@ function reactotronReducer(state: ReactotronState, action: ReactotronAction) { ...state, isSubscriptionModalOpen: false, } + case ReactotronActionType.DiagnosticModalOpen: + return { + ...state, + isDiagnosticModalOpen: true, + } + case ReactotronActionType.DiagnosticModalClose: + return { + ...state, + isDiagnosticModalOpen: false, + } default: return state } @@ -51,6 +64,7 @@ function useReactotron() { isDispatchModalOpen: false, dispatchModalInitialAction: "", isSubscriptionModalOpen: false, + isDiagnosticModalOpen: false, }) const openDispatchModal = useCallback((intiialAction: string) => { @@ -78,14 +92,29 @@ function useReactotron() { }) }, []) + const openDiagnosticModal = useCallback(() => { + dispatch({ + type: ReactotronActionType.DiagnosticModalOpen, + }) + }, []) + + const closeDiagnosticModal = useCallback(() => { + dispatch({ + type: ReactotronActionType.DiagnosticModalClose, + }) + }, []) + return { isDispatchModalOpen: state.isDispatchModalOpen, + isDiagnosticModalOpen: state.isDiagnosticModalOpen, dispatchModalInitialAction: state.dispatchModalInitialAction, openDispatchModal, closeDispatchModal, isSubscriptionModalOpen: state.isSubscriptionModalOpen, openSubscriptionModal, closeSubscriptionModal, + openDiagnosticModal, + closeDiagnosticModal } } diff --git a/lib/reactotron-core-ui/src/contexts/State/useSnapshots.test.tsx b/lib/reactotron-core-ui/src/contexts/State/useSnapshots.test.tsx index 5f26215bd..819bb2506 100644 --- a/lib/reactotron-core-ui/src/contexts/State/useSnapshots.test.tsx +++ b/lib/reactotron-core-ui/src/contexts/State/useSnapshots.test.tsx @@ -20,6 +20,9 @@ function buildContextValues({ addCommandListener = null } = {}) { isSubscriptionModalOpen: false, openSubscriptionModal: jest.fn(), closeSubscriptionModal: jest.fn(), + isDiagnosticModalOpen: false, + openDiagnosticModal: jest.fn(), + closeDiagnosticModal: jest.fn(), } } diff --git a/lib/reactotron-core-ui/src/contexts/State/useSubscriptions.test.tsx b/lib/reactotron-core-ui/src/contexts/State/useSubscriptions.test.tsx index 524263bcf..4a9d46911 100644 --- a/lib/reactotron-core-ui/src/contexts/State/useSubscriptions.test.tsx +++ b/lib/reactotron-core-ui/src/contexts/State/useSubscriptions.test.tsx @@ -19,6 +19,9 @@ function buildContextValues({ addCommandListener = null } = {}) { isSubscriptionModalOpen: false, openSubscriptionModal: jest.fn(), closeSubscriptionModal: jest.fn(), + isDiagnosticModalOpen: false, + openDiagnosticModal: jest.fn(), + closeDiagnosticModal: jest.fn(), } } diff --git a/lib/reactotron-core-ui/src/index.ts b/lib/reactotron-core-ui/src/index.ts index c456c1646..627b544ce 100644 --- a/lib/reactotron-core-ui/src/index.ts +++ b/lib/reactotron-core-ui/src/index.ts @@ -26,6 +26,7 @@ import DispatchActionModal from "./modals/DispatchActionModal" import SnapshotRenameModal from "./modals/SnapshotRenameModal" import SubscriptionAddModal from "./modals/SubscriptionAddModal" import TimelineFilterModal from "./modals/TimelineFilterModal" +import DiagnosticModal from "./modals/DiagnosticModal" // Timeline Commands import timelineCommandResolver from "./timelineCommands" @@ -50,6 +51,7 @@ export { SnapshotRenameModal, SubscriptionAddModal, TimelineFilterModal, + DiagnosticModal, Timestamp, TreeView, repairSerialization, diff --git a/lib/reactotron-core-ui/src/modals/DiagnosticModal/DiagnosticModal.story.tsx b/lib/reactotron-core-ui/src/modals/DiagnosticModal/DiagnosticModal.story.tsx new file mode 100644 index 000000000..f0fe8efbb --- /dev/null +++ b/lib/reactotron-core-ui/src/modals/DiagnosticModal/DiagnosticModal.story.tsx @@ -0,0 +1,12 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import React from "react" + +import DiagnosticModal from "./index" + +export default { + title: "Diagnostic Modal", +} + +export const Default = () => ( + {}} onRefresh={() => console.info("Refreshed")} port={"9090"} serverStatusText="started" /> +) diff --git a/lib/reactotron-core-ui/src/modals/DiagnosticModal/index.tsx b/lib/reactotron-core-ui/src/modals/DiagnosticModal/index.tsx new file mode 100644 index 000000000..10b8ae9bb --- /dev/null +++ b/lib/reactotron-core-ui/src/modals/DiagnosticModal/index.tsx @@ -0,0 +1,68 @@ +import React, { FunctionComponent, useCallback, useEffect } from "react" +import styled from "styled-components" + +import Modal, { Keystroke, KeystrokeContainer } from "../../components/Modal" + + +const Heading = styled.div` + font-size: 18px; + margin: 18px 0 10px; + padding-bottom: 2px; + border-bottom: 1px solid ${(props) => props.theme.highlight}; + color: ${(props) => props.theme.foregroundLight}; +` + +const Content = styled.div` + margin-top: 10px; + color: ${(props) => props.theme.string}; +` + +interface Props { + isOpen: boolean + onClose: () => void + port: string + serverStatusText: string + onRefresh: () => void +} + +const DiagnosticModal: FunctionComponent = ({ isOpen, onClose, port, serverStatusText, onRefresh }) => { + + const handleClose = useCallback(() => { + onClose() + }, [onClose]) + + useEffect(() => { + function onKeyPress(e: KeyboardEvent) { + if (e.key === "Enter") { + onRefresh () + } + } + + addEventListener("keypress", onKeyPress); + + return () => { + removeEventListener("keypress", onKeyPress); + } + }, [onRefresh]) + + return ( + + ENTER Refresh Connection + + } + > + Port + {port || "n/a"} + + Server Status + {serverStatusText} + + ) +} + +export default DiagnosticModal