Skip to content

Commit 175487e

Browse files
authored
Merge pull request #155 from PRO-Robotech/feature/dev
events headers | event handlers controls
2 parents 1f0d83b + 79ea642 commit 175487e

File tree

4 files changed

+110
-30
lines changed

4 files changed

+110
-30
lines changed

package-lock.json

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"@ant-design/icons": "5.6.0",
2121
"@monaco-editor/react": "4.6.0",
2222
"@originjs/vite-plugin-federation": "1.3.6",
23-
"@prorobotech/openapi-k8s-toolkit": "0.0.1-alpha.149",
23+
"@prorobotech/openapi-k8s-toolkit": "0.0.1-alpha.150",
2424
"@readme/openapi-parser": "4.0.0",
2525
"@reduxjs/toolkit": "2.2.5",
2626
"@tanstack/react-query": "5.62.2",

src/components/organisms/Events/Events.tsx

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable max-lines-per-function */
12
// ------------------------------------------------------------
23
// Simple, self-contained React component implementing:
34
// - WebSocket connection to your events endpoint
@@ -9,7 +10,8 @@
910
// ------------------------------------------------------------
1011

1112
import React, { FC, useCallback, useEffect, useReducer, useRef, useState } from 'react'
12-
import { theme as antdtheme } from 'antd'
13+
import { theme as antdtheme, Flex, Tooltip } from 'antd'
14+
import { ResumeCircleIcon, PauseCircleIcon, LockedIcon, UnlockedIcon } from '@prorobotech/openapi-k8s-toolkit'
1315
import { TScrollMsg, TServerFrame } from './types'
1416
import { eventKey } from './utils'
1517
import { reducer } from './reducer'
@@ -25,6 +27,23 @@ type TEventsProps = {
2527

2628
export const Events: FC<TEventsProps> = ({ wsUrl, pageSize = 50, height }) => {
2729
const { token } = antdtheme.useToken()
30+
31+
// pause behaviour
32+
const [isPaused, setIsPaused] = useState(false)
33+
const pausedRef = useRef(isPaused)
34+
35+
useEffect(() => {
36+
pausedRef.current = isPaused
37+
}, [isPaused])
38+
39+
// ignore REMOVE signal
40+
const [isRemoveIgnored, setIsRemoveIgnored] = useState(true)
41+
const removeIgnoredRef = useRef(isRemoveIgnored)
42+
43+
useEffect(() => {
44+
removeIgnoredRef.current = isRemoveIgnored
45+
}, [isRemoveIgnored])
46+
2847
// Reducer-backed store of events
2948
const [state, dispatch] = useReducer(reducer, { order: [], byKey: {} })
3049

@@ -107,15 +126,17 @@ export const Events: FC<TEventsProps> = ({ wsUrl, pageSize = 50, height }) => {
107126
return
108127
}
109128

110-
if (frame.type === 'ADDED' || frame.type === 'MODIFIED') {
111-
// Live update: insert or replace
112-
dispatch({ type: 'UPSERT', item: frame.item })
113-
return
114-
}
115-
116-
if (frame.type === 'DELETED') {
117-
// Live delete
118-
dispatch({ type: 'REMOVE', key: eventKey(frame.item) })
129+
if (!pausedRef.current) {
130+
if (frame.type === 'ADDED' || frame.type === 'MODIFIED') {
131+
// Live update: insert or replace
132+
dispatch({ type: 'UPSERT', item: frame.item })
133+
return
134+
}
135+
136+
if (!removeIgnoredRef.current && frame.type === 'DELETED') {
137+
// Live delete
138+
dispatch({ type: 'REMOVE', key: eventKey(frame.item) })
139+
}
119140
}
120141
}, [])
121142

@@ -209,14 +230,45 @@ export const Events: FC<TEventsProps> = ({ wsUrl, pageSize = 50, height }) => {
209230
return (
210231
<Styled.Root $maxHeight={height || 640}>
211232
<Styled.Header>
212-
<Styled.Status>
213-
{connStatus === 'connecting' && 'Connecting…'}
214-
{connStatus === 'open' && 'Live'}
215-
{connStatus === 'closed' && 'Reconnecting…'}
216-
{typeof total === 'number' ? ` · ${total} items` : ''}
217-
</Styled.Status>
218-
{hasMore ? <span>Scroll to load older events…</span> : <span>No more events.</span>}
219-
{lastError && <span aria-live="polite">· {lastError}</span>}
233+
<Styled.HeaderLeftSide>
234+
<Flex justify="start" align="center" gap={10}>
235+
<Styled.CursorPointerDiv
236+
onClick={() => {
237+
if (isPaused) {
238+
setIsPaused(false)
239+
} else {
240+
setIsPaused(true)
241+
}
242+
}}
243+
>
244+
{isPaused ? <ResumeCircleIcon /> : <PauseCircleIcon />}
245+
</Styled.CursorPointerDiv>
246+
<Styled.StatusText>
247+
{isPaused && 'Streaming paused'}
248+
{!isPaused && connStatus === 'connecting' && 'Connecting…'}
249+
{!isPaused && connStatus === 'open' && 'Streaming events...'}
250+
{!isPaused && connStatus === 'closed' && 'Reconnecting…'}
251+
</Styled.StatusText>
252+
</Flex>
253+
</Styled.HeaderLeftSide>
254+
<Styled.HeaderRightSide $colorTextDescription={token.colorTextDescription}>
255+
{!hasMore && <div>No more events · </div>}
256+
{typeof total === 'number' ? <div>Loaded {total} events</div> : ''}
257+
{lastError && <span aria-live="polite"> · {lastError}</span>}
258+
<Tooltip
259+
title={
260+
<div>
261+
<div>{isRemoveIgnored ? 'Handle REMOVE signals' : 'Ignore REMOVE signals'}</div>
262+
<Flex justify="end">Locked means ignore</Flex>
263+
</div>
264+
}
265+
placement="left"
266+
>
267+
<Styled.CursorPointerDiv onClick={() => setIsRemoveIgnored(!isRemoveIgnored)}>
268+
{isRemoveIgnored ? <LockedIcon size={16} /> : <UnlockedIcon size={16} />}
269+
</Styled.CursorPointerDiv>
270+
</Tooltip>
271+
</Styled.HeaderRightSide>
220272
</Styled.Header>
221273

222274
{/* Scrollable list of event rows */}

src/components/organisms/Events/styled.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,41 @@ const Root = styled.div<TRootProps>`
1515
position: relative;
1616
`
1717

18-
const Header = styled.header`
19-
padding: 12px 16px;
18+
const Header = styled.div`
2019
display: flex;
2120
align-items: center;
22-
justify-content: space-between;
21+
gap: 16px;
22+
align-self: stretch;
23+
margin-bottom: 16px;
24+
padding-left: 19px;
2325
`
2426

25-
const Status = styled.span`
26-
font-size: 12px;
27-
color: #6b7280;
27+
const HeaderLeftSide = styled.div`
28+
display: flex;
29+
align-items: center;
30+
gap: 10px;
31+
flex: 1 0 0;
32+
`
33+
34+
const CursorPointerDiv = styled.div`
35+
cursor: pointer;
36+
user-select: none;
37+
`
38+
39+
const StatusText = styled.div`
40+
font-size: 16px;
41+
line-height: 24px; /* 150% */
42+
`
43+
44+
type THeaderRightSideProps = {
45+
$colorTextDescription: string
46+
}
47+
48+
const HeaderRightSide = styled.div<THeaderRightSideProps>`
49+
display: flex;
50+
gap: 4px;
51+
text-align: right;
52+
color: ${({ $colorTextDescription }) => $colorTextDescription};
2853
`
2954

3055
const List = styled.div`
@@ -65,7 +90,10 @@ const Sentinel = styled.div`
6590
export const Styled = {
6691
Root,
6792
Header,
68-
Status,
93+
HeaderLeftSide,
94+
CursorPointerDiv,
95+
StatusText,
96+
HeaderRightSide,
6997
Timeline,
7098
List,
7199
Sentinel,

0 commit comments

Comments
 (0)