mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
feat(desktop): add browserPanelStore with history/loading/picker state
Per-session in-app mini-browser state: current URL, back/forward history stack (historyIndex with forward-truncation on navigate), loading flag, and element-picker toggle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
53724a6652
commit
633ab63f36
51
desktop/src/stores/browserPanelStore.test.ts
Normal file
51
desktop/src/stores/browserPanelStore.test.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { useBrowserPanelStore } from './browserPanelStore'
|
||||
|
||||
const reset = () => useBrowserPanelStore.setState(useBrowserPanelStore.getInitialState(), true)
|
||||
|
||||
describe('browserPanelStore', () => {
|
||||
beforeEach(reset)
|
||||
|
||||
it('opens a session at a url and records history', () => {
|
||||
useBrowserPanelStore.getState().open('s1', 'http://localhost:5173/')
|
||||
const s = useBrowserPanelStore.getState().bySession['s1']!
|
||||
expect(s.url).toBe('http://localhost:5173/')
|
||||
expect(s.isOpen).toBe(true)
|
||||
expect(s.history).toEqual(['http://localhost:5173/'])
|
||||
expect(s.historyIndex).toBe(0)
|
||||
expect(s.canGoBack).toBe(false)
|
||||
})
|
||||
|
||||
it('navigate pushes history and truncates forward entries', () => {
|
||||
const st = useBrowserPanelStore.getState()
|
||||
st.open('s1', 'http://localhost/a')
|
||||
st.navigate('s1', 'http://localhost/b')
|
||||
st.navigate('s1', 'http://localhost/c')
|
||||
st.goBack('s1')
|
||||
st.navigate('s1', 'http://localhost/d') // 截断 c
|
||||
const s = useBrowserPanelStore.getState().bySession['s1']!
|
||||
expect(s.history).toEqual(['http://localhost/a', 'http://localhost/b', 'http://localhost/d'])
|
||||
expect(s.url).toBe('http://localhost/d')
|
||||
expect(s.canGoForward).toBe(false)
|
||||
})
|
||||
|
||||
it('goBack/goForward move within history without mutating it', () => {
|
||||
const st = useBrowserPanelStore.getState()
|
||||
st.open('s1', 'http://localhost/a')
|
||||
st.navigate('s1', 'http://localhost/b')
|
||||
st.goBack('s1')
|
||||
expect(useBrowserPanelStore.getState().bySession['s1']!.url).toBe('http://localhost/a')
|
||||
expect(useBrowserPanelStore.getState().bySession['s1']!.canGoForward).toBe(true)
|
||||
st.goForward('s1')
|
||||
expect(useBrowserPanelStore.getState().bySession['s1']!.url).toBe('http://localhost/b')
|
||||
})
|
||||
|
||||
it('tracks loading and picker per session', () => {
|
||||
const st = useBrowserPanelStore.getState()
|
||||
st.open('s1', 'http://localhost/a')
|
||||
st.setLoading('s1', true)
|
||||
expect(useBrowserPanelStore.getState().bySession['s1']!.loading).toBe(true)
|
||||
st.setPicker('s1', true)
|
||||
expect(useBrowserPanelStore.getState().bySession['s1']!.pickerActive).toBe(true)
|
||||
})
|
||||
})
|
||||
67
desktop/src/stores/browserPanelStore.ts
Normal file
67
desktop/src/stores/browserPanelStore.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
export type BrowserSessionState = {
|
||||
isOpen: boolean
|
||||
url: string
|
||||
history: string[]
|
||||
historyIndex: number
|
||||
loading: boolean
|
||||
pickerActive: boolean
|
||||
canGoBack: boolean
|
||||
canGoForward: boolean
|
||||
}
|
||||
|
||||
type BrowserPanelState = {
|
||||
bySession: Record<string, BrowserSessionState>
|
||||
open: (sessionId: string, url: string) => void
|
||||
navigate: (sessionId: string, url: string) => void
|
||||
goBack: (sessionId: string) => void
|
||||
goForward: (sessionId: string) => void
|
||||
setLoading: (sessionId: string, loading: boolean) => void
|
||||
setPicker: (sessionId: string, active: boolean) => void
|
||||
close: (sessionId: string) => void
|
||||
}
|
||||
|
||||
const empty = (url: string): BrowserSessionState => ({
|
||||
isOpen: true, url, history: [url], historyIndex: 0,
|
||||
loading: false, pickerActive: false, canGoBack: false, canGoForward: false,
|
||||
})
|
||||
|
||||
const withNav = (s: BrowserSessionState): BrowserSessionState => ({
|
||||
...s,
|
||||
url: s.history[s.historyIndex]!,
|
||||
canGoBack: s.historyIndex > 0,
|
||||
canGoForward: s.historyIndex < s.history.length - 1,
|
||||
})
|
||||
|
||||
export const useBrowserPanelStore = create<BrowserPanelState>((set) => ({
|
||||
bySession: {},
|
||||
open: (sessionId, url) => set((st) => ({
|
||||
bySession: { ...st.bySession, [sessionId]: empty(url) },
|
||||
})),
|
||||
navigate: (sessionId, url) => set((st) => {
|
||||
const cur = st.bySession[sessionId] ?? empty(url)
|
||||
const history = [...cur.history.slice(0, cur.historyIndex + 1), url]
|
||||
return { bySession: { ...st.bySession, [sessionId]: withNav({ ...cur, isOpen: true, history, historyIndex: history.length - 1 }) } }
|
||||
}),
|
||||
goBack: (sessionId) => set((st) => {
|
||||
const cur = st.bySession[sessionId]; if (!cur || cur.historyIndex <= 0) return st
|
||||
return { bySession: { ...st.bySession, [sessionId]: withNav({ ...cur, historyIndex: cur.historyIndex - 1 }) } }
|
||||
}),
|
||||
goForward: (sessionId) => set((st) => {
|
||||
const cur = st.bySession[sessionId]; if (!cur || cur.historyIndex >= cur.history.length - 1) return st
|
||||
return { bySession: { ...st.bySession, [sessionId]: withNav({ ...cur, historyIndex: cur.historyIndex + 1 }) } }
|
||||
}),
|
||||
setLoading: (sessionId, loading) => set((st) => {
|
||||
const cur = st.bySession[sessionId]; if (!cur) return st
|
||||
return { bySession: { ...st.bySession, [sessionId]: { ...cur, loading } } }
|
||||
}),
|
||||
setPicker: (sessionId, active) => set((st) => {
|
||||
const cur = st.bySession[sessionId]; if (!cur) return st
|
||||
return { bySession: { ...st.bySession, [sessionId]: { ...cur, pickerActive: active } } }
|
||||
}),
|
||||
close: (sessionId) => set((st) => {
|
||||
const cur = st.bySession[sessionId]; if (!cur) return st
|
||||
return { bySession: { ...st.bySession, [sessionId]: { ...cur, isOpen: false, pickerActive: false } } }
|
||||
}),
|
||||
}))
|
||||
Loading…
x
Reference in New Issue
Block a user