diff --git a/src/Themes/component.tsx b/src/Themes/component.tsx
index 25b459e6..e2aa9732 100644
--- a/src/Themes/component.tsx
+++ b/src/Themes/component.tsx
@@ -1,16 +1,11 @@
import './component.css'
-import { FC, useEffect } from 'react'
import { allColorSchemes, allThemes } from './model'
-import { useSelectedColorScheme, useSelectedTheme } from './repository'
+import { useSelectedColorScheme, useSelectedTheme } from './hook'
+import { FC } from 'react'
const ThemesComponent: FC = () => {
- const [selectedTheme, setSelectedTheme] = useSelectedTheme('standard')
- const [selectedColorScheme, setSelectedColorScheme] = useSelectedColorScheme('auto')
- useEffect(() => {
- document.documentElement.dataset['theme'] = selectedTheme
- document.documentElement.dataset['colorScheme'] = selectedColorScheme
- }, [selectedTheme, selectedColorScheme])
-
+ const [selectedTheme, setSelectedTheme] = useSelectedTheme()
+ const [selectedColorScheme, setSelectedColorScheme] = useSelectedColorScheme()
return (
<>
diff --git a/src/Themes/hook.ts b/src/Themes/hook.ts
new file mode 100644
index 00000000..c483d12c
--- /dev/null
+++ b/src/Themes/hook.ts
@@ -0,0 +1,39 @@
+import { ColorScheme, Theme, isColorScheme, isTheme } from './model'
+import { Dispatch, useEffect } from 'react'
+import { Spec, useChromeStorageWithCache } from '../infrastructure/chromeStorage'
+import { getOrInitialValue } from '../infrastructure/localStorageCache'
+
+const selectedThemeSpec: Spec = {
+ areaName: 'sync',
+ key: 'v3.selectedTheme',
+ initialValue: 'standard',
+ isType: isTheme,
+}
+
+export const useSelectedTheme = (): [Theme, Dispatch] => {
+ const [theme, setTheme] = useChromeStorageWithCache(selectedThemeSpec)
+ useEffect(() => {
+ document.documentElement.dataset['theme'] = theme
+ }, [theme])
+ return [theme, setTheme]
+}
+
+const selectedColorSchemeSpec: Spec = {
+ areaName: 'sync',
+ key: 'v3.selectedColorScheme',
+ initialValue: 'auto',
+ isType: isColorScheme,
+}
+
+export const useSelectedColorScheme = (): [ColorScheme, Dispatch] => {
+ const [colorScheme, setColorScheme] = useChromeStorageWithCache(selectedColorSchemeSpec)
+ useEffect(() => {
+ document.documentElement.dataset['colorScheme'] = colorScheme
+ }, [colorScheme])
+ return [colorScheme, setColorScheme]
+}
+
+export const preloadFromCache = () => {
+ document.documentElement.dataset['theme'] = getOrInitialValue(selectedThemeSpec)
+ document.documentElement.dataset['colorScheme'] = getOrInitialValue(selectedColorSchemeSpec)
+}
diff --git a/src/Themes/repository.ts b/src/Themes/repository.ts
deleted file mode 100644
index b6a166af..00000000
--- a/src/Themes/repository.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { ColorScheme, Theme, isColorScheme, isTheme } from './model'
-import { useChromeStorage } from '../infrastructure/chromeStorage'
-
-export const useSelectedTheme = (initialValue: Theme) =>
- useChromeStorage({
- areaName: 'sync',
- key: 'v3.selectedTheme',
- initialValue,
- isType: isTheme,
- })
-
-export const useSelectedColorScheme = (initialValue: ColorScheme) =>
- useChromeStorage({
- areaName: 'sync',
- key: 'v3.selectedColorScheme',
- initialValue,
- isType: isColorScheme,
- })
diff --git a/src/index.tsx b/src/index.tsx
index 969a807a..bd347ffd 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -3,6 +3,7 @@ import App from './App/component'
import React from 'react'
import ReactDOM from 'react-dom/client'
import { migratePreferencesFromV2ToV3 } from './migration'
+import { preloadFromCache } from './Themes/hook'
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(
@@ -11,6 +12,9 @@ root.render(
)
+// preload the theme to prevent screen flicker
+preloadFromCache()
+
// the default size of popup is too small, so explicitly set it
// https://developer.chrome.com/docs/extensions/reference/action/#popup
if (document.location.hash === '#popup') {
diff --git a/src/infrastructure/chromeStorage.ts b/src/infrastructure/chromeStorage.ts
index 431f4a11..565c76a8 100644
--- a/src/infrastructure/chromeStorage.ts
+++ b/src/infrastructure/chromeStorage.ts
@@ -1,13 +1,14 @@
-import { useEffect, useState } from 'react'
+import { Dispatch, useEffect, useState } from 'react'
+import { useLocalStorageCache } from './localStorageCache'
-type Spec = {
+export type Spec = {
areaName: chrome.storage.AreaName
key: string
initialValue: T
isType: (value: unknown) => value is T
}
-export const useChromeStorage = (spec: Spec): readonly [T, (newValue: T) => void] => {
+export const useChromeStorage = (spec: Spec): readonly [T, Dispatch] => {
const [storedValue, setStoredValue] = useState(spec.initialValue)
useEffect(
() => {
@@ -27,7 +28,7 @@ export const useChromeStorage = (spec: Spec): readonly [T, (newValue: T) =
]
}
-const initialLoad = (spec: Spec, setStoredValue: (newValue: T) => void) => {
+const initialLoad = (spec: Spec, setStoredValue: Dispatch) => {
chrome.storage[spec.areaName]
.get(spec.key)
.then((items) => {
@@ -48,7 +49,7 @@ const initialLoad = (spec: Spec, setStoredValue: (newValue: T) => void) =>
.catch((e) => console.error(e))
}
-const subscribeChange = (spec: Spec, setStoredValue: (newValue: T) => void) => {
+const subscribeChange = (spec: Spec, setStoredValue: Dispatch) => {
const area = chrome.storage[spec.areaName]
const listener = (changes: { [key: string]: chrome.storage.StorageChange }) => {
if (!(spec.key in changes)) {
@@ -68,3 +69,15 @@ const subscribeChange = (spec: Spec, setStoredValue: (newValue: T) => void
area.onChanged.addListener(listener)
return () => area.onChanged.removeListener(listener)
}
+
+export const useChromeStorageWithCache = (spec: Spec): [T, Dispatch] => {
+ const [cache, setCache] = useLocalStorageCache(spec)
+ const [value, setValue] = useChromeStorage({
+ ...spec,
+ initialValue: cache,
+ })
+ useEffect(() => {
+ setCache(value)
+ }, [setCache, value])
+ return [value, setValue]
+}
diff --git a/src/infrastructure/localStorageCache.ts b/src/infrastructure/localStorageCache.ts
new file mode 100644
index 00000000..db71dc73
--- /dev/null
+++ b/src/infrastructure/localStorageCache.ts
@@ -0,0 +1,23 @@
+import { Dispatch, useEffect, useState } from 'react'
+
+type Spec = {
+ key: string
+ initialValue: T
+ isType: (value: unknown) => value is T
+}
+
+export const useLocalStorageCache = (spec: Spec): [T, Dispatch] => {
+ const [value, setValue] = useState(getOrInitialValue(spec))
+ useEffect(() => {
+ localStorage.setItem(spec.key, value)
+ }, [spec.key, value])
+ return [value, setValue]
+}
+
+export const getOrInitialValue = (spec: Spec): T => {
+ const cachedValue = localStorage.getItem(spec.key)
+ if (spec.isType(cachedValue)) {
+ return cachedValue
+ }
+ return spec.initialValue
+}
diff --git a/src/migration/folderItemPreferences.ts b/src/migration/folderItemPreferences.ts
index 84be7f28..e9f5fadb 100644
--- a/src/migration/folderItemPreferences.ts
+++ b/src/migration/folderItemPreferences.ts
@@ -27,4 +27,5 @@ export const migrate = async () => {
}
const shortcutMap = upgrade(folderItemPreferences)
await chrome.storage.sync.set({ [V3_KEY]: shortcutMap.serialize() })
+ localStorage.removeItem(V2_KEY)
}
diff --git a/src/migration/folderPreferences.ts b/src/migration/folderPreferences.ts
index d2500ee1..87dab905 100644
--- a/src/migration/folderPreferences.ts
+++ b/src/migration/folderPreferences.ts
@@ -26,4 +26,5 @@ export const migrate = async () => {
}
const folderCollapse = upgrade(folderPreferences)
await chrome.storage.sync.set({ [V3_KEY]: folderCollapse.serialize() })
+ localStorage.removeItem(V2_KEY)
}
diff --git a/src/migration/index.ts b/src/migration/index.ts
index 1fc4b950..f6c63889 100644
--- a/src/migration/index.ts
+++ b/src/migration/index.ts
@@ -3,10 +3,6 @@ import * as folderPreferences from './folderPreferences'
// Migrate preferences from v2 (Local Storage) to v3 (Chrome Storage)
export const migratePreferencesFromV2ToV3 = async () => {
- if (window.localStorage.length === 0) {
- return
- }
await folderPreferences.migrate()
await folderItemPreferences.migrate()
- window.localStorage.clear()
}