Skip to content

Commit 68e39a0

Browse files
MrFlashAccountFrizi
authored andcommitted
Refactor Categories Provider (#13064)
1 parent 06c1c2f commit 68e39a0

File tree

4 files changed

+110
-97
lines changed

4 files changed

+110
-97
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* @file
3+
*
4+
* Provider for the categories.
5+
*/
6+
7+
import { useEventCallback } from '#/hooks/eventCallbackHooks'
8+
import { useOffline } from '#/hooks/offlineHooks'
9+
import { useSearchParamsState } from '#/hooks/searchParamsStateHooks'
10+
import { pickBackend, useLocalBackend, useRemoteBackend } from '#/providers/BackendProvider'
11+
import type { ReactNode } from 'react'
12+
import type { Category, CategoryId } from './Category'
13+
import {
14+
CategoriesContext,
15+
categoryIdStore,
16+
useCategories,
17+
type CategoriesContextValue,
18+
} from './categoriesHooks'
19+
20+
/**
21+
* Props for the {@link CategoriesProvider}.
22+
*/
23+
export interface CategoriesProviderProps {
24+
readonly children: ReactNode | ((contextValue: CategoriesContextValue) => ReactNode)
25+
readonly onCategoryChange?: (previousCategory: Category | null, newCategory: Category) => void
26+
}
27+
28+
/**
29+
* Provider for the categories.
30+
*/
31+
export function CategoriesProvider(props: CategoriesProviderProps): React.JSX.Element {
32+
const { children, onCategoryChange = () => {} } = props
33+
34+
const { cloudCategories, localCategories, findCategoryById } = useCategories()
35+
const localBackend = useLocalBackend()
36+
const remoteBackend = useRemoteBackend()
37+
const { isOffline } = useOffline()
38+
39+
const [categoryId, privateSetCategoryId, privateResetCategoryId] =
40+
useSearchParamsState<CategoryId>(
41+
'driveCategory',
42+
() => {
43+
const savedId = categoryIdStore.getState().categoryId
44+
45+
if (savedId != null && findCategoryById(savedId) != null) {
46+
return savedId
47+
}
48+
49+
if (isOffline && localBackend != null) {
50+
return 'local'
51+
}
52+
53+
return localBackend != null ? 'local' : 'cloud'
54+
},
55+
// This is safe, because we enshure the type inside the function
56+
// eslint-disable-next-line no-restricted-syntax
57+
(value): value is CategoryId => findCategoryById(value as CategoryId) != null,
58+
)
59+
60+
const setCategoryId = useEventCallback((nextCategoryId: CategoryId) => {
61+
const previousCategory = findCategoryById(categoryId)
62+
privateSetCategoryId(nextCategoryId)
63+
categoryIdStore.setState({
64+
categoryId: nextCategoryId,
65+
})
66+
67+
// This is safe, because we know that the result will have the correct type.
68+
// eslint-disable-next-line no-restricted-syntax
69+
onCategoryChange(previousCategory, findCategoryById(nextCategoryId) as Category)
70+
})
71+
72+
const resetCategoryId = useEventCallback((replace?: boolean) => {
73+
privateResetCategoryId(replace)
74+
categoryIdStore.setState({
75+
categoryId: null,
76+
})
77+
})
78+
79+
const category = findCategoryById(categoryId)
80+
81+
// This usually doesn't happen but if so,
82+
// We reset the category to the default.
83+
if (category == null) {
84+
resetCategoryId(true)
85+
return <></>
86+
}
87+
88+
const backend = pickBackend(category, remoteBackend, localBackend)
89+
90+
const contextValue = {
91+
cloudCategories,
92+
localCategories,
93+
category,
94+
setCategory: setCategoryId,
95+
resetCategory: resetCategoryId,
96+
associatedBackend: backend,
97+
} satisfies CategoriesContextValue
98+
99+
return (
100+
<CategoriesContext.Provider value={contextValue}>
101+
{typeof children === 'function' ? children(contextValue) : children}
102+
</CategoriesContext.Provider>
103+
)
104+
}

app/gui/src/dashboard/layouts/Drive/Categories/categoriesHooks.tsx

Lines changed: 4 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ import RecentIcon from '#/assets/recent.svg'
1111
import { useUser } from '#/providers/AuthProvider'
1212

1313
import { useEventCallback } from '#/hooks/eventCallbackHooks'
14-
import { useOffline } from '#/hooks/offlineHooks'
15-
import { useSearchParamsState } from '#/hooks/searchParamsStateHooks'
16-
import { pickBackend, useLocalBackend, useRemoteBackend } from '#/providers/BackendProvider'
14+
import { useLocalBackend } from '#/providers/BackendProvider'
1715
import { useLocalStorageState } from '#/providers/LocalStorageProvider'
1816
import { useText } from '#/providers/TextProvider'
1917
import type Backend from '#/services/Backend'
@@ -22,7 +20,6 @@ import { newDirectoryId } from '#/services/LocalBackend'
2220
import { organizationIdToDirectoryId } from '#/services/RemoteBackend'
2321
import { getFileName } from '#/utilities/fileInfo'
2422
import LocalStorage from '#/utilities/LocalStorage'
25-
import type { ReactNode } from 'react'
2623
import { createContext, useContext } from 'react'
2724
import invariant from 'tiny-invariant'
2825
import { z } from 'zod'
@@ -60,7 +57,7 @@ interface CategoryIdStoreState {
6057
readonly categoryId: CategoryId | null
6158
}
6259

63-
const categoryIdStore = createStore<CategoryIdStoreState>()(
60+
export const categoryIdStore = createStore<CategoryIdStoreState>()(
6461
persist(
6562
(): CategoryIdStoreState => ({
6663
categoryId: null,
@@ -276,7 +273,6 @@ export type CategoriesResult = ReturnType<typeof useCategories>
276273
/**
277274
* List of all categories.
278275
*/
279-
// eslint-disable-next-line react-refresh/only-export-components
280276
export function useCategories() {
281277
const cloudCategories = useCloudCategoryList()
282278
const localCategories = useLocalCategoryList()
@@ -297,7 +293,7 @@ export function useCategories() {
297293
/**
298294
* Context value for the categories.
299295
*/
300-
interface CategoriesContextValue {
296+
export interface CategoriesContextValue {
301297
readonly cloudCategories: CloudCategoryResult
302298
readonly localCategories: LocalCategoryResult
303299
readonly category: Category
@@ -306,98 +302,11 @@ interface CategoriesContextValue {
306302
readonly associatedBackend: Backend
307303
}
308304

309-
const CategoriesContext = createContext<CategoriesContextValue | null>(null)
310-
311-
/**
312-
* Props for the {@link CategoriesProvider}.
313-
*/
314-
export interface CategoriesProviderProps {
315-
readonly children: ReactNode | ((contextValue: CategoriesContextValue) => ReactNode)
316-
readonly onCategoryChange?: (previousCategory: Category | null, newCategory: Category) => void
317-
}
318-
319-
/**
320-
* Provider for the categories.
321-
*/
322-
export function CategoriesProvider(props: CategoriesProviderProps): React.JSX.Element {
323-
const { children, onCategoryChange = () => {} } = props
324-
325-
const { cloudCategories, localCategories, findCategoryById } = useCategories()
326-
const localBackend = useLocalBackend()
327-
const remoteBackend = useRemoteBackend()
328-
const { isOffline } = useOffline()
329-
330-
const [categoryId, privateSetCategoryId, privateResetCategoryId] =
331-
useSearchParamsState<CategoryId>(
332-
'driveCategory',
333-
() => {
334-
const savedId = categoryIdStore.getState().categoryId
335-
336-
if (savedId != null && findCategoryById(savedId) != null) {
337-
return savedId
338-
}
339-
340-
if (isOffline && localBackend != null) {
341-
return 'local'
342-
}
343-
344-
return localBackend != null ? 'local' : 'cloud'
345-
},
346-
// This is safe, because we enshure the type inside the function
347-
// eslint-disable-next-line no-restricted-syntax
348-
(value): value is CategoryId => findCategoryById(value as CategoryId) != null,
349-
)
350-
351-
const setCategoryId = useEventCallback((nextCategoryId: CategoryId) => {
352-
const previousCategory = findCategoryById(categoryId)
353-
privateSetCategoryId(nextCategoryId)
354-
categoryIdStore.setState({
355-
categoryId: nextCategoryId,
356-
})
357-
358-
// This is safe, because we know that the result will have the correct type.
359-
// eslint-disable-next-line no-restricted-syntax
360-
onCategoryChange(previousCategory, findCategoryById(nextCategoryId) as Category)
361-
})
362-
363-
const resetCategoryId = useEventCallback((replace?: boolean) => {
364-
privateResetCategoryId(replace)
365-
categoryIdStore.setState({
366-
categoryId: null,
367-
})
368-
})
369-
370-
const category = findCategoryById(categoryId)
371-
372-
// This usually doesn't happen but if so,
373-
// We reset the category to the default.
374-
if (category == null) {
375-
resetCategoryId(true)
376-
return <></>
377-
}
378-
379-
const backend = pickBackend(category, remoteBackend, localBackend)
380-
381-
const contextValue = {
382-
cloudCategories,
383-
localCategories,
384-
category,
385-
setCategory: setCategoryId,
386-
resetCategory: resetCategoryId,
387-
associatedBackend: backend,
388-
} satisfies CategoriesContextValue
389-
390-
return (
391-
<CategoriesContext.Provider value={contextValue}>
392-
{typeof children === 'function' ? children(contextValue) : children}
393-
</CategoriesContext.Provider>
394-
)
395-
}
305+
export const CategoriesContext = createContext<CategoriesContextValue | null>(null)
396306

397307
/**
398308
* Returns the current category and the associated backend.
399309
*/
400-
// eslint-disable-next-line react-refresh/only-export-components
401310
export function useCategory() {
402311
const { category, associatedBackend } = useCategoriesAPI()
403312

@@ -407,7 +316,6 @@ export function useCategory() {
407316
/**
408317
* Gets the api to interact with the categories.
409318
*/
410-
// eslint-disable-next-line react-refresh/only-export-components
411319
export function useCategoriesAPI() {
412320
const context = useContext(CategoriesContext)
413321

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './categoriesHooks'
2+
export * from './CategoriesProvider'
23
export * from './Category'
34
export * from './transferBetweenCategoriesHooks'

app/gui/src/dashboard/pages/dashboard/Dashboard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as React from 'react'
77
import * as detect from 'enso-common/src/detect'
88

99
import * as projectHooks from '#/hooks/projectHooks'
10-
import { CategoriesProvider } from '#/layouts/Drive/Categories/categoriesHooks'
10+
import { CategoriesProvider } from '#/layouts/Drive/Categories'
1111
import DriveProvider from '#/providers/DriveProvider'
1212

1313
import * as backendProvider from '#/providers/BackendProvider'

0 commit comments

Comments
 (0)