@live-change/frontend-auto-form 0.9.85 → 0.9.87

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.
@@ -1,15 +1,6 @@
1
1
  <template>
2
2
  <div>
3
3
 
4
- <!-- <h4>identifiers</h4>
5
- <pre>{{ identifiers }}</pre>
6
-
7
- <h4>definition</h4>
8
- <pre>{{ modelDefinition }}</pre>
9
-
10
- <h4>object</h4>
11
- <pre>{{ object }}</pre>-->
12
-
13
4
  <div v-if="object">
14
5
  <ObjectPath :objectType="service + '_' + model" :object="object.to ?? object.id" class="mb-6" />
15
6
 
@@ -20,12 +11,12 @@
20
11
  </div>
21
12
  <div class="flex flex-row flex-wrap justify-between align-items-top">
22
13
  <div class="text-2xl mb-6">
23
- <strong>{{ model }}</strong>
14
+ <strong class="mr-2">{{ model }}</strong>
24
15
  <ObjectIdentification
25
16
  :objectType="service + '_' + model"
26
17
  :object="object.to ?? object.id"
27
18
  :data="object"
28
- class="ml-2"
19
+ class=""
29
20
  />
30
21
  </div>
31
22
  <div class="flex flex-row flex-wrap justify-between align-items-top gap-2">
@@ -41,6 +32,21 @@
41
32
 
42
33
  </div>
43
34
 
35
+ <div v-if="connectedActions"
36
+ class="bg-surface-0 dark:bg-surface-900 p-4 shadow-sm rounded-border mb-6">
37
+ <div class="text-xl mb-3">
38
+ Actions
39
+ </div>
40
+ <div class="flex flex-row flex-wrap gap-2">
41
+ <div v-for="action of connectedActions" class="mb-0">
42
+ <!-- <pre>{{ action }}</pre> -->
43
+ <router-link :to="actionRoute(action)">
44
+ <Button :label="action.label" icon="pi pi-play" class="p-button mb-0" />
45
+ </router-link>
46
+ </div>
47
+ </div>
48
+ </div>
49
+
44
50
  <div v-for="preparedRelation of visibleObjectRelations" class="mb-6">
45
51
  <ModelSingle :service="preparedRelation.service" :model="preparedRelation.model"
46
52
  :views="preparedRelation.views">
@@ -98,7 +104,7 @@
98
104
 
99
105
  <div class="bg-surface-0 dark:bg-surface-900 p-4 shadow-sm rounded-border">
100
106
 
101
- <pre>visibleRangeRelations = {{ visibleRangeRelations }}</pre>
107
+ <!-- <pre>visibleRangeRelations = {{ visibleRangeRelations }}</pre>
102
108
  <pre>preparedRelations = {{ preparedRelations }}</pre>
103
109
 
104
110
  <div v-if="backwardRelations">
@@ -109,9 +115,13 @@
109
115
  )
110
116
  }}</pre>
111
117
  </div>
118
+ <pre>accessControlRoles = {{ accessControlRoles }}</pre> -->
119
+
120
+ <pre>id = {{ id }}</pre>
112
121
 
122
+ <pre>definition = {{ modelDefinition }}</pre>
113
123
 
114
- <pre>accessControlRoles = {{ accessControlRoles }}</pre>
124
+ <pre>object = {{ object }}</pre>
115
125
 
116
126
  </div>
117
127
 
@@ -143,9 +153,9 @@
143
153
  type: String,
144
154
  required: true,
145
155
  },
146
- identifiers: {
147
- type: Object,
148
- default: () => ({})
156
+ id: {
157
+ type: String,
158
+ required: true,
149
159
  },
150
160
  attributes: {
151
161
  type: Object,
@@ -156,7 +166,7 @@
156
166
  default: ''
157
167
  }
158
168
  })
159
- const { service, model, identifiers, attributes, i18n } = toRefs(props)
169
+ const { service, model, id, attributes, i18n } = toRefs(props)
160
170
 
161
171
  const emit = defineEmits(['saved', 'draftSaved', 'draftDiscarded', 'saveError', 'created' ])
162
172
 
@@ -178,6 +188,25 @@
178
188
  return api.services?.[service.value]?.models?.[model.value]
179
189
  })
180
190
 
191
+ const connectedActions = computed(() => {
192
+ const srcActions = modelDefinition.value?.connectedActions
193
+ if(!srcActions) return null
194
+ return Object.values(srcActions).map(action => {
195
+ const config = {
196
+ service: service.value,
197
+ ...action
198
+ }
199
+ const actionDefinition = api.getServiceDefinition(config.service).actions[config.name]
200
+ if(!actionDefinition) throw new Error("Action " + config.service + "_" + config.name + " definition not found")
201
+ const label = actionDefinition.label ?? action.label ?? actionDefinition.name
202
+ return {
203
+ ...config,
204
+ definition: actionDefinition,
205
+ label
206
+ }
207
+ })
208
+ })
209
+
181
210
  import { getForwardRelations, getBackwardRelations, anyRelationsTypes, prepareObjectRelations }
182
211
  from '../../logic/relations.js'
183
212
  const forwardRelations = computed(() => getForwardRelations(modelDefinition.value, () => true, api))
@@ -188,7 +217,7 @@
188
217
  const viewDataPromise = viewData({
189
218
  service: service.value,
190
219
  model: model.value,
191
- identifiers: identifiers.value,
220
+ id: id.value,
192
221
  path, api
193
222
  })
194
223
 
@@ -205,14 +234,18 @@
205
234
  return prepareObjectRelations(objectType.value, object.value.to ?? object.value.id, api)
206
235
  })
207
236
 
208
- const visibleRangeRelations = computed(() => preparedRelations.value.map(preparedRelation => {
209
- const accessibleViews = preparedRelation.views.filter(view => preparedRelation.access.value[view.name])
210
- if(accessibleViews.length === 0) return null
211
- return {
212
- ...preparedRelation,
213
- views: accessibleViews
214
- }
215
- }).filter(x => x !== null))
237
+ const visibleRangeRelations = computed(() => preparedRelations.value
238
+ .filter(preparedRelation => !preparedRelation.singular)
239
+ .map(preparedRelation => {
240
+ const accessibleViews = preparedRelation.views.filter(view => preparedRelation.access.value[view.name])
241
+ if(accessibleViews.length === 0) return null
242
+ return {
243
+ ...preparedRelation,
244
+ views: accessibleViews
245
+ }
246
+ })
247
+ .filter(x => x !== null)
248
+ )
216
249
 
217
250
  const visibleObjectRelations = computed(() => preparedRelations.value.filter(preparedRelation => {
218
251
  if(!preparedRelation.singular) return false
@@ -228,14 +261,41 @@
228
261
  const accessControlRoles = computed(() => modelDefinition.value?.accessRoles ?? [])
229
262
 
230
263
  const editRoute = computed(() => ({
231
- name: 'auto-form:editor',
264
+ name: 'auto-form:edit',
232
265
  params: {
233
266
  serviceName: service.value,
234
267
  modelName: model.value,
235
- identifiers: Object.values(identifiers.value)
268
+ id: id.value
236
269
  }
237
270
  }))
238
271
 
272
+ function actionRoute(action) {
273
+ const myType = service.value + '_' + model.value
274
+ const parameterName = action.objectParameter ??
275
+ Object.entries(action.definition.properties).find(([key, value]) => value.type === myType)?.[0] ??
276
+ Object.keys(action.definition.properties)?.[0]
277
+
278
+ if(parameterName) {
279
+ const parametersJson = JSON.stringify({
280
+ [parameterName]: object.value.to ?? object.value.id
281
+ })
282
+ return {
283
+ name: 'auto-form:actionParameters',
284
+ params: {
285
+ serviceName: action.service,
286
+ actionName: action.name,
287
+ parametersJson
288
+ }
289
+ }
290
+ }
291
+ return {
292
+ name: 'auto-form:action',
293
+ params: {
294
+ serviceName: action.service,
295
+ actionName: action.name
296
+ }
297
+ }
298
+ }
239
299
 
240
300
  </script>
241
301
 
@@ -1,14 +1,14 @@
1
1
  <template>
2
- <div>
3
- <div class="surface-card p-3">
4
- <h4>#### Schema:</h4>
5
- <pre>{{ '```\n' + JSON.stringify(schema, null, 2) + '\n```' }}</pre>
6
- </div>
7
- <div class="surface-card p-3">
8
- <h4>#### Data:</h4>
9
- <pre>{{ '```\n' + JSON.stringify(clearData, null, 2) + '\n```' }}</pre>
10
- </div>
11
- </div>
2
+ <pre>
3
+ #### {{ prefix }} Data Schema
4
+
5
+ {{ JSON.stringify(schema, null, 2) }}
6
+
7
+
8
+ #### {{ prefix }} Data:
9
+
10
+ {{ JSON.stringify(clearData, null, 2) }}
11
+ </pre>
12
12
  </template>
13
13
 
14
14
  <script setup>
@@ -19,7 +19,11 @@
19
19
  data: {
20
20
  type: Object,
21
21
  required: true,
22
- }
22
+ },
23
+ prefix: {
24
+ type: String,
25
+ default: ''
26
+ }
23
27
  })
24
28
  const { data } = toRefs(props)
25
29
 
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <pre>{{ schema }}</pre>
3
+ </template>
4
+
5
+ <script setup>
6
+
7
+ import { schemaFromDefinition } from "../../logic/schema.js"
8
+ import { defineProps, toRefs, computed, getCurrentInstance } from "vue"
9
+
10
+ const props = defineProps({
11
+ definition: {
12
+ type: Object,
13
+ required: true,
14
+ }
15
+ })
16
+
17
+ const { definition } = toRefs(props)
18
+
19
+ const appContext = getCurrentInstance().appContext
20
+
21
+ const schema = computed(() => schemaFromDefinition(definition.value, undefined, undefined, appContext))
22
+
23
+ </script>
@@ -85,12 +85,25 @@
85
85
  filter: viewFilter.value
86
86
  }))
87
87
 
88
+ import { useApi } from '@live-change/vue3-ssr'
89
+ const api = useApi()
90
+
88
91
  const viewComponent = computed(() => {
89
92
  const type = definition.value.type ?? 'Object'
90
- const defaultComponent = (type === 'Object')
91
- ? defineAsyncComponent(() => import('./ObjectView.vue'))
92
- : defineAsyncComponent(() => import('./JsonView.vue'))
93
- return injectComponent(viewComponentRequest.value, defineAsyncComponent(() => import('./JsonView.vue')))
93
+ let defaultComponent = undefined
94
+ if(type.indexOf('_') > 0) {
95
+ const [service, model] = type.split('_')
96
+ const modelDefinition = api.getServiceDefinition(service)?.models[model]
97
+ if(modelDefinition) {
98
+ defaultComponent = defineAsyncComponent(() => import('../crud/InjectedObjectIndentification.vue'))
99
+ }
100
+ }
101
+ if(!defaultComponent) {
102
+ defaultComponent = (type === 'Object')
103
+ ? defineAsyncComponent(() => import('./ObjectView.vue'))
104
+ : defineAsyncComponent(() => import('./JsonView.vue'))
105
+ }
106
+ return injectComponent(viewComponentRequest.value, defaultComponent)
94
107
  })
95
108
 
96
109
  const viewClass = computed(() => [definition.value?.view?.class, props.class])
@@ -102,6 +115,8 @@
102
115
  ...(definition.value?.view?.attributes),
103
116
  ...(props.attributes),
104
117
  value: value.value,
118
+ type: definition.value.type,
119
+ object: value.value,
105
120
  definition: definition.value,
106
121
  class: viewClass.value,
107
122
  style: viewStyle.value,
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="field" :class="fieldClass" :style="fieldStyle">
2
+ <div class="field flex flex-col" :class="fieldClass" :style="fieldStyle">
3
3
  <slot name="label"
4
4
  v-bind="{ uid, value, definition, viewClass, viewStyle, attributes, propName, rootValue, i18n }">
5
5
  <label :for="uid" class="font-medium text-lg">{{ t( label ) }}</label>
@@ -11,7 +11,7 @@ export default async function actionData(options) {
11
11
  if(!options) throw new Error('options must be provided')
12
12
 
13
13
  const {
14
- parameters,
14
+ parameters = {},
15
15
  initialValue = {},
16
16
 
17
17
  service: serviceName,
@@ -50,7 +50,10 @@ export default async function actionData(options) {
50
50
  if(!service) throw new Error('service must be defined in options')
51
51
  const action = service.actions[actionName]
52
52
  if(!action) throw new Error('action must be defined in options')
53
-
53
+
54
+ const editableProperties = (action.definition.editableProperties ?? Object.keys(action.definition.properties))
55
+ .filter(key => !parameters[key])
56
+
54
57
  let draftIdParts = []
55
58
  let idKey = null
56
59
  for(const [parameterName, parameter] of Object.entries(parameters || {})) {
@@ -152,7 +155,6 @@ export default async function actionData(options) {
152
155
  const result = await submitData(synchronizedData.value.value)
153
156
  if(result === Error) return // silent return on error, because it's handled in onError
154
157
  if(draftData.value) await removeDraftAction(draftIdentifiers)
155
- onDone(result)
156
158
  if(toast && doneToast) toast.add({ severity: 'success', summary: doneToast, life: 1500 })
157
159
  }
158
160
 
@@ -179,6 +181,7 @@ export default async function actionData(options) {
179
181
  return {
180
182
  parameters,
181
183
  initialValue,
184
+ editableProperties,
182
185
  value: synchronizedData.value,
183
186
  changed,
184
187
  submit,
@@ -200,7 +203,6 @@ export default async function actionData(options) {
200
203
  async function submit() {
201
204
  const result = await submitData(formData.value)
202
205
  if(result === Error) return // silent return on error, because it's handled in onError
203
- onSaved(result)
204
206
  if(toast && doneToast) toast.add({ severity: 'success', summary: doneToast, life: 1500 })
205
207
  }
206
208
 
@@ -214,8 +216,9 @@ export default async function actionData(options) {
214
216
  }
215
217
 
216
218
  return {
217
- parameters,
219
+ parameters,
218
220
  initialValue,
221
+ editableProperties,
219
222
  value: formData,
220
223
  changed,
221
224
  submit,
@@ -28,27 +28,49 @@ function addSchema(schemas, schema) {
28
28
  export function schemaFromDefinition(definition, data, type, appContext = getCurrentInstance().appContext) {
29
29
  if(!type) type = definition.type
30
30
  if(type === 'Object') {
31
+ const properties = Object.fromEntries(
32
+ Object.entries(definition.properties).map(
33
+ ([key, value]) => [key, schemaFromDefinition(value, data?.[key], undefined, appContext)])
34
+ )
35
+ for(const key in definition.properties) {
36
+ const prop = definition.properties[key]
37
+ if(key.endsWith('Type') && prop.type === 'type') {
38
+ const keyWithoutType = key.slice(0, -4)
39
+ properties[key] = {
40
+ type: 'string',
41
+ enum: prop.enum,
42
+ enumDescriptions: prop.enumDescriptions,
43
+ description: `Type of ${keyWithoutType}`,
44
+ }
45
+ properties[keyWithoutType] = {
46
+ type: 'string',
47
+ description: `Id of Object with type defined in ${key}`,
48
+ }
49
+ }
50
+ }
31
51
  return {
32
52
  type: 'object',
33
- properties: Object.fromEntries(
34
- Object.entries(definition.properties).map(([key, value]) => [key, schemaFromDefinition(value, data[key], undefined, appContext)])
35
- ),
36
- description: definition.description
53
+ properties,
54
+ description: definition.description,
37
55
  }
38
56
  } else if(type === 'Array') {
39
57
  const schema = {
40
58
  type: 'array',
41
- items: schemaFromDefinition(definition.items ?? definition.of, data[0], undefined, appContext),
42
- description: definition.description
59
+ items: schemaFromDefinition(definition.items ?? definition.of, data?.[0], undefined, appContext),
60
+ description: definition.description,
43
61
  }
44
- for(const item of data) {
45
- extendSchema(schema.items, item, schema.items.modelName, schema.items.serviceName)
62
+ if(data) {
63
+ for(const item of data) {
64
+ extendSchema(schema.items, item, schema.items.modelName, schema.items.serviceName)
65
+ }
46
66
  }
47
67
  return schema
48
68
  } else if(type === 'String') {
49
69
  return {
50
70
  type: 'string',
51
- description: definition.description
71
+ description: definition.description,
72
+ enum: definition.enum,
73
+ enumDescriptions: definition.enumDescriptions
52
74
  }
53
75
  } else if(type === 'Number') {
54
76
  return {
@@ -70,9 +92,8 @@ export function schemaFromDefinition(definition, data, type, appContext = getCur
70
92
  const api = useApi(appContext)
71
93
  const [serviceName, modelName] = definition.type.split('_')
72
94
  const serviceDefinition = api.getServiceDefinition(serviceName)
73
- const modelDefinition = serviceDefinition?.models?.[modelName]
74
- //console.log("MODEL DEFINITION", modelDefinition, "DATA", data, typeof data)
75
- if(typeof data === 'string') {
95
+ const modelDefinition = serviceDefinition?.models?.[modelName]
96
+ if(!data || typeof data === 'string') {
76
97
  return {
77
98
  type: 'string',
78
99
  description: `Id of ${modelName} from ${serviceName} service.`
@@ -94,6 +115,7 @@ export function schemaFromDefinition(definition, data, type, appContext = getCur
94
115
  }
95
116
 
96
117
  function extendSchema(schema, data, viewName, serviceName, appContext) {
118
+ if(!data) return
97
119
  if(Array.isArray(data)) {
98
120
  if(!schema.items) {
99
121
  schema.items = generateSchema(data, schema, 'items', appContext)
@@ -108,7 +130,7 @@ function extendSchema(schema, data, viewName, serviceName, appContext) {
108
130
  description: schema.modelName ? `Id of ${schema.modelName} from ${schema.serviceName} service.` : `Id.`
109
131
  }
110
132
  } else {
111
- generateSchema(value, schema.properties, property)
133
+ generateSchema(value, schema.properties, property, appContext)
112
134
  }
113
135
  }
114
136
  }
@@ -116,7 +138,7 @@ function extendSchema(schema, data, viewName, serviceName, appContext) {
116
138
  }
117
139
 
118
140
  function generateSchema(data, schemaObject, schemaProperty, appContext) {
119
- const api = useApi(appContext)
141
+ const api = useApi(appContext)
120
142
  if(!schemaObject[schemaProperty]) {
121
143
  schemaObject[schemaProperty] = {
122
144
  anyOf: []
@@ -180,4 +202,4 @@ export function cleanData(data) {
180
202
  }
181
203
  return cleanedProperties
182
204
  }
183
- }
205
+ }
@@ -5,7 +5,7 @@ export default async function viewData(options) {
5
5
  if(!options) throw new Error('options must be provided')
6
6
 
7
7
  const {
8
- identifiers,
8
+ id,
9
9
  service: serviceName,
10
10
  model: modelName,
11
11
 
@@ -13,22 +13,22 @@ export default async function viewData(options) {
13
13
  api = useApi(),
14
14
  } = options
15
15
 
16
- if(!identifiers) throw new Error('identifiers must be defined')
16
+ if(!id) throw new Error('id must be defined')
17
17
  if(!serviceName || !modelName) throw new Error('service and model must be defined')
18
18
 
19
19
  const service = api.services[serviceName]
20
20
  const model = service.models[modelName]
21
21
  const {
22
- crudMethods = model.crud,
23
- identifiersNames = model.identifiers,
22
+ crudMethods = model.crud
24
23
  //editableProperties = model.editableProperties ?? Object.keys(model.properties)
25
24
  } = options
26
25
 
27
26
  if(!crudMethods) throw new Error('crud methods must be defined in model or options')
28
- if(!identifiersNames) throw new Error('identifiers names must be defined in model or options')
29
27
  // if(!editableProperties) throw new Error('editableProperties must be defined in model or options')
30
28
 
31
- const savedDataPath = path[serviceName][crudMethods.read](identifiers)
29
+ const savedDataPath = path[serviceName][crudMethods.read]({
30
+ [modelName[0].toLowerCase() + modelName.slice(1)]: id
31
+ })
32
32
 
33
33
  let data
34
34
  let error
@@ -1,18 +1,14 @@
1
1
  <template>
2
2
  <div class="w-full lg:w-8/12 md:w-11/12">
3
3
  <div class="bg-surface-0 dark:bg-surface-900 p-4 shadow-sm rounded-border">
4
- <div class="text-xl mb-2">
5
- Service <strong>{{ serviceName }}</strong>
6
- </div>
7
- <div class="text-2xl mb-4">
8
- Action <strong>{{ actionName }}</strong>
9
- </div>
10
4
 
11
5
  <ActionForm
12
6
  :service="serviceName"
13
- :action="actionName"
7
+ :action="actionName"
8
+ :parameters="parameters"
14
9
  @done="handleDone"
15
10
  />
11
+
16
12
  </div>
17
13
  </div>
18
14
  </template>
@@ -29,9 +25,14 @@
29
25
  actionName: {
30
26
  type: String,
31
27
  required: true
28
+ },
29
+ parametersJson: {
30
+ type: String
32
31
  }
33
32
  })
34
- const { serviceName, actionName } = toRefs(props)
33
+ const { serviceName, actionName, parametersJson } = toRefs(props)
34
+
35
+ const parameters = computed(() => parametersJson.value ? JSON.parse(parametersJson.value) : {})
35
36
 
36
37
  import { useApi } from '@live-change/vue3-ssr'
37
38
  const api = useApi()
@@ -8,7 +8,7 @@
8
8
  <div class="bg-surface-0 dark:bg-surface-900 p-4 shadow-sm rounded-border">
9
9
 
10
10
  <ModelEditor :service="serviceName" :model="modelName" :identifiers="identifiersObject" draft
11
- @created="handleCreated"/>
11
+ @created="handleCreated" @saved="handleSaved" />
12
12
 
13
13
  </div>
14
14
  </div>
@@ -29,20 +29,12 @@
29
29
  type: String,
30
30
  required: true,
31
31
  },
32
- identifiers: {
32
+ identifiersWithNames: {
33
33
  type: Array,
34
34
  default: () => []
35
- },
36
- identifiersTypes: {
37
- type: Array,
38
- default: () => undefined
39
- },
40
- identifiersProperties: {
41
- type: Array,
42
- default: () => undefined
43
35
  }
44
36
  })
45
- const { serviceName, modelName, identifiers } = toRefs(props)
37
+ const { serviceName, modelName, identifiersWithNames } = toRefs(props)
46
38
 
47
39
  import { useApi, usePath, live } from '@live-change/vue3-ssr'
48
40
  const api = useApi()
@@ -58,13 +50,10 @@
58
50
 
59
51
  const identifiersObject = computed(() => {
60
52
  const result = {}
61
- for(const [i, identifier] of Object.entries(identifiers.value)) {
62
- const identifierDefinition = modelDefinition.value.identifiers[i]
63
- if(typeof identifierDefinition === 'string') {
64
- result[identifierDefinition] = identifier
65
- } else {
66
- result[identifierDefinition.name] = identifier
67
- }
53
+ for(let i = 0; i < identifiersWithNames.value.length; i+=2) {
54
+ const name = identifiersWithNames.value[i]
55
+ const identifier = identifiersWithNames.value[i+1]
56
+ result[name] = identifier
68
57
  }
69
58
  return result
70
59
  })
@@ -73,24 +62,17 @@
73
62
  const router = useRouter()
74
63
 
75
64
  function handleCreated(id) {
76
- const newIdentifiers = modelDefinition.value.identifiers.map((identifier, i) => {
77
- if(typeof identifier === 'object' && identifier.field === 'id') {
78
- return id
65
+ console.log("HANDLE CREATED", id)
66
+ //console.log("newIdentifiers", newIdentifiers)
67
+ router.push({
68
+ name: 'auto-form:view',
69
+ params: {
70
+ serviceName: serviceName.value,
71
+ modelName: modelName.value,
72
+ id
79
73
  }
80
- return identifiers.value[i]
81
74
  })
82
-
83
- //console.log("newIdentifiers", newIdentifiers)
84
- if(JSON.stringify(identifiers.value) !== JSON.stringify(newIdentifiers)) {
85
- router.push({
86
- name: 'auto-form:editor',
87
- params: {
88
- serviceName: serviceName.value,
89
- modelName: modelName.value,
90
- identifiers: newIdentifiers
91
- }
92
- })
93
- }
75
+
94
76
  }
95
77
 
96
78
  </script>