Skip to content

Commit 9a32e28

Browse files
committed
Remove TState and add localStorage migrations
Move code from TState Stores to React States and contexts. Create functions to interact with localStorage with more type safety. Add Possibility to write localStorage migrations.
1 parent c565596 commit 9a32e28

File tree

17 files changed

+364
-279
lines changed

17 files changed

+364
-279
lines changed

package-lock.json

Lines changed: 0 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
"lint": "eslint ./src --ext .ts,.tsx"
1111
},
1212
"dependencies": {
13-
"@alinnert/tstate": "^1.0.3",
1413
"@mdi/js": "^5.6.55",
1514
"@mdi/react": "^1.4.0",
1615
"fuzzysort": "^1.1.4",

src/App.tsx

Lines changed: 124 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -25,146 +25,176 @@ import { FooterGroup } from './components/FooterGroup'
2525
import { FooterLink } from './components/FooterLink'
2626
import { LinkList } from './components/LinkList'
2727
import { Search } from './components/Search'
28-
import { links } from './links'
29-
import {
30-
getThemeStateSetting,
31-
setThemeStateSetting
32-
} from './services/localStorageService'
3328
import {
3429
AppMode,
35-
setMode,
36-
toggleMode,
37-
useCurrentMode
38-
} from './stores/currentModeStore'
39-
import { HiddenLinks, useHiddenLinks } from './stores/hiddenLinksStore'
30+
CurrentModeContext,
31+
CurrentModeContextValue,
32+
useCurrentModeContextValue
33+
} from './contexts/currentModeContext'
34+
import {
35+
HiddenLinksContext,
36+
useHiddenLinksContextValue
37+
} from './contexts/hiddenLinksContext'
38+
import { links } from './links'
39+
import {
40+
loadThemeSetting,
41+
saveThemeSetting
42+
} from './services/localStorage/values/themeSetting'
4043

4144
export const WebdevHome: FC = () => {
42-
const { mode } = useCurrentMode()
43-
const { handleCustomizeAction, hiddenLinks } = useCustomizeMode()
44-
const { handleSearchAction, searchTerm, setSearchTerm } = useSearchMode()
45-
const { themeSwitcherIcon, handleThemeSwitcherAction } = useThemeSwitcher()
45+
const currentModeContextValue = useCurrentModeContextValue()
46+
const hiddenLinksContextValue = useHiddenLinksContextValue()
47+
48+
const customizeMode = useCustomizeMode({ currentModeContextValue })
49+
const searchMode = useSearchMode({ currentModeContextValue })
50+
const themeSwitcher = useThemeSwitcher()
51+
52+
const { isCurrentMode } = currentModeContextValue
53+
const { hiddenLinks } = hiddenLinksContextValue
4654

4755
return (
48-
<div className="app">
49-
<AppHeader />
50-
51-
<AppActions>
52-
<AppAction
53-
icon={mdiMagnify}
54-
action={handleSearchAction}
55-
active={mode === AppMode.search}
56-
/>
57-
<AppAction
58-
icon={themeSwitcherIcon}
59-
action={handleThemeSwitcherAction}
60-
active={false}
61-
/>
62-
<AppAction
63-
icon={mdiFormatListChecks}
64-
action={handleCustomizeAction}
65-
active={mode === AppMode.customize}
66-
/>
67-
</AppActions>
68-
69-
{mode === AppMode.default || mode === AppMode.customize ? (
70-
<AppContent>
71-
<LinkList links={links.items} hiddenLinks={hiddenLinks.links} />
72-
</AppContent>
73-
) : (
74-
<Search searchTerm={searchTerm} setSearchTerm={setSearchTerm} />
75-
)}
76-
77-
<AppFooter>
78-
<FooterGroup title={'WebdevHome v' + version}>
79-
<FooterLink
80-
text="Changelog"
81-
url="https://github.com/webdevhome/webdevhome.github.io/releases"
82-
/>
83-
<FooterLink
84-
text="GitHub"
85-
url="https://github.com/webdevhome/webdevhome.github.io"
86-
/>
87-
</FooterGroup>
88-
89-
<FooterDivider />
90-
91-
<FooterGroup title="Icons">
92-
<FooterLink
93-
text="Material Design Icons"
94-
url="https://materialdesignicons.com"
95-
/>
96-
<FooterLink text="Simple Icons" url="https://simpleicons.org/" />
97-
</FooterGroup>
98-
</AppFooter>
99-
</div>
56+
<CurrentModeContext.Provider value={currentModeContextValue}>
57+
<HiddenLinksContext.Provider value={hiddenLinksContextValue}>
58+
<div className="app">
59+
<AppHeader />
60+
61+
<AppActions>
62+
<AppAction
63+
icon={mdiMagnify}
64+
action={searchMode.handleSearchAction}
65+
active={isCurrentMode(AppMode.search)}
66+
/>
67+
<AppAction
68+
icon={themeSwitcher.icon}
69+
action={themeSwitcher.switchTheme}
70+
active={false}
71+
/>
72+
<AppAction
73+
icon={mdiFormatListChecks}
74+
action={customizeMode.handleCustomizeAction}
75+
active={isCurrentMode(AppMode.customize)}
76+
/>
77+
</AppActions>
78+
79+
{isCurrentMode(AppMode.default, AppMode.customize) ? (
80+
<AppContent>
81+
<LinkList links={links.items} hiddenLinks={hiddenLinks} />
82+
</AppContent>
83+
) : (
84+
<Search
85+
searchTerm={searchMode.searchTerm}
86+
setSearchTerm={searchMode.setSearchTerm}
87+
/>
88+
)}
89+
90+
<AppFooter>
91+
<FooterGroup title={'WebdevHome v' + version}>
92+
<FooterLink
93+
text="Changelog"
94+
url="https://github.com/webdevhome/webdevhome.github.io/releases"
95+
/>
96+
<FooterLink
97+
text="GitHub"
98+
url="https://github.com/webdevhome/webdevhome.github.io"
99+
/>
100+
</FooterGroup>
101+
102+
<FooterDivider />
103+
104+
<FooterGroup title="Icons">
105+
<FooterLink
106+
text="Material Design Icons"
107+
url="https://materialdesignicons.com"
108+
/>
109+
<FooterLink text="Simple Icons" url="https://simpleicons.org/" />
110+
</FooterGroup>
111+
</AppFooter>
112+
</div>
113+
</HiddenLinksContext.Provider>
114+
</CurrentModeContext.Provider>
100115
)
101116
}
102117

103118
// #region customize feature
119+
interface UseCustomizeModeParams {
120+
currentModeContextValue: CurrentModeContextValue
121+
}
122+
104123
interface UseCustomizeModeReturn {
105-
hiddenLinks: HiddenLinks
106124
handleCustomizeAction: () => void
107125
}
108126

109-
function useCustomizeMode(): UseCustomizeModeReturn {
110-
const hiddenLinks = useHiddenLinks()
111-
const { mode } = useCurrentMode()
127+
function useCustomizeMode({
128+
currentModeContextValue,
129+
}: UseCustomizeModeParams): UseCustomizeModeReturn {
130+
const { isCurrentMode, setCurrentMode, toggleMode } = currentModeContextValue
112131

113132
useEffect(() => {
114133
document.addEventListener('keydown', handleGlobalKeydown)
115134

116135
function handleGlobalKeydown(event: KeyboardEvent): void {
117-
if (event.key === 'Escape' && mode === AppMode.customize) {
118-
setMode(AppMode.default)
136+
if (event.key === 'Escape' && isCurrentMode(AppMode.customize)) {
137+
setCurrentMode(AppMode.default)
119138
}
120139
}
121140

122141
return () => {
123142
document.removeEventListener('keydown', handleGlobalKeydown)
124143
}
125-
}, [mode])
144+
}, [isCurrentMode, setCurrentMode])
126145

127146
const handleCustomizeAction = useCallback((): void => {
128147
toggleMode(AppMode.customize)
129-
}, [])
148+
}, [toggleMode])
130149

131-
return { hiddenLinks, handleCustomizeAction }
150+
return { handleCustomizeAction }
132151
}
133152
// #endregion customize feature
134153

135154
// #region search feature
155+
interface UseSearchModeParams {
156+
currentModeContextValue: CurrentModeContextValue
157+
}
158+
136159
interface UseSearchModeReturn {
137160
handleSearchAction: () => void
138161
searchTerm: string
139162
setSearchTerm: Dispatch<SetStateAction<string>>
140163
}
141164

142-
function useSearchMode(): UseSearchModeReturn {
165+
function useSearchMode({
166+
currentModeContextValue,
167+
}: UseSearchModeParams): UseSearchModeReturn {
143168
const [searchTerm, setSearchTerm] = useState<string>('')
144-
const { mode } = useCurrentMode()
145169

146-
useEffect(() => {
147-
window.addEventListener('keypress', handleGlobalKeypress)
170+
const { isCurrentMode, setCurrentMode, toggleMode } = currentModeContextValue
148171

149-
function handleGlobalKeypress(event: KeyboardEvent): void {
150-
if (mode === AppMode.default) {
172+
const handleGlobalKeypress = useCallback(
173+
(event: KeyboardEvent) => {
174+
if (isCurrentMode(AppMode.default)) {
151175
if (event.key === '\n') {
152176
return
153177
}
154-
155-
setMode(AppMode.search)
178+
179+
setSearchTerm(event.key)
180+
setCurrentMode(AppMode.search)
156181
}
157-
}
182+
},
183+
[isCurrentMode, setCurrentMode]
184+
)
158185

159-
return (): void => {
186+
useEffect(() => {
187+
window.addEventListener('keypress', handleGlobalKeypress)
188+
189+
return () => {
160190
window.removeEventListener('keypress', handleGlobalKeypress)
161191
}
162-
}, [mode])
192+
}, [handleGlobalKeypress, isCurrentMode, setCurrentMode])
163193

164194
const handleSearchAction = useCallback((): void => {
165195
setSearchTerm('')
166196
toggleMode(AppMode.search)
167-
}, [])
197+
}, [toggleMode])
168198

169199
return { handleSearchAction, searchTerm, setSearchTerm }
170200
}
@@ -175,26 +205,24 @@ export const themeStates = ['auto', 'light', 'dark'] as const
175205
export type ThemeState = typeof themeStates[number]
176206

177207
interface UseThemeSwitcherReturn {
178-
themeSwitcherIcon: string
179-
handleThemeSwitcherAction: () => void
208+
icon: string
209+
switchTheme: () => void
180210
}
181211

182212
function useThemeSwitcher(): UseThemeSwitcherReturn {
183-
const [themeState, setThemeState] = useState<ThemeState>(
184-
getThemeStateSetting()
185-
)
213+
const [themeState, setThemeState] = useState<ThemeState>(loadThemeSetting())
186214

187215
const bodyElement = useMemo(
188216
() => globalThis.document.getElementsByTagName('body')[0],
189217
[]
190218
)
191219

192220
useEffect(() => {
193-
setThemeStateSetting(themeState)
221+
saveThemeSetting(themeState)
194222
bodyElement.className = `${themeState}-theme`
195223
}, [bodyElement.className, themeState])
196224

197-
const themeSwitcherIcon = useMemo((): string => {
225+
const icon = useMemo((): string => {
198226
if (themeState === 'light') {
199227
return mdiWeatherSunny
200228
}
@@ -204,7 +232,7 @@ function useThemeSwitcher(): UseThemeSwitcherReturn {
204232
return mdiThemeLightDark
205233
}, [themeState])
206234

207-
const handleThemeSwitcherAction = useCallback((): void => {
235+
const switchTheme = useCallback((): void => {
208236
switch (themeState) {
209237
case 'light':
210238
setThemeState('dark')
@@ -218,6 +246,6 @@ function useThemeSwitcher(): UseThemeSwitcherReturn {
218246
}
219247
}, [themeState])
220248

221-
return { themeSwitcherIcon, handleThemeSwitcherAction }
249+
return { icon, switchTheme }
222250
}
223251
// #endregion theme switcher

src/components/Link.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import { mdiEye, mdiEyeOff } from '@mdi/js'
2-
import React, { FC, memo, MouseEvent, useCallback, useMemo } from 'react'
2+
import React, {
3+
FC,
4+
memo,
5+
MouseEvent,
6+
useCallback,
7+
useContext,
8+
useMemo
9+
} from 'react'
310
import { ReactSVG } from 'react-svg'
4-
import { toggleLink } from '../stores/hiddenLinksStore'
11+
import { HiddenLinksContext } from '../contexts/hiddenLinksContext'
512
import { classes } from '../utils/jsx'
613
import { getIconUrl } from '../utils/misc'
714
import { DefaultIcon } from './DefaultIcon'
@@ -29,14 +36,16 @@ export const Link: FC<Props> = memo(
2936
visible = true,
3037
focus = false,
3138
}) => {
39+
const hiddenLinksContext = useContext(HiddenLinksContext)
3240
const handleLinkClick = useCallback(
3341
(event: MouseEvent<HTMLAnchorElement>): void => {
34-
if (customize) {
35-
event.preventDefault()
36-
toggleLink(url)
37-
}
42+
if (!customize) return
43+
if (hiddenLinksContext === null) return
44+
45+
event.preventDefault()
46+
hiddenLinksContext.toggleHiddenLink(url)
3847
},
39-
[customize, url]
48+
[customize, hiddenLinksContext, url]
4049
)
4150

4251
const linkClasses = useMemo(

0 commit comments

Comments
 (0)