share common code: dialogs

Massimo Melina committed Mar 7, 2022 at 15:32 UTC f8650d80ed99f9829eae7a3233ca87620a9428d9
3 files changed +100 -138
admin/src/dialog.ts
+6 -56
@@ -3,46 +3,18 @@
3 import { Box, Button, Dialog as MuiDialog, DialogContent, DialogTitle } from '@mui/material'
4 import {
5 createElement as h,
6 - Fragment,
7 - FunctionComponent,
6 isValidElement,
7 ReactElement,
10 - ReactNode,
8 useEffect,
9 useRef
10 } from 'react'
14 -import { proxy, useSnapshot } from 'valtio'
11 import { Check, Error as ErrorIcon, Info, Warning } from '@mui/icons-material'
12 +import { newDialog, closeDialog, dialogsDefaults, DialogOptions, Dialogs } from 'shared/lib/dialogs'
13 +export { Dialogs }
14 +export * from 'shared/lib/dialogs'
15
17 -interface DialogOptions {
18 - Content: FunctionComponent,
19 - closable?: boolean,
20 - onClose?: (v?:any)=> any,
21 - className?: string,
22 - icon?: string | FunctionComponent,
23 - closableContent?: string | ReactNode,
24 - reserveClosing?: true
25 - noFrame?: boolean
26 - title?: string
27 - padding?: boolean
28 - dialogProps?: Record<string, any>,
29 -}
30 -
31 -const dialogs = proxy<DialogOptions[]>([])
32 -
33 -export const dialogsDefaults = {
34 - closableContent: 'x',
35 - padding: true,
36 -}
37 -
38 -export function Dialogs() {
39 - const snap = useSnapshot(dialogs)
40 - return h(Fragment, {},
41 - snap.length > 0 && snap.map((d,i) =>
42 - h(Dialog, { key:i, ...d })))
43 -}
44 -
45 -function Dialog(d:DialogOptions) {
16 +dialogsDefaults.Container = Container
17 +function Container(d:DialogOptions) {
18 useEffect(()=>{
19 ref.current?.focus()
20 }, [])
@@ -56,35 +28,13 @@ function Dialog(d:DialogOptions) {
28 },
29 d.title && h(DialogTitle, {}, d.title),
30 h(DialogContent, {
31 + ref,
32 ...d.dialogProps,
33 sx:{ ...d.dialogProps?.sx, px: p, pb: p }
34 }, h(d.Content) )
35 )
36 }
37
65 -export function newDialog(options: DialogOptions) {
66 - const $id = Math.random()
67 - ;(options as any).$id = $id // object identity is not working because of the proxy. This is a possible workaround
68 - dialogs.push(options)
69 - return (v?:any) => {
70 - const i = dialogs.findIndex(x => (x as any).$id === $id)
71 - if (i < 0) return
72 - dialogs.splice(i,1)
73 - options.onClose?.(v)
74 - }
75 -}
76 -
77 -export function closeDialog(v?:any) {
78 - let i = dialogs.length
79 - while (i--) {
80 - const d = dialogs[i]
81 - if (d.reserveClosing)
82 - continue
83 - dialogs.splice(i,1)
84 - return d.onClose?.(v)
85 - }
86 -}
87 -
38 type AlertType = 'error' | 'warning' | 'info' | 'success'
39
40 const type2ico = {
frontend/src/dialog.ts
+3 -82
@@ -1,88 +1,9 @@
1 // This file is part of HFS - Copyright 2020-2021, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 -import { createElement as h, Fragment, FunctionComponent, ReactElement, ReactNode, useEffect, useRef } from 'react'
4 -import { proxy, useSnapshot } from 'valtio'
3 +import { createElement as h, ReactElement, useEffect, useRef } from 'react'
4 import './dialog.css'
6 -
7 -interface DialogOptions {
8 - Content: FunctionComponent,
9 - closable?: boolean,
10 - onClose?: (v?:any)=> any,
11 - className?: string,
12 - icon?: string | FunctionComponent,
13 - closableContent?: string | ReactNode,
14 - reserveClosing?: true
15 - noFrame?: boolean
16 -}
17 -
18 -const dialogs = proxy<DialogOptions[]>([])
19 -
20 -export const dialogsDefaults = {
21 - closableContent: 'x',
22 -}
23 -
24 -export function Dialogs() {
25 - const snap = useSnapshot(dialogs)
26 - return h(Fragment, {},
27 - snap.length > 0 && snap.map((d,i) =>
28 - h(Dialog, { key:i, ...d })))
29 -}
30 -
31 -function Dialog(d:DialogOptions) {
32 - useEffect(()=>{
33 - ref.current?.focus()
34 - }, [])
35 - const ref = useRef<HTMLElement>()
36 - d = { ...dialogsDefaults, ...d }
37 - return h('div', {
38 - ref,
39 - className: 'dialog-backdrop '+(d.className||''),
40 - tabIndex: 0,
41 - onKeyDown,
42 - onClick: ()=> closeDialog()
43 - },
44 - d.noFrame ? h(d.Content || 'div')
45 - : h('div', {
46 - className: 'dialog',
47 - onClick(ev:any){
48 - ev.stopPropagation()
49 - }
50 - },
51 - d.closable || d.closable===undefined && h('button', { className:'dialog-icon dialog-closer', onClick:()=> closeDialog() }, d.closableContent),
52 - d.icon && h('div', { className:'dialog-icon dialog-type' }, d.icon),
53 - h('div', { className:'dialog-content' }, h(d.Content || 'div'))
54 - )
55 - )
56 -}
57 -
58 -function onKeyDown(ev:any) {
59 - if (ev.key === 'Escape') {
60 - closeDialog()
61 - }
62 -}
63 -
64 -export function newDialog(options: DialogOptions) {
65 - const $id = Math.random()
66 - ;(options as any).$id = $id // object identity is not working because of the proxy. This is a possible workaround
67 - dialogs.push(options)
68 - return (v?:any) => {
69 - const i = dialogs.findIndex(x => (x as any).$id === $id)
70 - if (i < 0) return
71 - dialogs.splice(i,1)
72 - options.onClose?.(v)
73 - }
74 -}
75 -
76 -export function closeDialog(v?:any) {
77 - let i = dialogs.length
78 - while (i--) {
79 - const d = dialogs[i]
80 - if (d.reserveClosing)
81 - continue
82 - dialogs.splice(i,1)
83 - return d.onClose?.(v)
84 - }
85 -}
5 +import { newDialog, closeDialog } from 'shared/lib/dialogs'
6 +export * from 'shared/lib/dialogs'
7
8 interface PromptOptions { def?:string, type?:string }
9 export async function promptDialog(msg: string, { def, type }:PromptOptions={}) : Promise<string | null> {
shared/src/dialogs.ts new
+91
@@ -0,0 +1,91 @@
1 +import { createElement as h, Fragment, FunctionComponent, ReactNode, useEffect, useRef } from 'react'
2 +import { proxy, useSnapshot } from 'valtio'
3 +
4 +export interface DialogOptions {
5 + Content: FunctionComponent,
6 + closable?: boolean,
7 + onClose?: (v?:any)=> any,
8 + className?: string,
9 + icon?: string | ReactNode,
10 + closableContent?: string | ReactNode,
11 + reserveClosing?: true
12 + noFrame?: boolean
13 + title?: string
14 + padding?: boolean
15 + dialogProps?: Record<string, any>,
16 +
17 + Container?: FunctionComponent<DialogOptions>
18 +}
19 +
20 +const dialogs = proxy<DialogOptions[]>([])
21 +
22 +export const dialogsDefaults: Partial<DialogOptions> = {
23 + closableContent: 'x',
24 + padding: true,
25 +}
26 +
27 +export function Dialogs() {
28 + const snap = useSnapshot(dialogs)
29 + return h(Fragment, {},
30 + snap.length > 0 && snap.map((d,i) =>
31 + h(Dialog, { key:i, ...d })))
32 +}
33 +
34 +function Dialog(d:DialogOptions) {
35 + const ref = useRef<HTMLElement>()
36 + useEffect(()=>{
37 + ref.current?.focus()
38 + }, [])
39 + d = { ...dialogsDefaults, ...d }
40 + if (d.Container)
41 + return h(d.Container, d)
42 + return h('div', {
43 + ref,
44 + className: 'dialog-backdrop '+(d.className||''),
45 + tabIndex: 0,
46 + onKeyDown,
47 + onClick: ()=> closeDialog()
48 + },
49 + d.noFrame ? h(d.Content || 'div')
50 + : h('div', {
51 + className: 'dialog',
52 + onClick(ev:any){
53 + ev.stopPropagation()
54 + }
55 + },
56 + d.closable || d.closable===undefined && h('button', { className:'dialog-icon dialog-closer', onClick:()=> closeDialog() }, d.closableContent),
57 + d.icon && h('div', { className:'dialog-icon dialog-type' }, d.icon),
58 + h('div', { className:'dialog-content' }, h(d.Content || 'div'))
59 + )
60 + )
61 +}
62 +
63 +function onKeyDown(ev:any) {
64 + if (ev.key === 'Escape') {
65 + closeDialog()
66 + }
67 +}
68 +
69 +export function newDialog(options: DialogOptions) {
70 + const $id = Math.random()
71 + ;(options as any).$id = $id // object identity is not working because of the proxy. This is a possible workaround
72 + dialogs.push(options)
73 + return (v?:any) => {
74 + const i = dialogs.findIndex(x => (x as any).$id === $id)
75 + if (i < 0) return
76 + dialogs.splice(i,1)
77 + options.onClose?.(v)
78 + }
79 +}
80 +
81 +export function closeDialog(v?:any) {
82 + let i = dialogs.length
83 + while (i--) {
84 + const d = dialogs[i]
85 + if (d.reserveClosing)
86 + continue
87 + dialogs.splice(i,1)
88 + return d.onClose?.(v)
89 + }
90 +}
91 +