fix: admin/home: infinite warnings when the update is failing

Massimo Melina committed Jul 19, 2023 at 15:38 UTC cbf07db8a863a56aadced249b49f67d990ba8672
4 files changed +52 -42
admin/src/HomePage.ts
+4 -2
@@ -129,11 +129,13 @@ async function update(tag?: string) {
129 await apiCall('update', { tag })
130 toast("Restarting")
131 const restarting = Date.now()
132 + let warning: undefined | ReturnType<typeof alertDialog>
133 while (await apiCall('NONE').then(() => 0, e => !e.code)) { // while we get no response
133 - if (Date.now() - restarting > 10_000)
134 - toast("This is taking too long, please check your server", 'warning')
134 + if (!warning && Date.now() - restarting > 10_000)
135 + warning = alertDialog("This is taking too long, please check your server", 'warning')
136 await wait(500)
137 }
138 + warning?.close()
139 // the server is back on, SSE is restored and login dialog may appear, unwanted because we are just waiting to reload
140 subscribeKey(state, 'loginRequired', () => state.loginRequired = false)
141 await alertDialog("Procedure complete", 'success')
admin/src/dialog.ts
+38 -33
@@ -18,7 +18,7 @@ import {
18 useState
19 } from 'react'
20 import { Check, Close, Error as ErrorIcon, Forward, Info, Warning } from '@mui/icons-material'
21 -import { newDialog, closeDialog, dialogsDefaults, DialogOptions, componentOrNode } from '@hfs/shared'
21 +import { newDialog, closeDialog, dialogsDefaults, DialogOptions, componentOrNode, pendingPromise } from '@hfs/shared'
22 import { Form, FormProps } from '@hfs/mui-grid-form'
23 import { IconBtn, Flex } from './misc'
24 import { useDark } from './theme'
@@ -85,46 +85,49 @@ const type2ico = {
85 info: Info,
86 success: Check,
87 }
88 -export async function alertDialog(msg: ReactElement | string | Error, options?: AlertType | ({ type?:AlertType, icon?: ReactElement } & Partial<DialogOptions>)) {
89 - return new Promise(resolve => {
90 - const opt = typeof options === 'string' ? { type: options } : (options ?? {})
91 - let { type='info', ...rest } = opt
92 - if (msg instanceof Error) {
93 - msg = msg.message || String(msg)
94 - type = 'error'
88 +export function alertDialog(msg: ReactElement | string | Error, options?: AlertType | ({ type?:AlertType, icon?: ReactElement } & Partial<DialogOptions>)) {
89 + const opt = typeof options === 'string' ? { type: options } : (options ?? {})
90 + let { type='info', ...rest } = opt
91 + if (msg instanceof Error) {
92 + msg = msg.message || String(msg)
93 + type = 'error'
94 + }
95 +
96 + const promise = pendingPromise()
97 + const dialog = newDialog({
98 + className: 'dialog-alert-' + type,
99 + icon: '!',
100 + onClose: promise.resolve,
101 + ...rest,
102 + Content() {
103 + return h(Box, { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1 },
104 + h(IconButton, {
105 + onClick() {
106 + dialog.close()
107 + },
108 + size: 'small',
109 + sx: { position: 'absolute', right: 0, top: 0, opacity: .5 }
110 + }, h(Close)),
111 + opt.icon ?? h(type2ico[type], { color: type, fontSize: 'large' }),
112 + isValidElement(msg) ? msg
113 + : h(Box, { fontSize: 'large', mb: 1, lineHeight: '1.8em' }, String(msg)),
114 + )
115 }
96 - const { close } = newDialog({
97 - className: 'dialog-alert-' + type,
98 - icon: '!',
99 - onClose: resolve,
100 - ...rest,
101 - Content() {
102 - return h(Box, { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1 },
103 - h(IconButton, {
104 - onClick() {
105 - close()
106 - },
107 - size: 'small',
108 - sx: { position: 'absolute', right: 0, top: 0, opacity: .5 }
109 - }, h(Close)),
110 - opt.icon ?? h(type2ico[type], { color: type, fontSize: 'large' }),
111 - isValidElement(msg) ? msg
112 - : h(Box, { fontSize: 'large', mb: 1, lineHeight: '1.8em' }, String(msg)),
113 - )
114 - }
115 - })
116 })
117 + return Object.assign(promise, dialog)
118 }
119
120 interface ConfirmOptions extends Omit<DialogOptions, 'Content'> { href?: string }
120 -export async function confirmDialog(msg: ReactNode, { href, ...rest }: ConfirmOptions={}) : Promise<boolean> {
121 - return new Promise(resolve => newDialog({
121 +export function confirmDialog(msg: ReactNode, { href, ...rest }: ConfirmOptions={}) {
122 + const promise = pendingPromise<boolean>()
123 + const dialog = newDialog({
124 className: 'dialog-confirm',
125 icon: '?',
124 - onClose: resolve,
126 + onClose: promise.resolve,
127 ...rest,
128 Content
127 - }) )
129 + })
130 + return Object.assign(promise, dialog)
131
132 function Content() {
133 return h(Fragment, {},
@@ -212,7 +215,7 @@ export function waitDialog() {
215
216 export function toast(msg: string | ReactElement, type: AlertType | ReactElement='info') {
217 const ms = 3000
215 - const { close } = newDialog({
218 + const dialog = newDialog({
219 Content,
220 dialogProps: {
221 fullScreen: false,
@@ -225,7 +228,9 @@ export function toast(msg: string | ReactElement, type: AlertType | ReactElement
228 }
229 }
230 })
231 + const { close } = dialog
232 setTimeout(close, ms)
233 + return dialog
234
235 function Content(){
236 return h(Box, { display:'flex', flexDirection: 'column', alignItems: 'center', gap: 1 },
frontend/src/dialog.ts
+3 -3
@@ -95,15 +95,15 @@ export function confirmDialog(msg: ReactElement | string, options: ConfirmOption
95 const { href, afterButtons, timeout, timeoutConfirm=false, ...rest } = options
96 if (typeof msg === 'string')
97 msg = h('p', {}, msg)
98 - const ret = pendingPromise<boolean>()
98 + const promise = pendingPromise<boolean>()
99 const dialog = newDialog({
100 className: 'dialog-confirm',
101 icon: '?',
102 - onClose: ret.resolve,
102 + onClose: promise.resolve,
103 ...rest,
104 Content
105 })
106 - return Object.assign(ret, dialog)
106 + return Object.assign(promise, dialog)
107
108 function Content() {
109 const [sec,setSec] = useState(Math.ceil(timeout||0))
shared/dialogs.ts
+7 -4
@@ -107,8 +107,7 @@ export function newDialog(options: DialogOptions) {
107 function close(v?:any) {
108 const i = dialogs.findIndex(x => (x as any).$id === $id)
109 if (i < 0) return
110 - dialogs.splice(i,1)
111 - options.onClose?.(v)
110 + return closeDialogAt(i, v)
111 }
112 }
113
@@ -118,8 +117,12 @@ export function closeDialog(v?:any) {
117 const d = dialogs[i]
118 if (d.reserveClosing)
119 continue
121 - dialogs.splice(i,1)
122 - return d.onClose?.(v)
120 + closeDialogAt(i, v)
121 + return d
122 }
123 }
124
125 +function closeDialogAt(i: number, value?: any) {
126 + const [d] = dialogs.splice(i,1)
127 + return d?.onClose?.(value)
128 +}
\ No newline at end of file