@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 CHANGED
@@ -16,7 +16,7 @@
16
16
  ]
17
17
  },
18
18
  "name": "@kaspernj/api-maker",
19
- "version": "1.0.308",
19
+ "version": "1.0.310",
20
20
  "type": "module",
21
21
  "description": "",
22
22
  "main": "index.js",
@@ -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 Input from "../bootstrap/input"
4
+ import {Pressable, Text, TextInput, View} from "react-native"
5
5
  import Locales from "shared/locales"
6
- import {useCallback, memo} from "react"
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 camelizedLower = digg(modelClass.modelClassData(), "camelizedLower")
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 (e) => {
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
- await model.saveRaw(formData)
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
- <div className="super-admin--edit-page">
66
- <form onSubmit={onSubmit}>
67
- {model && attributes?.map((attribute) =>
68
- <div key={attribute.attribute}>
69
- {attribute.translated && availableLocales.map((locale) =>
70
- <div key={locale}>
71
- <Input
72
- attribute={`${attribute.attribute}${inflection.camelize(locale)}`}
73
- id={`${camelizedLower}_${inflection.underscore(attribute.attribute)}_${locale}`}
74
- label={`${modelClass.humanAttributeName(attribute.attribute)} (${locale})`}
75
- model={model}
76
- name={`${camelizedLower}[${inflection.underscore(attribute.attribute)}_${locale}]`}
77
- />
78
- </div>
79
- )}
80
- {!attribute.translated &&
81
- <Input
82
- attribute={attribute.attribute}
83
- id={`${camelizedLower}_${inflection.underscore(attribute.attribute)}`}
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
- </button>
95
- </form>
96
- </div>
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
- }, [modelName, modelId])
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
- {modelClass && pageToShow == "show" &&
79
+ {model && pageToShow == "show" &&
61
80
  <>
62
- <Link className="edit-model-link" to={Params.withParams({model: modelName, model_id: modelId, mode: "edit"})}>
63
- Edit
64
- </Link>
65
- <a className="destroy-model-link" href="#" onClick={onDestroyClicked}>
66
- Delete
67
- </a>
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
- [modelClass, pageToShow]
94
+ [model, pageToShow]
72
95
  )
73
96
 
74
97
  return (
@@ -1,20 +1,72 @@
1
- import AttributeRows from "../../bootstrap/attribute-rows"
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 withModel from "../../with-model"
9
+ import useModel from "../../use-model"
11
10
 
12
- const ApiMakerSuperAdminShowPage = ({modelClass, ...restProps}) => {
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(restProps, camelizedLower)
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
- <AttributeRows attributes={attributes} model={model} />
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
- const modelClassResolver = {callback: ({queryParams}) => {
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)