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