Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor LocalStorage and associated hooks #11625

Open
wants to merge 45 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9d2ae55
WIP: Refactor LocalStorage
somebody1234 Nov 20, 2024
9005f5e
Implement `defineLocalStorageKey`
somebody1234 Nov 22, 2024
5e83384
Switch to `defineLocalStorageKey` for `LocalStorage` keys in `App.tsx`
somebody1234 Nov 22, 2024
7a0b1c3
Fix errors in `EnsoDevtools`
somebody1234 Nov 22, 2024
aa9d26d
Use named imports in `EnsoDevtools`
somebody1234 Nov 22, 2024
a25c29b
Switch to `defineLocalStorageKey` for `LocalStorage` keys in `AssetsT…
somebody1234 Nov 22, 2024
2b23794
Rename `AssetsTable` hooks files from `.tsx` to `.ts`
somebody1234 Nov 22, 2024
880ddf3
Use `useLocalRootDirectoryState` in `directoryIdsHooks`
somebody1234 Nov 22, 2024
d72b605
Switch to `defineLocalStorageKey` for `LocalStorage` keys in `Categor…
somebody1234 Nov 22, 2024
723a9eb
Switch to `defineLocalStorageKey` for `LocalStorage` keys in `AssetPa…
somebody1234 Nov 22, 2024
17cc6d3
Rename `AssetPanel` `Visible`/`Expanded` state to `Open` state
somebody1234 Nov 22, 2024
6f68b87
Switch to `defineLocalStorageKey` for `LocalStorage` keys in `Registr…
somebody1234 Nov 22, 2024
0c1a84d
Remove some section headings
somebody1234 Nov 22, 2024
2ccfa79
Switch to `defineLocalStorageKey` for `LocalStorage` keys in `Feature…
somebody1234 Nov 22, 2024
8eb8b06
Switch to `defineLocalStorageKey` for `LocalStorage` keys in `Project…
somebody1234 Nov 22, 2024
d1abf4d
Use named imports in `ProjectsProvider`
somebody1234 Nov 22, 2024
2e4a37b
Fix type errors
somebody1234 Nov 22, 2024
fedf407
Switch more files to named imports
somebody1234 Nov 22, 2024
57ef0e9
Fix type errors
somebody1234 Nov 22, 2024
518e120
Fix lint errors
somebody1234 Nov 22, 2024
14ad291
Fix loading of `localStorage` data
somebody1234 Nov 22, 2024
cba46f1
Fix import paths
somebody1234 Nov 25, 2024
89994dc
Combine return values of `defineLocalStorageKey`
somebody1234 Nov 25, 2024
fd3bfd3
Check localStorage value against schema in `LocalStorage` class
somebody1234 Nov 26, 2024
2182117
Call `LocalStorage.subscribe` and `LocalStorage.subscribeAll` callbac…
somebody1234 Nov 26, 2024
ebfd7c3
oops
somebody1234 Nov 26, 2024
e2ee4a5
Reactive updates for `LocalStorage` `useState`
somebody1234 Nov 26, 2024
0165dda
Fix `subscribeAll` not calling with different reference, causing Reac…
somebody1234 Nov 26, 2024
82e79fe
Merge branch 'develop' into wip/sb/refactor-localstorage
somebody1234 Dec 11, 2024
16d6469
Merge branch 'develop' into wip/sb/refactor-localstorage
somebody1234 Dec 19, 2024
8040809
Merge branch 'develop' into wip/sb/refactor-localstorage
somebody1234 Dec 19, 2024
051bf61
Merge branch 'develop' into wip/sb/refactor-localstorage
somebody1234 Dec 30, 2024
a47d285
Fix error
somebody1234 Dec 30, 2024
20f5287
Merge branch 'develop' into wip/sb/refactor-localstorage
somebody1234 Jan 2, 2025
777512a
Fix errors
somebody1234 Jan 2, 2025
55c5ec9
Fix errors
somebody1234 Jan 2, 2025
c9049fa
Merge branch 'develop' into wip/sb/refactor-localstorage
somebody1234 Jan 8, 2025
5a79a81
Fix errors
somebody1234 Jan 8, 2025
3cb3d3e
Merge branch 'develop' into wip/sb/refactor-localstorage
somebody1234 Jan 14, 2025
dda8508
Merge branch 'develop' into wip/sb/refactor-localstorage
somebody1234 Jan 15, 2025
3044fe1
Fix Bazel on NixOS
somebody1234 Jan 16, 2025
331f198
Merge branch 'develop' into wip/sb/refactor-localstorage
somebody1234 Jan 16, 2025
96ddedf
Fix errors
somebody1234 Jan 16, 2025
1aba426
Merge branch 'develop' into wip/sb/refactor-localstorage
somebody1234 Feb 19, 2025
d23960e
[dashboard/EnsoDevtoolsImpl] Fix type errors
somebody1234 Feb 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 18 additions & 57 deletions app/gui/src/dashboard/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import * as React from 'react'
import * as reactQuery from '@tanstack/react-query'
import * as router from 'react-router-dom'
import * as toastify from 'react-toastify'
import * as z from 'zod'

import * as detect from 'enso-common/src/detect'

Expand All @@ -53,7 +52,7 @@ import BackendProvider, { useLocalBackend } from '#/providers/BackendProvider'
import DriveProvider from '#/providers/DriveProvider'
import { useHttpClient } from '#/providers/HttpClientProvider'
import InputBindingsProvider from '#/providers/InputBindingsProvider'
import LocalStorageProvider, * as localStorageProvider from '#/providers/LocalStorageProvider'
import LocalStorageProvider from '#/providers/LocalStorageProvider'
import { useLogger } from '#/providers/LoggerProvider'
import ModalProvider, * as modalProvider from '#/providers/ModalProvider'
import * as navigator2DProvider from '#/providers/Navigator2DProvider'
Expand Down Expand Up @@ -93,18 +92,19 @@ import RemoteBackend from '#/services/RemoteBackend'
import { FeatureFlagsProvider } from '#/providers/FeatureFlagsProvider'
import * as appBaseUrl from '#/utilities/appBaseUrl'
import * as eventModule from '#/utilities/event'
import LocalStorage from '#/utilities/LocalStorage'
import * as object from '#/utilities/object'
import { Path } from '#/utilities/path'
import { STATIC_QUERY_OPTIONS } from '#/utilities/reactQuery'

import {
useAcceptedPrivacyPolicyVersionState,
useAcceptedTermsOfServiceVersionState,
useInputBindings,
useLocalRootDirectoryState,
} from '#/appLocalStorage'
import { useInitAuthService } from '#/authentication/service'
import { InvitedToOrganizationModal } from '#/modals/InvitedToOrganizationModal'

// ============================
// === Global configuration ===
// ============================

const DEFAULT_TRANSITION_OPTIONS: Spring = {
type: 'spring',
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
Expand All @@ -115,44 +115,13 @@ const DEFAULT_TRANSITION_OPTIONS: Spring = {
velocity: 0,
}

declare module '#/utilities/LocalStorage' {
/** */
interface LocalStorageData {
readonly inputBindings: Readonly<Record<string, readonly string[]>>
readonly localRootDirectory: string
}
}

LocalStorage.registerKey('inputBindings', {
schema: z.record(z.string().array().readonly()).transform((value) =>
Object.fromEntries(
Object.entries<unknown>({ ...value }).flatMap((kv) => {
const [k, v] = kv
return Array.isArray(v) && v.every((item): item is string => typeof item === 'string') ?
[[k, v]]
: []
}),
),
),
})

LocalStorage.registerKey('localRootDirectory', { schema: z.string() })

// ======================
// === getMainPageUrl ===
// ======================

/** Returns the URL to the main page. This is the current URL, with the current route removed. */
function getMainPageUrl() {
const mainPageUrl = new URL(window.location.href)
mainPageUrl.pathname = mainPageUrl.pathname.replace(appUtils.ALL_PATHS_REGEX, '')
return mainPageUrl
}

// ===========
// === App ===
// ===========

/** Global configuration for the `App` component. */
export interface AppProps {
readonly vibrancy: boolean
Expand Down Expand Up @@ -277,10 +246,6 @@ export default function App(props: AppProps) {
)
}

// =================
// === AppRouter ===
// =================

/** Props for an {@link AppRouter}. */
export interface AppRouterProps extends AppProps {
readonly projectManagerRootDirectory: projectManager.Path | null
Expand All @@ -302,7 +267,6 @@ function AppRouter(props: AppRouterProps) {
const navigate = router.useNavigate()

const { getText } = textProvider.useText()
const { localStorage } = localStorageProvider.useLocalStorage()
const { setModal } = modalProvider.useSetModal()

const navigator2D = navigator2DProvider.useNavigator2D()
Expand All @@ -324,8 +288,10 @@ function AppRouter(props: AppRouterProps) {

const [inputBindingsRaw] = React.useState(() => inputBindingsModule.createBindings())

const { get: getInputBindings, set: setInputBindings } = useInputBindings()

React.useEffect(() => {
const savedInputBindings = localStorage.get('inputBindings')
const savedInputBindings = getInputBindings()
if (savedInputBindings != null) {
const filteredInputBindings = object.mapEntries(
inputBindingsRaw.metadata,
Expand All @@ -340,12 +306,11 @@ function AppRouter(props: AppRouterProps) {
}
}
}
}, [localStorage, inputBindingsRaw])
}, [inputBindingsRaw, getInputBindings])

const inputBindings = React.useMemo(() => {
const updateLocalStorage = () => {
localStorage.set(
'inputBindings',
setInputBindings(
Object.fromEntries(
Object.entries(inputBindingsRaw.metadata).map((kv) => {
const [k, v] = kv
Expand Down Expand Up @@ -388,14 +353,14 @@ function AppRouter(props: AppRouterProps) {
return inputBindingsRaw.unregister.bind(inputBindingsRaw)
},
}
}, [localStorage, inputBindingsRaw])
}, [inputBindingsRaw, setInputBindings])

const mainPageUrl = getMainPageUrl()

// Subscribe to `localStorage` updates to trigger a rerender when the terms of service
// or privacy policy have been accepted.
localStorageProvider.useLocalStorageState('termsOfService')
localStorageProvider.useLocalStorageState('privacyPolicy')
// Subscribe to `localStorage` updates (and ignore the value)
// to trigger a rerender when the terms of service or privacy policy have been accepted.
useAcceptedTermsOfServiceVersionState()
useAcceptedPrivacyPolicyVersionState()

const authService = useInitAuthService(props)

Expand Down Expand Up @@ -572,13 +537,9 @@ function AppRouter(props: AppRouterProps) {
)
}

// ====================================
// === LocalBackendPathSynchronizer ===
// ====================================

/** Keep `localBackend.rootPath` in sync with the saved root path state. */
function LocalBackendPathSynchronizer() {
const [localRootDirectory] = localStorageProvider.useLocalStorageState('localRootDirectory')
const [localRootDirectory] = useLocalRootDirectoryState()
const localBackend = useLocalBackend()
if (localBackend) {
if (localRootDirectory != null) {
Expand Down
42 changes: 42 additions & 0 deletions app/gui/src/dashboard/appLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/** @file App-wide local storage keys. */
import { defineLocalStorageKey } from '#/providers/LocalStorageProvider'

export const { use: useInputBindings, useState: useInputBindingsState } = defineLocalStorageKey(
'inputBindings',
{
schema: (z) =>
z
.record(z.string().array().readonly())
.transform((value): { readonly [k: string]: readonly string[] } =>
Object.fromEntries(
Object.entries<unknown>({ ...value }).flatMap((kv) => {
const [k, v] = kv
return (
Array.isArray(v) && v.every((item): item is string => typeof item === 'string')
) ?
[[k, v]]
: []
}),
),
),
},
)

export const { use: useLocalRootDirectory, useState: useLocalRootDirectoryState } =
defineLocalStorageKey('localRootDirectory', {
schema: (z) => z.string(),
})

export const {
use: useAcceptedTermsOfServiceVersion,
useState: useAcceptedTermsOfServiceVersionState,
} = defineLocalStorageKey('termsOfService', {
schema: (z) => z.object({ versionHash: z.string() }),
})

export const {
use: useAcceptedPrivacyPolicyVersion,
useState: useAcceptedPrivacyPolicyVersionState,
} = defineLocalStorageKey('privacyPolicy', {
schema: (z) => z.object({ versionHash: z.string() }),
})
Loading
Loading