Skip to content

Commit 3e3b569

Browse files
reglimfliiiix
authored andcommitted
fix: navigation
1 parent ba59990 commit 3e3b569

2 files changed

Lines changed: 122 additions & 100 deletions

File tree

web/src/components/Project.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export default function Project(props: Props): React.JSX.Element {
4646

4747
{props.project.logo ?
4848
<>
49-
<Link to={`${props.project.name}/latest`}>
49+
<Link to={`${props.project.name}/latest/`}>
5050
<img
5151
className={styles['project-logo']}
5252
src={ProjectRepository.getProjectLogoURL(props.project.name)}
@@ -57,7 +57,7 @@ export default function Project(props: Props): React.JSX.Element {
5757
}
5858

5959
<div className={styles['project-header']}>
60-
<Link to={`${props.project.name}/latest`}>
60+
<Link to={`${props.project.name}/latest/`}>
6161
<div className={styles['project-card-title']}>
6262
{props.project.name}{' '}
6363
<span className={styles['secondary-typography']}>

web/src/pages/Docs.tsx

Lines changed: 120 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -16,79 +16,37 @@ export default function Docs(): JSX.Element {
1616
const { showMessage, clearMessages } = useMessageBanner()
1717

1818
const [iframePageNotFound, setIframePageNotFound] = useState<boolean>(false)
19-
const [versions, setVersions] = useState<ProjectDetails[]>([])
19+
const [versions, setVersions] = useState<ProjectDetails[] | null>()
2020
const [loadingFailed, setLoadingFailed] = useState<boolean>(false)
2121

2222
const project = useRef(params.project ?? '')
23+
const version = useRef(params.version ?? 'latest')
2324
const page = useRef(params['*'] ?? '')
2425
const hash = useRef(location.hash)
2526

26-
const [version, setVersion] = useState<string>(params.version ?? 'latest')
27-
const [hideUi, setHideUi] = useState<boolean>(searchParams.get('hide-ui') === '' || searchParams.get('hide-ui') === 'true')
28-
const [iframeUpdateTrigger, setIframeUpdateTrigger] = useState<number>(0)
27+
const [hideUi, setHideUi] = useState<boolean>(
28+
searchParams.get('hide-ui') === '' || searchParams.get('hide-ui') === 'true'
29+
)
30+
const [iFrameUpdateTrigger, setIFrameUpdateTrigger] = useState<number>(0)
2931

3032
// This provides the url for the iframe.
3133
// It is always the same, except when the version changes,
3234
// as this memo will trigger a re-render of the iframe, which
3335
// is not needed when only the page or hash changes, because
3436
// the iframe keeps track of that itself.
3537
const iFrameSrc = useMemo(() => {
38+
if (versions == null) {
39+
return ''
40+
}
41+
3642
return ProjectRepository.getProjectDocsURL(
3743
project.current,
38-
version,
44+
version.current,
3945
page.current,
4046
hash.current
4147
)
4248
// eslint-disable-next-line react-hooks/exhaustive-deps
43-
}, [version, iframeUpdateTrigger])
44-
45-
useEffect(() => {
46-
if (project.current === '') {
47-
return
48-
}
49-
50-
void (async (): Promise<void> => {
51-
try {
52-
let allVersions = await ProjectRepository.getVersions(project.current)
53-
if (allVersions.length === 0) {
54-
setLoadingFailed(true)
55-
return
56-
}
57-
58-
allVersions = allVersions.sort((a, b) =>
59-
ProjectRepository.compareVersions(a, b)
60-
)
61-
setVersions(allVersions)
62-
63-
const latestVersion =
64-
ProjectRepository.getLatestVersion(allVersions).name
65-
if (version === 'latest') {
66-
if (latestVersion === 'latest') {
67-
return
68-
}
69-
setVersion(latestVersion)
70-
return
71-
}
72-
73-
// custom version -> check if it exists
74-
// if it does. do nothing, as it should be set already
75-
const versionsAndTags = allVersions
76-
.map((v) => [v.name, ...v.tags])
77-
.flat()
78-
if (versionsAndTags.includes(version)) {
79-
return
80-
}
81-
82-
// version does not exist -> fail
83-
setLoadingFailed(true)
84-
console.error(`Version '${version}' doesn't exist`)
85-
} catch (e) {
86-
console.error(e)
87-
setLoadingFailed(true)
88-
}
89-
})()
90-
// eslint-disable-next-line react-hooks/exhaustive-deps
91-
}, [project])
49+
}, [iFrameUpdateTrigger])
9250

9351
/** Encodes the url for the current page.
9452
* @example
@@ -105,109 +63,167 @@ export default function Docs(): JSX.Element {
10563
}
10664

10765
const updateUrl = (newVersion: string, hideUi: boolean): void => {
108-
const url = getUrl(
66+
const newUrl = getUrl(
10967
project.current,
11068
newVersion,
11169
page.current,
11270
hash.current,
11371
hideUi
11472
)
115-
window.history.pushState(null, '', url)
73+
74+
const currentUrl = location.pathname + location.search + location.hash
75+
if (newUrl !== currentUrl) {
76+
console.log(`Updating url from '${currentUrl}' to '${newUrl}'`)
77+
// avoid updating the url when it hasn't changed
78+
// because this results in the user having to click back twice or more.
79+
window.history.pushState(null, '', newUrl)
80+
}
11681
}
11782

11883
const updateTitle = (newTitle: string): void => {
11984
document.title = newTitle
12085
}
12186

122-
// Keep compatibility with encoded page path
123-
useEffect(() => {
124-
updateUrl(version, hideUi)
125-
// eslint-disable-next-line react-hooks/exhaustive-deps
126-
}, [])
87+
const triggerIFrameUpdate = (): void => {
88+
setIFrameUpdateTrigger((v) => v + 1)
89+
}
12790

128-
const iFramePageChanged = (urlPage: string, urlHash: string, title?: string): void => {
91+
const onIFramePageChanged = (
92+
urlPage: string,
93+
urlHash: string,
94+
title?: string
95+
): void => {
12996
if (title != null && title !== document.title) {
13097
updateTitle(title)
13198
}
99+
132100
if (urlPage === page.current) {
133101
return
134102
}
103+
135104
page.current = urlPage
136105
hash.current = urlHash
137-
updateUrl(version, hideUi)
106+
updateUrl(version.current, hideUi)
138107
}
139108

140-
const iFrameHashChanged = (newHash: string): void => {
109+
const onIFrameHashChanged = (newHash: string): void => {
141110
if (newHash === hash.current) {
142111
return
143112
}
144113
hash.current = newHash
145-
updateUrl(version, hideUi)
114+
updateUrl(version.current, hideUi)
146115
}
147116

148-
const iFrameNotFound = (): void => {
117+
const onIFrameNotFound = (): void => {
149118
setIframePageNotFound(true)
150119
}
151120

152-
const iFrameFaviconChanged = (faviconUrl: string | null): void => {
153-
const favicon = document.querySelector('link[rel="icon"]') as HTMLLinkElement | null
154-
if (favicon == null || faviconUrl == null) {
155-
return
121+
const onIFrameFaviconChanged = (faviconUrl: string | null): void => {
122+
const favicon = document.querySelector(
123+
'link[rel="icon"]'
124+
) as HTMLLinkElement | null
125+
if (favicon != null && faviconUrl != null) {
126+
favicon.href = faviconUrl
156127
}
157-
favicon.href = faviconUrl
158128
}
159129

160130
const onVersionChanged = (newVersion: string): void => {
161-
if (newVersion === version) {
131+
if (newVersion === version.current) {
162132
return
163133
}
164-
setVersion(newVersion)
134+
135+
version.current = newVersion
136+
triggerIFrameUpdate()
165137
updateUrl(newVersion, hideUi)
166138
}
167139

168140
useEffect(() => {
141+
console.log('location hook')
142+
// hook to parse url parameters into the state on first load
169143
const urlProject = params.project ?? ''
170144
const urlVersion = params.version ?? 'latest'
171145
const urlPage = params['*'] ?? ''
172146
const urlHash = location.hash
173-
const urlHideUi = searchParams.get('hide-ui') === '' || searchParams.get('hide-ui') === 'true'
147+
const urlHideUi =
148+
searchParams.get('hide-ui') === '' ||
149+
searchParams.get('hide-ui') === 'true'
174150

175-
// update the state to the url params on first loadon
176151
if (urlProject !== project.current) {
177152
setVersions([])
178153
project.current = urlProject
154+
triggerIFrameUpdate()
179155
}
180-
181-
if (urlVersion !== version) {
182-
setVersion(urlVersion)
156+
if (urlVersion !== version.current && urlVersion !== 'latest') {
157+
version.current = urlVersion
158+
triggerIFrameUpdate()
183159
}
184-
185-
if (urlHideUi !== hideUi) {
186-
setHideUi(urlHideUi)
187-
}
188-
189160
if (urlPage !== page.current) {
190161
page.current = urlPage
191-
setIframeUpdateTrigger((v) => v + 1)
162+
triggerIFrameUpdate()
192163
}
193164
if (urlHash !== hash.current) {
194165
hash.current = urlHash
195-
setIframeUpdateTrigger((v) => v + 1)
166+
triggerIFrameUpdate()
167+
}
168+
if (urlHideUi !== hideUi) {
169+
setHideUi(urlHideUi)
196170
}
197171

198172
setIframePageNotFound(false)
199173
// eslint-disable-next-line react-hooks/exhaustive-deps
200174
}, [location])
201175

176+
useEffect(() => {
177+
void (async (): Promise<void> => {
178+
try {
179+
const allVersions = await ProjectRepository.getVersions(project.current)
180+
if (allVersions.length === 0) {
181+
setLoadingFailed(true)
182+
return
183+
}
184+
185+
const sortedVersions = allVersions.sort((a, b) =>
186+
ProjectRepository.compareVersions(a, b)
187+
)
188+
setVersions(sortedVersions)
189+
190+
// custom version -> check if it exists
191+
// if it does, do nothing, as it should be set already
192+
const versionsAndTags = sortedVersions
193+
.map((v) => [v.name, ...v.tags])
194+
.flat()
195+
196+
if (version.current === 'latest') {
197+
version.current =
198+
ProjectRepository.getLatestVersion(sortedVersions).name
199+
}
200+
triggerIFrameUpdate()
201+
202+
if (
203+
versionsAndTags.length === 0 ||
204+
!versionsAndTags.includes(version.current)
205+
) {
206+
setLoadingFailed(true)
207+
console.error(`Version '${version}' doesn't exist`)
208+
}
209+
} catch (e) {
210+
console.error(e)
211+
setLoadingFailed(true)
212+
}
213+
})()
214+
}, [project])
215+
202216
useEffect(() => {
203217
// check every time the version changes whether the version
204218
// is the latest version and if not, show a banner
205-
if (versions.length === 0) {
219+
if (versions == null || loadingFailed) {
206220
return
207221
}
208222

209223
const latestVersion = ProjectRepository.getLatestVersion(versions).name
210-
if (version === latestVersion) {
224+
const isLatestVersion =
225+
version.current === latestVersion || version.current === 'latest'
226+
if (isLatestVersion) {
211227
clearMessages()
212228
return
213229
}
@@ -220,37 +236,43 @@ export default function Docs(): JSX.Element {
220236
// eslint-disable-next-line react-hooks/exhaustive-deps
221237
}, [version, versions])
222238

223-
if (loadingFailed || project.current === '') {
239+
// Keep compatibility with encoded page path
240+
useEffect(() => {
241+
updateUrl(version.current, hideUi)
242+
// eslint-disable-next-line react-hooks/exhaustive-deps
243+
}, [])
244+
245+
if (loadingFailed) {
224246
return <NotFound />
225247
}
226248

249+
if (versions == null) {
250+
return <LoadingPage />
251+
}
252+
227253
if (iframePageNotFound) {
228254
return (
229255
<IframeNotFound
230256
project={project.current}
231-
version={version}
257+
version={version.current}
232258
hideUi={hideUi}
233259
/>
234260
)
235261
}
236262

237-
if (versions.length === 0) {
238-
return <LoadingPage />
239-
}
240-
241263
return (
242264
<>
243265
<IFrame
244266
src={iFrameSrc}
245-
onPageChanged={iFramePageChanged}
246-
onHashChanged={iFrameHashChanged}
267+
onPageChanged={onIFramePageChanged}
268+
onHashChanged={onIFrameHashChanged}
247269
onTitleChanged={updateTitle}
248-
onNotFound={iFrameNotFound}
249-
onFaviconChanged={iFrameFaviconChanged}
270+
onNotFound={onIFrameNotFound}
271+
onFaviconChanged={onIFrameFaviconChanged}
250272
/>
251273
{!hideUi && (
252274
<DocumentControlButtons
253-
version={version}
275+
version={version.current}
254276
versions={versions}
255277
onVersionChange={onVersionChanged}
256278
/>

0 commit comments

Comments
 (0)