mirror of
https://github.com/NanmiCoder/claude-code-haha.git
synced 2026-10-10 20:03:13 +08:00
fix(desktop): align native preview with app zoom (#1042)
This commit is contained in:
@@ -28,6 +28,7 @@ import { getDefaultBaseUrl, setBaseUrl } from '../../api/client'
|
||||
import { useBrowserPanelStore } from '../../stores/browserPanelStore'
|
||||
import { useWorkspacePanelStore } from '../../stores/workspacePanelStore'
|
||||
import { useOverlayStore } from '../../stores/overlayStore'
|
||||
import { useSettingsStore } from '../../stores/settingsStore'
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
@@ -37,6 +38,7 @@ afterEach(() => {
|
||||
// browserPanelStore.open() now also opens the unified workbench; keep it isolated.
|
||||
useWorkspacePanelStore.setState(useWorkspacePanelStore.getInitialState(), true)
|
||||
useOverlayStore.setState(useOverlayStore.getInitialState(), true)
|
||||
useSettingsStore.setState({ uiZoom: 1 })
|
||||
setBaseUrl(getDefaultBaseUrl())
|
||||
})
|
||||
|
||||
@@ -49,6 +51,40 @@ describe('BrowserSurface', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('rescales native preview bounds when app zoom changes', async () => {
|
||||
vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({
|
||||
left: 180,
|
||||
top: 150,
|
||||
width: 420,
|
||||
height: 300,
|
||||
} as DOMRect)
|
||||
useSettingsStore.setState({ uiZoom: 1.25 })
|
||||
useBrowserPanelStore.getState().open('s1', 'http://localhost:5173/')
|
||||
render(<BrowserSurface sessionId="s1" />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(bridge.open).toHaveBeenCalledWith('http://localhost:5173/', {
|
||||
x: 225,
|
||||
y: 187.5,
|
||||
width: 525,
|
||||
height: 375,
|
||||
})
|
||||
})
|
||||
|
||||
act(() => {
|
||||
useSettingsStore.setState({ uiZoom: 1.5 })
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(bridge.setBounds).toHaveBeenLastCalledWith({
|
||||
x: 270,
|
||||
y: 225,
|
||||
width: 630,
|
||||
height: 450,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('waits for local preview URLs before opening the native preview', async () => {
|
||||
const url = 'http://127.0.0.1:59028/preview-fs/s1/66estmutl_files/index.html'
|
||||
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response(null, { status: 200 }))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useLayoutEffect, useRef } from 'react'
|
||||
import { useCallback, useEffect, useLayoutEffect, useRef } from 'react'
|
||||
import { Camera, Loader2, Minus, MousePointer2, Plus, RotateCcw } from 'lucide-react'
|
||||
import { BrowserAddressBar } from './BrowserAddressBar'
|
||||
import { computeWebviewBounds } from './computeWebviewBounds'
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
useBrowserPanelStore,
|
||||
} from '../../stores/browserPanelStore'
|
||||
import { useOverlayStore } from '../../stores/overlayStore'
|
||||
import { useSettingsStore } from '../../stores/settingsStore'
|
||||
|
||||
const LOCAL_PREVIEW_PATH_PREFIXES = ['/preview-fs/', '/local-file/']
|
||||
const LOCAL_PREVIEW_READY_TIMEOUT_MS = 2500
|
||||
@@ -68,6 +69,7 @@ export function BrowserSurface({ sessionId }: { sessionId: string }) {
|
||||
const requestedUrlRef = useRef<string | null>(null)
|
||||
const hasNativePreviewRef = useRef(false)
|
||||
const session = useBrowserPanelStore((s) => s.bySession[sessionId])
|
||||
const appZoom = useSettingsStore((s) => s.uiZoom)
|
||||
const store = useBrowserPanelStore.getState()
|
||||
const overlayCount = useOverlayStore((s) => s.count)
|
||||
const previewZoom = session?.zoom ?? DEFAULT_BROWSER_ZOOM
|
||||
@@ -75,11 +77,11 @@ export function BrowserSurface({ sessionId }: { sessionId: string }) {
|
||||
const canZoomOut = previewZoom > MIN_BROWSER_ZOOM
|
||||
const canZoomIn = previewZoom < MAX_BROWSER_ZOOM
|
||||
|
||||
const reportBounds = () => {
|
||||
const reportBounds = useCallback(() => {
|
||||
const el = hostRef.current
|
||||
if (!el) return
|
||||
previewBridge.setBounds(computeWebviewBounds(el.getBoundingClientRect()))
|
||||
}
|
||||
previewBridge.setBounds(computeWebviewBounds(el.getBoundingClientRect(), appZoom))
|
||||
}, [appZoom])
|
||||
|
||||
const loadNativePreview = (
|
||||
url: string,
|
||||
@@ -118,7 +120,7 @@ export function BrowserSurface({ sessionId }: { sessionId: string }) {
|
||||
const el = hostRef.current
|
||||
hasNativePreviewRef.current = true
|
||||
if (el) {
|
||||
await previewBridge.open(url, computeWebviewBounds(el.getBoundingClientRect()))
|
||||
await previewBridge.open(url, computeWebviewBounds(el.getBoundingClientRect(), appZoom))
|
||||
} else {
|
||||
await previewBridge.navigate(url)
|
||||
}
|
||||
@@ -168,8 +170,11 @@ export function BrowserSurface({ sessionId }: { sessionId: string }) {
|
||||
ro.observe(el)
|
||||
window.addEventListener('resize', reportBounds)
|
||||
return () => { ro.disconnect(); window.removeEventListener('resize', reportBounds) }
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [sessionId])
|
||||
}, [reportBounds, sessionId])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
reportBounds()
|
||||
}, [reportBounds, sessionId])
|
||||
|
||||
useEffect(() => {
|
||||
let unsub: (() => void) | undefined
|
||||
|
||||
@@ -7,6 +7,11 @@ describe('computeWebviewBounds', () => {
|
||||
expect(computeWebviewBounds(rect)).toEqual({ x: 100.4, y: 50.6, width: 800.2, height: 600.9 })
|
||||
})
|
||||
|
||||
it('maps zoomed CSS coordinates back to the native parent coordinate space', () => {
|
||||
const rect = { left: 180, top: 150, width: 420, height: 300 } as DOMRect
|
||||
expect(computeWebviewBounds(rect, 1.25)).toEqual({ x: 225, y: 187.5, width: 525, height: 375 })
|
||||
})
|
||||
|
||||
it('clamps negative/zero sizes to 0', () => {
|
||||
const rect = { left: -5, top: -5, width: -10, height: 0 } as DOMRect
|
||||
expect(computeWebviewBounds(rect)).toEqual({ x: -5, y: -5, width: 0, height: 0 })
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
export type WebviewBounds = { x: number; y: number; width: number; height: number }
|
||||
|
||||
export function computeWebviewBounds(rect: Pick<DOMRect, 'left' | 'top' | 'width' | 'height'>): WebviewBounds {
|
||||
export function computeWebviewBounds(
|
||||
rect: Pick<DOMRect, 'left' | 'top' | 'width' | 'height'>,
|
||||
appZoom = 1,
|
||||
): WebviewBounds {
|
||||
return {
|
||||
x: rect.left,
|
||||
y: rect.top,
|
||||
width: Math.max(0, rect.width),
|
||||
height: Math.max(0, rect.height),
|
||||
x: rect.left * appZoom,
|
||||
y: rect.top * appZoom,
|
||||
width: Math.max(0, rect.width * appZoom),
|
||||
height: Math.max(0, rect.height * appZoom),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user