@owlmeans/web-panel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1195 -0
- package/build/.gitkeep +0 -0
- package/build/auth/context.d.ts +5 -0
- package/build/auth/context.d.ts.map +1 -0
- package/build/auth/context.js +17 -0
- package/build/auth/context.js.map +1 -0
- package/build/auth/exports.d.ts +25 -0
- package/build/auth/exports.d.ts.map +1 -0
- package/build/auth/exports.js +19 -0
- package/build/auth/exports.js.map +1 -0
- package/build/auth/index.d.ts +5 -0
- package/build/auth/index.d.ts.map +1 -0
- package/build/auth/index.js +4 -0
- package/build/auth/index.js.map +1 -0
- package/build/auth/modules.d.ts +2 -0
- package/build/auth/modules.d.ts.map +1 -0
- package/build/auth/modules.js +4 -0
- package/build/auth/modules.js.map +1 -0
- package/build/auth/plugins/basic-ed25519.d.ts +3 -0
- package/build/auth/plugins/basic-ed25519.d.ts.map +1 -0
- package/build/auth/plugins/basic-ed25519.js +40 -0
- package/build/auth/plugins/basic-ed25519.js.map +1 -0
- package/build/auth/plugins/exports.d.ts +4 -0
- package/build/auth/plugins/exports.d.ts.map +1 -0
- package/build/auth/plugins/exports.js +4 -0
- package/build/auth/plugins/exports.js.map +1 -0
- package/build/auth/plugins/index.d.ts +6 -0
- package/build/auth/plugins/index.d.ts.map +1 -0
- package/build/auth/plugins/index.js +10 -0
- package/build/auth/plugins/index.js.map +1 -0
- package/build/auth/plugins/re-captcha.d.ts +3 -0
- package/build/auth/plugins/re-captcha.d.ts.map +1 -0
- package/build/auth/plugins/re-captcha.js +51 -0
- package/build/auth/plugins/re-captcha.js.map +1 -0
- package/build/auth/plugins/tunnel-consumer.d.ts +3 -0
- package/build/auth/plugins/tunnel-consumer.d.ts.map +1 -0
- package/build/auth/plugins/tunnel-consumer.js +40 -0
- package/build/auth/plugins/tunnel-consumer.js.map +1 -0
- package/build/auth/types.d.ts +10 -0
- package/build/auth/types.d.ts.map +1 -0
- package/build/auth/types.js +2 -0
- package/build/auth/types.js.map +1 -0
- package/build/components/block.d.ts +4 -0
- package/build/components/block.d.ts.map +1 -0
- package/build/components/block.js +15 -0
- package/build/components/block.js.map +1 -0
- package/build/components/button/index.d.ts +3 -0
- package/build/components/button/index.d.ts.map +1 -0
- package/build/components/button/index.js +2 -0
- package/build/components/button/index.js.map +1 -0
- package/build/components/button/selector.d.ts +4 -0
- package/build/components/button/selector.d.ts.map +1 -0
- package/build/components/button/selector.js +8 -0
- package/build/components/button/selector.js.map +1 -0
- package/build/components/button/types.d.ts +7 -0
- package/build/components/button/types.d.ts.map +1 -0
- package/build/components/button/types.js +2 -0
- package/build/components/button/types.js.map +1 -0
- package/build/components/form/button/component.d.ts +5 -0
- package/build/components/form/button/component.d.ts.map +1 -0
- package/build/components/form/button/component.js +34 -0
- package/build/components/form/button/component.js.map +1 -0
- package/build/components/form/button/index.d.ts +3 -0
- package/build/components/form/button/index.d.ts.map +1 -0
- package/build/components/form/button/index.js +3 -0
- package/build/components/form/button/index.js.map +1 -0
- package/build/components/form/button/types.d.ts +15 -0
- package/build/components/form/button/types.d.ts.map +1 -0
- package/build/components/form/button/types.js +2 -0
- package/build/components/form/button/types.js.map +1 -0
- package/build/components/form/component.d.ts +4 -0
- package/build/components/form/component.d.ts.map +1 -0
- package/build/components/form/component.js +59 -0
- package/build/components/form/component.js.map +1 -0
- package/build/components/form/index.d.ts +3 -0
- package/build/components/form/index.d.ts.map +1 -0
- package/build/components/form/index.js +2 -0
- package/build/components/form/index.js.map +1 -0
- package/build/components/form/text/component.d.ts +4 -0
- package/build/components/form/text/component.d.ts.map +1 -0
- package/build/components/form/text/component.js +34 -0
- package/build/components/form/text/component.js.map +1 -0
- package/build/components/form/text/index.d.ts +3 -0
- package/build/components/form/text/index.d.ts.map +1 -0
- package/build/components/form/text/index.js +3 -0
- package/build/components/form/text/index.js.map +1 -0
- package/build/components/form/text/types.d.ts +11 -0
- package/build/components/form/text/types.d.ts.map +1 -0
- package/build/components/form/text/types.js +2 -0
- package/build/components/form/text/types.js.map +1 -0
- package/build/components/form/types.d.ts +6 -0
- package/build/components/form/types.d.ts.map +1 -0
- package/build/components/form/types.js +2 -0
- package/build/components/form/types.js.map +1 -0
- package/build/components/helper.d.ts +6 -0
- package/build/components/helper.d.ts.map +1 -0
- package/build/components/helper.js +70 -0
- package/build/components/helper.js.map +1 -0
- package/build/components/index.d.ts +14 -0
- package/build/components/index.d.ts.map +1 -0
- package/build/components/index.js +13 -0
- package/build/components/index.js.map +1 -0
- package/build/components/layout/component.d.ts +4 -0
- package/build/components/layout/component.d.ts.map +1 -0
- package/build/components/layout/component.js +6 -0
- package/build/components/layout/component.js.map +1 -0
- package/build/components/layout/index.d.ts +3 -0
- package/build/components/layout/index.d.ts.map +1 -0
- package/build/components/layout/index.js +3 -0
- package/build/components/layout/index.js.map +1 -0
- package/build/components/layout/types.d.ts +4 -0
- package/build/components/layout/types.d.ts.map +1 -0
- package/build/components/layout/types.js +2 -0
- package/build/components/layout/types.js.map +1 -0
- package/build/components/link.d.ts +4 -0
- package/build/components/link.d.ts.map +1 -0
- package/build/components/link.js +27 -0
- package/build/components/link.js.map +1 -0
- package/build/components/panel-app/component.d.ts +4 -0
- package/build/components/panel-app/component.d.ts.map +1 -0
- package/build/components/panel-app/component.js +12 -0
- package/build/components/panel-app/component.js.map +1 -0
- package/build/components/panel-app/index.d.ts +3 -0
- package/build/components/panel-app/index.d.ts.map +1 -0
- package/build/components/panel-app/index.js +3 -0
- package/build/components/panel-app/index.js.map +1 -0
- package/build/components/panel-app/types.d.ts +6 -0
- package/build/components/panel-app/types.d.ts.map +1 -0
- package/build/components/panel-app/types.js +2 -0
- package/build/components/panel-app/types.js.map +1 -0
- package/build/components/status.d.ts +4 -0
- package/build/components/status.d.ts.map +1 -0
- package/build/components/status.js +19 -0
- package/build/components/status.js.map +1 -0
- package/build/components/text.d.ts +4 -0
- package/build/components/text.d.ts.map +1 -0
- package/build/components/text.js +9 -0
- package/build/components/text.js.map +1 -0
- package/build/components/types.d.ts +32 -0
- package/build/components/types.d.ts.map +1 -0
- package/build/components/types.js +2 -0
- package/build/components/types.js.map +1 -0
- package/build/components/uploader/image.d.ts +4 -0
- package/build/components/uploader/image.d.ts.map +1 -0
- package/build/components/uploader/image.js +24 -0
- package/build/components/uploader/image.js.map +1 -0
- package/build/components/uploader/index.d.ts +2 -0
- package/build/components/uploader/index.d.ts.map +1 -0
- package/build/components/uploader/index.js +2 -0
- package/build/components/uploader/index.js.map +1 -0
- package/build/components/uploader/types.d.ts +5 -0
- package/build/components/uploader/types.d.ts.map +1 -0
- package/build/components/uploader/types.js +2 -0
- package/build/components/uploader/types.js.map +1 -0
- package/build/context.d.ts +4 -0
- package/build/context.d.ts.map +1 -0
- package/build/context.js +12 -0
- package/build/context.js.map +1 -0
- package/build/exports.d.ts +19 -0
- package/build/exports.d.ts.map +1 -0
- package/build/exports.js +17 -0
- package/build/exports.js.map +1 -0
- package/build/index.d.ts +8 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +7 -0
- package/build/index.js.map +1 -0
- package/build/main.d.ts +5 -0
- package/build/main.d.ts.map +1 -0
- package/build/main.js +14 -0
- package/build/main.js.map +1 -0
- package/build/modules.d.ts +2 -0
- package/build/modules.d.ts.map +1 -0
- package/build/modules.js +4 -0
- package/build/modules.js.map +1 -0
- package/build/types.d.ts +9 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/package.json +89 -0
- package/src/auth/context.ts +25 -0
- package/src/auth/exports.ts +29 -0
- package/src/auth/index.ts +5 -0
- package/src/auth/modules.ts +5 -0
- package/src/auth/plugins/basic-ed25519.tsx +57 -0
- package/src/auth/plugins/exports.ts +4 -0
- package/src/auth/plugins/index.ts +15 -0
- package/src/auth/plugins/re-captcha.tsx +73 -0
- package/src/auth/plugins/tunnel-consumer.tsx +65 -0
- package/src/auth/types.ts +11 -0
- package/src/components/block.tsx +27 -0
- package/src/components/button/index.ts +3 -0
- package/src/components/button/selector.tsx +16 -0
- package/src/components/button/types.ts +7 -0
- package/src/components/form/button/component.tsx +53 -0
- package/src/components/form/button/index.ts +3 -0
- package/src/components/form/button/types.ts +16 -0
- package/src/components/form/component.tsx +101 -0
- package/src/components/form/index.ts +3 -0
- package/src/components/form/text/component.tsx +45 -0
- package/src/components/form/text/index.ts +3 -0
- package/src/components/form/text/types.ts +11 -0
- package/src/components/form/types.ts +6 -0
- package/src/components/helper.ts +79 -0
- package/src/components/index.ts +17 -0
- package/src/components/layout/component.tsx +7 -0
- package/src/components/layout/index.ts +3 -0
- package/src/components/layout/types.ts +4 -0
- package/src/components/link.tsx +34 -0
- package/src/components/panel-app/component.tsx +21 -0
- package/src/components/panel-app/index.ts +3 -0
- package/src/components/panel-app/types.ts +6 -0
- package/src/components/status.tsx +23 -0
- package/src/components/text.tsx +14 -0
- package/src/components/types.ts +35 -0
- package/src/components/uploader/image.tsx +31 -0
- package/src/components/uploader/index.ts +2 -0
- package/src/components/uploader/types.ts +6 -0
- package/src/context.ts +18 -0
- package/src/exports.ts +22 -0
- package/src/index.ts +9 -0
- package/src/main.tsx +19 -0
- package/src/modules.ts +5 -0
- package/src/types.ts +11 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import LinearProgress from '@mui/material/LinearProgress'
|
|
2
|
+
import { AuthenticationStage } from '@owlmeans/auth'
|
|
3
|
+
import type { RelyToken } from '@owlmeans/auth'
|
|
4
|
+
import type { TunnelAuthenticationRenderer } from '@owlmeans/client-auth/manager/plugins'
|
|
5
|
+
import { PinSchema } from '@owlmeans/client-auth/manager/plugins'
|
|
6
|
+
import { Form } from '../../components/form/component.js'
|
|
7
|
+
import { TextInput } from '../../components/form/text/component.js'
|
|
8
|
+
// import { Text } from '../../components/text.js'
|
|
9
|
+
import { useMemo } from 'react'
|
|
10
|
+
import { EnvelopeKind, makeEnvelopeModel } from '@owlmeans/basic-envelope'
|
|
11
|
+
import { Block } from '../../components/block.js'
|
|
12
|
+
import { BlockScaling } from '@owlmeans/client-panel'
|
|
13
|
+
import { Status } from '../../components/status.js'
|
|
14
|
+
import type { SxProps } from '@mui/material/styles'
|
|
15
|
+
import { useTheme } from '@mui/material/styles'
|
|
16
|
+
import { Button } from '../../components/form/button/component.js'
|
|
17
|
+
import { QRCodeCanvas } from 'qrcode.react'
|
|
18
|
+
import Box from '@mui/material/Box'
|
|
19
|
+
|
|
20
|
+
export const TunnelConsumerUIPlugin: TunnelAuthenticationRenderer = ({ type, stage, control, params, submit }) => {
|
|
21
|
+
const rely = useMemo(() => {
|
|
22
|
+
if (control.allowance == null) {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
return makeEnvelopeModel<RelyToken>(
|
|
26
|
+
makeEnvelopeModel<string>(control.allowance.challenge, EnvelopeKind.Wrap).message(true)
|
|
27
|
+
, EnvelopeKind.Wrap
|
|
28
|
+
).message()
|
|
29
|
+
}, [stage])
|
|
30
|
+
|
|
31
|
+
const theme = useTheme()
|
|
32
|
+
const prefix = "prefix" in params ? params.prefix as string : ""
|
|
33
|
+
const i18n = { ns: "lib", resource: 'client-panel-auth' }
|
|
34
|
+
|
|
35
|
+
const loadingStyle: SxProps = {
|
|
36
|
+
width: { xs: '100%', sm: '100%', md: '50%' }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
switch (stage) {
|
|
40
|
+
case AuthenticationStage.Authenticate:
|
|
41
|
+
return <Form decorate name={type} validation={PinSchema} onSubmit={submit} i18n={i18n}>
|
|
42
|
+
<Box width="fit-content" margin="auto">
|
|
43
|
+
{rely?.token != null && <QRCodeCanvas size={256}
|
|
44
|
+
value={`${prefix}${rely?.token ?? ""}`}
|
|
45
|
+
fgColor={theme.palette.primary.dark}
|
|
46
|
+
bgColor={theme.palette.background.paper}
|
|
47
|
+
/>}
|
|
48
|
+
</Box>
|
|
49
|
+
{/* <Text>{rely?.token}</Text> */}
|
|
50
|
+
<TextInput name="pin" label hint />
|
|
51
|
+
</Form>
|
|
52
|
+
case AuthenticationStage.Error:
|
|
53
|
+
return <Block horizontal={BlockScaling.Half} i18n={i18n} Actions={() =>
|
|
54
|
+
// @TODO document reload looks a bit dirty - cause we can loose the flow
|
|
55
|
+
<Button label="reset" onClick={async () => { document.location.reload() }} />
|
|
56
|
+
// <Button label="reset" onClick={async () => {
|
|
57
|
+
// await control.updateStage(AuthenticationStage.Authenticate)
|
|
58
|
+
// }} />
|
|
59
|
+
}>
|
|
60
|
+
<Status error={control.error} ok={false} />
|
|
61
|
+
</Block>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return <LinearProgress sx={loadingStyle} />
|
|
65
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ClientConfig } from '@owlmeans/client-context'
|
|
2
|
+
import type { ClientContext } from '@owlmeans/client'
|
|
3
|
+
import type { WithFlowConfig } from '@owlmeans/flow'
|
|
4
|
+
import type { FlowService } from '@owlmeans/web-flow'
|
|
5
|
+
|
|
6
|
+
export interface AppConfig extends ClientConfig, WithFlowConfig {
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface AppContext<C extends AppConfig = AppConfig> extends ClientContext<C> {
|
|
10
|
+
flow: () => FlowService
|
|
11
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import type { FC } from 'react'
|
|
3
|
+
import type { BlockProps } from './types.js'
|
|
4
|
+
import Card from '@mui/material/Card'
|
|
5
|
+
import CardContent from '@mui/material/CardContent'
|
|
6
|
+
import { PanelContext, usePanelHelper } from '@owlmeans/client-panel'
|
|
7
|
+
import type { SxProps } from '@mui/material/styles'
|
|
8
|
+
import useTheme from '@mui/material/styles/useTheme.js'
|
|
9
|
+
import CardActions from '@mui/material/CardActions'
|
|
10
|
+
import { scalingToStyles } from './helper.js'
|
|
11
|
+
|
|
12
|
+
export const Block: FC<BlockProps> = ({ children, horizontal, vertical, Actions, i18n, styles }) => {
|
|
13
|
+
const theme = useTheme()
|
|
14
|
+
|
|
15
|
+
const style: SxProps = useMemo(() => scalingToStyles(horizontal, vertical, theme), [horizontal])
|
|
16
|
+
|
|
17
|
+
const panelProps = { ...usePanelHelper(), ...i18n }
|
|
18
|
+
|
|
19
|
+
return <PanelContext {...panelProps}>
|
|
20
|
+
<Card sx={{ ...style, ...styles } as SxProps}>
|
|
21
|
+
<CardContent>{children}</CardContent>
|
|
22
|
+
{Actions != null && <CardActions sx={{ flexDirection: "row", justifyContent: "flex-end", pr: 2, pb: 2 }}>
|
|
23
|
+
<Actions />
|
|
24
|
+
</CardActions>}
|
|
25
|
+
</Card>
|
|
26
|
+
</PanelContext>
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
import ButtonGroup from '@mui/material/ButtonGroup'
|
|
3
|
+
import type { FC } from 'react'
|
|
4
|
+
import type { SelectorProps } from './types.js'
|
|
5
|
+
import { Button } from '../form/button/component.js'
|
|
6
|
+
|
|
7
|
+
export const ButtonSelector: FC<SelectorProps> = ({ name, options, current, onSelect }) => {
|
|
8
|
+
const prefix = name != null ? `${name}.` : ''
|
|
9
|
+
return <ButtonGroup>
|
|
10
|
+
{options.map(
|
|
11
|
+
option => <Button key={option} label={`${prefix}${option}`}
|
|
12
|
+
onClick={() => onSelect?.(option)}
|
|
13
|
+
variant={current === option ? 'contained' : 'outlined'} />
|
|
14
|
+
)}
|
|
15
|
+
</ButtonGroup>
|
|
16
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import type { FC } from 'react'
|
|
3
|
+
import { memo } from 'react'
|
|
4
|
+
import type { ButtonProps, SubmitProps } from './types'
|
|
5
|
+
|
|
6
|
+
import MUIButton from '@mui/material/Button'
|
|
7
|
+
import CircularProgress from '@mui/material/CircularProgress'
|
|
8
|
+
|
|
9
|
+
import { useFormContext } from 'react-hook-form'
|
|
10
|
+
import { I18nProps, useCommonI18n, useI18nApp, useI18nLib } from '@owlmeans/client-i18n'
|
|
11
|
+
import { useContext } from '@owlmeans/client'
|
|
12
|
+
import { useFormI18n, usePanelHelper } from '@owlmeans/client-panel'
|
|
13
|
+
|
|
14
|
+
export const Button: FC<ButtonProps> = memo(({ label, onClick, i18n, loader, size, fullWidth, variant = 'contained' }) => {
|
|
15
|
+
const context = useContext()
|
|
16
|
+
const panel = usePanelHelper()
|
|
17
|
+
const t = useCommonI18n(
|
|
18
|
+
i18n?.resource ?? panel.resource ?? context.cfg.service,
|
|
19
|
+
i18n?.ns ?? panel.ns,
|
|
20
|
+
i18n?.prefix ?? panel.prefix
|
|
21
|
+
)
|
|
22
|
+
const appT = useI18nApp(context.cfg.service, 'buttons')
|
|
23
|
+
const libT = useI18nLib('client-panel', 'buttons')
|
|
24
|
+
label = useMemo(() => i18n?.suppress ? label : t(label, {
|
|
25
|
+
defaultValue: appT(label, { defaultValue: libT(label) })
|
|
26
|
+
}), [i18n?.suppress, label])
|
|
27
|
+
|
|
28
|
+
size = size ?? 'medium'
|
|
29
|
+
const progressSize = size === 'large'
|
|
30
|
+
? 20
|
|
31
|
+
: size === 'medium' ? 16 : 14
|
|
32
|
+
|
|
33
|
+
return <MUIButton variant={variant as any} size={size} fullWidth={fullWidth}
|
|
34
|
+
startIcon={loader != null && loader.opened === true ? <CircularProgress size={progressSize} /> : undefined}
|
|
35
|
+
disabled={loader != null && loader.opened === true}
|
|
36
|
+
onClick={onClick}>{label}</MUIButton>
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export const SubmitButton: FC<SubmitProps> = memo((props) => {
|
|
40
|
+
let { i18n, label } = props
|
|
41
|
+
const { handleSubmit } = useFormContext()
|
|
42
|
+
const t = useFormI18n()
|
|
43
|
+
|
|
44
|
+
label = label ?? 'submit'
|
|
45
|
+
const _i18n: I18nProps["i18n"] = { ...i18n }
|
|
46
|
+
_i18n.suppress = true
|
|
47
|
+
|
|
48
|
+
return <Button {...props} label={t(label)} i18n={_i18n}
|
|
49
|
+
onClick={handleSubmit(
|
|
50
|
+
props.onSubmit ?? props.onClick ?? (() => { console.info('Empty submit') }),
|
|
51
|
+
problem => console.error('Failed to submit form with error: ', problem)
|
|
52
|
+
)} />
|
|
53
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Toggleable } from '@owlmeans/client'
|
|
2
|
+
import type { I18nProps } from '@owlmeans/client-i18n'
|
|
3
|
+
|
|
4
|
+
export interface ButtonProps extends I18nProps {
|
|
5
|
+
size?: 'small' | 'medium' | 'large'
|
|
6
|
+
fullWidth?: boolean
|
|
7
|
+
variant?: string
|
|
8
|
+
label: string
|
|
9
|
+
loader?: Toggleable
|
|
10
|
+
onClick?: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SubmitProps extends Omit<ButtonProps, "label"> {
|
|
14
|
+
label?: string
|
|
15
|
+
onSubmit?: (data: any) => Promise<void> | void
|
|
16
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { FC } from 'react'
|
|
2
|
+
import { useCallback, useMemo } from 'react'
|
|
3
|
+
import { FormProvider, useForm } from 'react-hook-form'
|
|
4
|
+
import { ajvResolver } from '@hookform/resolvers/ajv'
|
|
5
|
+
import Grid from '@mui/material/Grid'
|
|
6
|
+
import Card from '@mui/material/Card'
|
|
7
|
+
import CardContent from '@mui/material/CardContent'
|
|
8
|
+
import CardActions from '@mui/material/CardActions'
|
|
9
|
+
import { FormContext, schemaToFormDefault } from '@owlmeans/client-panel'
|
|
10
|
+
import type { JSONSchemaType } from 'ajv'
|
|
11
|
+
import Ajv from 'ajv'
|
|
12
|
+
import formatsPlugin from 'ajv-formats'
|
|
13
|
+
import type { SxProps } from '@mui/material'
|
|
14
|
+
import { SubmitButton } from './button/component.js'
|
|
15
|
+
import { useToggle } from '@owlmeans/client'
|
|
16
|
+
import { scalingToStyles } from '../helper.js'
|
|
17
|
+
import { ResilientError } from '@owlmeans/error'
|
|
18
|
+
import { Status } from '../status.js'
|
|
19
|
+
import useTheme from '@mui/material/styles/useTheme.js'
|
|
20
|
+
import type { WebFormProps } from './types.js'
|
|
21
|
+
|
|
22
|
+
const ajv = new Ajv({ coerceTypes: true })
|
|
23
|
+
formatsPlugin(ajv)
|
|
24
|
+
|
|
25
|
+
export const Form: FC<WebFormProps> = (props) => {
|
|
26
|
+
const {
|
|
27
|
+
defaults, children, formRef, validation, name, horizontal, vertical,
|
|
28
|
+
decorate, onSubmit, i18n, styles
|
|
29
|
+
} = props
|
|
30
|
+
const theme = useTheme()
|
|
31
|
+
const _defaults = useMemo(
|
|
32
|
+
() => defaults ?? (validation != null ? schemaToFormDefault(validation) : undefined),
|
|
33
|
+
[name, defaults != null, validation != null]
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
const loader = useToggle(false)
|
|
37
|
+
|
|
38
|
+
const form = useForm({
|
|
39
|
+
mode: 'all',
|
|
40
|
+
defaultValues: _defaults,
|
|
41
|
+
resolver: validation
|
|
42
|
+
? ajvResolver(validation as JSONSchemaType<unknown>, { formats: ajv.formats, coerceTypes: true })
|
|
43
|
+
: undefined,
|
|
44
|
+
delayError: 300
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const update = useCallback(((data: Record<string, any>) => {
|
|
48
|
+
const fields = validation != null
|
|
49
|
+
? Object.keys((validation as JSONSchemaType<any>).properties)
|
|
50
|
+
: Object.keys(data)
|
|
51
|
+
fields.forEach(key => {
|
|
52
|
+
form.setValue(key, data[key])
|
|
53
|
+
})
|
|
54
|
+
}) as <T>(data: T) => void, [name])
|
|
55
|
+
|
|
56
|
+
const setError = useCallback((error: unknown, target: string = 'root') => {
|
|
57
|
+
form.setError(target, {
|
|
58
|
+
message: ResilientError.ensure(
|
|
59
|
+
error instanceof Error ? error : `${error}`
|
|
60
|
+
).marshal().message
|
|
61
|
+
})
|
|
62
|
+
}, [name])
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if (formRef != null) {
|
|
66
|
+
formRef.current = { form, update, loader, error: setError }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const style: SxProps = useMemo(() => scalingToStyles(horizontal, vertical, theme), [horizontal])
|
|
70
|
+
|
|
71
|
+
const content = () =>
|
|
72
|
+
<Grid container direction="column" justifyContent="flex-start" alignItems="stretch"
|
|
73
|
+
rowSpacing={2} sx={!decorate ? style : {}}>{
|
|
74
|
+
Array.isArray(children)
|
|
75
|
+
? children.map((child, index) =>
|
|
76
|
+
<Grid item key={index}>{child}</Grid>
|
|
77
|
+
) : children
|
|
78
|
+
}</Grid>
|
|
79
|
+
|
|
80
|
+
if (decorate === true) {
|
|
81
|
+
const root = form.getFieldState('root')
|
|
82
|
+
return <FormProvider {...form}>
|
|
83
|
+
<FormContext {...props} loader={loader}>
|
|
84
|
+
<Card sx={{ ...style, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', ...styles } as SxProps}>
|
|
85
|
+
<CardContent>
|
|
86
|
+
{content()}
|
|
87
|
+
{root.invalid && root.error?.message &&
|
|
88
|
+
<Status ok={false} i18n={i18n} error={ResilientError.ensure(root.error.message)} />}
|
|
89
|
+
</CardContent>
|
|
90
|
+
{onSubmit != null ? <CardActions sx={{ flexDirection: 'row', justifyContent: 'flex-end', pr: 2, pb: 2 }}>
|
|
91
|
+
<SubmitButton loader={loader} onSubmit={async data => onSubmit(data, update)} />
|
|
92
|
+
</CardActions> : undefined}
|
|
93
|
+
</Card>
|
|
94
|
+
</FormContext>
|
|
95
|
+
</FormProvider>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return <FormProvider {...form}>
|
|
99
|
+
<FormContext {...props} loader={loader}>{content()}</FormContext>
|
|
100
|
+
</FormProvider>
|
|
101
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { FC } from 'react'
|
|
2
|
+
import { useFormContext, Controller } from 'react-hook-form'
|
|
3
|
+
import { TextInputProps } from './types.js'
|
|
4
|
+
|
|
5
|
+
import TextField from '@mui/material/TextField'
|
|
6
|
+
import { useFormError, useFormI18n } from '@owlmeans/client-panel'
|
|
7
|
+
import { useClientFormContext } from '@owlmeans/client-panel'
|
|
8
|
+
|
|
9
|
+
export const TextInput: FC<TextInputProps> = ({ name, label, placeholder, hint, type, def, disableAutocomplete }) => {
|
|
10
|
+
const { control } = useFormContext()
|
|
11
|
+
const t = useFormI18n()
|
|
12
|
+
const key = name
|
|
13
|
+
if (typeof label === 'boolean' && label) {
|
|
14
|
+
label = t(`${key}.label`)
|
|
15
|
+
} else {
|
|
16
|
+
label = undefined
|
|
17
|
+
}
|
|
18
|
+
if (typeof placeholder === 'boolean' && placeholder) {
|
|
19
|
+
placeholder = t(`${key}.placeholder`)
|
|
20
|
+
} else {
|
|
21
|
+
placeholder = undefined
|
|
22
|
+
}
|
|
23
|
+
if (typeof hint === 'boolean' && hint) {
|
|
24
|
+
hint = t(`${key}.hint`)
|
|
25
|
+
} else {
|
|
26
|
+
hint = undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return <Controller control={control} name={name} defaultValue={def} render={
|
|
30
|
+
({ field, fieldState }) => {
|
|
31
|
+
const error = useFormError(name, fieldState.error)
|
|
32
|
+
const { loader } = useClientFormContext()
|
|
33
|
+
|
|
34
|
+
return <TextField fullWidth {...field}
|
|
35
|
+
error={fieldState.error != null}
|
|
36
|
+
disabled={loader != null && loader.opened === true}
|
|
37
|
+
label={label}
|
|
38
|
+
type={type ?? 'text'}
|
|
39
|
+
placeholder={placeholder}
|
|
40
|
+
autoComplete={disableAutocomplete ? 'off' : 'on'}
|
|
41
|
+
helperText={error ?? hint}
|
|
42
|
+
/>
|
|
43
|
+
}
|
|
44
|
+
} />
|
|
45
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FormFieldProps } from '@owlmeans/client-panel'
|
|
2
|
+
import type { HTMLInputTypeAttribute } from 'react'
|
|
3
|
+
|
|
4
|
+
export interface TextInputProps extends FormFieldProps {
|
|
5
|
+
name: string
|
|
6
|
+
label?: string | boolean
|
|
7
|
+
placeholder?: string | boolean
|
|
8
|
+
hint?: string | boolean
|
|
9
|
+
type?: HTMLInputTypeAttribute
|
|
10
|
+
disableAutocomplete?: boolean
|
|
11
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { SxProps, Theme } from '@mui/material/styles'
|
|
2
|
+
import useTheme from '@mui/material/styles/useTheme'
|
|
3
|
+
import useMediaQuery from '@mui/material/useMediaQuery'
|
|
4
|
+
import { BlockScaling } from '@owlmeans/client-panel'
|
|
5
|
+
|
|
6
|
+
export const scalingToStyles = (
|
|
7
|
+
horizontal?: BlockScaling,
|
|
8
|
+
vertical?: BlockScaling,
|
|
9
|
+
theme?: Theme
|
|
10
|
+
): SxProps => {
|
|
11
|
+
const style: SxProps = {}
|
|
12
|
+
switch (horizontal) {
|
|
13
|
+
case BlockScaling.Half:
|
|
14
|
+
Object.assign(style, {
|
|
15
|
+
maxWidth: '50%',
|
|
16
|
+
[theme?.breakpoints.down('md') ?? 'xs']: {
|
|
17
|
+
maxWidth: '90%'
|
|
18
|
+
},
|
|
19
|
+
flexGrow: 1
|
|
20
|
+
})
|
|
21
|
+
break
|
|
22
|
+
case BlockScaling.Wide:
|
|
23
|
+
Object.assign(style, { mx: '10%', flexGrow: 1 })
|
|
24
|
+
break
|
|
25
|
+
case BlockScaling.Full:
|
|
26
|
+
Object.assign(style, { flexGrow: 1 })
|
|
27
|
+
break
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
switch (vertical) {
|
|
31
|
+
case BlockScaling.Half:
|
|
32
|
+
Object.assign(style, {
|
|
33
|
+
maxHeight: '50%',
|
|
34
|
+
[theme?.breakpoints.down('md') ?? 'xs']: {
|
|
35
|
+
maxHeight: '90%'
|
|
36
|
+
},
|
|
37
|
+
flexGrow: 1
|
|
38
|
+
})
|
|
39
|
+
break
|
|
40
|
+
case BlockScaling.Wide:
|
|
41
|
+
Object.assign(style, { my: '10%', flexGrow: 1 })
|
|
42
|
+
break
|
|
43
|
+
case BlockScaling.Full:
|
|
44
|
+
Object.assign(style, { height: '100%', flexGrow: 1 })
|
|
45
|
+
break
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return style
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const useBreakPoint = () => {
|
|
52
|
+
const theme = useTheme()
|
|
53
|
+
|
|
54
|
+
// Define breakpoints
|
|
55
|
+
const isXs = useMediaQuery(theme.breakpoints.only('xs'))
|
|
56
|
+
const isSm = useMediaQuery(theme.breakpoints.only('sm'))
|
|
57
|
+
const isMd = useMediaQuery(theme.breakpoints.only('md'))
|
|
58
|
+
const isLg = useMediaQuery(theme.breakpoints.only('lg'))
|
|
59
|
+
const isXl = useMediaQuery(theme.breakpoints.only('xl'))
|
|
60
|
+
|
|
61
|
+
let currentBreakpoint = isXs ? 'xs' : 'unknown' // Default value
|
|
62
|
+
if (isSm) currentBreakpoint = 'sm'
|
|
63
|
+
if (isMd) currentBreakpoint = 'md'
|
|
64
|
+
if (isLg) currentBreakpoint = 'lg'
|
|
65
|
+
if (isXl) currentBreakpoint = 'xl'
|
|
66
|
+
|
|
67
|
+
return currentBreakpoint
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const useMapBreakpoint = <T>(map: Record<string, T>, def?: T, breakpoint?: string): T => {
|
|
71
|
+
const _breakpoint = useBreakPoint()
|
|
72
|
+
breakpoint = breakpoint ?? _breakpoint
|
|
73
|
+
const result = map[breakpoint] ?? def
|
|
74
|
+
if (result == null) {
|
|
75
|
+
throw new SyntaxError(`Breakpoint should always return value. We have ${breakpoint}, but ${Object.keys(map).join(', ')} are available`)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result
|
|
79
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type * from './types.js'
|
|
2
|
+
|
|
3
|
+
export * from './panel-app/index.js'
|
|
4
|
+
export * from './layout/index.js'
|
|
5
|
+
|
|
6
|
+
export * from './form/index.js'
|
|
7
|
+
export * from './form/text/index.js'
|
|
8
|
+
export * from './form/button/index.js'
|
|
9
|
+
export * from './uploader/index.js'
|
|
10
|
+
|
|
11
|
+
export * from './button/index.js'
|
|
12
|
+
|
|
13
|
+
export * from './block.js'
|
|
14
|
+
export * from './text.js'
|
|
15
|
+
export * from './link.js'
|
|
16
|
+
export * from './helper.js'
|
|
17
|
+
export * from './status.js'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { usePanelI18n } from '@owlmeans/client-panel'
|
|
2
|
+
import type { FC } from 'react'
|
|
3
|
+
import type { LinkProps } from './types.js'
|
|
4
|
+
import MUILink from '@mui/material/Link'
|
|
5
|
+
import type { Variant } from '@mui/material/styles/createTypography.js'
|
|
6
|
+
import { useValue } from '@owlmeans/client'
|
|
7
|
+
import { useContext } from '@owlmeans/web-client'
|
|
8
|
+
import type { ClientModule } from '@owlmeans/client-module'
|
|
9
|
+
|
|
10
|
+
export const Link: FC<LinkProps> = ({ src, module, name, variant, children, center, open, styles }) => {
|
|
11
|
+
const t = usePanelI18n()
|
|
12
|
+
const context = useContext()
|
|
13
|
+
|
|
14
|
+
const href = useValue(async () => {
|
|
15
|
+
if (src != null) {
|
|
16
|
+
return src
|
|
17
|
+
}
|
|
18
|
+
if (module != null) {
|
|
19
|
+
module = typeof module === 'string' ? context.module<ClientModule<string>>(module) : module
|
|
20
|
+
const [url] = await module.call<string>()
|
|
21
|
+
|
|
22
|
+
return url
|
|
23
|
+
}
|
|
24
|
+
return null
|
|
25
|
+
}, [src, module])
|
|
26
|
+
|
|
27
|
+
const label = name != null
|
|
28
|
+
? t(name)
|
|
29
|
+
: children != null || module == null
|
|
30
|
+
? undefined : t(`modules.${typeof module == 'string' ? module : module.alias}`)
|
|
31
|
+
const target = open ? '_blank' : undefined
|
|
32
|
+
return <MUILink href={href ?? undefined} target={target} variant={variant as Variant}
|
|
33
|
+
sx={{ textAlign: center ? 'center' : 'inherit', ...styles }}>{label ?? children}</MUILink>
|
|
34
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FC } from 'react'
|
|
2
|
+
import { useMemo } from 'react'
|
|
3
|
+
import type { PanelAppProps } from './types.js'
|
|
4
|
+
|
|
5
|
+
import { App } from '@owlmeans/client'
|
|
6
|
+
import { I18nContext } from '@owlmeans/client-i18n'
|
|
7
|
+
import CssBaseline from '@mui/material/CssBaseline'
|
|
8
|
+
import ThemeProvider from '@mui/material/styles/ThemeProvider.js'
|
|
9
|
+
import createTheme from '@mui/material/styles/createTheme.js'
|
|
10
|
+
|
|
11
|
+
export const PanelApp: FC<PanelAppProps> = ({ context, provide, children, theme }) => {
|
|
12
|
+
theme = useMemo(() => theme ?? createTheme(), [theme])
|
|
13
|
+
return <ThemeProvider theme={theme}>
|
|
14
|
+
<I18nContext config={context.cfg}>
|
|
15
|
+
<App context={context} provide={provide}>
|
|
16
|
+
<CssBaseline />
|
|
17
|
+
{children}
|
|
18
|
+
</App >
|
|
19
|
+
</I18nContext>
|
|
20
|
+
</ThemeProvider>
|
|
21
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { FC } from 'react'
|
|
2
|
+
import { useMemo } from 'react'
|
|
3
|
+
import type { StatusProps } from './types.js'
|
|
4
|
+
import { usePanelI18n } from '@owlmeans/client-panel'
|
|
5
|
+
import { ResilientError } from '@owlmeans/error'
|
|
6
|
+
import Alert from '@mui/material/Alert'
|
|
7
|
+
|
|
8
|
+
const prepareMessage = (msg: string) => msg.replace(/\:/g, '.')
|
|
9
|
+
|
|
10
|
+
export const Status: FC<StatusProps> = ({ ok, name, i18n, children, variant, message, error }) => {
|
|
11
|
+
variant = useMemo(() => variant ?? (ok ? 'success' : 'error'), [ok, variant])
|
|
12
|
+
const t = usePanelI18n(name ?? variant, i18n)
|
|
13
|
+
message = useMemo(() => {
|
|
14
|
+
const resilient = error != null ? ResilientError.ensure(error) : null
|
|
15
|
+
return message != null ? t(message) : resilient != null ? t(
|
|
16
|
+
[
|
|
17
|
+
`${resilient.type}.${prepareMessage(resilient.message)}`,
|
|
18
|
+
prepareMessage(resilient.message)
|
|
19
|
+
],
|
|
20
|
+
) : t(variant)
|
|
21
|
+
}, [message, error?.name, ok, variant])
|
|
22
|
+
return <Alert severity={variant as any}>{children ?? message}</Alert>
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
import { usePanelI18n } from '@owlmeans/client-panel'
|
|
3
|
+
import type { FC } from 'react'
|
|
4
|
+
import type { TextProps } from './types.js'
|
|
5
|
+
import Typography from '@mui/material/Typography'
|
|
6
|
+
import type { Variant } from '@mui/material/styles/createTypography.js'
|
|
7
|
+
|
|
8
|
+
export const Text: FC<TextProps> = ({ variant, name, children, center, styles, nested, i18n }) => {
|
|
9
|
+
const t = usePanelI18n(undefined, i18n)
|
|
10
|
+
|
|
11
|
+
const label = name != null ? t(name) : undefined
|
|
12
|
+
return <Typography component={nested ? 'span' : 'p'} variant={variant as Variant}
|
|
13
|
+
sx={{ textAlign: center ? 'center' : 'inherit', ...styles }}>{label ?? children}</Typography>
|
|
14
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { SxProps } from '@mui/material/styles'
|
|
2
|
+
import type { I18nProps } from '@owlmeans/client-i18n'
|
|
3
|
+
import type { BlockScaling } from '@owlmeans/client-panel'
|
|
4
|
+
import type { FC, PropsWithChildren } from 'react'
|
|
5
|
+
import type { Variant } from '@mui/material/styles/createTypography'
|
|
6
|
+
import type { ClientModule } from '@owlmeans/client-module'
|
|
7
|
+
|
|
8
|
+
export interface BlockProps extends PropsWithChildren<I18nProps> {
|
|
9
|
+
horizontal?: BlockScaling
|
|
10
|
+
vertical?: BlockScaling
|
|
11
|
+
Actions?: FC
|
|
12
|
+
styles?: SxProps
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TextProps extends PropsWithChildren<I18nProps> {
|
|
16
|
+
name?: string
|
|
17
|
+
variant?: Variant
|
|
18
|
+
center?: boolean
|
|
19
|
+
styles?: SxProps
|
|
20
|
+
nested?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LinkProps extends TextProps {
|
|
24
|
+
src?: string
|
|
25
|
+
module?: string | ClientModule
|
|
26
|
+
open?: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface StatusProps extends PropsWithChildren<I18nProps> {
|
|
30
|
+
name?: string
|
|
31
|
+
ok?: boolean
|
|
32
|
+
variant?: string
|
|
33
|
+
error?: Error
|
|
34
|
+
message?: string
|
|
35
|
+
}
|