Skip to content

Commit 9f0ae17

Browse files
committed
feat(reactotron-app): add delete button for disconnected connections
1 parent 9dcedf2 commit 9f0ae17

7 files changed

Lines changed: 195 additions & 95 deletions

File tree

.gitignore

Lines changed: 72 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,72 @@
1-
.vscode
2-
jsconfig.json
3-
4-
# Logs
5-
logs
6-
*.log
7-
8-
# Runtime data
9-
pids
10-
*.pid
11-
*.seed
12-
13-
# Directory for instrumented libs generated by jscoverage/JSCover
14-
lib-cov
15-
16-
# Coverage directory used by tools like istanbul
17-
coverage
18-
19-
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
20-
.grunt
21-
22-
# node-waf configuration
23-
.lock-wscript
24-
25-
# Compiled binary addons (http://nodejs.org/api/addons.html)
26-
build/Release
27-
.eslintcache
28-
29-
# Dependency directory
30-
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
31-
node_modules
32-
yarn-error.log
33-
34-
# OSX
35-
.DS_Store
36-
37-
# flow-typed
38-
flow-typed/npm/*
39-
!flow-typed/npm/module_vx.x.x.js
40-
41-
# App packaged
42-
release
43-
src/main.prod.js
44-
src/main.prod.js.map
45-
src/renderer.prod.js
46-
src/renderer.prod.js.map
47-
src/style.css
48-
src/style.css.map
49-
dist
50-
dll
51-
main.js
52-
main.js.map
53-
54-
.idea
55-
npm-debug.log.*
56-
57-
TODO
58-
59-
.npmrc
60-
*.p12
61-
62-
# Yarn 3
63-
.yarn/*
64-
!.yarn/patches
65-
!.yarn/plugins
66-
!.yarn/releases
67-
!.yarn/sdks
68-
!.yarn/versions
69-
.nx/cache
1+
.vscode
2+
jsconfig.json
3+
4+
# Logs
5+
logs
6+
*.log
7+
8+
# Runtime data
9+
pids
10+
*.pid
11+
*.seed
12+
13+
# Directory for instrumented libs generated by jscoverage/JSCover
14+
lib-cov
15+
16+
# Coverage directory used by tools like istanbul
17+
coverage
18+
19+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
20+
.grunt
21+
22+
# node-waf configuration
23+
.lock-wscript
24+
25+
# Compiled binary addons (http://nodejs.org/api/addons.html)
26+
build/Release
27+
.eslintcache
28+
29+
# Dependency directory
30+
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
31+
node_modules
32+
yarn-error.log
33+
34+
# OSX
35+
.DS_Store
36+
37+
# flow-typed
38+
flow-typed/npm/*
39+
!flow-typed/npm/module_vx.x.x.js
40+
41+
# App packaged
42+
release
43+
src/main.prod.js
44+
src/main.prod.js.map
45+
src/renderer.prod.js
46+
src/renderer.prod.js.map
47+
src/style.css
48+
src/style.css.map
49+
dist
50+
dll
51+
main.js
52+
main.js.map
53+
54+
.idea
55+
npm-debug.log.*
56+
57+
TODO
58+
59+
.npmrc
60+
*.p12
61+
62+
# Yarn 3
63+
.yarn/*
64+
!.yarn/patches
65+
!.yarn/plugins
66+
!.yarn/releases
67+
!.yarn/sdks
68+
!.yarn/versions
69+
.nx/cache
70+
temp_auto_push.bat
71+
temp_interactive_push.bat
72+
config.bat

apps/example-app/babel.config.js

Lines changed: 19 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/reactotron-app/src/renderer/components/ConnectionSelector/index.tsx

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useMemo } from "react"
22
import styled from "styled-components"
3-
import { MdCheckCircle as Checkmark } from "react-icons/md"
3+
import { MdCheckCircle as Checkmark, MdClose as CloseIcon } from "react-icons/md"
44

55
import {
66
getIcon,
@@ -10,13 +10,18 @@ import {
1010
} from "../../util/connectionHelpers"
1111
import { Connection } from "../../contexts/Standalone/useStandalone"
1212

13-
const Container = styled.div`
13+
interface ContainerProps {
14+
$isConnected: boolean
15+
}
16+
17+
const Container = styled.div<ContainerProps>`
1418
display: flex;
1519
flex-direction: row;
1620
flex: 0 0 auto;
1721
cursor: pointer;
1822
margin-right: 20px;
1923
align-items: center;
24+
opacity: ${(props) => (props.$isConnected ? 1 : 0.5)};
2025
`
2126

2227
const IconContainer = styled.div`
@@ -30,22 +35,57 @@ const CheckmarkContainer = styled.div`
3035
color: green;
3136
`
3237

38+
interface StatusDotProps {
39+
$isConnected: boolean
40+
}
41+
42+
const StatusDot = styled.div<StatusDotProps>`
43+
position: absolute;
44+
top: -2px;
45+
left: -2px;
46+
width: 10px;
47+
height: 10px;
48+
border-radius: 50%;
49+
background-color: ${(props) => (props.$isConnected ? "#50c878" : "#ff6b6b")};
50+
border: 2px solid ${(props) => props.theme.subtleLine};
51+
`
52+
3353
const InfoContainer = styled.div`
3454
margin-left: 10px;
3555
`
3656

57+
const DeleteButton = styled.div`
58+
margin-left: 8px;
59+
width: 20px;
60+
height: 20px;
61+
border-radius: 50%;
62+
background-color: #ff6b6b;
63+
display: flex;
64+
align-items: center;
65+
justify-content: center;
66+
cursor: pointer;
67+
flex-shrink: 0;
68+
69+
&:hover {
70+
background-color: #ff4444;
71+
}
72+
`
73+
3774
interface ConnectionSelectorProps {
3875
selectedConnection: Connection | null
3976
connection: Connection
4077
onClick: () => void
78+
onDelete?: (clientId: string) => void
4179
}
4280

4381
export default function ConnectionSelector({
4482
selectedConnection,
4583
connection,
4684
onClick,
85+
onDelete,
4786
}: ConnectionSelectorProps) {
4887
const isSelected = selectedConnection && selectedConnection.clientId === connection.clientId
88+
const isConnected = connection.connected
4989

5090
const [ConnectionIcon, platformName, platformDetails, connectionName] = useMemo(() => {
5191
return [
@@ -56,10 +96,18 @@ export default function ConnectionSelector({
5696
]
5797
}, [connection])
5898

99+
const handleDelete = (e: React.MouseEvent) => {
100+
e.stopPropagation()
101+
if (onDelete && !isConnected) {
102+
onDelete(connection.clientId)
103+
}
104+
}
105+
59106
return (
60-
<Container onClick={onClick}>
107+
<Container onClick={onClick} $isConnected={isConnected}>
61108
<IconContainer>
62109
<ConnectionIcon size={32} />
110+
<StatusDot $isConnected={isConnected} title={isConnected ? "Connected" : "Disconnected"} />
63111
{isSelected && (
64112
<CheckmarkContainer>
65113
<Checkmark />
@@ -72,6 +120,11 @@ export default function ConnectionSelector({
72120
{platformName} {platformDetails}
73121
</div>
74122
</InfoContainer>
123+
{!isConnected && onDelete && (
124+
<DeleteButton onClick={handleDelete} title="Remove disconnected connection">
125+
<CloseIcon size={12} color="white" />
126+
</DeleteButton>
127+
)}
75128
</Container>
76129
)
77130
}

apps/reactotron-app/src/renderer/components/Footer/Stateless.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ function renderExpanded(
8282
serverStatus: ServerStatus,
8383
connections: Connection[],
8484
selectedConnection: Connection | null,
85-
onChangeConnection: (clientId: string | null) => void
85+
onChangeConnection: (clientId: string | null) => void,
86+
onDeleteConnection?: (clientId: string) => void
8687
) {
8788
return (
8889
<ConnectionContainer>
@@ -92,6 +93,7 @@ function renderExpanded(
9293
selectedConnection={selectedConnection}
9394
connection={c}
9495
onClick={() => onChangeConnection(c.clientId)}
96+
onDelete={onDeleteConnection}
9597
/>
9698
))}
9799
</ConnectionContainer>
@@ -111,10 +113,12 @@ function renderCollapsed(
111113
connections: Connection[],
112114
selectedConnection: Connection | null
113115
) {
116+
const activeConnections = connections.filter((c) => c.connected).length
117+
const totalConnections = connections.length
114118
return (
115119
<>
116120
<ConnectionInfo>
117-
port {config.get("serverPort")} | {connections.length} connections
121+
port {config.get("serverPort")} | {activeConnections}/{totalConnections} connections
118122
</ConnectionInfo>
119123
{serverStatus === "portUnavailable" && (
120124
<ConnectionInfo>Port 9090 unavailable.</ConnectionInfo>
@@ -134,6 +138,7 @@ interface Props {
134138
isOpen: boolean
135139
setIsOpen: (isOpen: boolean) => void
136140
onChangeConnection: (clientId: string | null) => void
141+
onDeleteConnection?: (clientId: string) => void
137142
mcpStatus: McpStatus
138143
mcpPort: number | null
139144
onToggleMcp: () => void
@@ -146,16 +151,22 @@ function Header({
146151
isOpen,
147152
setIsOpen,
148153
onChangeConnection,
154+
onDeleteConnection,
149155
mcpStatus,
150156
mcpPort,
151157
onToggleMcp,
152158
}: Props) {
153-
const renderMethod = isOpen ? renderExpanded : renderCollapsed
159+
const renderContent = () => {
160+
if (isOpen) {
161+
return renderExpanded(serverStatus, connections, selectedConnection, onChangeConnection, onDeleteConnection)
162+
}
163+
return renderCollapsed(serverStatus, connections, selectedConnection)
164+
}
154165

155166
return (
156167
<Container>
157168
<ContentContainer onClick={() => !isOpen && setIsOpen(true)} $isOpen={isOpen}>
158-
{renderMethod(serverStatus, connections, selectedConnection, onChangeConnection)}
169+
{renderContent()}
159170
<McpButton
160171
$active={mcpStatus === "started"}
161172
onClick={(e) => { e.stopPropagation(); onToggleMcp() }}

apps/reactotron-app/src/renderer/components/Footer/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import StandaloneContext from "../../contexts/Standalone"
55
import Footer from "./Stateless"
66

77
export default function ConnectedFooter() {
8-
const { serverStatus, connections, selectedConnection, selectConnection, mcpStatus, mcpPort, toggleMcp } =
8+
const { serverStatus, connections, selectedConnection, selectConnection, deleteConnection, mcpStatus, mcpPort, toggleMcp } =
99
useContext(StandaloneContext)
1010
const [isOpen, setIsOpen] = useState(false)
1111

@@ -15,6 +15,7 @@ export default function ConnectedFooter() {
1515
connections={connections}
1616
selectedConnection={selectedConnection}
1717
onChangeConnection={selectConnection}
18+
onDeleteConnection={deleteConnection}
1819
isOpen={isOpen}
1920
setIsOpen={setIsOpen}
2021
mcpStatus={mcpStatus}

0 commit comments

Comments
 (0)