feat(web): configure session preview limit (#629)

This commit is contained in:
NightWatcher314
2026-05-17 11:52:30 +08:00
committed by GitHub
parent 86780e94d0
commit 7bb7c7d91a
7 changed files with 322 additions and 4 deletions
@@ -0,0 +1,41 @@
import { beforeEach, describe, expect, it } from 'vitest'
import {
DEFAULT_SESSION_PREVIEW_LIMIT,
MAX_SESSION_PREVIEW_LIMIT,
MIN_SESSION_PREVIEW_LIMIT,
getInitialSessionPreviewLimit,
normalizeSessionPreviewLimit,
} from './useSessionPreviewLimit'
describe('useSessionPreviewLimit helpers', () => {
beforeEach(() => {
window.localStorage.clear()
})
it('falls back to the default for missing or invalid values', () => {
expect(getInitialSessionPreviewLimit()).toBe(DEFAULT_SESSION_PREVIEW_LIMIT)
window.localStorage.setItem('hapi-session-preview-limit', 'invalid')
expect(getInitialSessionPreviewLimit()).toBe(DEFAULT_SESSION_PREVIEW_LIMIT)
window.localStorage.setItem('hapi-session-preview-limit', '12.5')
expect(getInitialSessionPreviewLimit()).toBe(DEFAULT_SESSION_PREVIEW_LIMIT)
})
it('reads valid stored values', () => {
window.localStorage.setItem('hapi-session-preview-limit', '12')
expect(getInitialSessionPreviewLimit()).toBe(12)
})
it('clamps values to the supported range', () => {
expect(normalizeSessionPreviewLimit(0)).toBe(MIN_SESSION_PREVIEW_LIMIT)
expect(normalizeSessionPreviewLimit(120)).toBe(MAX_SESSION_PREVIEW_LIMIT)
window.localStorage.setItem('hapi-session-preview-limit', '0')
expect(getInitialSessionPreviewLimit()).toBe(MIN_SESSION_PREVIEW_LIMIT)
window.localStorage.setItem('hapi-session-preview-limit', '120')
expect(getInitialSessionPreviewLimit()).toBe(MAX_SESSION_PREVIEW_LIMIT)
})
})
+117
View File
@@ -0,0 +1,117 @@
import { useCallback, useEffect, useState } from 'react'
export const DEFAULT_SESSION_PREVIEW_LIMIT = 8
export const MIN_SESSION_PREVIEW_LIMIT = 1
export const MAX_SESSION_PREVIEW_LIMIT = 99
const SESSION_PREVIEW_LIMIT_CHANGED_EVENT = 'hapi-session-preview-limit-changed'
function getSessionPreviewLimitStorageKey(): string {
return 'hapi-session-preview-limit'
}
function isBrowser(): boolean {
return typeof window !== 'undefined' && typeof document !== 'undefined'
}
function safeGetItem(key: string): string | null {
if (!isBrowser()) {
return null
}
try {
return localStorage.getItem(key)
} catch {
return null
}
}
function safeSetItem(key: string, value: string): void {
if (!isBrowser()) {
return
}
try {
localStorage.setItem(key, value)
} catch {
// Ignore storage errors
}
}
function safeRemoveItem(key: string): void {
if (!isBrowser()) {
return
}
try {
localStorage.removeItem(key)
} catch {
// Ignore storage errors
}
}
export function normalizeSessionPreviewLimit(value: number): number {
if (!Number.isInteger(value)) {
return DEFAULT_SESSION_PREVIEW_LIMIT
}
return Math.min(MAX_SESSION_PREVIEW_LIMIT, Math.max(MIN_SESSION_PREVIEW_LIMIT, value))
}
function parseSessionPreviewLimit(raw: string | null): number {
if (raw === null || raw.trim() === '') {
return DEFAULT_SESSION_PREVIEW_LIMIT
}
const value = Number(raw)
return normalizeSessionPreviewLimit(value)
}
export function getInitialSessionPreviewLimit(): number {
return parseSessionPreviewLimit(safeGetItem(getSessionPreviewLimitStorageKey()))
}
export function useSessionPreviewLimit(): {
sessionPreviewLimit: number
setSessionPreviewLimit: (limit: number) => void
} {
const [sessionPreviewLimit, setSessionPreviewLimitState] = useState<number>(getInitialSessionPreviewLimit)
useEffect(() => {
if (!isBrowser()) {
return
}
const onStorage = (event: StorageEvent) => {
if (event.key !== getSessionPreviewLimitStorageKey()) {
return
}
setSessionPreviewLimitState(parseSessionPreviewLimit(event.newValue))
}
const onLocalChange = (event: Event) => {
const next = event instanceof CustomEvent ? event.detail : null
if (typeof next === 'number') {
setSessionPreviewLimitState(normalizeSessionPreviewLimit(next))
}
}
window.addEventListener('storage', onStorage)
window.addEventListener(SESSION_PREVIEW_LIMIT_CHANGED_EVENT, onLocalChange)
return () => {
window.removeEventListener('storage', onStorage)
window.removeEventListener(SESSION_PREVIEW_LIMIT_CHANGED_EVENT, onLocalChange)
}
}, [])
const setSessionPreviewLimit = useCallback((limit: number) => {
const normalized = normalizeSessionPreviewLimit(limit)
setSessionPreviewLimitState(normalized)
if (normalized === DEFAULT_SESSION_PREVIEW_LIMIT) {
safeRemoveItem(getSessionPreviewLimitStorageKey())
} else {
safeSetItem(getSessionPreviewLimitStorageKey(), String(normalized))
}
if (isBrowser()) {
window.dispatchEvent(new CustomEvent(SESSION_PREVIEW_LIMIT_CHANGED_EVENT, { detail: normalized }))
}
}, [])
return { sessionPreviewLimit, setSessionPreviewLimit }
}