@kaspernj/api-maker 1.0.308 → 1.0.310
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/super-admin/edit-page.jsx +125 -41
- package/src/super-admin/index.jsx +35 -12
- package/src/super-admin/show-page/index.jsx +62 -60
package/package.json
CHANGED
|
@@ -1,19 +1,115 @@
|
|
|
1
1
|
import ConfigReader from "./config-reader"
|
|
2
2
|
import {digg} from "diggerize"
|
|
3
3
|
import * as inflection from "inflection"
|
|
4
|
-
import
|
|
4
|
+
import {Pressable, Text, TextInput, View} from "react-native"
|
|
5
5
|
import Locales from "shared/locales"
|
|
6
|
-
import {useCallback,
|
|
6
|
+
import {memo, useCallback, useEffect, useMemo, useState} from "react"
|
|
7
7
|
import useCurrentUser from "../use-current-user"
|
|
8
8
|
import useModel from "../use-model"
|
|
9
9
|
import useQueryParams from "on-location-changed/src/use-query-params"
|
|
10
10
|
|
|
11
|
+
const EditAttributeInput = ({attributeName, id, inputs, label, model, name}) => {
|
|
12
|
+
const defaultValue = useCallback(() => model[attributeName]() || "")
|
|
13
|
+
const [value, setValue] = useState(() => defaultValue())
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
inputs[name] = value
|
|
17
|
+
}, [])
|
|
18
|
+
|
|
19
|
+
const onChangeText = useCallback((newValue) => {
|
|
20
|
+
inputs[name] = newValue
|
|
21
|
+
setValue(newValue)
|
|
22
|
+
}, [])
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<View style={{marginBottom: 12}}>
|
|
26
|
+
<Text>{label}</Text>
|
|
27
|
+
<View>
|
|
28
|
+
<TextInput
|
|
29
|
+
dataSet={{
|
|
30
|
+
attribute: attributeName,
|
|
31
|
+
id,
|
|
32
|
+
name
|
|
33
|
+
}}
|
|
34
|
+
onChangeText={onChangeText}
|
|
35
|
+
style={{paddingTop: 9, paddingRight: 13, paddingBottom: 9, paddingLeft: 13, borderRadius: 5, backgroundColor: "#fff", border: "1px solid #cecece"}}
|
|
36
|
+
value={value}
|
|
37
|
+
/>
|
|
38
|
+
</View>
|
|
39
|
+
</View>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const EditAttributeContent = ({attribute, id, inputs, model, name}) => {
|
|
44
|
+
const defaultValue = useCallback(() => model[attribute.attribute]() || "")
|
|
45
|
+
const [value, setValue] = useState(() => defaultValue())
|
|
46
|
+
const onChangeValue = useCallback((newValue) => {
|
|
47
|
+
inputs[name] = newValue
|
|
48
|
+
setValue(newValue)
|
|
49
|
+
})
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
inputs[name] = value
|
|
52
|
+
}, [])
|
|
53
|
+
|
|
54
|
+
const contentArgs = () => ({
|
|
55
|
+
inputProps: {
|
|
56
|
+
attribute: attribute.attribute,
|
|
57
|
+
defaultValue: defaultValue(),
|
|
58
|
+
id,
|
|
59
|
+
model
|
|
60
|
+
},
|
|
61
|
+
onChangeValue
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
return attribute.content(contentArgs())
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const EditAttribute = ({attribute, inputs, model, modelClass}) => {
|
|
68
|
+
const availableLocales = Locales.availableLocales()
|
|
69
|
+
const camelizedLower = digg(modelClass.modelClassData(), "camelizedLower")
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<>
|
|
73
|
+
{attribute.content &&
|
|
74
|
+
<EditAttributeContent
|
|
75
|
+
attribute={attribute}
|
|
76
|
+
id={`${camelizedLower}_${inflection.underscore(attribute.attribute)}`}
|
|
77
|
+
inputs={inputs}
|
|
78
|
+
model={model}
|
|
79
|
+
name={inflection.underscore(attribute.attribute)}
|
|
80
|
+
/>
|
|
81
|
+
}
|
|
82
|
+
{!attribute.content && attribute.translated && availableLocales.map((locale) =>
|
|
83
|
+
<EditAttributeInput
|
|
84
|
+
attributeName={`${attribute.attribute}${inflection.camelize(locale)}`}
|
|
85
|
+
id={`${camelizedLower}_${inflection.underscore(attribute.attribute)}_${locale}`}
|
|
86
|
+
inputs={inputs}
|
|
87
|
+
label={`${modelClass.humanAttributeName(attribute.attribute)} (${locale})`}
|
|
88
|
+
model={model}
|
|
89
|
+
name={`${inflection.underscore(attribute.attribute)}_${locale}`}
|
|
90
|
+
key={locale}
|
|
91
|
+
/>
|
|
92
|
+
)}
|
|
93
|
+
{!attribute.content && !attribute.translated &&
|
|
94
|
+
<EditAttributeInput
|
|
95
|
+
attributeName={attribute.attribute}
|
|
96
|
+
id={`${camelizedLower}_${inflection.underscore(attribute.attribute)}`}
|
|
97
|
+
inputs={inputs}
|
|
98
|
+
label={modelClass.humanAttributeName(attribute.attribute)}
|
|
99
|
+
model={model}
|
|
100
|
+
name={inflection.underscore(attribute.attribute)}
|
|
101
|
+
/>
|
|
102
|
+
}
|
|
103
|
+
</>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
11
107
|
const EditPage = ({modelClass}) => {
|
|
12
108
|
const availableLocales = Locales.availableLocales()
|
|
13
109
|
const currentUser = useCurrentUser()
|
|
14
110
|
const queryParams = useQueryParams()
|
|
15
111
|
const configReader = ConfigReader.forModel(modelClass)
|
|
16
|
-
const
|
|
112
|
+
const inputs = useMemo(() => ({}))
|
|
17
113
|
const modelClassName = modelClass.modelClassData().name
|
|
18
114
|
const modelIdVarName = `${inflection.camelize(modelClass.modelClassData().name, true)}Id`
|
|
19
115
|
const modelVarName = inflection.camelize(modelClass.modelClassData().name, true)
|
|
@@ -47,14 +143,11 @@ const EditPage = ({modelClass}) => {
|
|
|
47
143
|
|
|
48
144
|
modelArgs[modelIdVarName] = modelId
|
|
49
145
|
|
|
50
|
-
const onSubmit = useCallback(async (
|
|
51
|
-
e.preventDefault()
|
|
52
|
-
|
|
53
|
-
const form = digg(e, "target")
|
|
54
|
-
const formData = new FormData(form)
|
|
55
|
-
|
|
146
|
+
const onSubmit = useCallback(async () => {
|
|
56
147
|
try {
|
|
57
|
-
|
|
148
|
+
model.assignAttributes(inputs)
|
|
149
|
+
|
|
150
|
+
await model.save(inputs)
|
|
58
151
|
Params.changeParams({mode: undefined, model_id: model.id()})
|
|
59
152
|
} catch (error) {
|
|
60
153
|
FlashMessage.errorResponse(error)
|
|
@@ -62,38 +155,29 @@ const EditPage = ({modelClass}) => {
|
|
|
62
155
|
}, [model])
|
|
63
156
|
|
|
64
157
|
return (
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
{
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
label={modelClass.humanAttributeName(attribute.attribute)}
|
|
85
|
-
model={model}
|
|
86
|
-
name={`${camelizedLower}[${inflection.underscore(attribute.attribute)}]`}
|
|
87
|
-
/>
|
|
88
|
-
}
|
|
89
|
-
</div>
|
|
90
|
-
)}
|
|
91
|
-
{extraContent && extraContent(modelArgs)}
|
|
92
|
-
<button style={{marginTop: "10px"}} type="submit">
|
|
158
|
+
<View dataSet={{class: "super-admin--edit-page"}}>
|
|
159
|
+
{model && attributes?.map((attribute) =>
|
|
160
|
+
<EditAttribute attribute={attribute} inputs={inputs} key={attribute.attribute} model={model} modelClass={modelClass} />
|
|
161
|
+
)}
|
|
162
|
+
{extraContent && extraContent(modelArgs)}
|
|
163
|
+
<Pressable
|
|
164
|
+
dataSet={{class: "submit-button"}}
|
|
165
|
+
onPress={onSubmit}
|
|
166
|
+
style={{
|
|
167
|
+
paddingTop: 18,
|
|
168
|
+
paddingRight: 24,
|
|
169
|
+
paddingBottom: 18,
|
|
170
|
+
paddingLeft: 24,
|
|
171
|
+
borderRadius: 10,
|
|
172
|
+
backgroundColor: "#4c93ff",
|
|
173
|
+
marginTop: "10px"
|
|
174
|
+
}}
|
|
175
|
+
>
|
|
176
|
+
<Text style={{color: "#fff"}}>
|
|
93
177
|
Submit
|
|
94
|
-
</
|
|
95
|
-
</
|
|
96
|
-
</
|
|
178
|
+
</Text>
|
|
179
|
+
</Pressable>
|
|
180
|
+
</View>
|
|
97
181
|
)
|
|
98
182
|
}
|
|
99
183
|
|
|
@@ -4,7 +4,7 @@ import Layout from "./layout"
|
|
|
4
4
|
import Link from "../link"
|
|
5
5
|
import {memo, useMemo} from "react"
|
|
6
6
|
import * as modelsModule from "@kaspernj/api-maker/src/models.mjs.erb"
|
|
7
|
-
import {useCallback} from "react"
|
|
7
|
+
import {useCallback, useEffect, useState} from "react"
|
|
8
8
|
import ShowPage from "./show-page"
|
|
9
9
|
import ShowReflectionPage from "./show-reflection-page"
|
|
10
10
|
import useQueryParams from "on-location-changed/src/use-query-params"
|
|
@@ -17,6 +17,27 @@ const ApiMakerSuperAdmin = () => {
|
|
|
17
17
|
|
|
18
18
|
const modelId = queryParams.model_id
|
|
19
19
|
const modelName = modelClass?.modelClassData()?.name
|
|
20
|
+
const [model, setModel] = useState()
|
|
21
|
+
|
|
22
|
+
const loadModel = useCallback(async () => {
|
|
23
|
+
if (modelId && modelClass) {
|
|
24
|
+
const abilities = {}
|
|
25
|
+
const abilitiesForModel = ["destroy", "edit"]
|
|
26
|
+
|
|
27
|
+
abilities[modelName] = abilitiesForModel
|
|
28
|
+
|
|
29
|
+
const model = await modelClass
|
|
30
|
+
.ransack({id_eq: modelId})
|
|
31
|
+
.abilities(abilities)
|
|
32
|
+
.first()
|
|
33
|
+
|
|
34
|
+
setModel(model)
|
|
35
|
+
} else {
|
|
36
|
+
setModel(undefined)
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
useEffect(() => { loadModel() }, [modelId])
|
|
20
41
|
|
|
21
42
|
if (queryParams.model && queryParams.model_id && queryParams.model_reflection) {
|
|
22
43
|
pageToShow = "show_reflection"
|
|
@@ -40,15 +61,13 @@ const ApiMakerSuperAdmin = () => {
|
|
|
40
61
|
}
|
|
41
62
|
|
|
42
63
|
try {
|
|
43
|
-
const model = await modelClass.find(modelId)
|
|
44
|
-
|
|
45
64
|
await model.destroy()
|
|
46
65
|
|
|
47
66
|
Params.changeParams({mode: undefined, model_id: undefined})
|
|
48
67
|
} catch (error) {
|
|
49
68
|
FlashMessage.errorResponse(error)
|
|
50
69
|
}
|
|
51
|
-
}, [
|
|
70
|
+
}, [model])
|
|
52
71
|
|
|
53
72
|
const actions = useMemo(
|
|
54
73
|
() => <>
|
|
@@ -57,18 +76,22 @@ const ApiMakerSuperAdmin = () => {
|
|
|
57
76
|
Create new
|
|
58
77
|
</Link>
|
|
59
78
|
}
|
|
60
|
-
{
|
|
79
|
+
{model && pageToShow == "show" &&
|
|
61
80
|
<>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
{model.can("edit") &&
|
|
82
|
+
<Link className="edit-model-link" to={Params.withParams({model: modelName, model_id: modelId, mode: "edit"})}>
|
|
83
|
+
Edit
|
|
84
|
+
</Link>
|
|
85
|
+
}
|
|
86
|
+
{model.can("destroy") &&
|
|
87
|
+
<a className="destroy-model-link" href="#" onClick={onDestroyClicked}>
|
|
88
|
+
Delete
|
|
89
|
+
</a>
|
|
90
|
+
}
|
|
68
91
|
</>
|
|
69
92
|
}
|
|
70
93
|
</>,
|
|
71
|
-
[
|
|
94
|
+
[model, pageToShow]
|
|
72
95
|
)
|
|
73
96
|
|
|
74
97
|
return (
|
|
@@ -1,20 +1,72 @@
|
|
|
1
|
-
import
|
|
1
|
+
import AttributeRow from "../../bootstrap/attribute-row"
|
|
2
2
|
import BelongsToAttributeRow from "./belongs-to-attribute-row"
|
|
3
3
|
import ConfigReader from "../config-reader"
|
|
4
4
|
import {digg} from "diggerize"
|
|
5
5
|
import * as inflection from "inflection"
|
|
6
|
-
import Link from "../../link"
|
|
7
6
|
import PropTypes from "prop-types"
|
|
8
7
|
import {memo} from "react"
|
|
9
8
|
import ShowNav from "../show-nav"
|
|
10
|
-
import
|
|
9
|
+
import useModel from "../../use-model"
|
|
11
10
|
|
|
12
|
-
const
|
|
11
|
+
const AttributePresenter = ({attribute, model, modelArgs}) => {
|
|
12
|
+
const attributeRowProps = {}
|
|
13
|
+
|
|
14
|
+
if (typeof attribute == "object") {
|
|
15
|
+
attributeRowProps.attribute = attribute.attribute
|
|
16
|
+
if (attribute.content) attributeRowProps.children = attribute.content(modelArgs)
|
|
17
|
+
} else {
|
|
18
|
+
attributeRowProps.attribute = attribute
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<AttributeRow model={model} {...attributeRowProps} />
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ApiMakerSuperAdminShowPage = ({modelClass}) => {
|
|
13
27
|
const configReader = ConfigReader.forModel(modelClass)
|
|
28
|
+
const showConfig = configReader.modelConfig?.show
|
|
14
29
|
const attributes = configReader.attributesToShow()
|
|
30
|
+
const extraContent = showConfig?.extraContent
|
|
31
|
+
const modelClassName = modelClass.modelClassData().name
|
|
32
|
+
const primaryKeyName = modelClass.primaryKey()
|
|
33
|
+
const preload = []
|
|
34
|
+
const select = showConfig?.extraSelect || {}
|
|
35
|
+
const modelClassSelect = select[modelClassName] || []
|
|
36
|
+
|
|
37
|
+
if (!(modelClassName in select)) select[modelClassName] = modelClassSelect
|
|
38
|
+
if (!modelClassSelect.includes(primaryKeyName)) modelClassSelect.push(primaryKeyName)
|
|
39
|
+
|
|
40
|
+
// Select all attributes selected by default because they will be shown by default
|
|
41
|
+
for (const attribute of modelClass.attributes()) {
|
|
42
|
+
if (attribute.isSelectedByDefault() && !modelClassSelect.includes(attribute.name())) modelClassSelect.push(attribute.name())
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const reflection of modelClass.reflections()) {
|
|
46
|
+
if (reflection.macro() != "belongs_to") continue
|
|
47
|
+
|
|
48
|
+
const reflectionModelClass = reflection.modelClass()
|
|
49
|
+
const reflectionModelClassName = reflectionModelClass.modelClassData().name
|
|
50
|
+
const reflectionModelClassAttributes = reflectionModelClass.attributes()
|
|
51
|
+
const nameAttribute = reflectionModelClassAttributes.find((attribute) => attribute.name() == "name")
|
|
52
|
+
|
|
53
|
+
preload.push(inflection.underscore(reflection.name()))
|
|
54
|
+
|
|
55
|
+
if (!(reflectionModelClassName in select)) select[reflectionModelClassName] = []
|
|
56
|
+
if (!select[reflectionModelClassName].includes("id")) select[reflectionModelClassName].push("id")
|
|
57
|
+
if (nameAttribute && !select[reflectionModelClassName].includes("name")) select[reflectionModelClassName].push("name")
|
|
58
|
+
|
|
59
|
+
// The foreign key is needed to look up any belongs-to-relationships
|
|
60
|
+
if (!modelClassSelect.includes(reflection.foreignKey())) modelClassSelect.push(reflection.foreignKey())
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const useModelResult = useModel(modelClass, {
|
|
64
|
+
loadByQueryParam: ({queryParams}) => queryParams.model_id,
|
|
65
|
+
preload,
|
|
66
|
+
select
|
|
67
|
+
})
|
|
15
68
|
const camelizedLower = digg(modelClass.modelClassData(), "camelizedLower")
|
|
16
|
-
const model = digg(
|
|
17
|
-
const extraContent = configReader.modelConfig?.show?.extraContent
|
|
69
|
+
const model = digg(useModelResult, camelizedLower)
|
|
18
70
|
const modelArgs = {}
|
|
19
71
|
|
|
20
72
|
modelArgs[inflection.camelize(modelClass.modelClassData().name, true)] = model
|
|
@@ -24,9 +76,9 @@ const ApiMakerSuperAdminShowPage = ({modelClass, ...restProps}) => {
|
|
|
24
76
|
{model &&
|
|
25
77
|
<ShowNav model={model} modelClass={modelClass} />
|
|
26
78
|
}
|
|
27
|
-
{attributes && model &&
|
|
28
|
-
<
|
|
29
|
-
}
|
|
79
|
+
{attributes && model && attributes.map((attribute) =>
|
|
80
|
+
<AttributePresenter attribute={attribute} key={attribute.attribute || attribute} modelArgs={modelArgs} model={model} />
|
|
81
|
+
)}
|
|
30
82
|
{model && modelClass.reflections().filter((reflection) => reflection.macro() == "belongs_to").map((reflection) =>
|
|
31
83
|
<BelongsToAttributeRow key={reflection.name()} model={model} modelClass={modelClass} reflection={reflection} />
|
|
32
84
|
)}
|
|
@@ -39,54 +91,4 @@ ApiMakerSuperAdminShowPage.propTypes = {
|
|
|
39
91
|
modelClass: PropTypes.func.isRequired
|
|
40
92
|
}
|
|
41
93
|
|
|
42
|
-
|
|
43
|
-
const modelClassName = digg(queryParams, "model")
|
|
44
|
-
const modelClass = digg(require("../../models.mjs.erb"), modelClassName)
|
|
45
|
-
|
|
46
|
-
return modelClass
|
|
47
|
-
}}
|
|
48
|
-
|
|
49
|
-
export default withModel(
|
|
50
|
-
memo(ApiMakerSuperAdminShowPage),
|
|
51
|
-
modelClassResolver,
|
|
52
|
-
({modelClass}) => {
|
|
53
|
-
const preload = []
|
|
54
|
-
const configReader = ConfigReader.forModel(modelClass)
|
|
55
|
-
const select = configReader.modelConfig?.show?.extraSelect || {}
|
|
56
|
-
const modelClassName = modelClass.modelClassData().name
|
|
57
|
-
const modelClassSelect = select[modelClassName] || []
|
|
58
|
-
const primaryKeyName = modelClass.primaryKey()
|
|
59
|
-
|
|
60
|
-
if (!(modelClassName in select)) select[modelClassName] = modelClassSelect
|
|
61
|
-
if (!modelClassSelect.includes(primaryKeyName)) modelClassSelect.push(primaryKeyName)
|
|
62
|
-
|
|
63
|
-
// Select all attributes selected by default because they will be shown by default
|
|
64
|
-
for (const attribute of modelClass.attributes()) {
|
|
65
|
-
if (attribute.isSelectedByDefault() && !modelClassSelect.includes(attribute.name())) modelClassSelect.push(attribute.name())
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
for (const reflection of modelClass.reflections()) {
|
|
69
|
-
if (reflection.macro() != "belongs_to") continue
|
|
70
|
-
|
|
71
|
-
const reflectionModelClass = reflection.modelClass()
|
|
72
|
-
const reflectionModelClassName = reflectionModelClass.modelClassData().name
|
|
73
|
-
const reflectionModelClassAttributes = reflectionModelClass.attributes()
|
|
74
|
-
const nameAttribute = reflectionModelClassAttributes.find((attribute) => attribute.name() == "name")
|
|
75
|
-
|
|
76
|
-
preload.push(inflection.underscore(reflection.name()))
|
|
77
|
-
|
|
78
|
-
if (!(reflectionModelClassName in select)) select[reflectionModelClassName] = []
|
|
79
|
-
if (!select[reflectionModelClassName].includes("id")) select[reflectionModelClassName].push("id")
|
|
80
|
-
if (nameAttribute && !select[reflectionModelClassName].includes("name")) select[reflectionModelClassName].push("name")
|
|
81
|
-
|
|
82
|
-
// The foreign key is needed to look up any belongs-to-relationships
|
|
83
|
-
if (!modelClassSelect.includes(reflection.foreignKey())) modelClassSelect.push(reflection.foreignKey())
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
loadByQueryParam: ({props}) => props.queryParams.model_id,
|
|
88
|
-
preload,
|
|
89
|
-
select
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
)
|
|
94
|
+
export default memo(ApiMakerSuperAdminShowPage)
|