@owlmeans/client 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 +688 -0
- package/build/app.d.ts +4 -0
- package/build/app.d.ts.map +1 -0
- package/build/app.js +10 -0
- package/build/app.js.map +1 -0
- package/build/components/index.d.ts +4 -0
- package/build/components/index.d.ts.map +1 -0
- package/build/components/index.js +3 -0
- package/build/components/index.js.map +1 -0
- package/build/components/modal.d.ts +7 -0
- package/build/components/modal.d.ts.map +1 -0
- package/build/components/modal.js +66 -0
- package/build/components/modal.js.map +1 -0
- package/build/components/toggle.d.ts +3 -0
- package/build/components/toggle.d.ts.map +1 -0
- package/build/components/toggle.js +20 -0
- package/build/components/toggle.js.map +1 -0
- package/build/components/types.d.ts +50 -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/consts.d.ts +5 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +5 -0
- package/build/consts.js.map +1 -0
- package/build/context.d.ts +8 -0
- package/build/context.d.ts.map +1 -0
- package/build/context.js +47 -0
- package/build/context.js.map +1 -0
- package/build/debug.d.ts +4 -0
- package/build/debug.d.ts.map +1 -0
- package/build/debug.js +12 -0
- package/build/debug.js.map +1 -0
- package/build/errors.d.ts +18 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +34 -0
- package/build/errors.js.map +1 -0
- package/build/helper.d.ts +4 -0
- package/build/helper.d.ts.map +1 -0
- package/build/helper.js +30 -0
- package/build/helper.js.map +1 -0
- package/build/index.d.ts +15 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +15 -0
- package/build/index.js.map +1 -0
- package/build/module.d.ts +3 -0
- package/build/module.d.ts.map +1 -0
- package/build/module.js +4 -0
- package/build/module.js.map +1 -0
- package/build/navigate.d.ts +3 -0
- package/build/navigate.d.ts.map +1 -0
- package/build/navigate.js +39 -0
- package/build/navigate.js.map +1 -0
- package/build/router.d.ts +5 -0
- package/build/router.d.ts.map +1 -0
- package/build/router.js +53 -0
- package/build/router.js.map +1 -0
- package/build/services/debug.d.ts +6 -0
- package/build/services/debug.d.ts.map +1 -0
- package/build/services/debug.js +83 -0
- package/build/services/debug.js.map +1 -0
- package/build/services/index.d.ts +2 -0
- package/build/services/index.d.ts.map +1 -0
- package/build/services/index.js +2 -0
- package/build/services/index.js.map +1 -0
- package/build/store.d.ts +4 -0
- package/build/store.d.ts.map +1 -0
- package/build/store.js +49 -0
- package/build/store.js.map +1 -0
- package/build/types.d.ts +57 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/utils/index.d.ts +4 -0
- package/build/utils/index.d.ts.map +1 -0
- package/build/utils/index.js +4 -0
- package/build/utils/index.js.map +1 -0
- package/build/utils/module.d.ts +3 -0
- package/build/utils/module.d.ts.map +1 -0
- package/build/utils/module.js +8 -0
- package/build/utils/module.js.map +1 -0
- package/build/utils/route.d.ts +16 -0
- package/build/utils/route.d.ts.map +1 -0
- package/build/utils/route.js +65 -0
- package/build/utils/route.js.map +1 -0
- package/build/utils/router.d.ts +16 -0
- package/build/utils/router.d.ts.map +1 -0
- package/build/utils/router.js +36 -0
- package/build/utils/router.js.map +1 -0
- package/build/value.d.ts +4 -0
- package/build/value.d.ts.map +1 -0
- package/build/value.js +48 -0
- package/build/value.js.map +1 -0
- package/package.json +66 -0
- package/src/app.tsx +17 -0
- package/src/components/index.ts +4 -0
- package/src/components/modal.ts +89 -0
- package/src/components/toggle.ts +22 -0
- package/src/components/types.ts +57 -0
- package/src/consts.ts +7 -0
- package/src/context.ts +61 -0
- package/src/debug.ts +14 -0
- package/src/errors.ts +44 -0
- package/src/helper.tsx +51 -0
- package/src/index.ts +15 -0
- package/src/module.ts +6 -0
- package/src/navigate.ts +51 -0
- package/src/router.ts +78 -0
- package/src/services/debug.ts +102 -0
- package/src/services/index.ts +2 -0
- package/src/store.ts +57 -0
- package/src/types.ts +72 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/module.ts +14 -0
- package/src/utils/route.tsx +82 -0
- package/src/utils/router.ts +60 -0
- package/src/value.ts +60 -0
- package/tsconfig.json +15 -0
- package/tsconfig.tsbuildinfo +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@owlmeans/client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "tsc -b",
|
|
7
|
+
"dev": "sleep 54 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
|
|
8
|
+
"watch": "tsc -b -w --preserveWatchOutput --pretty"
|
|
9
|
+
},
|
|
10
|
+
"main": "build/index.js",
|
|
11
|
+
"module": "build/index.js",
|
|
12
|
+
"types": "build/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./build/index.js",
|
|
16
|
+
"require": "./build/index.js",
|
|
17
|
+
"default": "./build/index.js",
|
|
18
|
+
"module": "./build/index.js",
|
|
19
|
+
"types": "./build/index.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./utils": {
|
|
22
|
+
"import": "./build/utils/index.js",
|
|
23
|
+
"require": "./build/utils/index.js",
|
|
24
|
+
"default": "./build/utils/index.js",
|
|
25
|
+
"module": "./build/utils/index.js",
|
|
26
|
+
"types": "./build/utils/index.d.ts"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@owlmeans/auth": "0.1.0",
|
|
31
|
+
"@owlmeans/client-context": "0.1.0",
|
|
32
|
+
"@owlmeans/client-module": "0.1.0",
|
|
33
|
+
"@owlmeans/client-resource": "0.1.0",
|
|
34
|
+
"@owlmeans/config": "0.1.0",
|
|
35
|
+
"@owlmeans/context": "0.1.0",
|
|
36
|
+
"@owlmeans/error": "0.1.0",
|
|
37
|
+
"@owlmeans/module": "0.1.0",
|
|
38
|
+
"@owlmeans/resource": "0.1.0",
|
|
39
|
+
"@owlmeans/state": "0.1.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@remix-run/router": "*",
|
|
43
|
+
"react": "*",
|
|
44
|
+
"react-router": "*",
|
|
45
|
+
"react-router-dom": "*"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"@remix-run/router": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"react-router-dom": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/react": "^18.3.11",
|
|
57
|
+
"nodemon": "^3.1.7",
|
|
58
|
+
"npm-check": "^6.0.1",
|
|
59
|
+
"react-router": "^6.26.2",
|
|
60
|
+
"typescript": "^5.6.3"
|
|
61
|
+
},
|
|
62
|
+
"private": false,
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/app.tsx
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
import type { FC } from 'react'
|
|
3
|
+
import type { AppProps } from './types.js'
|
|
4
|
+
import { useEffect, useState } from 'react'
|
|
5
|
+
import { Context } from './context.js'
|
|
6
|
+
import { Router } from './router.js'
|
|
7
|
+
|
|
8
|
+
export const App: FC<AppProps> = ({ context, provide, children }) => {
|
|
9
|
+
const [render, rerender] = useState(0)
|
|
10
|
+
|
|
11
|
+
useEffect(() => context.registerRerenderer(() => rerender(render + 1)), [])
|
|
12
|
+
|
|
13
|
+
return <Context value={context}>
|
|
14
|
+
{children}
|
|
15
|
+
{provide != null ? <Router provide={provide} /> : undefined}
|
|
16
|
+
</Context>
|
|
17
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createService } from '@owlmeans/context'
|
|
2
|
+
import { DEF_MODAL_ALIAS } from '../consts.js'
|
|
3
|
+
import type { ModalService, ModalServiceAppend, ModalStackLayer } from './types.js'
|
|
4
|
+
import type { ClientConfig } from '@owlmeans/client-context'
|
|
5
|
+
import type { ClientContext } from '../types.js'
|
|
6
|
+
import { useContext } from '../context.js'
|
|
7
|
+
import { useNavigate } from '../navigate.js'
|
|
8
|
+
import { useEffect } from 'react'
|
|
9
|
+
|
|
10
|
+
export const createModalService = (alias: string = DEF_MODAL_ALIAS): ModalService => {
|
|
11
|
+
const close = () => {
|
|
12
|
+
service.stack.pop()
|
|
13
|
+
service.toggle.close()
|
|
14
|
+
if (service.stack.length > 0) {
|
|
15
|
+
// @TODO Figure out how to remove this hack
|
|
16
|
+
setTimeout(() => service.toggle.open(), 500)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const service: ModalService = createService<ModalService>(alias, {
|
|
21
|
+
stack: [],
|
|
22
|
+
|
|
23
|
+
link: toggle => service.toggle = toggle,
|
|
24
|
+
|
|
25
|
+
layer: () => {
|
|
26
|
+
if (service.stack.length === 0) {
|
|
27
|
+
return undefined
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return service.stack[service.stack.length - 1]
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
request: async Com => {
|
|
34
|
+
const layer: ModalStackLayer = { Com }
|
|
35
|
+
const defer = new Promise<any | null>((resolve, reject) => {
|
|
36
|
+
layer.resolve = resolve
|
|
37
|
+
layer.reject = reject
|
|
38
|
+
})
|
|
39
|
+
service.stack.push(layer)
|
|
40
|
+
|
|
41
|
+
service.toggle.open()
|
|
42
|
+
|
|
43
|
+
const result = await defer
|
|
44
|
+
service.stack.pop()
|
|
45
|
+
|
|
46
|
+
return result
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
error: error => {
|
|
50
|
+
service.layer()?.reject?.(error)
|
|
51
|
+
close()
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
response: result => {
|
|
55
|
+
service.layer()?.resolve?.(result)
|
|
56
|
+
close()
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
cancel: () => {
|
|
60
|
+
service.layer()?.resolve?.(null)
|
|
61
|
+
close()
|
|
62
|
+
}
|
|
63
|
+
}, service => async () => { service.initialized = true })
|
|
64
|
+
|
|
65
|
+
return service
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const appendModalService = <C extends ClientConfig, T extends ClientContext<C>>(
|
|
69
|
+
ctx: T, alias: string = DEF_MODAL_ALIAS
|
|
70
|
+
): T & ModalServiceAppend => {
|
|
71
|
+
const service = createModalService(alias)
|
|
72
|
+
|
|
73
|
+
const _ctx = ctx as T & ModalServiceAppend
|
|
74
|
+
|
|
75
|
+
_ctx.registerService(service)
|
|
76
|
+
|
|
77
|
+
_ctx.modal = () => _ctx.service(alias)
|
|
78
|
+
|
|
79
|
+
return _ctx
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const useSetupModalNavigator = () => {
|
|
83
|
+
const context = useContext()
|
|
84
|
+
const navigator = useNavigate()
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
const modal = context.modal()
|
|
87
|
+
modal.navigator = navigator
|
|
88
|
+
}, [])
|
|
89
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react'
|
|
2
|
+
import type { Toggleable } from './types.js'
|
|
3
|
+
|
|
4
|
+
export const useToggle = (opened?: boolean): Toggleable => {
|
|
5
|
+
if (opened === undefined) {
|
|
6
|
+
opened = false
|
|
7
|
+
}
|
|
8
|
+
const [_opened, setOpened] = useState<boolean>(opened)
|
|
9
|
+
return useMemo<Toggleable>(() => {
|
|
10
|
+
const _handler: Toggleable = {
|
|
11
|
+
opened: _opened === undefined ? false : _opened,
|
|
12
|
+
set: opened => {
|
|
13
|
+
setOpened(_handler.opened = opened)
|
|
14
|
+
},
|
|
15
|
+
open: () => { _handler.set(true) },
|
|
16
|
+
close: () => { _handler.set(false) },
|
|
17
|
+
toggle: () => { _handler.set(!_handler.opened) }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return _handler
|
|
21
|
+
}, [])
|
|
22
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { InitializedService, LazyService } from '@owlmeans/context'
|
|
2
|
+
import type { FC } from 'react'
|
|
3
|
+
import type { ClientContext, Navigator } from '../types.js'
|
|
4
|
+
|
|
5
|
+
export interface Toggleable {
|
|
6
|
+
opened: boolean
|
|
7
|
+
open: () => void
|
|
8
|
+
close: () => void
|
|
9
|
+
set: (opened: boolean) => void
|
|
10
|
+
toggle: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ModalService extends InitializedService {
|
|
14
|
+
toggle: Toggleable
|
|
15
|
+
stack: ModalStackLayer[]
|
|
16
|
+
navigator?: Navigator
|
|
17
|
+
layer: () => ModalStackLayer | undefined
|
|
18
|
+
link: (toggle: Toggleable) => void
|
|
19
|
+
request: <T>(Com: FC<ModalBodyProps>) => Promise<T | null>
|
|
20
|
+
error: (error: Error) => void
|
|
21
|
+
response: <T>(value: T) => void
|
|
22
|
+
cancel: () => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ModalStackLayer {
|
|
26
|
+
Com: FC<ModalBodyProps>
|
|
27
|
+
resolve?: (v: unknown | null) => void,
|
|
28
|
+
reject?: (e: Error) => void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ModalBodyProps {
|
|
32
|
+
modal?: ModalService
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ModalServiceAppend {
|
|
36
|
+
modal: () => ModalService
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DebugService extends LazyService {
|
|
40
|
+
Debug: FC<ModalBodyProps>
|
|
41
|
+
items: DebugMenuItem[]
|
|
42
|
+
addItem: (item: DebugMenuItem) => void
|
|
43
|
+
erase: (states: string[]) => Promise<void>
|
|
44
|
+
select: (alias: string) => void
|
|
45
|
+
open: () => void
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface DebugMenuItem {
|
|
49
|
+
alias: string
|
|
50
|
+
title: string
|
|
51
|
+
Com?: FC<ModalBodyProps>
|
|
52
|
+
action?: (modal: ModalService, context: ClientContext) => Promise<void | string>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface DebugServiceAppend {
|
|
56
|
+
debug: () => DebugService | undefined
|
|
57
|
+
}
|
package/src/consts.ts
ADDED
package/src/context.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
|
|
2
|
+
import { createContext, useContext as useCtx } from 'react'
|
|
3
|
+
import type { Context as ReactContext } from 'react'
|
|
4
|
+
import { makeClientContext as makeBasicContext, PLUGINS } from '@owlmeans/client-context'
|
|
5
|
+
import type { ClientConfig } from '@owlmeans/client-context'
|
|
6
|
+
import { AppType, CONFIG_RECORD, Layer } from '@owlmeans/context'
|
|
7
|
+
import type { ClientContext } from './types.js'
|
|
8
|
+
import { appendStateResource } from '@owlmeans/state'
|
|
9
|
+
import { appendModalService } from './components/modal.js'
|
|
10
|
+
import { appendDebugService } from './services/debug.js'
|
|
11
|
+
import { appendConfigResource, PLUGIN_RECORD } from '@owlmeans/config'
|
|
12
|
+
|
|
13
|
+
const defaultCfg: ClientConfig = {
|
|
14
|
+
services: {},
|
|
15
|
+
brand: {},
|
|
16
|
+
layer: Layer.Service,
|
|
17
|
+
trusted: [],
|
|
18
|
+
[CONFIG_RECORD]: [],
|
|
19
|
+
ready: false,
|
|
20
|
+
service: '',
|
|
21
|
+
debug: {},
|
|
22
|
+
type: AppType.Frontend,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const makeClientContext = <C extends ClientConfig, T extends ClientContext<C> = ClientContext<C>>(cfg: C): T => {
|
|
26
|
+
const context = makeBasicContext(cfg) as T
|
|
27
|
+
appendStateResource<C, T>(context)
|
|
28
|
+
appendModalService<C, T>(context)
|
|
29
|
+
appendConfigResource<C, T>(context)
|
|
30
|
+
appendConfigResource<C, T>(context, PLUGINS, PLUGIN_RECORD)
|
|
31
|
+
appendDebugService<C, T>(context)
|
|
32
|
+
|
|
33
|
+
if (context.registerRerenderer == null) {
|
|
34
|
+
const rerenderers: CallableFunction[] = []
|
|
35
|
+
|
|
36
|
+
context.registerRerenderer = listener => {
|
|
37
|
+
rerenderers.push(listener)
|
|
38
|
+
return () => {
|
|
39
|
+
const index = rerenderers.indexOf(listener)
|
|
40
|
+
if (index >= 0) {
|
|
41
|
+
rerenderers.splice(index, 1)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
context.rerender = () => {
|
|
46
|
+
rerenderers.forEach(callback => callback())
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
context.makeContext = makeClientContext as typeof context.makeContext
|
|
51
|
+
|
|
52
|
+
return context
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const ClientContextContainer = createContext(makeClientContext(defaultCfg))
|
|
56
|
+
|
|
57
|
+
export const Context = ClientContextContainer.Provider
|
|
58
|
+
|
|
59
|
+
export const useContext = <C extends ClientConfig, T extends ClientContext<C>>() => useCtx<T>(
|
|
60
|
+
ClientContextContainer as unknown as ReactContext<T>
|
|
61
|
+
)
|
package/src/debug.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ClientConfig } from '@owlmeans/client-context'
|
|
2
|
+
import { DEBUG_CONFIG_KEY } from './consts.js'
|
|
3
|
+
import type { ClientContext, DebugConfigRecord } from './types.js'
|
|
4
|
+
|
|
5
|
+
export const appendStateDebug = <C extends ClientConfig, T extends ClientContext<C>>(ctx: T, state: string) => {
|
|
6
|
+
let debugRecord = ctx.cfg.records
|
|
7
|
+
.find(record => record.id === DEBUG_CONFIG_KEY) as DebugConfigRecord
|
|
8
|
+
if (debugRecord == null) {
|
|
9
|
+
debugRecord = { id: DEBUG_CONFIG_KEY }
|
|
10
|
+
ctx.cfg.records.push(debugRecord)
|
|
11
|
+
}
|
|
12
|
+
debugRecord.states = debugRecord.states ?? []
|
|
13
|
+
debugRecord.states.push(state)
|
|
14
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
|
|
2
|
+
import { ResilientError } from '@owlmeans/error'
|
|
3
|
+
|
|
4
|
+
export class ClientError extends ResilientError {
|
|
5
|
+
public static override typeName: string = 'ClientError'
|
|
6
|
+
|
|
7
|
+
constructor(message: string = 'error') {
|
|
8
|
+
super(ClientError.typeName, `client:${message}`)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class ComponentError extends ClientError {
|
|
13
|
+
public static override typeName: string = `${ClientError.typeName}Component`
|
|
14
|
+
|
|
15
|
+
constructor(message: string = 'error') {
|
|
16
|
+
super(`component:${message}`)
|
|
17
|
+
this.type = ComponentError.typeName
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class ComponentPropError extends ComponentError {
|
|
22
|
+
public static override typeName: string = `${ComponentError.typeName}Prop`
|
|
23
|
+
|
|
24
|
+
constructor(message: string = 'error') {
|
|
25
|
+
super(`prop:${message}`)
|
|
26
|
+
this.type = ComponentPropError.typeName
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class ComponentPropUndefined extends ComponentPropError {
|
|
31
|
+
public static override typeName: string = `${ComponentPropError.typeName}Undefined`
|
|
32
|
+
|
|
33
|
+
constructor(message: string = 'error') {
|
|
34
|
+
super(`undefined:${message}`)
|
|
35
|
+
this.type = ComponentPropUndefined.typeName
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
ResilientError.registerErrorClass(ClientError)
|
|
41
|
+
ResilientError.registerErrorClass(ComponentError)
|
|
42
|
+
ResilientError.registerErrorClass(ComponentPropError)
|
|
43
|
+
ResilientError.registerErrorClass(ComponentPropUndefined)
|
|
44
|
+
ResilientError.registerErrorClass(ComponentPropUndefined)
|
package/src/helper.tsx
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { AbstractRequest, AbstractResponse } from '@owlmeans/module'
|
|
2
|
+
import type { ModuleContextParams, RoutedComponent, ClientContext } from './types.js'
|
|
3
|
+
import type { RefedModuleHandler } from '@owlmeans/client-module'
|
|
4
|
+
import { HandledRenderer } from './utils/route.js'
|
|
5
|
+
import { isValidElement } from 'react'
|
|
6
|
+
import type { PropsWithChildren } from 'react'
|
|
7
|
+
import { ModuleContext } from './utils/module.js'
|
|
8
|
+
import type { ClientConfig } from '@owlmeans/client-context'
|
|
9
|
+
import { assertContext } from '@owlmeans/context'
|
|
10
|
+
|
|
11
|
+
type Config = ClientConfig
|
|
12
|
+
interface Context<C extends Config = Config> extends ClientContext<C> { }
|
|
13
|
+
|
|
14
|
+
export const handler = <T extends {}>(
|
|
15
|
+
Component: HandledRenderer<T>, preprender?: boolean
|
|
16
|
+
): RefedModuleHandler<T> => ref => <
|
|
17
|
+
R extends AbstractRequest = AbstractRequest,
|
|
18
|
+
P extends AbstractResponse<HandledRenderer<T>> = AbstractResponse<HandledRenderer<T>>
|
|
19
|
+
>(req: R, res: P): any => {
|
|
20
|
+
const location = `client-handler:${ref.ref?.getAlias() ?? 'unknown'}`
|
|
21
|
+
if (ref.ref == null) {
|
|
22
|
+
throw new SyntaxError('Module reference is not provided')
|
|
23
|
+
}
|
|
24
|
+
const ctx = assertContext<Config, Context>(ref.ref.ctx as Context, location)
|
|
25
|
+
if (ctx == null) {
|
|
26
|
+
throw new SyntaxError('Module context is not provided')
|
|
27
|
+
}
|
|
28
|
+
if (isValidElement(Component)) {
|
|
29
|
+
res.resolve(Component)
|
|
30
|
+
return Component
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (preprender === true) {
|
|
34
|
+
const Renderer = Component as RendererType
|
|
35
|
+
const element = <Renderer {...req} context={ctx} />
|
|
36
|
+
res.resolve(element as HandledRenderer<T>)
|
|
37
|
+
|
|
38
|
+
return element
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const Renderer: RoutedComponent = ({ children, ...props }) => {
|
|
42
|
+
const Renderer = Component as RendererType
|
|
43
|
+
return <ModuleContext.Provider value={props}>
|
|
44
|
+
<Renderer {...props}>{children}</Renderer>
|
|
45
|
+
</ModuleContext.Provider>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return Renderer
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type RendererType = HandledRenderer<PropsWithChildren<ModuleContextParams>> & RoutedComponent
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
export * from './context.js'
|
|
3
|
+
export * from './components/index.js'
|
|
4
|
+
export * from './services/index.js'
|
|
5
|
+
export * from './helper.js'
|
|
6
|
+
export * from './navigate.js'
|
|
7
|
+
export * from './module.js'
|
|
8
|
+
export * from './router.js'
|
|
9
|
+
export * from './types.js'
|
|
10
|
+
export * from './store.js'
|
|
11
|
+
export * from './value.js'
|
|
12
|
+
export * from './consts.js'
|
|
13
|
+
export * from './debug.js'
|
|
14
|
+
export * from './app.js'
|
|
15
|
+
export * from './errors.js'
|
package/src/module.ts
ADDED
package/src/navigate.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import { useLocation, useNavigate as useNav } from 'react-router'
|
|
3
|
+
import type { Navigator } from './types.js'
|
|
4
|
+
import { ModuleOutcome } from '@owlmeans/module'
|
|
5
|
+
import { useContext } from './context.js'
|
|
6
|
+
import type { ClientModule } from '@owlmeans/client-module'
|
|
7
|
+
|
|
8
|
+
export const useNavigate = (): Navigator => {
|
|
9
|
+
const context = useContext()
|
|
10
|
+
const navigate = useNav()
|
|
11
|
+
const location = useLocation()
|
|
12
|
+
const navigator: Navigator = useMemo(() => {
|
|
13
|
+
const navigator: Navigator = {
|
|
14
|
+
_navigate: navigate,
|
|
15
|
+
|
|
16
|
+
navigate: async (module, request) => {
|
|
17
|
+
const [url, ok] = await module.call(request)
|
|
18
|
+
|
|
19
|
+
if (ok === ModuleOutcome.Ok) {
|
|
20
|
+
navigate(url, {
|
|
21
|
+
state: {
|
|
22
|
+
...module.route.route, silent: request?.silent
|
|
23
|
+
},
|
|
24
|
+
replace: request?.replace ?? false
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
go: async (alias, request) =>
|
|
30
|
+
navigator.navigate(context.module<ClientModule<string>>(alias), request),
|
|
31
|
+
|
|
32
|
+
press: (alias, request) => () => {
|
|
33
|
+
void navigator.go(alias, request)
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
back: async () => {
|
|
37
|
+
navigate(-1)
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
pressBack: () => () => {
|
|
41
|
+
void navigator.back()
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
location: () => location
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return navigator
|
|
48
|
+
}, [navigate])
|
|
49
|
+
|
|
50
|
+
return navigator
|
|
51
|
+
}
|
package/src/router.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { FC } from 'react'
|
|
2
|
+
import type { RouterModel, RouterProps } from './types.js'
|
|
3
|
+
import type { RouteObject } from 'react-router'
|
|
4
|
+
import { createElement, useEffect, useRef, useState } from 'react'
|
|
5
|
+
import { buildModuleTree, initializeRouter, visitModuleTree } from './utils/router.js'
|
|
6
|
+
import { createRouteRenderer } from './utils/route.js'
|
|
7
|
+
import { RouterProvider } from 'react-router'
|
|
8
|
+
import type { Router as RemixRouter } from '@remix-run/router'
|
|
9
|
+
import { useContext } from './context.js'
|
|
10
|
+
import type { ClientConfig } from '@owlmeans/client-context'
|
|
11
|
+
import type { ClientContext } from './types.js'
|
|
12
|
+
import { assertContext } from '@owlmeans/context'
|
|
13
|
+
import type { BasicConfig, BasicContext } from '@owlmeans/context'
|
|
14
|
+
|
|
15
|
+
type Config = ClientConfig
|
|
16
|
+
interface Context<C extends Config = Config> extends ClientContext<C> { }
|
|
17
|
+
|
|
18
|
+
export const Router: FC<RouterProps> = ({ provide }) => {
|
|
19
|
+
const progress = useRef(false)
|
|
20
|
+
const context = useContext()
|
|
21
|
+
|
|
22
|
+
const [router, setRouter] = useState<RemixRouter>(
|
|
23
|
+
(typeof provide === 'function' ? undefined : provide) as RemixRouter
|
|
24
|
+
)
|
|
25
|
+
// @TODO We expect that this use memo will do the trick and we don't need to useEffect
|
|
26
|
+
// @TODO Show debug only in debug mode
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!progress.current && !context.cfg.ready) {
|
|
29
|
+
progress.current = true
|
|
30
|
+
|
|
31
|
+
if (typeof provide === 'function') {
|
|
32
|
+
initializeRouter(context as any).then(router => provide(router))
|
|
33
|
+
.then(routes => setRouter(routes))
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}, [])
|
|
37
|
+
|
|
38
|
+
// @TODO We need to allow drawing something here :)
|
|
39
|
+
if (router == null) {
|
|
40
|
+
return undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return createElement(RouterProvider, { router })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const makeRouterModel = (): RouterModel => {
|
|
47
|
+
const location = `client-router`
|
|
48
|
+
const model: RouterModel = {
|
|
49
|
+
routes: [],
|
|
50
|
+
resolve: async (ctx) => {
|
|
51
|
+
const context = assertContext<Config, Context>(ctx as Context, location)
|
|
52
|
+
const moduleTree = buildModuleTree(context as Context)
|
|
53
|
+
|
|
54
|
+
const reactRoutes: RouteObject[] = await visitModuleTree(moduleTree, async (module, children) => {
|
|
55
|
+
const ctx = assertContext<BasicConfig, BasicContext<BasicConfig>>(
|
|
56
|
+
(module.ctx ?? context) as BasicContext<BasicConfig>, location
|
|
57
|
+
)
|
|
58
|
+
await module.route.resolve(ctx)
|
|
59
|
+
|
|
60
|
+
const renderer = module.handle != null
|
|
61
|
+
? { Component: createRouteRenderer({ context, module, hasChildren: children.length > 0 }) }
|
|
62
|
+
: undefined
|
|
63
|
+
|
|
64
|
+
const route: RouteObject = {
|
|
65
|
+
...(module.route.route.default ? { index: true } as any : undefined),
|
|
66
|
+
...(!module.route.route.default ? { path: module.getPath(true), children } : undefined),
|
|
67
|
+
...renderer
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return route
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
return model.routes = reactRoutes
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return model
|
|
78
|
+
}
|