better code: split file
Massimo Melina committed
Dec 22, 2021 at 23:24 UTC
bc5690478bfed74f3cf69585ab7579b8682755c2
2 files changed
+88
-85
frontend/src/BrowseFiles.ts
+3
-85
@@ -1,13 +1,13 @@
1
import { Link, useLocation } from 'react-router-dom'
2
-import { apiCall, apiEvents } from './api'
3
-import { createContext, createElement as h, Fragment, useContext, useEffect, useRef, useState } from 'react'
2
+import { createContext, createElement as h, Fragment, useContext } from 'react'
3
import { formatBytes, hError, hIcon } from './misc'
4
import { Spinner } from './components'
5
import { Head } from './Head'
6
import { state, useSnapState } from './state'
7
import _ from 'lodash'
8
+import useFetchList from './useFetchList'
9
10
-function usePath() {
10
+export function usePath() {
11
return decodeURI(useLocation().pathname)
12
}
13
@@ -28,88 +28,6 @@ export function BrowseFiles() {
28
h(FilesList))
29
}
30
31
-function useFetchList() {
32
- const snap = useSnapState()
33
- const desiredPath = usePath()
34
- const search = snap.remoteSearch || undefined
35
- const [list, setList] = useState<DirList>([])
36
- const [unfinished, setUnfinished] = useState(true)
37
- const [error, setError] = useState<Error>()
38
- const lastPath = useRef('')
39
- useEffect(()=>{
40
- const loc = window.location
41
- if (!desiredPath.endsWith('/')) { // useful only in dev, while accessing the frontend directly without passing by the main server
42
- loc.href = loc.href + '/'
43
- return
44
- }
45
- const previous = lastPath.current
46
- lastPath.current = desiredPath
47
- if (previous !== desiredPath && search) {
48
- state.remoteSearch = ''
49
- state.stopSearch?.()
50
- return
51
- }
52
-
53
- ;(async ()=>{
54
- const API = 'file_list'
55
- const sse = search
56
- const baseParams = { path:desiredPath, search, sse, omit:'c' }
57
- let list: DirList = []
58
- setUnfinished(true)
59
- setList(list)
60
-
61
- if (sse) { // buffering entries is necessary against burst of events that will hang the browser
62
- const buffer:DirList = []
63
- const flush = () => {
64
- const chunk = buffer.splice(0, Infinity)
65
- if (chunk.length)
66
- setList(list = [...list, ...chunk])
67
- }
68
- const timer = setInterval(flush, 1000)
69
- const src = apiEvents(API, baseParams, (type, data) => {
70
- switch (type) {
71
- case 'error':
72
- return setError(Error(JSON.stringify(data)))
73
- case 'closed':
74
- clearInterval(timer)
75
- flush()
76
- return setUnfinished(false)
77
- case 'msg':
78
- if (src?.readyState === src?.CLOSED)
79
- return state.stopSearch?.()
80
- let { entry } = data
81
- console.log(entry.n)
82
- buffer.push(entry)
83
- }
84
- })
85
- state.stopSearch = ()=>{
86
- buffer.length = 0
87
- clearInterval(timer)
88
- state.stopSearch = undefined
89
- src.close()
90
- }
91
- return
92
- }
93
-
94
- let offset = 0
95
- while (1) {
96
- const limit = list.length ? 1000 : 100
97
- const res = await apiCall(API, { ...baseParams, offset, limit })
98
- || Error()
99
- if (res instanceof Error)
100
- return setError(res)
101
- const chunk = res.list
102
- setList(list = [ ...list, ...chunk ])
103
- if (chunk.length < limit)
104
- break
105
- offset = list.length
106
- }
107
- setUnfinished(false)
108
- })()
109
- }, [desiredPath, search])
110
- return { list, unfinished, error }
111
-}
112
-
31
function FilesList() {
32
const { list, unfinished } = useContext(ListContext)
33
const snap = useSnapState()
frontend/src/useFetchList.ts
new
+85
@@ -0,0 +1,85 @@
1
+import { state, useSnapState } from './state'
2
+import { useEffect, useRef, useState } from 'react'
3
+import { apiCall, apiEvents } from './api'
4
+import { DirList, usePath } from './BrowseFiles'
5
+
6
+export default function useFetchList() {
7
+ const snap = useSnapState()
8
+ const desiredPath = usePath()
9
+ const search = snap.remoteSearch || undefined
10
+ const [list, setList] = useState<DirList>([])
11
+ const [unfinished, setUnfinished] = useState(true)
12
+ const [error, setError] = useState<Error>()
13
+ const lastPath = useRef('')
14
+ useEffect(()=>{
15
+ const loc = window.location
16
+ if (!desiredPath.endsWith('/')) { // useful only in dev, while accessing the frontend directly without passing by the main server
17
+ loc.href = loc.href + '/'
18
+ return
19
+ }
20
+ const previous = lastPath.current
21
+ lastPath.current = desiredPath
22
+ if (previous !== desiredPath && search) {
23
+ state.remoteSearch = ''
24
+ state.stopSearch?.()
25
+ return
26
+ }
27
+
28
+ ;(async ()=>{
29
+ const API = 'file_list'
30
+ const sse = search
31
+ const baseParams = { path:desiredPath, search, sse, omit:'c' }
32
+ let list: DirList = []
33
+ setUnfinished(true)
34
+ setList(list)
35
+
36
+ if (sse) { // buffering entries is necessary against burst of events that will hang the browser
37
+ const buffer:DirList = []
38
+ const flush = () => {
39
+ const chunk = buffer.splice(0, Infinity)
40
+ if (chunk.length)
41
+ setList(list = [...list, ...chunk])
42
+ }
43
+ const timer = setInterval(flush, 1000)
44
+ const src = apiEvents(API, baseParams, (type, data) => {
45
+ switch (type) {
46
+ case 'error':
47
+ return setError(Error(JSON.stringify(data)))
48
+ case 'closed':
49
+ flush()
50
+ state.stopSearch?.()
51
+ return setUnfinished(false)
52
+ case 'msg':
53
+ if (src?.readyState === src?.CLOSED)
54
+ return state.stopSearch?.()
55
+ buffer.push(data.entry)
56
+ }
57
+ })
58
+ state.stopSearch = ()=>{
59
+ buffer.length = 0
60
+ clearInterval(timer)
61
+ state.stopSearch = undefined
62
+ src.close()
63
+ }
64
+ return
65
+ }
66
+
67
+ let offset = 0
68
+ while (1) {
69
+ const limit = list.length ? 1000 : 100
70
+ const res = await apiCall(API, { ...baseParams, offset, limit })
71
+ || Error()
72
+ if (res instanceof Error)
73
+ return setError(res)
74
+ const chunk = res.list
75
+ setList(list = [ ...list, ...chunk ])
76
+ if (chunk.length < limit)
77
+ break
78
+ offset = list.length
79
+ }
80
+ setUnfinished(false)
81
+ })()
82
+ }, [desiredPath, search])
83
+ return { list, unfinished, error }
84
+}
85
+