better code: simpler api
Massimo Melina committed
Jul 13, 2023 at 23:54 UTC
52295707e16379754032e5635e8ec89d137ad07d
4 files changed
+30
-29
frontend/src/dialog.ts
+21
-20
@@ -2,11 +2,11 @@
2
3
import { createElement as h, ReactElement, ReactNode, useEffect, useRef, useState } from 'react'
4
import './dialog.css'
5
-import { newDialog, closeDialog, DialogOptions, DialogCloser } from '@hfs/shared/dialogs'
5
+import { newDialog, closeDialog, DialogOptions } from '@hfs/shared/dialogs'
6
import _ from 'lodash'
7
import { useInterval } from 'usehooks-ts'
8
import { t } from './i18n'
9
-import { err2msg } from './misc'
9
+import { err2msg, pendingPromise } from './misc'
10
export * from '@hfs/shared/dialogs'
11
12
interface PromptOptions extends Partial<DialogOptions> { def?:string, type?:string, trim?: boolean }
@@ -63,16 +63,17 @@ export async function promptDialog(msg: string, { def, type, trim=true, ...rest
63
64
type AlertType = 'error' | 'warning' | 'info'
65
66
-export async function alertDialog(msg: ReactElement | string | Error, type:AlertType='info', { getClose=_.noop }={}) {
66
+export function alertDialog(msg: ReactElement | string | Error, type:AlertType='info') {
67
if (msg instanceof Error)
68
type = 'error'
69
- return new Promise(resolve => getClose(newDialog({
69
+ const ret = pendingPromise()
70
+ return Object.assign(ret, newDialog({
71
className: 'dialog-alert dialog-alert-'+type,
72
title: t(_.capitalize(type)),
73
icon: '!',
73
- onClose: resolve,
74
+ onClose: ret.resolve,
75
Content
75
- }).close))
76
+ }))
77
78
function Content(){
79
if (msg instanceof Error)
@@ -89,20 +90,20 @@ export interface ConfirmOptions extends Partial<DialogOptions> {
90
afterButtons?: ReactNode
91
timeout?: number
92
timeoutConfirm?: boolean
92
- getClose?: (cb: DialogCloser) => unknown
93
}
94
-export async function confirmDialog(msg: ReactElement | string, options: ConfirmOptions={}) : Promise<unknown> {
95
- const { href, afterButtons, timeout, timeoutConfirm=false, getClose=_.noop, ...rest } = options
94
+export function confirmDialog(msg: ReactElement | string, options: ConfirmOptions={}) {
95
+ const { href, afterButtons, timeout, timeoutConfirm=false, ...rest } = options
96
if (typeof msg === 'string')
97
msg = h('p', {}, msg)
98
- return new Promise(resolve =>
99
- getClose(newDialog({
100
- className: 'dialog-confirm',
101
- icon: '?',
102
- onClose: resolve,
103
- ...rest,
104
- Content
105
- }).close) )
98
+ const ret = pendingPromise<boolean>()
99
+ const dialog = newDialog({
100
+ className: 'dialog-confirm',
101
+ icon: '?',
102
+ onClose: ret.resolve,
103
+ ...rest,
104
+ Content
105
+ })
106
+ return Object.assign(ret, dialog)
107
108
function Content() {
109
const [sec,setSec] = useState(Math.ceil(timeout||0))
@@ -110,7 +111,7 @@ export async function confirmDialog(msg: ReactElement | string, options: Confirm
111
const missingText = timeout!>0 && ` (${sec})`
112
useEffect(() => {
113
if (timeout && !sec)
113
- closeDialog(timeoutConfirm)
114
+ dialog.close(timeoutConfirm)
115
}, [sec])
116
return h('div', {},
117
msg,
@@ -124,10 +125,10 @@ export async function confirmDialog(msg: ReactElement | string, options: Confirm
125
},
126
h('a', {
127
href,
127
- onClick() { closeDialog(true) },
128
+ onClick() { dialog.close(true) },
129
}, h('button', {}, t`Confirm`, timeoutConfirm && missingText)),
130
h('button', {
130
- onClick() { closeDialog(false) },
131
+ onClick() { dialog.close(false) },
132
}, t`Don't`, !timeoutConfirm && missingText),
133
afterButtons,
134
)
frontend/src/upload.ts
+6
-5
@@ -4,7 +4,6 @@ import { createElement as h, Fragment, useMemo, useState } from 'react'
4
import { Checkbox, Flex, FlexV, iconBtn } from './components'
5
import {
6
closeDialog,
7
- DialogCloser,
7
formatBytes,
8
formatPerc,
9
hIcon,
@@ -122,7 +121,7 @@ export function showUpload() {
121
h('button', {
122
className: 'upload-send',
123
onClick() {
125
- enqueue(files)
124
+ enqueue(files).then()
125
setFiles([])
126
}
127
}, t('send_files', { n: files.length, size }, "Send {n,plural,one{# file} other{# files}}, {size}")),
@@ -253,7 +252,7 @@ let req: XMLHttpRequest | undefined
252
let overrideStatus = 0
253
let notificationChannel = ''
254
let notificationSource: EventSource
256
-let closeLast: DialogCloser | undefined
255
+let closeLast: undefined | (() => void)
256
257
async function startUpload(f: File, to: string, resume=0) {
258
let resuming = false
@@ -306,7 +305,9 @@ async function startUpload(f: File, to: string, resume=0) {
305
const cancelSub = subscribeKey(uploadState, 'partial', v =>
306
v >= size && closeLast?.() ) // dismiss dialog as soon as we pass the threshold
307
const msg = t('confirm_resume', "Resume upload?") + ` (${formatPerc(size/f.size)} = ${formatBytes(size)})`
309
- const confirmed = await confirmDialog(msg, { timeout, getClose: x => closeLast=x })
308
+ const dialog = confirmDialog(msg, { timeout })
309
+ closeLast = dialog.close
310
+ const confirmed = await dialog
311
cancelSub()
312
if (!confirmed) return
313
if (uploading !== uploadState.uploading) return // too late
@@ -331,7 +332,7 @@ async function startUpload(f: File, to: string, resume=0) {
332
const specifier = (ERRORS as any)[status]
333
const msg = t('failed_upload', f, "Couldn't upload {name}") + prefix(': ', specifier)
334
closeLast?.()
334
- return alertDialog(msg, 'error', { getClose: x => closeLast=x })
335
+ closeLast = alertDialog(msg, 'error').close
336
}
337
338
function done() {
shared/dialogs.ts
-1
@@ -22,7 +22,6 @@ export interface DialogOptions {
22
}
23
24
const dialogs = proxy<DialogOptions[]>([])
25
-export type DialogCloser = ReturnType<typeof newDialog>['close']
25
26
export const dialogsDefaults: Partial<DialogOptions> = {
27
closableContent: 'x',
src/log.ts
+3
-3
@@ -49,7 +49,7 @@ const accessLogger = new Logger('log')
49
const accessErrorLog = new Logger('error_log')
50
export const loggers = [accessLogger, accessErrorLog]
51
52
-defineConfig('log', 'logs/access.log').sub(path => {
52
+defineConfig(accessLogger.name, 'logs/access.log').sub(path => {
53
console.debug('access log file: ' + (path || 'disabled'))
54
accessLogger.setPath(path)
55
})
@@ -89,8 +89,8 @@ export const logMw: Koa.Middleware = async (ctx, next) => {
89
try { // other logging requests shouldn't happen while we are renaming. Since this is very infrequent we can tolerate solving this by making it sync.
90
renameSync(path, path + '-' + postfix)
91
}
92
- catch(e) { // ok, rename failed, but this doesn't mean we ain't gonna log
93
- console.error(e)
92
+ catch(e: any) { // ok, rename failed, but this doesn't mean we ain't gonna log
93
+ console.error(String(e || e.message))
94
}
95
stream = logger.reopen() // keep variable updated
96
if (!stream) return