reload by clicking on last breadcrumb
Massimo Melina committed
Jan 1, 2022 at 20:56 UTC
307e06aec737e24602a56d7443270c26716a95dd
6 files changed
+65
-13
frontend/src/BrowseFiles.ts
+3
-3
@@ -13,13 +13,13 @@ export function usePath() {
13
14
interface DirEntry { n:string, s?:number, m?:string, c?:string }
15
export type DirList = DirEntry[]
16
-interface ListRes { list:DirList, loading?:boolean, err?:Error }
16
+interface ListRes { list:DirList, loading?:boolean, err?:Error, reload?:()=>void }
17
18
export const ListContext = createContext<ListRes>({ list:[], loading: false })
19
20
export function BrowseFiles() {
21
- const { list, loading, error } = useFetchList()
22
- return h(ListContext.Provider, { value:{ list, loading } },
21
+ const { list, loading, error, reload } = useFetchList()
22
+ return h(ListContext.Provider, { value:{ list, loading, reload } },
23
h(Head),
24
hError(error && 'Failed to retrieve list') || h(list ? FilesList : Spinner))
25
}
frontend/src/Head.ts
+20
-7
@@ -6,7 +6,7 @@ import { formatBytes, hIcon, prefix } from './misc'
6
import { Spinner } from './components'
7
import { state, useSnapState } from './state'
8
import { useDebounce } from 'use-debounce'
9
-import { alertDialog, closeDialog, newDialog, promptDialog } from './dialog'
9
+import { alertDialog, closeDialog, confirmDialog, newDialog, promptDialog } from './dialog'
10
import { apiCall } from './api'
11
12
export function Head() {
@@ -168,20 +168,33 @@ function FolderStats() {
168
}
169
170
function Breadcrumbs() {
171
- const path = useLocation().pathname.slice(1,-1)
171
+ const currentPath = useLocation().pathname.slice(1,-1)
172
let prev = ''
173
- const breadcrumbs = path ? path.split('/').map(x => [prev = prev + x + '/', decodeURIComponent(x)]) : []
173
+ const breadcrumbs = currentPath ? currentPath.split('/').map(x => [prev = prev + x + '/', decodeURIComponent(x)]) : []
174
return h(Fragment, {},
175
h(Breadcrumb),
176
- breadcrumbs.map(([path,label]) => h(Breadcrumb, { key: path, path, label }))
176
+ breadcrumbs.map(([path,label]) =>
177
+ h(Breadcrumb, {
178
+ key: path,
179
+ path,
180
+ label,
181
+ current: path === currentPath+'/',
182
+ }) )
183
)
184
}
185
180
-function Breadcrumb({ path, label }:{ path?: string, label?: string }) {
186
+function Breadcrumb({ path, label, current }:{ current: boolean, path?: string, label?: string }) {
187
const PAD = '\u00A0' // make small elements easier to tap. Don't use min-width 'cause it requires display-inline that breaks word-wrapping
188
if (label && label.length < 3)
189
label = PAD+label+PAD
184
- return h(Link, { className:'breadcrumb', to:path||'/' },
185
- label || hIcon('home') )
190
+ const { reload } = useContext(ListContext)
191
+ return h(Link, {
192
+ className: 'breadcrumb',
193
+ to: path || '/',
194
+ async onClick() {
195
+ if (current && await confirmDialog('Reload?'))
196
+ reload?.()
197
+ }
198
+ }, label || hIcon('home') )
199
}
200
frontend/src/dialog.css
+4
@@ -42,6 +42,10 @@
42
line-height: 1.7em;
43
background-color: var(--color);
44
}
45
+.dialog-confirm button {
46
+ margin-top: 1em;
47
+}
48
+.dialog-confirm,
49
.dialog-prompt {
50
--color: #228
51
}
frontend/src/dialog.ts
+21
@@ -121,3 +121,24 @@ export async function alertDialog(msg: string | Error, type:AlertType='info') {
121
}))
122
}
123
124
+export async function confirmDialog(msg: string) : Promise<boolean> {
125
+ return new Promise(resolve => newDialog({
126
+ className: 'dialog-confirm',
127
+ icon: '?',
128
+ onClose: resolve,
129
+ content: Content
130
+ }) )
131
+
132
+ function Content() {
133
+ return h('div', {},
134
+ h('div', {}, msg),
135
+ h('button', {
136
+ autoFocus: true,
137
+ onClick(){
138
+ closeDialog(true)
139
+ }
140
+ }, 'Confirm')
141
+ )
142
+ }
143
+}
144
+
frontend/src/misc.ts
+6
-1
@@ -1,4 +1,4 @@
1
-import { createElement as h } from 'react'
1
+import { createElement as h, useCallback, useState } from 'react'
2
import { Spinner } from './components'
3
import { newDialog } from './dialog'
4
import { Icon } from './icons'
@@ -73,3 +73,8 @@ export function working() {
73
}
74
})
75
}
76
+
77
+export function useForceUpdate(): [()=>void, number] {
78
+ const [n, setN] = useState(0)
79
+ return [ useCallback(()=> setN(n => n+1), [setN]), n ]
80
+}
frontend/src/useFetchList.ts
+11
-2
@@ -2,6 +2,7 @@ import { state, useSnapState } from './state'
2
import { useEffect, useRef, useState } from 'react'
3
import { apiEvents } from './api'
4
import { DirList, usePath } from './BrowseFiles'
5
+import { useForceUpdate } from './misc'
6
7
export default function useFetchList() {
8
const snap = useSnapState()
@@ -11,6 +12,7 @@ export default function useFetchList() {
12
const [loading, setLoading] = useState(false)
13
const [error, setError] = useState<Error>()
14
const lastPath = useRef('')
15
+ const [reload, forcer] = useForceUpdate()
16
useEffect(()=>{
17
const loc = window.location
18
if (!desiredPath.endsWith('/')) { // useful only in dev, while accessing the frontend directly without passing by the main server
@@ -66,7 +68,14 @@ export default function useFetchList() {
68
}
69
70
})()
69
- }, [desiredPath, search, snap.username])
70
- return { list, loading, error }
71
+ }, [desiredPath, search, snap.username, forcer])
72
+ return {
73
+ list, loading, error,
74
+ reload() {
75
+ state.remoteSearch = ''
76
+ state.stopSearch?.()
77
+ reload()
78
+ }
79
+ }
80
}
81