@kaspernj/api-maker 1.0.328 → 1.0.330
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/package.json +1 -1
- package/src/base-model/reflection.mjs +1 -0
- package/src/router.jsx +28 -35
- package/src/super-admin/show-page/index.jsx +32 -16
- package/src/use-router.jsx +125 -0
- package/src/with-router.jsx +7 -130
package/package.json
CHANGED
|
@@ -11,4 +11,5 @@ export default class ApiMakerBaseModelReflection {
|
|
|
11
11
|
macro = () => digg(this, "reflectionData", "macro")
|
|
12
12
|
modelClass = () => modelClassRequire(inflection.singularize(inflection.camelize(digg(this, "reflectionData", "resource_name"))))
|
|
13
13
|
name = () => inflection.camelize(digg(this, "reflectionData", "name"), true)
|
|
14
|
+
through = () => digg(this, "reflectionData", "through")
|
|
14
15
|
}
|
package/src/router.jsx
CHANGED
|
@@ -1,45 +1,38 @@
|
|
|
1
1
|
import PropTypes from "prop-types"
|
|
2
|
-
import React from "react"
|
|
3
|
-
import shouldComponentUpdate from "set-state-compare/src/should-component-update"
|
|
2
|
+
import React, {memo} from "react"
|
|
4
3
|
import {Suspense} from "react"
|
|
5
4
|
import withRouter from "./with-router"
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
const ApiMakerRouter = (props) => {
|
|
7
|
+
const {match, ...restProps} = props
|
|
8
|
+
const {matchingRoute} = match
|
|
9
|
+
|
|
10
|
+
if (!matchingRoute) {
|
|
11
|
+
if (props.notFoundComponent) {
|
|
12
|
+
const NotFoundComponent = props.notFoundComponent
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Suspense fallback={<div />}>
|
|
16
|
+
<NotFoundComponent match={match} />
|
|
17
|
+
</Suspense>
|
|
18
|
+
)
|
|
19
|
+
} else {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
15
22
|
}
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
const {match, ...restProps} = this.props
|
|
19
|
-
const {matchingRoute} = match
|
|
20
|
-
|
|
21
|
-
if (!matchingRoute) {
|
|
22
|
-
if (this.props.notFoundComponent) {
|
|
23
|
-
const NotFoundComponent = this.props.notFoundComponent
|
|
24
|
+
const Component = props.requireComponent({routeDefinition: matchingRoute.parsedRouteDefinition.routeDefinition})
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return null
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const Component = this.props.requireComponent({routeDefinition: matchingRoute.parsedRouteDefinition.routeDefinition})
|
|
26
|
+
return (
|
|
27
|
+
<Suspense fallback={<div />}>
|
|
28
|
+
<Component match={match} {...restProps} />
|
|
29
|
+
</Suspense>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
36
32
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
</Suspense>
|
|
41
|
-
)
|
|
42
|
-
}
|
|
33
|
+
ApiMakerRouter.propTypes = {
|
|
34
|
+
notFoundComponent: PropTypes.elementType,
|
|
35
|
+
requireComponent: PropTypes.func.isRequired
|
|
43
36
|
}
|
|
44
37
|
|
|
45
|
-
export default withRouter(ApiMakerRouter)
|
|
38
|
+
export default withRouter(memo(ApiMakerRouter))
|
|
@@ -43,21 +43,37 @@ const ApiMakerSuperAdminShowPage = ({modelClass}) => {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
for (const reflection of modelClass.reflections()) {
|
|
46
|
-
if (reflection.macro()
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (
|
|
46
|
+
if (reflection.macro() == "belongs_to") {
|
|
47
|
+
const reflectionModelClass = reflection.modelClass()
|
|
48
|
+
const reflectionModelClassName = reflectionModelClass.modelClassData().name
|
|
49
|
+
const reflectionModelClassAttributes = reflectionModelClass.attributes()
|
|
50
|
+
const nameAttribute = reflectionModelClassAttributes.find((attribute) => attribute.name() == "name")
|
|
51
|
+
|
|
52
|
+
preload.push(inflection.underscore(reflection.name()))
|
|
53
|
+
|
|
54
|
+
if (!(reflectionModelClassName in select)) select[reflectionModelClassName] = []
|
|
55
|
+
if (!select[reflectionModelClassName].includes("id")) select[reflectionModelClassName].push("id")
|
|
56
|
+
if (nameAttribute && !select[reflectionModelClassName].includes("name")) select[reflectionModelClassName].push("name")
|
|
57
|
+
|
|
58
|
+
// The foreign key is needed to look up any belongs-to-relationships
|
|
59
|
+
if (!modelClassSelect.includes(reflection.foreignKey())) modelClassSelect.push(reflection.foreignKey())
|
|
60
|
+
} else if (reflection.macro() == "has_one") {
|
|
61
|
+
const reflectionModelClass = reflection.modelClass()
|
|
62
|
+
const reflectionModelClassName = reflectionModelClass.modelClassData().name
|
|
63
|
+
const reflectionModelClassAttributes = reflectionModelClass.attributes()
|
|
64
|
+
const nameAttribute = reflectionModelClassAttributes.find((attribute) => attribute.name() == "name")
|
|
65
|
+
|
|
66
|
+
preload.push(inflection.underscore(reflection.name()))
|
|
67
|
+
|
|
68
|
+
if (!(reflectionModelClassName in select)) select[reflectionModelClassName] = []
|
|
69
|
+
if (!select[reflectionModelClassName].includes("id")) select[reflectionModelClassName].push("id")
|
|
70
|
+
if (nameAttribute && !select[reflectionModelClassName].includes("name")) select[reflectionModelClassName].push("name")
|
|
71
|
+
|
|
72
|
+
// The foreign key is needed to look up any has-one-relationships
|
|
73
|
+
if (!modelClassSelect.includes(reflection.foreignKey()) && !select[reflectionModelClassName].includes(reflection.foreignKey()) && !reflection.through()) {
|
|
74
|
+
select[reflectionModelClassName].push(reflection.foreignKey())
|
|
75
|
+
}
|
|
76
|
+
}
|
|
61
77
|
}
|
|
62
78
|
|
|
63
79
|
const useModelResult = useModel(modelClass, {
|
|
@@ -79,7 +95,7 @@ const ApiMakerSuperAdminShowPage = ({modelClass}) => {
|
|
|
79
95
|
{attributes && model && attributes.map((attribute) =>
|
|
80
96
|
<AttributePresenter attribute={attribute} key={attribute.attribute || attribute} modelArgs={modelArgs} model={model} />
|
|
81
97
|
)}
|
|
82
|
-
{model && modelClass.reflections().filter((reflection) => reflection.macro() == "belongs_to").map((reflection) =>
|
|
98
|
+
{model && modelClass.reflections().filter((reflection) => reflection.macro() == "belongs_to" || reflection.macro() == "has_one").map((reflection) =>
|
|
83
99
|
<BelongsToAttributeRow key={reflection.name()} model={model} modelClass={modelClass} reflection={reflection} />
|
|
84
100
|
)}
|
|
85
101
|
{model && extraContent && extraContent(modelArgs)}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import config from "./config.mjs"
|
|
2
|
+
import escapeStringRegexp from "escape-string-regexp"
|
|
3
|
+
import * as inflection from "inflection"
|
|
4
|
+
import PropTypes from "prop-types"
|
|
5
|
+
import {useCallback, useMemo} from "react"
|
|
6
|
+
import useShape from "set-state-compare/src/use-shape.js"
|
|
7
|
+
|
|
8
|
+
const useRouter = (props) => {
|
|
9
|
+
const s = useShape(props)
|
|
10
|
+
|
|
11
|
+
const findRouteParams = useCallback((routeDefinition) => {
|
|
12
|
+
const result = []
|
|
13
|
+
const parts = routeDefinition.path.split("/")
|
|
14
|
+
|
|
15
|
+
for (const part of parts) {
|
|
16
|
+
if (part.match(/^:([a-z_]+)$/))
|
|
17
|
+
result.push(part)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return result
|
|
21
|
+
}, [])
|
|
22
|
+
|
|
23
|
+
const getPath = useCallback(() => {
|
|
24
|
+
let path = s.p.path || window.location.pathname
|
|
25
|
+
|
|
26
|
+
path = path.replace(/[/]+$/, "")
|
|
27
|
+
|
|
28
|
+
return path
|
|
29
|
+
}, [])
|
|
30
|
+
|
|
31
|
+
const getRouteDefinitions = useCallback(() => s.p.routeDefinitions || config.getRouteDefinitions(), [])
|
|
32
|
+
const getRoutes = useCallback(() => s.p.routes || config.getRoutes(), [])
|
|
33
|
+
|
|
34
|
+
const parseRouteDefinitions = useCallback(() => {
|
|
35
|
+
const Locales = require("shared/locales").default
|
|
36
|
+
const routeDefinitions = getRouteDefinitions()
|
|
37
|
+
const routes = getRoutes()
|
|
38
|
+
const regex = /:([A-z\d_]+)/
|
|
39
|
+
const parsedRouteDefinitions = []
|
|
40
|
+
|
|
41
|
+
for (const locale of Locales.availableLocales()) {
|
|
42
|
+
for (const routeDefinition of routeDefinitions.routes) {
|
|
43
|
+
const routePathName = `${inflection.camelize(routeDefinition.name, true)}Path`
|
|
44
|
+
const params = findRouteParams(routeDefinition)
|
|
45
|
+
|
|
46
|
+
params.push({locale})
|
|
47
|
+
|
|
48
|
+
if (!(routePathName in routes))
|
|
49
|
+
throw new Error(`${routePathName} not found in routes: ${Object.keys(routes, ", ")}`)
|
|
50
|
+
|
|
51
|
+
const routePath = routes[routePathName](...params).replace(/[/]+$/, "")
|
|
52
|
+
const groups = []
|
|
53
|
+
let pathRegexString = "^"
|
|
54
|
+
|
|
55
|
+
pathRegexString += escapeStringRegexp(routePath)
|
|
56
|
+
|
|
57
|
+
while (true) {
|
|
58
|
+
const match = pathRegexString.match(regex)
|
|
59
|
+
|
|
60
|
+
if (!match) break
|
|
61
|
+
|
|
62
|
+
const variableName = match[1]
|
|
63
|
+
|
|
64
|
+
groups.push(variableName)
|
|
65
|
+
|
|
66
|
+
pathRegexString = pathRegexString.replace(match[0], "([^/]+)")
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
pathRegexString += "$"
|
|
70
|
+
|
|
71
|
+
const pathRegex = new RegExp(pathRegexString)
|
|
72
|
+
|
|
73
|
+
parsedRouteDefinitions.push({groups, pathRegex, routeDefinition})
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return parsedRouteDefinitions
|
|
78
|
+
}, [])
|
|
79
|
+
|
|
80
|
+
const parsedRouteDefinitions = useMemo(() => parseRouteDefinitions(), [])
|
|
81
|
+
|
|
82
|
+
s.updateMeta({parsedRouteDefinitions})
|
|
83
|
+
|
|
84
|
+
const findMatchingRoute = useCallback(() => {
|
|
85
|
+
const path = getPath()
|
|
86
|
+
|
|
87
|
+
for (const parsedRouteDefinition of s.m.parsedRouteDefinitions) {
|
|
88
|
+
const match = path.match(parsedRouteDefinition.pathRegex)
|
|
89
|
+
let matched, params
|
|
90
|
+
|
|
91
|
+
if (match) {
|
|
92
|
+
matched = true
|
|
93
|
+
params = {}
|
|
94
|
+
|
|
95
|
+
for (const groupKey in parsedRouteDefinition.groups) {
|
|
96
|
+
const groupName = parsedRouteDefinition.groups[groupKey]
|
|
97
|
+
|
|
98
|
+
params[groupName] = match[Number(groupKey) + 1]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (path == "" && parsedRouteDefinition.routeDefinition.path == "/") matched = true
|
|
103
|
+
if (matched) {
|
|
104
|
+
return {params, parsedRouteDefinition}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}, [])
|
|
108
|
+
|
|
109
|
+
const matchingRoute = findMatchingRoute()
|
|
110
|
+
const params = matchingRoute?.params || {}
|
|
111
|
+
const match = {
|
|
112
|
+
matchingRoute,
|
|
113
|
+
params
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {match}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
useRouter.propTypes = {
|
|
120
|
+
path: PropTypes.string,
|
|
121
|
+
routeDefinitions: PropTypes.object,
|
|
122
|
+
routes: PropTypes.object
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default useRouter
|
package/src/with-router.jsx
CHANGED
|
@@ -1,133 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import escapeStringRegexp from "escape-string-regexp"
|
|
3
|
-
import * as inflection from "inflection"
|
|
4
|
-
import PropTypes from "prop-types"
|
|
5
|
-
import React from "react"
|
|
6
|
-
import shouldComponentUpdate from "set-state-compare/src/should-component-update"
|
|
1
|
+
import useRouter from "./use-router"
|
|
7
2
|
|
|
8
|
-
export default (WrapperComponent) =>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
routeDefinitions: PropTypes.object,
|
|
12
|
-
routes: PropTypes.object
|
|
13
|
-
}
|
|
3
|
+
export default (WrapperComponent) => (props) => {
|
|
4
|
+
const {path, routes, routeDefinitions, ...restProps} = props
|
|
5
|
+
const {match} = useRouter({path, routes, routeDefinitions})
|
|
14
6
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return shouldComponentUpdate(this, nextProps, nextState)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
findRouteParams(routeDefinition) {
|
|
22
|
-
const result = []
|
|
23
|
-
const parts = routeDefinition.path.split("/")
|
|
24
|
-
|
|
25
|
-
for (const part of parts) {
|
|
26
|
-
if (part.match(/^:([a-z_]+)$/))
|
|
27
|
-
result.push(part)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return result
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
path() {
|
|
34
|
-
let path = this.props.path || window.location.pathname
|
|
35
|
-
|
|
36
|
-
path = path.replace(/[/]+$/, "")
|
|
37
|
-
|
|
38
|
-
return path
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
routeDefinitions() {
|
|
42
|
-
return this.props.routeDefinitions || config.getRouteDefinitions()
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
routes() {
|
|
46
|
-
return this.props.routes || config.getRoutes()
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
parseRouteDefinitions() {
|
|
50
|
-
const Locales = require("shared/locales").default
|
|
51
|
-
const routeDefinitions = this.routeDefinitions()
|
|
52
|
-
const routes = this.routes()
|
|
53
|
-
const regex = /:([A-z\d_]+)/
|
|
54
|
-
const parsedRouteDefinitions = []
|
|
55
|
-
|
|
56
|
-
for (const locale of Locales.availableLocales()) {
|
|
57
|
-
for (const routeDefinition of routeDefinitions.routes) {
|
|
58
|
-
const routePathName = `${inflection.camelize(routeDefinition.name, true)}Path`
|
|
59
|
-
const params = this.findRouteParams(routeDefinition)
|
|
60
|
-
|
|
61
|
-
params.push({locale})
|
|
62
|
-
|
|
63
|
-
if (!(routePathName in routes))
|
|
64
|
-
throw new Error(`${routePathName} not found in routes: ${Object.keys(routes, ", ")}`)
|
|
65
|
-
|
|
66
|
-
const routePath = routes[routePathName](...params).replace(/[/]+$/, "")
|
|
67
|
-
const groups = []
|
|
68
|
-
let pathRegexString = "^"
|
|
69
|
-
|
|
70
|
-
pathRegexString += escapeStringRegexp(routePath)
|
|
71
|
-
|
|
72
|
-
while (true) {
|
|
73
|
-
const match = pathRegexString.match(regex)
|
|
74
|
-
|
|
75
|
-
if (!match) break
|
|
76
|
-
|
|
77
|
-
const variableName = match[1]
|
|
78
|
-
|
|
79
|
-
groups.push(variableName)
|
|
80
|
-
|
|
81
|
-
pathRegexString = pathRegexString.replace(match[0], "([^/]+)")
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
pathRegexString += "$"
|
|
85
|
-
|
|
86
|
-
const pathRegex = new RegExp(pathRegexString)
|
|
87
|
-
|
|
88
|
-
parsedRouteDefinitions.push({groups, pathRegex, routeDefinition})
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return parsedRouteDefinitions
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
findMatchingRoute() {
|
|
96
|
-
const path = this.path()
|
|
97
|
-
|
|
98
|
-
for (const parsedRouteDefinition of this.parsedRouteDefinitions) {
|
|
99
|
-
const match = path.match(parsedRouteDefinition.pathRegex)
|
|
100
|
-
let matched, params
|
|
101
|
-
|
|
102
|
-
if (match) {
|
|
103
|
-
matched = true
|
|
104
|
-
params = {}
|
|
105
|
-
|
|
106
|
-
for (const groupKey in parsedRouteDefinition.groups) {
|
|
107
|
-
const groupName = parsedRouteDefinition.groups[groupKey]
|
|
108
|
-
|
|
109
|
-
params[groupName] = match[Number(groupKey) + 1]
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (path == "" && parsedRouteDefinition.routeDefinition.path == "/") matched = true
|
|
114
|
-
if (matched) {
|
|
115
|
-
return {params, parsedRouteDefinition}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
render() {
|
|
121
|
-
const {path, routes, routeDefinitions, ...restProps} = this.props
|
|
122
|
-
const matchingRoute = this.findMatchingRoute()
|
|
123
|
-
const params = matchingRoute?.params || {}
|
|
124
|
-
const match = {
|
|
125
|
-
matchingRoute,
|
|
126
|
-
params
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return (
|
|
130
|
-
<WrapperComponent match={match} {...restProps} />
|
|
131
|
-
)
|
|
132
|
-
}
|
|
7
|
+
return (
|
|
8
|
+
<WrapperComponent match={match} {...restProps} />
|
|
9
|
+
)
|
|
133
10
|
}
|