mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-18 13:23:33 +08:00
feat(desktop): subscribe preview bridge events into browser store
Routes preview://event (navigated/ready) from Rust into the browser store via a new subscribePreviewEvents subscriber; adds title field and setNavigated/setReady reducers to BrowserSessionState; BrowserSurface subscribes on mount to replace M1 optimistic nav-state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
36a3ffff34
commit
5d02f4c4e7
@ -10,6 +10,7 @@ const { bridge } = vi.hoisted(() => ({
|
|||||||
bridge: { open: vi.fn(), navigate: vi.fn(), setBounds: vi.fn(), setVisible: vi.fn(), close: vi.fn() },
|
bridge: { open: vi.fn(), navigate: vi.fn(), setBounds: vi.fn(), setVisible: vi.fn(), close: vi.fn() },
|
||||||
}))
|
}))
|
||||||
vi.mock('../../lib/previewBridge', () => ({ previewBridge: bridge }))
|
vi.mock('../../lib/previewBridge', () => ({ previewBridge: bridge }))
|
||||||
|
vi.mock('@tauri-apps/api/event', () => ({ listen: () => Promise.resolve(() => {}) }))
|
||||||
|
|
||||||
import { BrowserSurface } from './BrowserSurface'
|
import { BrowserSurface } from './BrowserSurface'
|
||||||
import { useBrowserPanelStore } from '../../stores/browserPanelStore'
|
import { useBrowserPanelStore } from '../../stores/browserPanelStore'
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { useEffect, useLayoutEffect, useRef } from 'react'
|
|||||||
import { BrowserAddressBar } from './BrowserAddressBar'
|
import { BrowserAddressBar } from './BrowserAddressBar'
|
||||||
import { computeWebviewBounds } from './computeWebviewBounds'
|
import { computeWebviewBounds } from './computeWebviewBounds'
|
||||||
import { previewBridge } from '../../lib/previewBridge'
|
import { previewBridge } from '../../lib/previewBridge'
|
||||||
|
import { subscribePreviewEvents } from '../../lib/previewEvents'
|
||||||
import { useBrowserPanelStore } from '../../stores/browserPanelStore'
|
import { useBrowserPanelStore } from '../../stores/browserPanelStore'
|
||||||
|
|
||||||
export function BrowserSurface({ sessionId }: { sessionId: string }) {
|
export function BrowserSurface({ sessionId }: { sessionId: string }) {
|
||||||
@ -33,6 +34,13 @@ export function BrowserSurface({ sessionId }: { sessionId: string }) {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [sessionId])
|
}, [sessionId])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let unsub: (() => void) | undefined
|
||||||
|
void subscribePreviewEvents(sessionId).then((u) => { unsub = u })
|
||||||
|
return () => { unsub?.() }
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [sessionId])
|
||||||
|
|
||||||
useEffect(() => () => { previewBridge.setVisible(false) }, [])
|
useEffect(() => () => { previewBridge.setVisible(false) }, [])
|
||||||
|
|
||||||
if (!session) return null
|
if (!session) return null
|
||||||
|
|||||||
18
desktop/src/lib/previewEvents.test.ts
Normal file
18
desktop/src/lib/previewEvents.test.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
const listeners: Record<string, (e: { payload: string }) => void> = {}
|
||||||
|
vi.mock('@tauri-apps/api/event', () => ({
|
||||||
|
listen: (name: string, cb: (e: { payload: string }) => void) => { listeners[name] = cb; return Promise.resolve(() => {}) },
|
||||||
|
}))
|
||||||
|
|
||||||
|
import { subscribePreviewEvents } from './previewEvents'
|
||||||
|
import { useBrowserPanelStore } from '../stores/browserPanelStore'
|
||||||
|
|
||||||
|
describe('subscribePreviewEvents', () => {
|
||||||
|
it('routes navigated event to the store', async () => {
|
||||||
|
useBrowserPanelStore.getState().open('s1', 'http://x/a')
|
||||||
|
await subscribePreviewEvents('s1')
|
||||||
|
listeners['preview://event']!({ payload: JSON.stringify({ v: 1, type: 'navigated', url: 'http://x/c', title: 'C' }) })
|
||||||
|
expect(useBrowserPanelStore.getState().bySession['s1']!.url).toBe('http://x/c')
|
||||||
|
})
|
||||||
|
})
|
||||||
13
desktop/src/lib/previewEvents.ts
Normal file
13
desktop/src/lib/previewEvents.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { listen } from '@tauri-apps/api/event'
|
||||||
|
import { useBrowserPanelStore } from '../stores/browserPanelStore'
|
||||||
|
|
||||||
|
export async function subscribePreviewEvents(sessionId: string): Promise<() => void> {
|
||||||
|
return listen<string>('preview://event', (e) => {
|
||||||
|
let msg: { type?: string; url?: string; title?: string }
|
||||||
|
try { msg = JSON.parse(e.payload) } catch { return }
|
||||||
|
const store = useBrowserPanelStore.getState()
|
||||||
|
if (msg.type === 'navigated' && msg.url) store.setNavigated(sessionId, msg.url, msg.title ?? '')
|
||||||
|
else if (msg.type === 'ready') store.setReady(sessionId)
|
||||||
|
// selection / screenshot 在 M4/M5 接入
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -48,4 +48,13 @@ describe('browserPanelStore', () => {
|
|||||||
st.setPicker('s1', true)
|
st.setPicker('s1', true)
|
||||||
expect(useBrowserPanelStore.getState().bySession['s1']!.pickerActive).toBe(true)
|
expect(useBrowserPanelStore.getState().bySession['s1']!.pickerActive).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('setNavigated updates url/title without growing history', () => {
|
||||||
|
const st = useBrowserPanelStore.getState()
|
||||||
|
st.open('s1', 'http://x/a')
|
||||||
|
st.setNavigated('s1', 'http://x/b', 'B')
|
||||||
|
const s = useBrowserPanelStore.getState().bySession['s1']!
|
||||||
|
expect(s.url).toBe('http://x/b')
|
||||||
|
expect(s.title).toBe('B')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { create } from 'zustand'
|
|||||||
export type BrowserSessionState = {
|
export type BrowserSessionState = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
url: string
|
url: string
|
||||||
|
title: string
|
||||||
history: string[]
|
history: string[]
|
||||||
historyIndex: number
|
historyIndex: number
|
||||||
loading: boolean
|
loading: boolean
|
||||||
@ -20,10 +21,12 @@ type BrowserPanelState = {
|
|||||||
setLoading: (sessionId: string, loading: boolean) => void
|
setLoading: (sessionId: string, loading: boolean) => void
|
||||||
setPicker: (sessionId: string, active: boolean) => void
|
setPicker: (sessionId: string, active: boolean) => void
|
||||||
close: (sessionId: string) => void
|
close: (sessionId: string) => void
|
||||||
|
setNavigated: (sessionId: string, url: string, title: string) => void
|
||||||
|
setReady: (sessionId: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const empty = (url: string): BrowserSessionState => ({
|
const empty = (url: string): BrowserSessionState => ({
|
||||||
isOpen: true, url, history: [url], historyIndex: 0,
|
isOpen: true, url, title: '', history: [url], historyIndex: 0,
|
||||||
loading: false, pickerActive: false, canGoBack: false, canGoForward: false,
|
loading: false, pickerActive: false, canGoBack: false, canGoForward: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -64,4 +67,12 @@ export const useBrowserPanelStore = create<BrowserPanelState>((set) => ({
|
|||||||
const cur = st.bySession[sessionId]; if (!cur) return st
|
const cur = st.bySession[sessionId]; if (!cur) return st
|
||||||
return { bySession: { ...st.bySession, [sessionId]: { ...cur, isOpen: false, pickerActive: false } } }
|
return { bySession: { ...st.bySession, [sessionId]: { ...cur, isOpen: false, pickerActive: false } } }
|
||||||
}),
|
}),
|
||||||
|
setNavigated: (sessionId, url, title) => set((st) => {
|
||||||
|
const cur = st.bySession[sessionId]; if (!cur) return st
|
||||||
|
return { bySession: { ...st.bySession, [sessionId]: { ...cur, url, title, loading: false } } }
|
||||||
|
}),
|
||||||
|
setReady: (sessionId) => set((st) => {
|
||||||
|
const cur = st.bySession[sessionId]; if (!cur) return st
|
||||||
|
return { bySession: { ...st.bySession, [sessionId]: { ...cur, loading: false } } }
|
||||||
|
}),
|
||||||
}))
|
}))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user