admin: use toast-like notification for plugin start/stop and config saving
Massimo Melina committed
May 1, 2022 at 13:11 UTC
a43f17551d02eb231ee0ce6322fa705ebabf8710
3 files changed
+46
-13
admin/src/ConfigPage.ts
+2
-2
@@ -10,7 +10,7 @@ import { subscribeKey } from 'valtio/utils'
10
import { Form, BoolField, NumberField, SelectField, FieldProps, Field } from './Form';
11
import StringStringField from './StringStringField'
12
import FileField from './FileField'
13
-import { alertDialog, closeDialog, confirmDialog, formDialog, newDialog, waitDialog } from './dialog'
13
+import { alertDialog, closeDialog, confirmDialog, formDialog, newDialog, toast, waitDialog } from './dialog'
14
import { proxyWarning } from './HomePage'
15
16
let loaded: Dict | undefined
@@ -134,7 +134,7 @@ export default function ConfigPage() {
134
setTimeout(reloadStatus, 1000)
135
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
136
recalculateChanges()
137
- await alertDialog("Changes applied", 'success')
137
+ toast("Changes applied", 'success')
138
}
139
}
140
admin/src/PluginsPage.ts
+13
-7
@@ -3,8 +3,8 @@ import { apiCall, useApiList } from './api'
3
import { DataGrid } from '@mui/x-data-grid'
4
import { Alert, Box } from '@mui/material'
5
import { IconBtn } from './misc'
6
-import { PowerSettingsNew, Settings } from '@mui/icons-material'
7
-import { alertDialog, formDialog } from './dialog'
6
+import { PlayCircle, Settings, StopCircle } from '@mui/icons-material'
7
+import { toast, formDialog } from './dialog'
8
import { BoolField, Field, MultiSelectField, NumberField, SelectField, StringField } from './Form'
9
import { ArrayField } from './ArrayField'
10
@@ -44,12 +44,18 @@ export default function PluginsPage() {
44
renderCell({ row }) {
45
const { config, id } = row
46
return h('div', {},
47
- h(IconBtn, {
48
- icon: PowerSettingsNew,
49
- title: (row.started ? "Stop" : "Start") + ' ' + id,
47
+ h(IconBtn, row.started ? {
48
+ icon: StopCircle,
49
+ title: `Stop ${id}`,
50
+ onClick: () =>
51
+ apiCall('set_plugin', { id, enabled: false }).then(() =>
52
+ toast("Plugin is stopping", h(StopCircle, { color: 'warning' })))
53
+ } : {
54
+ icon: PlayCircle,
55
+ title: `Start ${id}`,
56
onClick: () =>
51
- apiCall('set_plugin', { id, enabled: !row.started }).then(() =>
52
- alertDialog(row.started ? "Plugin is stopping" : "Plugin is starting")),
57
+ apiCall('set_plugin', { id, enabled: true }).then(() =>
58
+ toast("Plugin is starting", h(PlayCircle, { color: 'success' }))),
59
}),
60
h(IconBtn, {
61
icon: Settings,
admin/src/dialog.ts
+31
-4
@@ -28,12 +28,13 @@ dialogsDefaults.Container = function Container(d:DialogOptions) {
28
}, [])
29
const ref = useRef<HTMLElement>()
30
d = { ...dialogsDefaults, ...d }
31
- const { sx, ...rest } = d.dialogProps||{}
31
+ const { sx, root, ...rest } = d.dialogProps||{}
32
const p = d.padding ? 2 : 0
33
return h(MuiDialog, {
34
open: true,
35
maxWidth: 'lg',
36
...rest,
37
+ ...root,
38
onClose: ()=> closeDialog(),
39
},
40
d.title && h(DialogTitle, {}, d.title),
@@ -52,8 +53,9 @@ const type2ico = {
53
info: Info,
54
success: Check,
55
}
55
-
56
-export async function alertDialog(msg: ReactElement | string | Error, type:AlertType='info', icon?: ReactElement) {
56
+export async function alertDialog(msg: ReactElement | string | Error, options?: AlertType | ({ type?:AlertType, icon?: ReactElement } & Partial<DialogOptions>)) {
57
+ const opt = typeof options === 'string' ? { type: options } : (options ?? {})
58
+ let { type='info', ...rest } = opt
59
if (msg instanceof Error) {
60
msg = msg.message || String(msg)
61
type = 'error'
@@ -62,12 +64,13 @@ export async function alertDialog(msg: ReactElement | string | Error, type:Alert
64
className: 'dialog-alert-'+type,
65
icon: '!',
66
onClose: resolve,
67
+ ...rest,
68
Content
69
}))
70
71
function Content(){
72
return h(Box, { display:'flex', flexDirection: 'column', alignItems: 'center', gap: 1 },
70
- icon ?? h(type2ico[type], { color:type }),
73
+ opt.icon ?? h(type2ico[type], { color:type }),
74
isValidElement(msg) ? msg : h('div', {}, String(msg))
75
)
76
}
@@ -156,3 +159,27 @@ export async function promptDialog(msg: string, props:any={}) : Promise<string |
159
export function waitDialog() {
160
return newDialog({ Content: CircularProgress, closable: false })
161
}
162
+
163
+export function toast(msg: string | ReactElement, type: AlertType | ReactElement='info') {
164
+ const ms = 3000
165
+ const close = newDialog({
166
+ Content,
167
+ dialogProps: {
168
+ PaperProps: {
169
+ sx: { transition: `opacity ${ms}ms ease-in` },
170
+ ref(x: HTMLElement) { // we need to set opacity later, to trigger transition
171
+ if (x)
172
+ x.style.opacity = '0'
173
+ }
174
+ }
175
+ }
176
+ })
177
+ setTimeout(close, ms)
178
+
179
+ function Content(){
180
+ return h(Box, { display:'flex', flexDirection: 'column', alignItems: 'center', gap: 1 },
181
+ isValidElement(type) ? type : h(type2ico[type], { color:type }),
182
+ isValidElement(msg) ? msg : h('div', {}, String(msg))
183
+ )
184
+ }
185
+}