@kaspernj/api-maker 1.0.414 → 1.0.416

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaspernj/api-maker",
3
- "version": "1.0.414",
3
+ "version": "1.0.416",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "main": "index.js",
@@ -77,7 +77,7 @@ const Route = memo(shapeComponent(class Route extends BaseComponent {
77
77
  this.props.onMatch()
78
78
  }
79
79
 
80
- if ((!this.props.children && this.props.path) || this.props.component || this.props.componentPath) {
80
+ if (!this.props.children && (this.props.path || this.props.component || this.props.componentPath)) {
81
81
  this.loadComponent()
82
82
  }
83
83
  }
@@ -1,37 +1,39 @@
1
- import Attribute from "../../base-model/attribute"
2
1
  import BaseComponent from "../../base-component"
3
- import {digg, digs} from "diggerize"
4
- import * as inflection from "inflection"
2
+ import {digg} from "diggerize"
5
3
  import PropTypes from "prop-types"
6
4
  import PropTypesExact from "prop-types-exact"
7
5
  import {memo} from "react"
6
+ import {Pressable, Text} from "react-native"
8
7
  import {shapeComponent} from "set-state-compare/src/shape-component"
9
8
 
10
9
  export default memo(shapeComponent(class AttributeElement extends BaseComponent {
11
10
  static propTypes = PropTypesExact({
12
11
  active: PropTypes.bool.isRequired,
13
- attribute: PropTypes.instanceOf(Attribute).isRequired,
14
- currentModelClass: PropTypes.func.isRequired,
12
+ attribute: PropTypes.object.isRequired,
13
+ modelClassName: PropTypes.string.isRequired,
15
14
  fikter: PropTypes.object,
16
15
  onClick: PropTypes.func.isRequired
17
16
  })
18
17
 
19
18
  render() {
20
- const {active, attribute, currentModelClass} = this.p
19
+ const {active, attribute, modelClassName} = this.p
21
20
  const style = {}
22
21
 
23
22
  if (active) style.fontWeight = "bold"
24
23
 
25
24
  return (
26
- <div
27
- className="attribute-element"
28
- data-attribute-name={attribute.name()}
29
- data-model-class={currentModelClass.modelClassData().name}
30
- onClick={digg(this, "onAttributeClicked")}
31
- style={style}
25
+ <Pressable
26
+ dataSet={{
27
+ class: "attribute-element",
28
+ attributeName: digg(attribute, "attributeName"),
29
+ modelClass: modelClassName
30
+ }}
31
+ onPress={this.tt.onAttributeClicked}
32
32
  >
33
- {currentModelClass.humanAttributeName(inflection.camelize(attribute.name(), true))}
34
- </div>
33
+ <Text style={style}>
34
+ {digg(attribute, "humanName")}
35
+ </Text>
36
+ </Pressable>
35
37
  )
36
38
  }
37
39
 
@@ -2,10 +2,12 @@ import AttributeElement from "./attribute-element"
2
2
  import BaseComponent from "../../base-component"
3
3
  import {digg, digs} from "diggerize"
4
4
  import * as inflection from "inflection"
5
+ import {Form} from "../../form"
5
6
  import Input from "../../inputs/input"
6
7
  import PropTypes from "prop-types"
7
8
  import PropTypesExact from "prop-types-exact"
8
9
  import {memo, useMemo, useRef} from "react"
10
+ import {ActivityIndicator, Text, View} from "react-native"
9
11
  import ReflectionElement from "./reflection-element"
10
12
  import ScopeElement from "./scope-element"
11
13
  import Select from "../../inputs/select"
@@ -22,41 +24,127 @@ export default memo(shapeComponent(class ApiMakerTableFiltersFilterForm extends
22
24
 
23
25
  setup() {
24
26
  this.useStates({
25
- attribute: () => this.currentModelClassFromPath(this.props.filter.p || [])
26
- .ransackableAttributes()
27
- .find((attribute) => attribute.name() == this.props.filter.a),
28
- path: this.props.filter.p || [],
27
+ associations: null,
28
+ attribute: undefined,
29
+ actualCurrentModelClass: () => ({modelClass: this.p.modelClass}),
30
+ loading: 0,
31
+ modelClassName: digg(this.p.modelClass.modelClassData(), "className"),
32
+ path: [],
29
33
  predicate: undefined,
30
34
  predicates: undefined,
35
+ ransackableAttributes: undefined,
36
+ ransackableScopes: undefined,
31
37
  scope: this.props.filter.sc,
32
38
  value: this.props.filter.v
33
39
  })
34
- this.valueInputRef = useRef()
40
+
41
+ this.setInstance({valueInputRef: useRef()})
35
42
 
36
43
  useMemo(() => {
37
44
  this.loadRansackPredicates()
45
+
46
+ if (this.props.filter.v) {
47
+ this.loadInitialValuesWithLoadingIndicator()
48
+ }
38
49
  }, [])
50
+
51
+ useMemo(() => {
52
+ this.loadAssociations()
53
+ }, [this.s.modelClassName])
39
54
  }
40
55
 
41
- async loadRansackPredicates() {
42
- const response = await Services.current().sendRequest("Ransack::Predicates")
43
- const predicates = digg(response, "predicates")
44
- let currentPredicate
56
+ currentModelClass = () => digg(this.s.actualCurrentModelClass, "modelClass")
57
+
58
+ parseAssociationData(result) {
59
+ const associations = result.associations.map(({human_name, model_class_name, reflection_name, resource}) => ({
60
+ humanName: human_name,
61
+ modelClassName: model_class_name,
62
+ reflectionName: inflection.camelize(reflection_name, true),
63
+ resource
64
+ }))
65
+ const ransackableAttributes = digg(result, "ransackable_attributes").map(({attribute_name: attributeName, human_name: humanName}) => ({
66
+ attributeName, humanName
67
+ }))
68
+ const ransackableScopes = digg(result, "ransackable_scopes")
69
+
70
+ return {associations, ransackableAttributes, ransackableScopes}
71
+ }
72
+
73
+ async loadAssociations() {
74
+ this.increaseLoading()
75
+
76
+ try {
77
+ const result = await Services.current().sendRequest("Models::Associations", {model_class_name: this.s.modelClassName})
78
+ const {associations, ransackableAttributes, ransackableScopes} = this.parseAssociationData(result)
45
79
 
46
- if (this.props.filter.pre) {
47
- currentPredicate = predicates.find((predicate) => predicate.name == this.props.filter.pre)
80
+ this.setState({associations, ransackableAttributes, ransackableScopes})
81
+ } finally {
82
+ this.decreaseLoading()
48
83
  }
84
+ }
49
85
 
50
- this.setState({
51
- predicate: currentPredicate,
52
- predicates
53
- })
86
+ decreaseLoading = () => this.setState((prevState) => ({loading: prevState.loading - 1}))
87
+ increaseLoading = () => this.setState((prevState) => ({loading: prevState.loading + 1}))
88
+
89
+ async loadInitialValuesWithLoadingIndicator() {
90
+ try {
91
+ this.increaseLoading()
92
+ await this.loadInitialValues()
93
+ } finally {
94
+ this.decreaseLoading()
95
+ }
96
+ }
97
+
98
+ async loadInitialValues() {
99
+ let result = await Services.current().sendRequest("Models::Associations", {model_class_name: this.s.modelClassName})
100
+ let data = this.parseAssociationData(result)
101
+ let modelClassName
102
+ const path = []
103
+
104
+ for (const pathPart of this.props.filter.p) {
105
+ const reflection = data.associations.find((association) => digg(association, "reflectionName") == inflection.camelize(pathPart, true))
106
+
107
+ if (!reflection) throw new Error(`Couldn't find association by that name ${this.s.modelClassName}#${pathPart}`)
108
+
109
+ modelClassName = digg(reflection, "modelClassName")
110
+
111
+ result = await Services.current().sendRequest("Models::Associations", {model_class_name: modelClassName})
112
+ data = this.parseAssociationData(result)
113
+
114
+ path.push(reflection)
115
+ }
116
+
117
+ const {ransackableAttributes} = data
118
+ const attribute = this.p.filter.a
119
+ const ransackableAttribute = ransackableAttributes.find((ransackableAttribute) => digg(ransackableAttribute, "attributeName") == attribute)
120
+
121
+ this.setState({attribute: ransackableAttribute, modelClassName, path})
122
+ }
123
+
124
+ async loadRansackPredicates() {
125
+ this.increaseLoading()
126
+
127
+ try {
128
+ const response = await Services.current().sendRequest("Ransack::Predicates")
129
+ const predicates = digg(response, "predicates")
130
+ let currentPredicate
131
+
132
+ if (this.props.filter.pre) {
133
+ currentPredicate = predicates.find((predicate) => predicate.name == this.props.filter.pre)
134
+ }
135
+
136
+ this.setState({
137
+ predicate: currentPredicate,
138
+ predicates
139
+ })
140
+ } finally {
141
+ this.decreaseLoading()
142
+ }
54
143
  }
55
144
 
56
145
  render() {
57
146
  const {valueInputRef} = digs(this, "valueInputRef")
58
- const currentModelClass = this.currentModelClass()
59
- const {attribute, predicate, predicates, scope, value} = this.s
147
+ const {attribute, path, predicate, predicates, scope, value} = this.s
60
148
  let submitEnabled = false
61
149
 
62
150
  if (attribute && predicate) {
@@ -66,51 +154,53 @@ export default memo(shapeComponent(class ApiMakerTableFiltersFilterForm extends
66
154
  }
67
155
 
68
156
  return (
69
- <div className="api-maker--table--filters--filter-form">
70
- <form onSubmit={this.tt.onSubmit}>
71
- <div>
72
- {this.currentPathParts().map(({translation}, pathPartIndex) =>
73
- <span key={`${pathPartIndex}-${translation}`}>
157
+ <View dataSet={{class: "api-maker--table--filters--filter-form"}} style={{minWidth: 50, minHeight: 50}}>
158
+ <Form onSubmit={this.tt.onSubmit}>
159
+ <View style={{flexDirection: "row"}}>
160
+ {path.map(({humanName, reflectionName}, pathPartIndex) =>
161
+ <View key={`${pathPartIndex}-${reflectionName}`} style={{flexDirection: "row"}}>
74
162
  {pathPartIndex > 0 &&
75
- <span style={{marginRight: "5px", marginLeft: "5px"}}>
163
+ <Text style={{marginRight: 5, marginLeft: 5}}>
76
164
  -
77
- </span>
165
+ </Text>
78
166
  }
79
- {translation}
80
- </span>
167
+ <Text>
168
+ {humanName}
169
+ </Text>
170
+ </View>
81
171
  )}
82
- </div>
83
- <div style={{display: "flex"}}>
84
- <div>
85
- {this.sortedByName(this.reflectionsWithModelClass(currentModelClass.ransackableAssociations()), currentModelClass).map((reflection) =>
172
+ </View>
173
+ <View style={{flexDirection: "row"}}>
174
+ <View>
175
+ {this.s.associations?.map((reflection) =>
86
176
  <ReflectionElement
87
- currentModelClass={currentModelClass}
88
- key={reflection.name()}
177
+ key={reflection.reflectionName}
178
+ modelClassName={this.s.modelClassName}
89
179
  onClick={this.tt.onReflectionClicked}
90
180
  reflection={reflection}
91
181
  />
92
182
  )}
93
- </div>
94
- <div>
95
- {this.sortedByName(currentModelClass.ransackableAttributes(), currentModelClass).map((attribute) =>
183
+ </View>
184
+ <View>
185
+ {this.s.ransackableAttributes?.map((attribute) =>
96
186
  <AttributeElement
97
- active={attribute.name() == this.state.attribute?.name()}
187
+ active={attribute.attributeName == this.s.attribute?.attributeName}
98
188
  attribute={attribute}
99
- currentModelClass={currentModelClass}
100
- key={attribute.name()}
189
+ key={attribute.attributeName}
190
+ modelClassName={this.s.modelClassName}
101
191
  onClick={this.tt.onAttributeClicked}
102
192
  />
103
193
  )}
104
- {currentModelClass.ransackableScopes().map((scope) =>
194
+ {this.s.ransackableScopes?.map((scope) =>
105
195
  <ScopeElement
106
- active={scope.name() == this.state.scope?.name()}
107
- key={scope.name()}
196
+ active={scope == this.s.scope}
197
+ key={scope}
108
198
  scope={scope}
109
199
  onScopeClicked={this.tt.onScopeClicked}
110
200
  />
111
201
  )}
112
- </div>
113
- <div>
202
+ </View>
203
+ <View>
114
204
  {predicates && !this.state.scope &&
115
205
  <Select
116
206
  className="predicate-select"
@@ -120,25 +210,36 @@ export default memo(shapeComponent(class ApiMakerTableFiltersFilterForm extends
120
210
  options={predicates.map((predicate) => digg(predicate, "name"))}
121
211
  />
122
212
  }
123
- </div>
124
- <div>
213
+ </View>
214
+ <View>
125
215
  {((attribute && predicate) || scope) &&
126
216
  <Input className="value-input" defaultValue={value} inputRef={valueInputRef} />
127
217
  }
128
- </div>
129
- </div>
130
- <div>
218
+ </View>
219
+ </View>
220
+ <View>
131
221
  <button className="apply-filter-button" disabled={!submitEnabled}>
132
222
  {I18n.t("js.api_maker.table.filters.relationship_select.apply", {defaultValue: "Apply"})}
133
223
  </button>
134
- </div>
135
- </form>
136
- </div>
224
+ </View>
225
+ </Form>
226
+ {this.s.loading > 0 &&
227
+ <View
228
+ style={{
229
+ alignItems: "center",
230
+ justifyContent: "center",
231
+ position: "absolute",
232
+ width: "100%",
233
+ height: "100%"
234
+ }}
235
+ >
236
+ <ActivityIndicator size="large" />
237
+ </View>
238
+ }
239
+ </View>
137
240
  )
138
241
  }
139
242
 
140
- currentModelClass = () => this.currentModelClassFromPath(this.s.path)
141
-
142
243
  currentModelClassFromPath(path) {
143
244
  const {modelClass} = this.p
144
245
  let currentModelClass = modelClass
@@ -195,21 +296,22 @@ export default memo(shapeComponent(class ApiMakerTableFiltersFilterForm extends
195
296
 
196
297
  onPredicateChanged = (e) => {
197
298
  const chosenPredicateName = digg(e, "target", "value")
198
- const predicate = this.state.predicates.find((predicate) => predicate.name == chosenPredicateName)
299
+ const predicate = this.s.predicates.find((predicate) => predicate.name == chosenPredicateName)
199
300
 
200
301
  this.setState({predicate})
201
302
  }
202
303
 
203
304
  onReflectionClicked = ({reflection}) => {
204
- const newPath = this.state.path.concat([inflection.underscore(reflection.name())])
305
+ const newPath = this.s.path.concat([reflection])
205
306
 
206
307
  this.setState({
308
+ associations: null,
207
309
  attribute: undefined,
310
+ actualCurrentModelClass: {modelClass: digg(reflection, "resource")},
311
+ modelClassName: digg(reflection, "modelClassName"),
208
312
  path: newPath,
209
313
  predicate: undefined
210
314
  })
211
-
212
- this.props.onPathChanged
213
315
  }
214
316
 
215
317
  onScopeClicked = ({scope}) => {
@@ -219,21 +321,20 @@ export default memo(shapeComponent(class ApiMakerTableFiltersFilterForm extends
219
321
  })
220
322
  }
221
323
 
222
- onSubmit = (e) => {
223
- e.preventDefault()
224
-
324
+ onSubmit = () => {
225
325
  const {filter, querySearchName} = this.p
226
326
  const {attribute, path, predicate, scope} = this.s
227
327
  const {filterIndex} = digs(filter, "filterIndex")
228
328
  const searchParams = Params.parse()[querySearchName] || {}
229
329
  const value = digg(this.tt.valueInputRef, "current", "value")
330
+ const p = path.map((reflection) => inflection.underscore(reflection.reflectionName))
230
331
  const newSearchParams = {
231
- p: path,
332
+ p,
232
333
  v: value
233
334
  }
234
335
 
235
336
  if (attribute) {
236
- newSearchParams.a = attribute.name()
337
+ newSearchParams.a = digg(attribute, "attributeName")
237
338
  newSearchParams.pre = digg(predicate, "name")
238
339
  } else if (scope) {
239
340
  newSearchParams.sc = inflection.underscore(scope.name())
@@ -1,5 +1,6 @@
1
1
  import {Pressable, Text, View} from "react-native"
2
2
  import BaseComponent from "../../base-component"
3
+ import FontAwesomeIcon from "react-native-vector-icons/FontAwesome"
3
4
  import PropTypes from "prop-types"
4
5
  import PropTypesExact from "prop-types-exact"
5
6
  import {memo} from "react"
@@ -27,7 +28,7 @@ export default memo(shapeComponent(class ApiMakerTableFilter extends BaseCompone
27
28
  const {a, pre, sc} = this.props
28
29
 
29
30
  return (
30
- <View style={{display: "flex", flexDirection: "row", backgroundColor: "grey", paddingVertical: 10, paddingHorizontal: 6}}>
31
+ <View style={{alignItems: "center", flexDirection: "row", backgroundColor: "grey", paddingVertical: 10, paddingHorizontal: 6}}>
31
32
  <Pressable dataSet={{class: "filter-label"}} onPress={this.tt.onFilterPressed}>
32
33
  <Text>
33
34
  {p.length > 0 &&
@@ -37,9 +38,7 @@ export default memo(shapeComponent(class ApiMakerTableFilter extends BaseCompone
37
38
  </Text>
38
39
  </Pressable>
39
40
  <Pressable dataSet={{class: "remove-filter-button"}} onPress={this.tt.onRemoveFilterPressed} style={{marginLeft: 6}}>
40
- <Text>
41
- &#10006;
42
- </Text>
41
+ <FontAwesomeIcon name="remove" />
43
42
  </Pressable>
44
43
  </View>
45
44
  )
@@ -1,36 +1,36 @@
1
1
  import BaseComponent from "../../base-component"
2
- import {digg, digs} from "diggerize"
3
2
  import PropTypes from "prop-types"
4
3
  import PropTypesExact from "prop-types-exact"
5
4
  import {memo} from "react"
6
- import Reflection from "../../base-model/reflection"
5
+ import {Pressable, Text} from "react-native"
7
6
  import {shapeComponent} from "set-state-compare/src/shape-component"
8
7
 
9
8
  export default memo(shapeComponent(class ReflectionElement extends BaseComponent {
10
9
  static propTypes = PropTypesExact({
11
- currentModelClass: PropTypes.func.isRequired,
10
+ modelClassName: PropTypes.string.isRequired,
12
11
  onClick: PropTypes.func.isRequired,
13
- reflection: PropTypes.instanceOf(Reflection).isRequired
12
+ reflection: PropTypes.object.isRequired
14
13
  })
15
14
 
16
15
  render() {
17
- const {currentModelClass, reflection} = this.p
16
+ const {modelClassName, reflection} = this.p
17
+ const {humanName, reflectionName} = reflection
18
18
 
19
19
  return (
20
- <div
21
- className="reflection-element"
22
- data-model-class={currentModelClass.modelClassData().name}
23
- data-reflection-name={reflection.name()}
24
- onClick={digg(this, "onReflectionClicked")}
20
+ <Pressable
21
+ dataSet={{
22
+ class: "reflection-element",
23
+ modelClass: modelClassName,
24
+ reflectionName
25
+ }}
26
+ onPress={this.tt.onReflectionClicked}
25
27
  >
26
- {currentModelClass.humanAttributeName(reflection.name())}
27
- </div>
28
+ <Text>
29
+ {humanName}
30
+ </Text>
31
+ </Pressable>
28
32
  )
29
33
  }
30
34
 
31
- onReflectionClicked = (e) => {
32
- e.preventDefault()
33
-
34
- this.p.onClick({reflection: digg(this, "props", "reflection")})
35
- }
35
+ onReflectionClicked = () => this.p.onClick({reflection: this.p.reflection})
36
36
  }))
@@ -1,7 +1,8 @@
1
1
  import apiMakerConfig from "@kaspernj/api-maker/src/config.mjs"
2
2
  import BaseComponent from "../../base-component"
3
3
  import Checkbox from "../../bootstrap/checkbox"
4
- import {digg, digs} from "diggerize"
4
+ import {digg} from "diggerize"
5
+ import {Form} from "../../form"
5
6
  import Input from "../../bootstrap/input"
6
7
  import {shapeComponent} from "set-state-compare/src/shape-component.js"
7
8
  import {memo} from "react"
@@ -12,6 +13,7 @@ export default memo(shapeComponent(class ApiMakerTableFiltersSaveSearchModal ext
12
13
  const {t} = useI18n({namespace: "js.api_maker.table.filters.save_search_modal"})
13
14
 
14
15
  this.t = t
16
+ this.useStates({form: null})
15
17
  }
16
18
 
17
19
  render() {
@@ -20,7 +22,7 @@ export default memo(shapeComponent(class ApiMakerTableFiltersSaveSearchModal ext
20
22
 
21
23
  return (
22
24
  <Modal onRequestClose={onRequestClose} {...restProps}>
23
- <form onSubmit={this.onSaveSearchSubmit}>
25
+ <Form onSubmit={this.tt.onSaveSearchSubmit} setForm={this.setStates.form}>
24
26
  <Input
25
27
  defaultValue={search.name()}
26
28
  id="table_search_name"
@@ -36,27 +38,24 @@ export default memo(shapeComponent(class ApiMakerTableFiltersSaveSearchModal ext
36
38
  <button className="save-search-submit-button">
37
39
  {this.t(".save_search", {defaultValue: "Save search"})}
38
40
  </button>
39
- </form>
41
+ </Form>
40
42
  </Modal>
41
43
  )
42
44
  }
43
45
 
44
- onSaveSearchSubmit = async (e) => {
45
- e.preventDefault()
46
-
47
- const form = digg(e, "target")
48
- const formData = new FormData(form)
46
+ onSaveSearchSubmit = async () => {
47
+ const formData = this.s.form.asObject()
49
48
  const {currentFilters, currentUser, onRequestClose, search} = this.p
50
49
 
51
50
  if (search.isNewRecord()) {
52
- formData.append("table_search[query_params]", JSON.stringify(currentFilters()))
51
+ formData.table_search.query_params = JSON.stringify(currentFilters())
53
52
  }
54
53
 
55
- formData.append("table_search[user_type]", digg(currentUser.modelClassData(), "className"))
56
- formData.append("table_search[user_id]", currentUser.id())
54
+ formData.table_search.user_type = digg(currentUser.modelClassData(), "className")
55
+ formData.table_search.user_id = currentUser.id()
57
56
 
58
57
  try {
59
- await search.saveRaw(formData, {form})
58
+ await search.saveRaw(formData)
60
59
  onRequestClose()
61
60
  } catch (error) {
62
61
  FlashMessage.errorResponse(error)
@@ -1,7 +1,7 @@
1
1
  import BaseComponent from "../../base-component"
2
- import {digg} from "diggerize"
3
2
  import PropTypes from "prop-types"
4
3
  import {memo} from "react"
4
+ import {Pressable, Text} from "react-native"
5
5
  import {shapeComponent} from "set-state-compare/src/shape-component"
6
6
 
7
7
  export default memo(shapeComponent(class ScopeElement extends BaseComponent {
@@ -12,7 +12,7 @@ export default memo(shapeComponent(class ScopeElement extends BaseComponent {
12
12
  static propTypes = {
13
13
  active: PropTypes.bool.isRequired,
14
14
  onScopeClicked: PropTypes.func.isRequired,
15
- scope: PropTypes.object.isRequired
15
+ scope: PropTypes.string.isRequired
16
16
  }
17
17
 
18
18
  render() {
@@ -22,14 +22,16 @@ export default memo(shapeComponent(class ScopeElement extends BaseComponent {
22
22
  if (active) style.fontWeight = "bold"
23
23
 
24
24
  return (
25
- <div
26
- className="scope-element"
27
- key={scope.name()}
28
- onClick={digg(this, "onScopeClicked")}
29
- style={style}
25
+ <Pressable
26
+ dataSet={{class: "scope-element"}}
27
+ key={scope}
28
+ onPress={this.tt.onScopeClicked}
29
+
30
30
  >
31
- {scope.name()}
32
- </div>
31
+ <Text style={style}>
32
+ {scope}
33
+ </Text>
34
+ </Pressable>
33
35
  )
34
36
  }
35
37