Skip to content

Commit 51d9328

Browse files
committed
fix(desktop): enhance error handling and focus management in components
- Updated PageError component to allow pointer events for better user interaction. - Refactored FeedColumn to utilize useFocusable for improved keyboard navigation and removed unnecessary focus state management. - Enhanced highlightElement function to support customizable highlight colors and optimized canvas usage. These changes improve accessibility and user experience by refining error handling and focus management across components. Signed-off-by: Innei <[email protected]>
1 parent 0667b2b commit 51d9328

File tree

4 files changed

+53
-37
lines changed

4 files changed

+53
-37
lines changed

apps/desktop/layer/renderer/src/components/errors/PageError.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const PageErrorFallback: FC<AppErrorFallbackProps> = (props) => {
1111
const { message, stack } = parseError(props.error)
1212
useResetErrorWhenRouteChange(props.resetError)
1313
return (
14-
<div className="bg-theme-background flex w-full flex-col items-center justify-center rounded-md p-2">
14+
<div className="bg-theme-background pointer-events-auto flex w-full flex-col items-center justify-center rounded-md p-2">
1515
<div className="m-auto max-w-prose text-center">
1616
<div className="mb-4">
1717
<i className="i-mgc-bug-cute-re text-4xl text-red-500" />

apps/desktop/layer/renderer/src/modules/timeline-column/index.tsx

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useFocusable } from "@follow/components/common/Focusable/hooks.js"
12
import { ActionButton } from "@follow/components/ui/button/index.js"
23
import { RootPortal } from "@follow/components/ui/portal/index.js"
34
import { Routes } from "@follow/constants"
@@ -105,17 +106,9 @@ export function FeedColumn({ children, className }: PropsWithChildren<{ classNam
105106
const feedColumnShow = useTimelineColumnShow()
106107
const rootContainerElement = useRootContainerElement()
107108

108-
const { setIsFocus } = useRegisterCommands({ setActive, timelineList })
109-
110109
return (
111110
<WindowUnderBlur
112111
data-hide-in-print
113-
onFocus={useCallback(() => {
114-
setIsFocus(true)
115-
}, [setIsFocus])}
116-
onBlur={useCallback(() => {
117-
setIsFocus(false)
118-
}, [setIsFocus])}
119112
className={cn(
120113
"relative flex h-full flex-col pt-2.5",
121114

@@ -128,6 +121,7 @@ export function FeedColumn({ children, className }: PropsWithChildren<{ classNam
128121
}
129122
}, [navigateBackHome])}
130123
>
124+
<CommandsHandler setActive={setActive} timelineList={timelineList} />
131125
<TimelineColumnHeader />
132126
{!feedColumnShow && (
133127
<RootPortal to={rootContainerElement}>
@@ -234,7 +228,7 @@ const SwipeWrapper: FC<{ active: string; children: React.JSX.Element[] }> = memo
234228
},
235229
)
236230

237-
const useRegisterCommands = ({
231+
const CommandsHandler = ({
238232
setActive,
239233
timelineList,
240234
}: {
@@ -266,10 +260,8 @@ const useRegisterCommands = ({
266260
})
267261
}, [activeScope, setActive, timelineList])
268262

269-
const [isFocus, setIsFocus] = useState(false)
263+
const focus = useFocusable()
270264

271-
useConditionalHotkeyScope(HotkeyScope.SubscriptionList, isFocus, true)
272-
return {
273-
setIsFocus,
274-
}
265+
useConditionalHotkeyScope(HotkeyScope.SubscriptionList, focus, true)
266+
return null
275267
}

packages/internal/components/src/common/Focusable/Focusable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const Focusable: Component<
2727
setIsFocusWithIn(true)
2828
focusTargetRef.current = e.target as HTMLElement
2929
if (import.meta.env.DEV) {
30-
highlightElement(containerRef.current!)
30+
highlightElement(containerRef.current!, "14, 165, 233")
3131
}
3232
} else {
3333
setIsFocusWithIn(false)

packages/internal/components/src/common/Focusable/utils.ts

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,28 +36,41 @@ function cubicBezier(t: number, p1x: number, p1y: number, p2x: number, p2y: numb
3636
return sampleCurveY(solveCurveX(t))
3737
}
3838

39-
export function highlightElement(element: HTMLElement | null): void {
39+
// Shared canvas element for all highlight operations
40+
let sharedCanvas: HTMLCanvasElement | null = null
41+
let currentAnimationId: number | null = null
42+
43+
export function highlightElement(element: HTMLElement | null, colorString = "255, 165, 0"): void {
4044
if (!element) return
4145

46+
// Cancel any ongoing animation
47+
if (currentAnimationId !== null) {
48+
cancelAnimationFrame(currentAnimationId)
49+
currentAnimationId = null
50+
}
51+
4252
// Get element's bounding rectangle
4353
const rect: DOMRect = element.getBoundingClientRect()
4454
const padding = 10 // Extra space for shadow effect
4555

46-
// Create canvas
47-
const canvas: HTMLCanvasElement = document.createElement("canvas")
48-
canvas.style.position = "absolute"
49-
canvas.style.top = `${rect.top + window.scrollY - padding}px`
50-
canvas.style.left = `${rect.left + window.scrollX - padding}px`
51-
canvas.width = rect.width + padding * 2
52-
canvas.height = rect.height + padding * 2
56+
// Create or reuse canvas
57+
if (!sharedCanvas) {
58+
sharedCanvas = document.createElement("canvas")
59+
sharedCanvas.style.position = "absolute"
60+
sharedCanvas.style.pointerEvents = "none"
61+
sharedCanvas.style.zIndex = "999999"
62+
sharedCanvas.id = "follow-highlight-canvas"
63+
document.body.append(sharedCanvas)
64+
}
5365

54-
canvas.style.pointerEvents = "none"
55-
canvas.style.zIndex = "999999"
56-
document.body.append(canvas)
66+
// Update canvas position and size
67+
sharedCanvas.style.top = `${rect.top + window.scrollY - padding}px`
68+
sharedCanvas.style.left = `${rect.left + window.scrollX - padding}px`
69+
sharedCanvas.width = rect.width + padding * 2
70+
sharedCanvas.height = rect.height + padding * 2
5771

58-
const ctx: CanvasRenderingContext2D = canvas.getContext("2d")!
72+
const ctx: CanvasRenderingContext2D = sharedCanvas.getContext("2d")!
5973
if (!ctx) {
60-
canvas.remove()
6174
return
6275
}
6376

@@ -105,8 +118,11 @@ export function highlightElement(element: HTMLElement | null): void {
105118
const segmentT: number = (t - segmentIndex * segmentDuration) / segmentDuration
106119

107120
if (segmentIndex >= keyframes.length - 1) {
108-
// Animation complete, remove canvas
109-
canvas.remove()
121+
// Animation complete, hide canvas
122+
if (sharedCanvas) {
123+
sharedCanvas.style.display = "none"
124+
}
125+
currentAnimationId = null
110126
return
111127
}
112128

@@ -132,11 +148,11 @@ export function highlightElement(element: HTMLElement | null): void {
132148
)
133149

134150
// Clear canvas
135-
ctx.clearRect(0, 0, canvas.width, canvas.height)
151+
ctx.clearRect(0, 0, sharedCanvas!.width, sharedCanvas!.height)
136152

137153
// Draw shadow (approximated as thick border with rounded corners)
138154
if (shadowWidth > 0) {
139-
ctx.strokeStyle = `rgba(255, 165, 0, ${shadowOpacity})` // Orange color
155+
ctx.strokeStyle = `rgba(${colorString}, ${shadowOpacity})` // Orange color
140156
ctx.lineWidth = shadowWidth
141157
ctx.beginPath()
142158
ctx.roundRect(
@@ -151,7 +167,7 @@ export function highlightElement(element: HTMLElement | null): void {
151167

152168
// Draw outline (with rounded corners)
153169
if (outlineWidth > 0) {
154-
ctx.strokeStyle = `rgba(255, 165, 0, ${outlineOpacity})`
170+
ctx.strokeStyle = `rgba(${colorString}, ${outlineOpacity})`
155171
ctx.lineWidth = outlineWidth
156172
ctx.beginPath()
157173
ctx.roundRect(
@@ -165,11 +181,19 @@ export function highlightElement(element: HTMLElement | null): void {
165181
}
166182

167183
if (t < 1) {
168-
requestAnimationFrame(animate)
184+
currentAnimationId = requestAnimationFrame(animate)
169185
} else {
170-
canvas.remove()
186+
if (sharedCanvas) {
187+
sharedCanvas.style.display = "none"
188+
}
189+
currentAnimationId = null
171190
}
172191
}
173192

174-
requestAnimationFrame(animate)
193+
// Show canvas before starting animation
194+
if (sharedCanvas) {
195+
sharedCanvas.style.display = "block"
196+
}
197+
198+
currentAnimationId = requestAnimationFrame(animate)
175199
}

0 commit comments

Comments
 (0)