removed legacy code: wouter wrapper for frontend app
Massimo Melina committed
May 14, 2026 at 12:27 UTC
8ab999efe1ecdf0ab0ebda322b0392076ca1b7eb
8 files changed
+40
-94
frontend/src/App.ts
+15
-22
@@ -1,31 +1,34 @@
1
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
-import { BrowserRouter, Route, Routes, useNavigate } from './router'
4
-import { createElement as h, Fragment } from 'react'
3
+import { useLocation } from 'wouter'
4
+import { createElement as h } from 'react'
5
import { BrowseFiles } from "./BrowseFiles"
6
import { alertDialog, Dialogs } from './dialog'
7
import useTheme from "./useTheme"
8
import { state, useSnapState } from './state'
9
import { acceptDropFiles } from './upload'
10
import { enqueueUpload, getFilePath, uploadState } from './uploadQueue'
11
-import i18n from './i18n'
12
-const { t } = i18n
11
import { proxy, ref, useSnapshot } from "valtio"
12
import { Spinner } from "./components"
13
import { enforceStarting, getHFS, getPrefixUrl, loadScript } from '@hfs/shared'
14
import { Toasts } from './toasts'
15
+import i18n from './i18n'
16
+const { t } = i18n
17
18
const { i18nWrapperProps } = i18n
19
20
-function App() {
20
+export default function App() {
21
useTheme()
22
+ const go = useLocation()[1] // expose navigate function for programmatic usage
23
+ getHFS().navigate = (uri: string) => go(getPrefixUrl() + enforceStarting('/', uri))
24
+
25
const { ready } = useSnapshot(pageState) // wait for all plugins to be loaded
26
const { messageOnly } = useSnapState()
27
if (messageOnly)
28
return h('h1', { style: { textAlign: 'center'} }, messageOnly)
29
if (!ready)
30
return h(Spinner, { style: { margin: 'auto' } })
28
- installScript() // do this only after react has started working
31
+ installScript() // do this only after React has started working
32
return h('div', {
33
...i18nWrapperProps(),
34
...acceptDropFiles((files, to) => {
@@ -36,16 +39,10 @@ function App() {
39
: alertDialog(t("Upload not available"), 'warning')
40
})
41
},
39
- h(BrowserRouter, {},
40
- h(NavigationExtractor, {},
41
- h(Toasts),
42
- h(Dialogs, {},
43
- h(Routes, {},
44
- h(Route, { path: '*', element: h(BrowseFiles) })
45
- ),
46
- ),
47
- ),
48
- )
42
+ h(Toasts),
43
+ h(Dialogs, {},
44
+ h(BrowseFiles)
45
+ ),
46
)
47
}
48
@@ -62,14 +59,10 @@ function installScript() {
59
document.head.appendChild(el)
60
}
61
65
-function NavigationExtractor(props: any) {
66
- const go = useNavigate() // expose navigate function for programmatic usage
67
- getHFS().navigate = (uri: string) => go(getPrefixUrl() + enforceStarting('/', uri))
68
- return h(Fragment, props)
62
+export function navigate(uri: string) {
63
+ return getHFS().navigate(uri)
64
}
65
71
-export default App;
72
-
66
const pageState = proxy({ ready: document.readyState === 'complete' })
67
document.addEventListener('readystatechange', () => {
68
pageState.ready = document.readyState === 'complete'
frontend/src/Breadcrumbs.ts
+6
-6
@@ -1,7 +1,7 @@
1
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
-import { Link, LinkProps } from './router'
4
-import { createElement as h, Fragment, ReactElement } from 'react'
3
+import { createElement as h, Fragment, MouseEvent, ReactElement } from 'react'
4
+import { Link } from 'wouter'
5
import { getPrefixUrl, hIcon } from './misc'
6
import { DirEntry, state, useSnapState } from './state'
7
import { usePath, reloadList } from './useFetchList'
@@ -33,7 +33,7 @@ export function Breadcrumbs() {
33
}
34
}
35
36
-function Breadcrumb({ path, label, current, ...rest }: { current?: boolean, path: string, label?: string | ReactElement } & Omit<LinkProps,'to'>) {
36
+function Breadcrumb({ path, label, current, id }: { id?: string, current?: boolean, path: string, label?: string | ReactElement }) {
37
const PAD = '\u00A0\u00A0' // make small elements easier to tap. Don't use min-width 'cause it requires display-inline that breaks word-wrapping
38
if (typeof label === 'string' && label.length < 3)
39
label = PAD + label + PAD
@@ -42,10 +42,10 @@ function Breadcrumb({ path, label, current, ...rest }: { current?: boolean, path
42
const p = props?.can_archive ? '' : 'a'
43
return h(Link, {
44
className: 'breadcrumb',
45
- to: path || '/',
45
+ href: path || '/',
46
...!current && dragFilesDestination, // we don't really know if this folder allows upload, but in the worst case the user will get an error
47
- ...rest,
48
- onClick(ev) {
47
+ id,
48
+ onClick(ev: MouseEvent) {
49
if (!current) return
50
ev.preventDefault()
51
void openFileMenu(new DirEntry(decodeURIComponent(path), { p, comment: props?.comment }), ev, [
frontend/src/BrowseFiles.ts
+3
-3
@@ -1,6 +1,7 @@
1
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
-import { Link, useNavigate } from './router'
3
+import { Link } from 'wouter'
4
+import { navigate } from './App'
5
import {
6
createElement as h, Fragment, memo, MouseEvent, useCallback, useEffect, useMemo, useRef, useState, useId
7
} from 'react'
@@ -121,7 +122,6 @@ function FilesList() {
122
useEffect(() => setFocus(''), [theList]) // reset
123
const focusTypingId = 'focus-typing'
124
const endReached = () => restartAnimation(document.getElementById(focusTypingId), 'spin .3s')
124
- const navigate = useNavigate()
125
const timeout = useRef()
126
useEventListener('keydown', ev => {
127
if (anyDialogOpen()) return // won't work while dialogs are open
@@ -317,7 +317,7 @@ const Entry = ({ entry, midnight, separator }: EntryProps) => {
317
!isFolder ? h('a', { href: uri, ...commonProps, target: entry.target, rel: entry.target && 'noopener noreferrer' })
318
: h(Fragment, {},
319
// without reloadDocument, once you enter the web page, the back button won't bring you back to the frontend
320
- h(Link, { to: uri, reloadDocument: entry.web, ...commonProps }), // Link = internal navigation
320
+ h(entry.web ? 'a' : Link, { href: uri, ...commonProps }), // Link = internal navigation
321
// popup button is here to be able to detect link-wrapper:hover
322
file_menu_on_link && !showingButton && h('button', {
323
className: 'popup-menu-button',
frontend/src/clip.ts
+3
-4
@@ -2,7 +2,7 @@ import { createElement as h, Fragment } from 'react'
2
import { DirList, state, useSnapState } from './state'
3
import { Btn } from './components'
4
import { alertDialog, toast } from './dialog'
5
-import { useNavigate } from './router'
5
+import { navigate } from './App'
6
import { apiCall } from '@hfs/shared/api'
7
import { reloadList, usePath } from './useFetchList'
8
import _ from 'lodash'
@@ -13,8 +13,7 @@ const { t, useI18N } = i18n
13
export function ClipBar() {
14
const { clip, props } = useSnapState()
15
const { t } = useI18N()
16
- const go = useNavigate()
17
- const here = usePath()
16
+ const here = usePath()
17
if (!clip.length)
18
return null
19
const there = dirname(clip[0].uri) + '/'
@@ -33,7 +32,7 @@ export function ClipBar() {
32
}
33
34
function goBack() {
36
- go(there)
35
+ navigate(there)
36
}
37
38
function show() {
frontend/src/fileMenu.ts
+7
-7
@@ -8,7 +8,7 @@ import _ from 'lodash'
8
import { getEntryIcon, MISSING_PERM } from './BrowseFiles'
9
import { DirEntry, state } from './state'
10
import { deleteFiles } from './menu'
11
-import { Link, LinkProps } from './router'
11
+import { Link, LinkProps } from 'wouter'
12
import { fileShow, getShowComponent } from './show'
13
import { alertDialog, promptDialog, toast } from './dialog'
14
import { apiCall, useApi } from '@hfs/shared/api'
@@ -84,7 +84,7 @@ export async function openFileMenu(entry: DirEntry, ev: MouseEvent, addToMenu: (
84
id: 'folder',
85
label: t`Folder`,
86
value: h(Link, {
87
- to: (folder.startsWith('/') ? '' : location.pathname) + pathEncode(folder) + '/',
87
+ href: (folder.startsWith('/') ? '' : location.pathname) + pathEncode(folder) + '/',
88
onClick: () => closeDialog(null, true)
89
}, folder.replaceAll('/', ' / '))
90
},
@@ -214,15 +214,15 @@ function updateEntry(entry: DirEntry, cb: (e: DirEntry) => unknown) {
214
cb(_.find(state.list, { n: entry.n })!)
215
}
216
217
-export function LinkClosingDialog(props: LinkProps) {
218
- return h(Link, props.reloadDocument ? props : {
217
+export function LinkClosingDialog({ to, reloadDocument, ...props }: LinkProps & { reloadDocument?: boolean }) {
218
+ return reloadDocument ? h('a', { ...props, href: to }) : h(Link, {
219
...props,
220
- to: '', // workaround to get dialogs and browser-history work correctly
221
- async onClick(ev) {
220
+ href: '', // workaround to get dialogs and browser-history work correctly
221
+ async onClick(ev: MouseEvent) {
222
ev.preventDefault()
223
while (anyDialogOpen())
224
await closeDialog()?.closed
225
- getHFS().navigate(props.to)
225
+ getHFS().navigate(to)
226
}
227
})
228
}
frontend/src/router.ts
deleted
-47
@@ -1,47 +0,0 @@
1
-import { createElement as h, Fragment } from 'react'
2
-import { Link as WouterLink, Route as WouterRoute, Switch, useLocation as useWouterLocation } from 'wouter'
3
-import type { AnchorHTMLAttributes, ComponentType, ReactElement, ReactNode } from 'react'
4
-
5
-// this module is designed to offer a minimal compatibility interface as that of react-router-dom
6
-
7
-export type LinkProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> & {
8
- to: string
9
- reloadDocument?: boolean
10
-}
11
-
12
-export const Routes = Switch as unknown as ComponentType<any>
13
-export const BrowserRouter = Fragment
14
-
15
-export function Link({ to, reloadDocument, ...rest }: LinkProps) {
16
- if (reloadDocument)
17
- return h('a', { href: to, ...rest })
18
- return h(WouterLink as unknown as ComponentType<any>, { href: to, ...rest })
19
-}
20
-
21
-export function useNavigate() {
22
- const [, setLocation] = useWouterLocation()
23
- return (to: string, options?: { replace?: boolean }) => {
24
- setLocation(to, options)
25
- }
26
-}
27
-
28
-export function useLocation(): { pathname: string } {
29
- useWouterLocation() // used just to cause render
30
- return { pathname: location.pathname }
31
-}
32
-
33
-type RouteProps = {
34
- path?: string
35
- element?: ReactElement
36
- children?: ReactNode
37
- component?: ComponentType<any>
38
-}
39
-
40
-export function Route({ path, element, children, component, ...rest }: RouteProps) {
41
- const resolvedPath = path === '*' ? '/:rest*' : path
42
- if (element)
43
- return h(WouterRoute, { path: resolvedPath, ...rest }, element)
44
- if (component)
45
- return h(WouterRoute, { path: resolvedPath, component, ...rest })
46
- return h(WouterRoute, { path: resolvedPath, ...rest }, children)
47
-}
frontend/src/upload.ts
+2
-2
@@ -13,7 +13,7 @@ import { alertDialog, promptDialog } from './dialog'
13
import { reloadList } from './useFetchList'
14
import { apiCall } from '@hfs/shared/api'
15
import { state, useSnapState } from './state'
16
-import { Link } from './router'
16
+import { Link } from 'wouter'
17
import { LinkClosingDialog } from './fileMenu'
18
import {
19
abortCurrentUpload, enqueueUpload, getFilePath, normalizeAccept, resetCounters, resetReloadOnClose,
@@ -138,7 +138,7 @@ export function showUpload() {
138
),
139
qs.map((q,idx) =>
140
h('div', { key: q.to },
141
- h(Link, { to: q.to, onClick: close }, t`Destination`, ' ', decodeURI(q.to)),
141
+ h(Link, { href: q.to, onClick: close }, t`Destination`, ' ', decodeURI(q.to)),
142
h(FileList, {
143
entries: uploadState.qs[idx].entries,
144
actions: {
frontend/src/useFetchList.ts
+4
-3
@@ -11,14 +11,16 @@ import {
11
hfsEvent, LIST, urlParams, xlate, objFromKeys, getHFS,
12
HTTP_MESSAGES, HTTP_METHOD_NOT_ALLOWED, HTTP_UNAUTHORIZED,
13
} from './misc'
14
-import { useLocation, useNavigate } from './router'
14
+import { useLocation } from 'wouter'
15
+import { navigate } from './App'
16
import { closeLoginDialog } from './login'
17
import { fileShow, getShowComponent } from './show'
18
import i18n from './i18n'
19
const { t } = i18n
20
21
export function usePath() {
21
- return useLocation().pathname
22
+ useLocation() // used just to cause render
23
+ return location.pathname // this is encoded, while useLocation returned decoded
24
}
25
26
// allow links with ?search
@@ -38,7 +40,6 @@ export default function useFetchList() {
40
const lastParams = useRef<any>()
41
const lastReloader = useRef(snap.listReloader)
42
const isMounted = useIsMounted()
41
- const navigate = useNavigate()
43
const { loginRequired=false } = snap // undefined=false
44
useEffect(()=>{
45
const previous = lastUri.current