github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/web/src/ResourceGroupsContext.tsx (about) 1 import { createContext, PropsWithChildren, useContext, useMemo } from "react" 2 import { AnalyticsAction, AnalyticsType, incr } from "./analytics" 3 import { usePersistentState } from "./BrowserStorage" 4 5 export type GroupState = { expanded: boolean } 6 7 export type GroupsState = { 8 [key: string]: GroupState 9 } 10 11 type ResourceGroupsContext = { 12 groups: GroupsState 13 getGroup: (groupLabel: string) => GroupState 14 toggleGroupExpanded: (groupLabel: string, page: AnalyticsType) => void 15 expandAll: () => void 16 collapseAll: (groups: string[]) => void 17 } 18 19 export const DEFAULT_EXPANDED_STATE = true 20 export const DEFAULT_GROUP_STATE: GroupState = { 21 expanded: DEFAULT_EXPANDED_STATE, 22 } 23 24 const resourceGroupsContext = createContext<ResourceGroupsContext>({ 25 groups: {}, 26 toggleGroupExpanded: () => { 27 console.warn("Resource group context is not set.") 28 }, 29 getGroup: () => { 30 console.warn("Resource group context is not set.") 31 return { ...DEFAULT_GROUP_STATE } 32 }, 33 expandAll: () => void 0, 34 collapseAll: (groups: string[]) => void 0, 35 }) 36 37 export function useResourceGroups(): ResourceGroupsContext { 38 return useContext(resourceGroupsContext) 39 } 40 41 export function ResourceGroupsContextProvider( 42 props: PropsWithChildren<{ initialValuesForTesting?: GroupsState }> 43 ) { 44 const defaultPersistentValue = props.initialValuesForTesting ?? {} 45 const [groups, setGroups] = usePersistentState<GroupsState>( 46 "resource-groups", 47 defaultPersistentValue 48 ) 49 50 const value: ResourceGroupsContext = useMemo(() => { 51 function toggleGroupExpanded(groupLabel: string, page: AnalyticsType) { 52 const currentGroupState = groups[groupLabel] ?? { ...DEFAULT_GROUP_STATE } 53 const nextGroupState = { 54 ...currentGroupState, 55 expanded: !currentGroupState.expanded, 56 } 57 58 const action = nextGroupState.expanded 59 ? AnalyticsAction.Expand 60 : AnalyticsAction.Collapse 61 incr("ui.web.resourceGroup", { action, type: page }) 62 63 setGroups((prevState) => { 64 return { 65 ...prevState, 66 [groupLabel]: nextGroupState, 67 } 68 }) 69 } 70 71 function getGroup(groupLabel: string) { 72 return groups[groupLabel] ?? { ...DEFAULT_GROUP_STATE } 73 } 74 75 // We expand all groups by resetting the collapse state to empty. 76 // 77 // This ensures that even hidden groups are expanded. 78 // 79 // NOTE(nick): expandAll and collapseAll are non-symmetric - they have 80 // very different behavior for groups that are currently hidden, or 81 // for new groups created after the button is clicked. We deliberately 82 // err on the side of expanding. 83 function expandAll() { 84 setGroups({}) // Reset state. 85 } 86 87 // We can collapse all groups currently on-screen. 88 // 89 // If new resources with new names come later, we'll leave them expanded. 90 function collapseAll(groupNames: string[]) { 91 let newState: GroupsState = {} 92 groupNames.forEach((group) => (newState[group] = { expanded: false })) 93 setGroups(newState) 94 } 95 96 return { 97 groups, 98 toggleGroupExpanded, 99 getGroup, 100 expandAll, 101 collapseAll, 102 } 103 }, [groups, setGroups]) 104 105 return ( 106 <resourceGroupsContext.Provider value={value}> 107 {props.children} 108 </resourceGroupsContext.Provider> 109 ) 110 }