frontend optimization: since icon-loading detection mechanism was introduced, performance were inadequate in case of thousands of files, hanging the browser
Massimo Melina committed
Dec 21, 2021 at 11:54 UTC
f4853e0e0bdb1f3d159ec9ebfaaee74de772e3cc
7 files changed
+35
-67
frontend/src/App.tsx
+1
-1
@@ -10,4 +10,4 @@ function App() {
10
)
11
}
12
13
-export default App;
\ No newline at end of file
13
+export default App;
frontend/src/BrowseFiles.ts
+2
-2
@@ -2,7 +2,7 @@ import { Link, useLocation } from 'react-router-dom'
2
import { useApi } from './api'
3
import { createContext, createElement as h, Fragment, useContext, useEffect, useMemo, useState } from 'react'
4
import { formatBytes, hError, hIcon } from './misc'
5
-import { Loading, Spinner } from './components'
5
+import { Spinner } from './components'
6
import { Head } from './Head'
7
import { state, useSnapState } from './state'
8
import _ from 'lodash'
@@ -16,7 +16,7 @@ export const ListContext = createContext<{ list:DirList, unfinished:boolean }>({
16
export function BrowseFiles() {
17
const [list, unfinished] = useFetchList()
18
if (!list)
19
- return h(Loading)
19
+ return h(Spinner)
20
if (list instanceof Error)
21
return hError(list)
22
return h(ListContext.Provider, { value:{ list, unfinished } },
frontend/src/components.ts
+2
-26
@@ -1,30 +1,6 @@
1
-import { createElement as h } from 'react'
2
-import { ICON_FONT_NAME, useIconsReady, usePromise } from './hooks'
3
-
4
-const SYS_ICONS: Record<string,string> = {
5
- login: 'person',
6
- user: 'account_circle',
7
- file: 'description',
8
- spinner: 'sports_baseball',
9
- filter: 'filter_alt',
10
-}
11
-
12
-const iconClass = ICON_FONT_NAME.then(v => v.replace(/ /g,'-').toLowerCase())
13
-export function Icon({ name, forced, ...props }: { name:string, forced?:boolean, style?:any }) {
14
- name = SYS_ICONS[name] || name
15
- const cl = usePromise(iconClass)
16
- return h('span',{
17
- className: cl+' icon',
18
- ...props
19
- }, useIconsReady() || forced ? name : '')
20
-}
21
-
22
-export function Loading() {
23
- return useIconsReady() ? h(Spinner)
24
- : h('span', {}, 'Loading...')
25
-}
1
+import { hIcon } from './misc'
2
3
export function Spinner() {
28
- return h(Icon, { name:'spinner', style: { animation:'1s spin infinite' } })
4
+ return hIcon('spinner', { style: { animation:'1s spin infinite' } })
5
}
6
frontend/src/hooks.ts
deleted
-37
@@ -1,37 +0,0 @@
1
-import { useCallback, useEffect, useRef, useState } from 'react'
2
-import { waitFor } from './misc'
3
-
4
-export function useIsMounted() {
5
- const ref = useRef(true)
6
-
7
- useEffect(() => () => {
8
- ref.current = false
9
- }, [])
10
-
11
- return useCallback(()=> ref.current, [ref])
12
-}
13
-
14
-export function useStateMounted(init?: any) {
15
- const isMounted = useIsMounted()
16
- const [v,set] = useState(init)
17
- const setIfMounted = useCallback(x => {
18
- if (isMounted())
19
- set(x)
20
- }, [isMounted,set])
21
- return [v, setIfMounted, isMounted]
22
-}
23
-
24
-export const ICON_FONT_NAME = waitFor(()=> document.getElementById('iconsFile')).then(el => decodeURIComponent((el as HTMLLinkElement).href.split('=')[1].replace(/\+/g, ' ')))
25
-const iconsReady = document.fonts.ready.then(()=> ICON_FONT_NAME).then(name => document.fonts.load(`9px "${name}"`))
26
-
27
-export function usePromise<T>(p:Promise<T>): T | undefined {
28
- const [v, setV] = useStateMounted()
29
- useEffect(()=>{
30
- p.then(setV)
31
- }, [])
32
- return v
33
-}
34
-
35
-export function useIconsReady() {
36
- return usePromise(iconsReady)
37
-}
frontend/src/icons.ts
new
+28
@@ -0,0 +1,28 @@
1
+import { state, useSnapState } from './state'
2
+import { createElement as h } from 'react'
3
+import { waitFor } from './misc'
4
+
5
+const SYS_ICONS: Record<string,string> = {
6
+ login: 'person',
7
+ user: 'account_circle',
8
+ file: 'description',
9
+ spinner: 'sports_baseball',
10
+ filter: 'filter_alt',
11
+}
12
+
13
+document.fonts.ready.then(async ()=> {
14
+ const el = await waitFor(()=> document.getElementById('iconsFile'))
15
+ const name = decodeURIComponent((el as HTMLLinkElement).href.split('=')[1].replace(/\+/g, ' '))
16
+ await document.fonts.load(`9px "${name}"`) // force font to be loaded even if we didn't display anything with it yet
17
+ state.iconsClass = name.replace(/ /g,'-').toLowerCase()
18
+})
19
+
20
+export function Icon({ name, ...props }: { name:string, style?:any }) {
21
+ name = SYS_ICONS[name] || name
22
+ const { iconsClass } = useSnapState()
23
+ return h('span',{
24
+ className: iconsClass+' icon',
25
+ ...props
26
+ }, iconsClass ? name : '#')
27
+}
28
+
frontend/src/misc.ts
+1
-1
@@ -1,5 +1,5 @@
1
import { createElement as h } from 'react'
2
-import { Icon } from './components'
2
+import { Icon } from './icons'
3
4
export type Falsy = false | null | undefined | '' | 0
5
frontend/src/state.ts
+1
@@ -1,6 +1,7 @@
1
import { proxy, useSnapshot } from 'valtio'
2
3
export const state = proxy({
4
+ iconsClass: '',
5
username: '',
6
listFilter: '',
7
remoteSearch: '',