@samitouri / QOSami-HFS / commits / 8ab999ef

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