better api: return object as it's clearer

Massimo Melina committed Sep 12, 2023 at 14:45 UTC eff151f2c898cca1b529de6910806cb45ae051b1
6 files changed +23 -21
admin/src/HomePage.ts
+2 -2
@@ -2,7 +2,7 @@
2
3 import { createElement as h, ReactNode, useState } from 'react'
4 import { Box, Button, Card, CardContent, LinearProgress, Link } from '@mui/material'
5 -import { apiCall, useApi, useApiEx, useApiList } from './api'
5 +import { apiCall, useApiEx, useApiList } from './api'
6 import {
7 Btn,
8 dontBotherWithKeys,
@@ -43,7 +43,7 @@ export default function HomePage() {
43 const { username } = useSnapState()
44 const { data: status, reload: reloadStatus, element: statusEl } = useApiEx<Status>('get_status')
45 const { data: vfs } = useApiEx<{ root?: VfsNode }>('get_vfs')
46 - const [account] = useApi<Account>(username && 'get_account')
46 + const { data: account } = useApiEx<Account>(username && 'get_account')
47 const { data: cfg, reload: reloadCfg } = useApiEx('get_config', { only: ['https_port', 'cert', 'private_key', 'proxies', 'update_to_beta'] })
48 const { list: plugins } = useApiList('get_plugins')
49 const [updates, setUpdates] = useState<undefined | any[]>()
admin/src/OptionsPage.ts
+2 -2
@@ -2,7 +2,7 @@
2
3 import { Box, Button, FormHelperText, Link } from '@mui/material';
4 import { createElement as h, Fragment, useEffect, useRef } from 'react';
5 -import { apiCall, useApi, useApiEx } from './api'
5 +import { apiCall, useApiEx } from './api'
6 import { state, useSnapState } from './state'
7 import { Info, Refresh, Warning } from '@mui/icons-material'
8 import { Dict, Flex, iconTooltip, LinkBtn, modifiedSx, REPO_URL, wikiLink, with_ } from './misc'
@@ -46,7 +46,7 @@ export default function OptionsPage() {
46 useEffect(() => void(reloadStatus()), [data]) //eslint-disable-line
47 useEffect(() => () => exposedReloadStatus = undefined, []) // clear on unmount
48
49 - const admins = useApi('get_admins')[0]?.list
49 + const admins = useApiEx('get_admins').data?.list
50
51 if (element)
52 return element
admin/src/VfsTree.ts
+1 -1
@@ -9,7 +9,7 @@ import {
9 } from '@mui/icons-material'
10 import { Box } from '@mui/material'
11 import { reloadVfs, VfsNode, Who } from './VfsPage'
12 -import { iconTooltip, isWindowsDrive, onlyTruthy } from './misc'
12 +import { iconTooltip, onlyTruthy } from './misc'
13 import { apiCall } from './api'
14 import { alertDialog, confirmDialog } from './dialog'
15
admin/src/api.ts
+10 -8
@@ -19,16 +19,18 @@ setDefaultApiCallOptions({
19 }
20 })
21
22 +// expand useApi with things that cannot be shared with Frontend
23 export function useApiEx<T=any>(...args: Parameters<typeof useApi>) {
23 - const [data, error, reload, loading] = useApi<T>(...args)
24 - const cmd = args[0]
25 - const element = useMemo(() =>
26 - !cmd ? null
27 - : error ? h(Alert, { severity: 'error' }, String(error), h(IconBtn, { icon: Refresh, onClick: reload, sx: { m:'-8px 0 -8px 16px' } }))
28 - : loading ? spinner()
24 + const res = useApi<T>(...args)
25 + return {
26 + ...res,
27 + element: useMemo(() =>
28 + !args[0] ? null
29 + : res.error ? h(Alert, { severity: 'error' }, String(res.error), h(IconBtn, { icon: Refresh, onClick: res.reload, sx: { m: '-8px 0 -8px 16px' } }))
30 + : res.loading ? spinner()
31 : null,
30 - [error, cmd, loading, reload])
31 - return { data, error, reload, loading, element }
32 + Object.values(res))
33 + }
34 }
35
36 export function useApiList<T=any, S=T>(cmd:string|Falsy, params: Dict={}, { map }: { map?: (rec: S) => T }={}) {
frontend/src/fileMenu.ts
+1 -1
@@ -68,7 +68,7 @@ export function openFileMenu(entry: DirEntry, ev: MouseEvent, addToMenu: (FileMe
68 : [ev.pageX, ev.pageY - scrollY] as [number, number],
69 Content() {
70 const {t} = useI18N()
71 - const [details] = useApi('get_file_details', { uris: [entry.uri] });
71 + const details = useApi('get_file_details', { uris: [entry.uri] }).data
72 const showProps = [ ...props,
73 with_(details?.[0]?.upload, x => x && [ t`Uploader`, x.ip + prefix(' (', x.username, ')') ])
74 ]
shared/api.ts
+7 -7
@@ -68,20 +68,20 @@ export class ApiError extends Error {
68 }
69 }
70
71 -export function useApi<T=any>(cmd: string | Falsy, params?: object) : [T | undefined, undefined | Error, ()=>void, boolean] {
72 - const [ret, setRet] = useStateMounted<T | undefined>(undefined)
73 - const [err, setErr] = useStateMounted<Error | undefined>(undefined)
71 +export function useApi<T=any>(cmd: string | Falsy, params?: object) {
72 + const [data, setData] = useStateMounted<T | undefined>(undefined)
73 + const [error, setError] = useStateMounted<Error | undefined>(undefined)
74 const [forcer, setForcer] = useStateMounted(0)
75 const loadingRef = useRef<ReturnType<typeof apiCall>>()
76 const reloadingRef = useRef<any>()
77 useEffect(()=>{
78 loadingRef.current?.abort()
79 - setRet(undefined)
80 - setErr(undefined)
79 + setData(undefined)
80 + setError(undefined)
81 if (!cmd) return
82 let aborted = false
83 const req = apiCall<T>(cmd, params)
84 - const wholePromise = req.then(x => aborted || setRet(x), x => aborted || setErr(x))
84 + const wholePromise = req.then(x => aborted || setData(x), x => aborted || setError(x))
85 .finally(() => loadingRef.current = reloadingRef.current = undefined)
86 loadingRef.current = Object.assign(wholePromise, {
87 abort() {
@@ -94,7 +94,7 @@ export function useApi<T=any>(cmd: string | Falsy, params?: object) : [T | undef
94 const reload = useCallback(() => loadingRef.current
95 || setForcer(v => v+1) || (reloadingRef.current = pendingPromise()),
96 [setForcer])
97 - return [ret, err, reload, ret === undefined || Boolean(loadingRef.current || reloadingRef.current)]
97 + return { data, error, reload, loading: data === undefined || Boolean(loadingRef.current || reloadingRef.current) }
98 }
99
100 type EventHandler = (type:string, data?:any) => void