leverage new useApiEx()
Massimo Melina committed
Jun 3, 2022 at 14:20 UTC
d5c6ac8e726d42bc2b46aaadea1e17f073b3a144
8 files changed
+65
-65
admin/src/AccountsPage.ts
+9
-9
@@ -1,7 +1,7 @@
1
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
-import { isValidElement, createElement as h, useState, useEffect, Fragment, useRef } from "react"
4
-import { apiCall, useApiComp } from './api'
3
+import { createElement as h, useState, useEffect, Fragment, useRef } from "react"
4
+import { apiCall, useApiEx } from './api'
5
import { Box, Button, Card, CardContent, Grid, List, ListItem, ListItemText, Typography } from '@mui/material'
6
import { Delete, Group, MilitaryTech, Person, PersonAdd, Refresh } from '@mui/icons-material'
7
import { BoolField, Form, MultiSelectField } from '@hfs/mui-grid-form'
@@ -32,17 +32,17 @@ export interface Account {
32
}
33
34
export default function AccountsPage() {
35
- const [res, reload] = useApiComp('get_accounts')
35
+ const { data, reload, element } = useApiEx('get_accounts')
36
const [sel, setSel] = useState<string[] | 'new-group' | 'new-user'>([])
37
const selectionMode = Array.isArray(sel)
38
const styles = useStyles()
39
useEffect(() => { // if accounts are reloaded, review the selection to remove elements that don't exist anymore
40
- if (Array.isArray(res?.list) && selectionMode)
41
- setSel( sel.filter(u => res.list.find((e:any) => e?.username === u)) ) // remove elements that don't exist anymore
42
- }, [res]) //eslint-disable-line -- Don't fall for its suggestion to add `sel` here: we modify it and declaring it as a dependency would cause a logical loop
43
- if (isValidElement(res))
44
- return res
45
- const { list }: { list: Account[] } = res
40
+ if (Array.isArray(data?.list) && selectionMode)
41
+ setSel( sel.filter(u => data.list.find((e:any) => e?.username === u)) ) // remove elements that don't exist anymore
42
+ }, [data]) //eslint-disable-line -- Don't fall for its suggestion to add `sel` here: we modify it and declaring it as a dependency would cause a logical loop
43
+ if (element)
44
+ return element
45
+ const { list }: { list: Account[] } = data
46
return h(Grid, { container: true, maxWidth: '80em' },
47
h(Grid, { item: true, xs: 12 },
48
h(Box, {
admin/src/ConfigPage.ts
+12
-11
@@ -1,8 +1,8 @@
1
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
import { Box, Button, FormHelperText, Link } from '@mui/material';
4
-import { createElement as h, isValidElement, useEffect, useRef } from 'react';
5
-import { apiCall, useApi, useApiEx, useApiComp } from './api'
4
+import { createElement as h, useEffect, useRef } from 'react';
5
+import { apiCall, useApi, useApiEx } from './api'
6
import { state, useSnapState } from './state'
7
import { Info, Refresh } from '@mui/icons-material'
8
import { Dict, modifiedSx } from './misc'
@@ -24,23 +24,24 @@ export const logLabels = {
24
}
25
26
export default function ConfigPage() {
27
- const [res, reloadConfig] = useApiComp('get_config', { omit: ['vfs'] })
27
+ const { data, reload: reloadConfig, element } = useApiEx('get_config', { omit: ['vfs'] })
28
let snap = useSnapState()
29
- const statusApi = useApiEx(res && 'get_status')
29
+ const statusApi = useApiEx(data && 'get_status')
30
const status = statusApi.data
31
- useEffect(statusApi.reload, [res]) //eslint-disable-line
31
+ const reloadStatus = statusApi.reload
32
+ useEffect(reloadStatus, [data]) //eslint-disable-line
33
33
- exposedReloadStatus = statusApi.reload
34
+ exposedReloadStatus = reloadStatus
35
useEffect(() => () => exposedReloadStatus = undefined, []) // clear on unmount
36
37
const admins = useApi('get_admins')[0]?.list
38
38
- if (isValidElement(res))
39
- return res
39
+ if (element)
40
+ return element
41
if (statusApi.error)
42
return statusApi.element
43
const { changes } = snap
43
- const values = (loaded !== res) ? (state.config = loaded = res) : snap.config
44
+ const values = (loaded !== data) ? (state.config = loaded = data) : snap.config
45
const maxSpeedDefaults = {
46
comp: NumberField,
47
min: 1,
@@ -62,7 +63,7 @@ export default function ConfigPage() {
63
addToBar: [h(Button, {
64
onClick() {
65
reloadConfig()
65
- statusApi.reload()
66
+ reloadStatus()
67
},
68
startIcon: h(Refresh),
69
}, "Reload")],
@@ -140,7 +141,7 @@ export default function ConfigPage() {
141
await alertDialog("You are being redirected but in some cases this may fail. Hold on tight!", 'warning')
142
return window.location.href = loc.protocol + '//' + loc.hostname + ':' + newPort + loc.pathname
143
}
143
- setTimeout(statusApi.reload, 2000) // in case of busy port, finding the name of the process can be a lengthy task. Worst case we'll get the generic error message
144
+ setTimeout(reloadStatus, 2000) // in case of busy port, finding the name of the process can be a lengthy task. Worst case we'll get the generic error message
145
Object.assign(loaded, values) // since changes are recalculated subscribing state.config, but it depends on 'loaded' to (which cannot be subscribed), be sure to update loaded first
146
recalculateChanges()
147
toast("Changes applied", 'success')
admin/src/FileForm.ts
+6
-6
@@ -1,10 +1,10 @@
1
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
import { state } from './state'
4
-import { createElement as h, isValidElement, useEffect, useMemo, useState } from 'react'
4
+import { createElement as h, useEffect, useMemo, useState } from 'react'
5
import { Alert, Button } from '@mui/material'
6
import { BoolField, DisplayField, Field, FieldProps, Form, MultiSelectField, SelectField } from '@hfs/mui-grid-form'
7
-import { apiCall, useApiComp } from './api'
7
+import { apiCall, useApiEx } from './api'
8
import { formatBytes, isEqualLax, modifiedSx, onlyTruthy } from './misc'
9
import { reloadVfs, VfsNode, Who } from './VfsPage'
10
import md from './md'
@@ -40,10 +40,10 @@ export default function FileForm({ file }: { file: VfsNode }) {
40
const showCanSee = (values.can_read ?? inheritedPerms.can_read) === true
41
const showTimestamps = hasSource && Boolean(values.ctime)
42
43
- let [accountsRes] = useApiComp<{ list: Account[] }>('get_accounts')
44
- if (isValidElement(accountsRes))
45
- return accountsRes
46
- const accounts = accountsRes.list
43
+ const { data, element } = useApiEx<{ list: Account[] }>('get_accounts')
44
+ if (element || !data)
45
+ return element
46
+ const accounts = data.list
47
48
return h(Form, {
49
values,
admin/src/HomePage.ts
+9
-9
@@ -1,8 +1,8 @@
1
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
-import { createElement as h, isValidElement } from 'react'
4
-import { Box, Button, Link } from '@mui/material'
5
-import { apiCall, useApi, useApiComp, useApiList } from './api'
3
+import { createElement as h } from 'react'
4
+import { Box, Button, LinearProgress, Link } from '@mui/material'
5
+import { apiCall, useApi, useApiEx, useApiList } from './api'
6
import { Dict, dontBotherWithKeys, InLink, objSameKeys, onlyTruthy } from './misc'
7
import { CheckCircle, Error, Info, Launch, Warning } from '@mui/icons-material'
8
import md from './md'
@@ -17,13 +17,13 @@ interface ServerStatus { listening: boolean, port: number, error?: string, busy?
17
export default function HomePage() {
18
const SOLUTION_SEP = " — "
19
const { username } = useSnapState()
20
- const [status, reloadStatus] = useApiComp<Dict<ServerStatus>>('get_status')
21
- const [vfs] = useApiComp<{ root?: VfsNode }>('get_vfs')
20
+ const { data: status, reload: reloadStatus, element: statusEl } = useApiEx<Dict<ServerStatus>>('get_status')
21
+ const { data: vfs } = useApiEx<{ root?: VfsNode }>('get_vfs')
22
const [account] = useApi<Account>(username && 'get_account')
23
- const [cfg, reloadCfg] = useApiComp('get_config', { only: ['https_port', 'cert', 'private_key', 'proxies', 'ignore_proxies'] })
23
+ const { data: cfg, reload: reloadCfg } = useApiEx('get_config', { only: ['https_port', 'cert', 'private_key', 'proxies', 'ignore_proxies'] })
24
const { list: plugins } = useApiList('get_plugins')
25
- if (!status || isValidElement(status))
26
- return status
25
+ if (statusEl || !status)
26
+ return statusEl
27
const { http, https } = status
28
const goSecure = !http?.listening && https?.listening ? 's' : ''
29
const srv = goSecure ? https : (http?.listening && http)
@@ -41,7 +41,7 @@ export default function HomePage() {
41
username && entry('', "Welcome "+username),
42
errors.length ? dontBotherWithKeys(errors.map(msg => entry('error', dontBotherWithKeys(msg))))
43
: entry('success', "Server is working"),
44
- !vfs || isValidElement(vfs) ? vfs
44
+ !vfs ? h(LinearProgress)
45
: !vfs.root?.children?.length && !vfs.root?.source ? entry('warning', "You have no files shared", SOLUTION_SEP, fsLink("add some"))
46
: entry('', md("Here you manage your server. There is a _separated_ interface to access your shared files: "),
47
h(Link, { target:'frontend', href: '/' }, "Frontend interface", h(Launch, { sx: { verticalAlign: 'sub', ml: '.2em' } }))),
admin/src/LogoutPage.ts
+5
-5
@@ -1,14 +1,14 @@
1
-import { createElement as h, isValidElement } from "react"
1
+import { createElement as h } from "react"
2
import { Alert, Box, Button } from '@mui/material'
3
-import { apiCall, useApiComp } from './api'
3
+import { apiCall, useApiEx } from './api'
4
import { alertDialog } from "./dialog"
5
import { useSnapState } from './state'
6
7
export default function LogoutPage() {
8
- const [cfg] = useApiComp('get_config', { only: [] }) // sort of noop
8
+ const { element } = useApiEx('get_config', { only: [] }) // sort of noop, just to get the 'element' part
9
const { username } = useSnapState()
10
- if (isValidElement(cfg))
11
- return cfg
10
+ if (element)
11
+ return element
12
if (!username)
13
return h(Alert, { severity: 'info' }, "You are not logged in, because authentication is not required on localhost")
14
return h(Box, { display: 'flex', flexDirection:'column', gap: 2 },
admin/src/MonitorPage.ts
+9
-10
@@ -1,8 +1,8 @@
1
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
import _ from "lodash"
4
-import { isValidElement, createElement as h, useMemo, Fragment, useState } from "react"
5
-import { apiCall, useApiComp, useApiList } from "./api"
4
+import { createElement as h, useMemo, Fragment, useState } from "react"
5
+import { apiCall, useApiEx, useApiList } from "./api"
6
import { PauseCircle, PlayCircle, Delete, Lock, Block, FolderZip } from '@mui/icons-material'
7
import { Box, Chip } from '@mui/material'
8
import { DataGrid } from "@mui/x-data-grid"
@@ -21,20 +21,19 @@ export default function MonitorPage() {
21
const isoDateRe = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
22
23
function MoreInfo() {
24
- const [res] = useApiComp('get_status')
24
+ const { data, element } = useApiEx('get_status')
25
return !useBreakpoint('md') ? null
26
- : isValidElement(res) ? res :
27
- h(Box, { display: 'flex', flexWrap: 'wrap', gap: '1em', mb: 2 },
28
- pair('started'),
29
- pair('http', "HTTP", port),
30
- pair('https', "HTTPS", port),
31
- )
26
+ : element || h(Box, { display: 'flex', flexWrap: 'wrap', gap: '1em', mb: 2 },
27
+ pair('started'),
28
+ pair('http', "HTTP", port),
29
+ pair('https', "HTTPS", port),
30
+ )
31
32
type Color = Parameters<typeof Chip>[0]['color']
33
type Render = (v:any) => [string, Color?]
34
35
function pair(k: string, label: string='', render?:Render) {
37
- let v = _.get(res, k)
36
+ let v = _.get(data, k)
37
if (v === undefined)
38
return null
39
if (typeof v === 'string' && isoDateRe.test(v))
admin/src/PermField.ts
+6
-6
@@ -1,16 +1,16 @@
1
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
import { Dict, useStateMounted } from './misc'
4
-import { createElement as h, Fragment, isValidElement } from 'react'
4
+import { createElement as h, Fragment } from 'react'
5
import { Button, Grid } from '@mui/material'
6
import { Field, FieldProps, SelectField } from '@hfs/mui-grid-form'
7
import _ from 'lodash'
8
-import { useApiComp } from './api'
8
+import { useApiEx } from './api'
9
10
export default function PermField({ label, value, onChange }: FieldProps<Dict<string> | null> & { keyLabel:string }) {
11
const [temp, setTemp] = useStateMounted<string|undefined>(undefined)
12
- const [res] = useApiComp('get_usernames')
13
- const usernames = res.list
12
+ const { data, element } = useApiEx('get_usernames')
13
+ const usernames = data?.list || []
14
15
const permOptions = [{ label:'read', value:'r' }, { label:'none', value:'' }]
16
const usernamesLeft = _.difference(usernames, Object.keys(value||{}))
@@ -18,13 +18,13 @@ export default function PermField({ label, value, onChange }: FieldProps<Dict<st
18
return h(Grid, { container: true },
19
label && h(Grid, { item: true, xs: 12, pl: 2, py: 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between' },
20
label,
21
- !isValidElement(res) && h(Button, {
21
+ !element && h(Button, {
22
onClick(event){
23
setTemp(undefined)
24
onChange(null, { event, was: value })
25
}
26
}, 'Clear')),
27
- isValidElement(res) ? res : h(Fragment, {},
27
+ element || h(Fragment, {},
28
// existing entries
29
Object.entries(value||{}).map(([username, perm]) => [
30
h(Grid, { key:'k', item: true, xs: 6 },
admin/src/VfsPage.ts
+9
-9
@@ -1,7 +1,7 @@
1
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
-import { createElement as h, isValidElement, useEffect, useMemo, useState } from 'react'
4
-import { useApi, useApiComp } from './api'
3
+import { createElement as h, useEffect, useMemo, useState } from 'react'
4
+import { useApi, useApiEx } from './api'
5
import { Alert, Grid, Link, List, ListItem, ListItemText, Typography } from '@mui/material'
6
import { state, useSnapState } from './state'
7
import VfsMenuBar from './VfsMenuBar'
@@ -17,14 +17,14 @@ let selectOnReload: string[] | undefined
17
export default function VfsPage() {
18
const [id2node] = useState(() => new Map<string, VfsNode>())
19
const snap = useSnapState()
20
- const [res, reload] = useApiComp('get_vfs')
20
+ const { data, reload, element } = useApiEx('get_vfs')
21
useMemo(() => snap.vfs || reload(), [snap.vfs, reload])
22
useEffect(() => {
23
state.vfs = undefined
24
- if (!res) return
24
+ if (!data) return
25
// rebuild id2node
26
id2node.clear()
27
- const { root } = res
27
+ const { root } = data
28
if (!root) return
29
recur(root) // this must be done before state change that would cause Tree to render and expecting id2node
30
root.isRoot = true
@@ -45,7 +45,7 @@ export default function VfsPage() {
45
recur(n, (pre && node.id) + '/', node)
46
}
47
48
- }, [res, id2node])
48
+ }, [data, id2node])
49
const [status] = useApi(window.location.host === 'localhost' && 'get_status')
50
const urls = useMemo(() =>
51
typeof status === 'object'
@@ -54,11 +54,11 @@ export default function VfsPage() {
54
url => url.includes('[')
55
),
56
[status])
57
- if (isValidElement(res)) {
57
+ if (element) {
58
id2node.clear()
59
- return res
59
+ return element
60
}
61
- const anythingShared = !res?.root?.children?.length && !res?.root?.source
61
+ const anythingShared = !data?.root?.children?.length && !data?.root?.source
62
const alert: AlertProps | false = anythingShared ? {
63
severity: 'warning',
64
children: "Add something to your shared files — click Add"