mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-08 07:17:39 +00:00
feat(web): replace machine tree level with filter chips in session list
Single-machine users no longer expand a redundant machine layer; with multiple machines a chip filter bar (persisted, with hover health popup) replaces the collapsible machine headers. Directory groups now render top-level with machine-name suffixes when unfiltered. Also removes the redundant session/project count header text.
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
DEFAULT_SESSION_LIST_MACHINE_FILTER,
|
||||
getInitialSessionListMachineFilter,
|
||||
} from './useSessionListMachineFilter'
|
||||
|
||||
describe('useSessionListMachineFilter helpers', () => {
|
||||
beforeEach(() => {
|
||||
window.localStorage.clear()
|
||||
})
|
||||
|
||||
it('defaults to null (all machines) for missing or blank storage values', () => {
|
||||
expect(getInitialSessionListMachineFilter()).toBe(DEFAULT_SESSION_LIST_MACHINE_FILTER)
|
||||
expect(getInitialSessionListMachineFilter()).toBeNull()
|
||||
|
||||
window.localStorage.setItem('hapi-session-list-machine-filter', '')
|
||||
expect(getInitialSessionListMachineFilter()).toBeNull()
|
||||
|
||||
window.localStorage.setItem('hapi-session-list-machine-filter', ' ')
|
||||
expect(getInitialSessionListMachineFilter()).toBeNull()
|
||||
})
|
||||
|
||||
it('reads a stored machine id', () => {
|
||||
window.localStorage.setItem('hapi-session-list-machine-filter', 'machine-1')
|
||||
|
||||
expect(getInitialSessionListMachineFilter()).toBe('machine-1')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,91 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
// null = "All machines" (no filtering). A string is a machine id, or
|
||||
// UNKNOWN_MACHINE_ID ('__unknown__') for sessions without machine metadata.
|
||||
export type SessionListMachineFilter = string | null
|
||||
|
||||
export const DEFAULT_SESSION_LIST_MACHINE_FILTER: SessionListMachineFilter = null
|
||||
|
||||
function getSessionListMachineFilterStorageKey(): string {
|
||||
return 'hapi-session-list-machine-filter'
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
function parseSessionListMachineFilter(raw: string | null): SessionListMachineFilter {
|
||||
return raw && raw.trim().length > 0 ? raw : DEFAULT_SESSION_LIST_MACHINE_FILTER
|
||||
}
|
||||
|
||||
export function getInitialSessionListMachineFilter(): SessionListMachineFilter {
|
||||
return parseSessionListMachineFilter(safeGetItem(getSessionListMachineFilterStorageKey()))
|
||||
}
|
||||
|
||||
export function useSessionListMachineFilter(): {
|
||||
machineFilter: SessionListMachineFilter
|
||||
setMachineFilter: (filter: SessionListMachineFilter) => void
|
||||
} {
|
||||
const [machineFilter, setMachineFilterState] = useState<SessionListMachineFilter>(getInitialSessionListMachineFilter)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isBrowser()) {
|
||||
return
|
||||
}
|
||||
|
||||
const onStorage = (event: StorageEvent) => {
|
||||
if (event.key !== getSessionListMachineFilterStorageKey()) {
|
||||
return
|
||||
}
|
||||
setMachineFilterState(parseSessionListMachineFilter(event.newValue))
|
||||
}
|
||||
|
||||
window.addEventListener('storage', onStorage)
|
||||
return () => window.removeEventListener('storage', onStorage)
|
||||
}, [])
|
||||
|
||||
const setMachineFilter = useCallback((filter: SessionListMachineFilter) => {
|
||||
setMachineFilterState(filter)
|
||||
|
||||
if (filter === null) {
|
||||
safeRemoveItem(getSessionListMachineFilterStorageKey())
|
||||
} else {
|
||||
safeSetItem(getSessionListMachineFilterStorageKey(), filter)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return { machineFilter, setMachineFilter }
|
||||
}
|
||||
Reference in New Issue
Block a user